summaryrefslogtreecommitdiffstats
path: root/third_party
diff options
context:
space:
mode:
Diffstat (limited to 'third_party')
-rw-r--r--third_party/aom/CHANGELOG19
-rw-r--r--third_party/aom/CMakeLists.txt22
-rw-r--r--third_party/aom/README.md26
-rw-r--r--third_party/aom/aom/aom_encoder.h5
-rw-r--r--third_party/aom/aom/aomdx.h7
-rw-r--r--third_party/aom/aom/src/aom_encoder.c5
-rw-r--r--third_party/aom/aom/src/aom_image.c2
-rw-r--r--third_party/aom/aom_dsp/aom_dsp.cmake15
-rwxr-xr-xthird_party/aom/aom_dsp/aom_dsp_rtcd_defs.pl118
-rw-r--r--third_party/aom/aom_dsp/arm/aom_convolve8_neon_dotprod.c116
-rw-r--r--third_party/aom/aom_dsp/arm/aom_convolve8_neon_i8mm.c73
-rw-r--r--third_party/aom/aom_dsp/arm/aom_filter.h33
-rw-r--r--third_party/aom/aom_dsp/arm/aom_neon_sve2_bridge.h36
-rw-r--r--third_party/aom/aom_dsp/arm/aom_neon_sve_bridge.h (renamed from third_party/aom/aom_dsp/arm/dot_sve.h)24
-rw-r--r--third_party/aom/aom_dsp/arm/avg_sve.c2
-rw-r--r--third_party/aom/aom_dsp/arm/blk_sse_sum_sve.c2
-rw-r--r--third_party/aom/aom_dsp/arm/highbd_convolve8_sve.c681
-rw-r--r--third_party/aom/aom_dsp/arm/highbd_sse_sve.c2
-rw-r--r--third_party/aom/aom_dsp/arm/highbd_variance_sve.c2
-rw-r--r--third_party/aom/aom_dsp/arm/mem_neon.h56
-rw-r--r--third_party/aom/aom_dsp/arm/sum_squares_sve.c2
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/corner_detect.c44
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/corner_detect.h5
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/corner_match.c317
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/corner_match.h12
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/disflow.c36
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/disflow.h11
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/flow_estimation.c20
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/flow_estimation.h7
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/ransac.c349
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/x86/corner_match_avx2.c148
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/x86/corner_match_sse4.c171
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/x86/disflow_avx2.c417
-rw-r--r--third_party/aom/aom_dsp/flow_estimation/x86/disflow_sse4.c424
-rw-r--r--third_party/aom/aom_dsp/mathutils.h1
-rw-r--r--third_party/aom/aom_dsp/noise_model.c6
-rw-r--r--third_party/aom/aom_dsp/noise_model.h6
-rw-r--r--third_party/aom/aom_dsp/pyramid.c181
-rw-r--r--third_party/aom/aom_dsp/pyramid.h61
-rw-r--r--third_party/aom/aom_dsp/rect.h35
-rw-r--r--third_party/aom/aom_dsp/variance.c125
-rw-r--r--third_party/aom/aom_dsp/x86/aom_asm_stubs.c34
-rw-r--r--third_party/aom/aom_dsp/x86/aom_subpixel_8t_intrin_sse2.c569
-rw-r--r--third_party/aom/aom_dsp/x86/aom_subpixel_8t_sse2.asm615
-rw-r--r--third_party/aom/aom_dsp/x86/aom_subpixel_bilinear_sse2.asm295
-rw-r--r--third_party/aom/aom_dsp/x86/avg_intrin_sse2.c2
-rw-r--r--third_party/aom/aom_dsp/x86/fwd_txfm_impl_sse2.h6
-rw-r--r--third_party/aom/aom_dsp/x86/highbd_variance_avx2.c63
-rw-r--r--third_party/aom/aom_dsp/x86/highbd_variance_sse2.c12
-rw-r--r--third_party/aom/aom_dsp/x86/intrapred_ssse3.c8
-rw-r--r--third_party/aom/aom_dsp/x86/masked_sad4d_ssse3.c50
-rw-r--r--third_party/aom/aom_dsp/x86/subpel_variance_ssse3.asm (renamed from third_party/aom/aom_dsp/x86/subpel_variance_sse2.asm)28
-rw-r--r--third_party/aom/aom_dsp/x86/synonyms.h19
-rw-r--r--third_party/aom/aom_dsp/x86/synonyms_avx2.h25
-rw-r--r--third_party/aom/aom_dsp/x86/variance_avx2.c26
-rw-r--r--third_party/aom/aom_dsp/x86/variance_impl_avx2.c6
-rw-r--r--third_party/aom/aom_dsp/x86/variance_sse2.c16
-rw-r--r--third_party/aom/aom_ports/aarch64_cpudetect.c16
-rw-r--r--third_party/aom/aom_ports/arm.h2
-rw-r--r--third_party/aom/aom_ports/mem.h8
-rw-r--r--third_party/aom/aom_scale/aom_scale_rtcd.pl12
-rw-r--r--third_party/aom/aom_scale/generic/yv12config.c34
-rw-r--r--third_party/aom/aom_scale/generic/yv12extend.c42
-rw-r--r--third_party/aom/aom_scale/yv12config.h31
-rw-r--r--third_party/aom/aom_util/aom_pthread.h172
-rw-r--r--third_party/aom/aom_util/aom_thread.c56
-rw-r--r--third_party/aom/aom_util/aom_thread.h146
-rw-r--r--third_party/aom/aom_util/aom_util.cmake3
-rw-r--r--third_party/aom/apps/aomenc.c4
-rw-r--r--third_party/aom/av1/av1.cmake22
-rw-r--r--third_party/aom/av1/av1_cx_iface.c49
-rw-r--r--third_party/aom/av1/av1_dx_iface.c17
-rw-r--r--third_party/aom/av1/common/alloccommon.c6
-rw-r--r--third_party/aom/av1/common/arm/highbd_compound_convolve_neon.c532
-rw-r--r--third_party/aom/av1/common/arm/highbd_compound_convolve_neon.h293
-rw-r--r--third_party/aom/av1/common/arm/highbd_compound_convolve_sve2.c1555
-rw-r--r--third_party/aom/av1/common/arm/highbd_convolve_sve2.c1720
-rw-r--r--third_party/aom/av1/common/arm/highbd_convolve_sve2.h97
-rw-r--r--third_party/aom/av1/common/arm/highbd_warp_plane_neon.c30
-rw-r--r--third_party/aom/av1/common/arm/highbd_warp_plane_neon.h60
-rw-r--r--third_party/aom/av1/common/arm/highbd_warp_plane_sve.c32
-rw-r--r--third_party/aom/av1/common/arm/warp_plane_neon.c38
-rw-r--r--third_party/aom/av1/common/arm/warp_plane_neon.h60
-rw-r--r--third_party/aom/av1/common/arm/warp_plane_neon_i8mm.c38
-rw-r--r--third_party/aom/av1/common/arm/warp_plane_sve.c40
-rw-r--r--third_party/aom/av1/common/av1_common_int.h2
-rw-r--r--third_party/aom/av1/common/av1_rtcd_defs.pl54
-rw-r--r--third_party/aom/av1/common/cdef.c13
-rw-r--r--third_party/aom/av1/common/entropymode.h9
-rw-r--r--third_party/aom/av1/common/quant_common.c18
-rw-r--r--third_party/aom/av1/common/reconintra.c6
-rw-r--r--third_party/aom/av1/common/resize.c52
-rw-r--r--third_party/aom/av1/common/resize.h44
-rw-r--r--third_party/aom/av1/common/restoration.c35
-rw-r--r--third_party/aom/av1/common/thread_common.c7
-rw-r--r--third_party/aom/av1/common/thread_common.h1
-rw-r--r--third_party/aom/av1/common/tile_common.c61
-rw-r--r--third_party/aom/av1/common/tile_common.h15
-rw-r--r--third_party/aom/av1/common/x86/cdef_block_sse2.c40
-rw-r--r--third_party/aom/av1/common/x86/cdef_block_ssse3.c11
-rw-r--r--third_party/aom/av1/common/x86/convolve_2d_avx2.c18
-rw-r--r--third_party/aom/av1/common/x86/convolve_2d_sse2.c17
-rw-r--r--third_party/aom/av1/common/x86/convolve_sse2.c26
-rw-r--r--third_party/aom/av1/common/x86/jnt_convolve_sse2.c229
-rw-r--r--third_party/aom/av1/decoder/decodeframe.c49
-rw-r--r--third_party/aom/av1/decoder/decodemv.h2
-rw-r--r--third_party/aom/av1/decoder/decoder.c1
-rw-r--r--third_party/aom/av1/decoder/dthread.h1
-rw-r--r--third_party/aom/av1/decoder/obu.c41
-rw-r--r--third_party/aom/av1/encoder/allintra_vis.c4
-rw-r--r--third_party/aom/av1/encoder/aq_cyclicrefresh.c50
-rw-r--r--third_party/aom/av1/encoder/arm/neon/av1_error_sve.c2
-rw-r--r--third_party/aom/av1/encoder/arm/neon/temporal_filter_neon_dotprod.c58
-rw-r--r--third_party/aom/av1/encoder/arm/neon/wedge_utils_sve.c92
-rw-r--r--third_party/aom/av1/encoder/av1_temporal_denoiser.c8
-rw-r--r--third_party/aom/av1/encoder/bitstream.c19
-rw-r--r--third_party/aom/av1/encoder/bitstream.h1
-rw-r--r--third_party/aom/av1/encoder/block.h3
-rw-r--r--third_party/aom/av1/encoder/cnn.c10
-rw-r--r--third_party/aom/av1/encoder/encode_strategy.c27
-rw-r--r--third_party/aom/av1/encoder/encodeframe.c20
-rw-r--r--third_party/aom/av1/encoder/encodeframe_utils.c6
-rw-r--r--third_party/aom/av1/encoder/encoder.c94
-rw-r--r--third_party/aom/av1/encoder/encoder.h9
-rw-r--r--third_party/aom/av1/encoder/encoder_alloc.h3
-rw-r--r--third_party/aom/av1/encoder/encoder_utils.c20
-rw-r--r--third_party/aom/av1/encoder/encodetxb.c26
-rw-r--r--third_party/aom/av1/encoder/ethread.c8
-rw-r--r--third_party/aom/av1/encoder/firstpass.c1
-rw-r--r--third_party/aom/av1/encoder/global_motion.c82
-rw-r--r--third_party/aom/av1/encoder/global_motion.h32
-rw-r--r--third_party/aom/av1/encoder/global_motion_facade.c47
-rw-r--r--third_party/aom/av1/encoder/k_means_template.h10
-rw-r--r--third_party/aom/av1/encoder/lookahead.c19
-rw-r--r--third_party/aom/av1/encoder/lookahead.h20
-rw-r--r--third_party/aom/av1/encoder/nonrd_pickmode.c7
-rw-r--r--third_party/aom/av1/encoder/palette.c2
-rw-r--r--third_party/aom/av1/encoder/palette.h2
-rw-r--r--third_party/aom/av1/encoder/partition_search.c48
-rw-r--r--third_party/aom/av1/encoder/partition_strategy.c2
-rw-r--r--third_party/aom/av1/encoder/pass2_strategy.c100
-rw-r--r--third_party/aom/av1/encoder/pickcdef.c2
-rw-r--r--third_party/aom/av1/encoder/picklpf.c21
-rw-r--r--third_party/aom/av1/encoder/pickrst.c111
-rw-r--r--third_party/aom/av1/encoder/ratectrl.c120
-rw-r--r--third_party/aom/av1/encoder/ratectrl.h3
-rw-r--r--third_party/aom/av1/encoder/speed_features.c9
-rw-r--r--third_party/aom/av1/encoder/speed_features.h7
-rw-r--r--third_party/aom/av1/encoder/superres_scale.c2
-rw-r--r--third_party/aom/av1/encoder/svc_layercontext.c12
-rw-r--r--third_party/aom/av1/encoder/svc_layercontext.h15
-rw-r--r--third_party/aom/av1/encoder/temporal_filter.c21
-rw-r--r--third_party/aom/av1/encoder/temporal_filter.h2
-rw-r--r--third_party/aom/av1/encoder/tpl_model.c3
-rw-r--r--third_party/aom/av1/encoder/tpl_model.h1
-rw-r--r--third_party/aom/av1/encoder/tune_butteraugli.c10
-rw-r--r--third_party/aom/av1/encoder/tune_vmaf.c105
-rw-r--r--third_party/aom/av1/encoder/tune_vmaf.h6
-rw-r--r--third_party/aom/av1/encoder/tx_search.c23
-rw-r--r--third_party/aom/av1/encoder/x86/av1_fwd_txfm_sse2.c6
-rw-r--r--third_party/aom/av1/encoder/x86/cnn_avx2.c2
-rw-r--r--third_party/aom/build/cmake/aom_config_defaults.cmake6
-rw-r--r--third_party/aom/build/cmake/aom_configure.cmake11
-rw-r--r--third_party/aom/build/cmake/compiler_flags.cmake4
-rw-r--r--third_party/aom/build/cmake/cpu.cmake21
-rwxr-xr-xthird_party/aom/build/cmake/rtcd.pl2
-rw-r--r--third_party/aom/doc/dev_guide/av1_encoder.dox28
-rw-r--r--third_party/aom/examples/av1_dec_fuzzer.cc15
-rw-r--r--third_party/aom/examples/svc_encoder_rtc.cc34
-rw-r--r--third_party/aom/libs.doxy_template57
-rw-r--r--third_party/aom/test/active_map_test.cc18
-rw-r--r--third_party/aom/test/aom_image_test.cc12
-rw-r--r--third_party/aom/test/av1_convolve_test.cc38
-rw-r--r--third_party/aom/test/av1_fwd_txfm2d_test.cc15
-rw-r--r--third_party/aom/test/av1_wedge_utils_test.cc12
-rw-r--r--third_party/aom/test/cdef_test.cc72
-rw-r--r--third_party/aom/test/convolve_test.cc35
-rw-r--r--third_party/aom/test/corner_match_test.cc221
-rw-r--r--third_party/aom/test/disflow_test.cc5
-rw-r--r--third_party/aom/test/encode_api_test.cc79
-rw-r--r--third_party/aom/test/hbd_metrics_test.cc8
-rw-r--r--third_party/aom/test/level_test.cc14
-rw-r--r--third_party/aom/test/quantize_func_test.cc9
-rw-r--r--third_party/aom/test/resize_test.cc40
-rw-r--r--third_party/aom/test/sad_test.cc2
-rw-r--r--third_party/aom/test/segment_binarization_sync.cc11
-rw-r--r--third_party/aom/test/sharpness_test.cc2
-rw-r--r--third_party/aom/test/test.cmake48
-rw-r--r--third_party/aom/test/test_libaom.cc1
-rw-r--r--third_party/aom/test/variance_test.cc78
-rw-r--r--third_party/aom/test/wiener_test.cc382
-rw-r--r--third_party/aom/third_party/libwebm/README.libaom2
-rw-r--r--third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxer.cc102
-rw-r--r--third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxer.h2
-rw-r--r--third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxerutil.cc10
-rw-r--r--third_party/aom/third_party/libwebm/mkvparser/mkvparser.cc11
-rw-r--r--third_party/content_analysis_sdk/.gitignore6
-rw-r--r--third_party/content_analysis_sdk/agent/src/event_win.cc1
-rw-r--r--third_party/content_analysis_sdk/agent_improvements.patch480
-rw-r--r--third_party/content_analysis_sdk/browser/src/client_mac.cc4
-rw-r--r--third_party/content_analysis_sdk/browser/src/client_posix.cc2
-rw-r--r--third_party/content_analysis_sdk/demo/agent.cc7
-rw-r--r--third_party/content_analysis_sdk/demo/client.cc2
-rw-r--r--third_party/content_analysis_sdk/demo/handler.h208
-rw-r--r--third_party/content_analysis_sdk/demo/handler_misbehaving.h257
-rw-r--r--third_party/content_analysis_sdk/moz.yaml33
-rw-r--r--third_party/content_analysis_sdk/prepare_build96
-rw-r--r--third_party/content_analysis_sdk/proto/content_analysis/sdk/analysis.proto24
-rw-r--r--third_party/dav1d/NEWS18
-rw-r--r--third_party/dav1d/THANKS.md33
-rw-r--r--third_party/dav1d/gcovr.cfg2
-rw-r--r--third_party/dav1d/meson.build77
-rw-r--r--third_party/dav1d/src/arm/32/itx.S79
-rw-r--r--third_party/dav1d/src/arm/32/itx16.S19
-rw-r--r--third_party/dav1d/src/arm/32/msac.S167
-rw-r--r--third_party/dav1d/src/arm/64/itx.S99
-rw-r--r--third_party/dav1d/src/arm/64/itx16.S21
-rw-r--r--third_party/dav1d/src/arm/64/mc.S411
-rw-r--r--third_party/dav1d/src/arm/64/mc16.S373
-rw-r--r--third_party/dav1d/src/arm/64/msac.S167
-rw-r--r--third_party/dav1d/src/arm/64/util.S49
-rw-r--r--third_party/dav1d/src/arm/asm.S44
-rw-r--r--third_party/dav1d/src/arm/cpu.c137
-rw-r--r--third_party/dav1d/src/arm/cpu.h4
-rw-r--r--third_party/dav1d/src/arm/itx.h4
-rw-r--r--third_party/dav1d/src/arm/msac.h2
-rw-r--r--third_party/dav1d/src/cpu.h14
-rw-r--r--third_party/dav1d/src/ext/x86/x86inc.asm198
-rw-r--r--third_party/dav1d/src/itx_1d.c5
-rw-r--r--third_party/dav1d/src/itx_tmpl.c10
-rw-r--r--third_party/dav1d/src/loongarch/msac.S216
-rw-r--r--third_party/dav1d/src/msac.c58
-rw-r--r--third_party/dav1d/src/ppc/cdef_tmpl.c399
-rw-r--r--third_party/dav1d/src/riscv/64/itx.S1061
-rw-r--r--third_party/dav1d/src/riscv/asm.S2
-rw-r--r--third_party/dav1d/src/riscv/itx.h12
-rw-r--r--third_party/dav1d/src/x86/cdef_avx2.asm7
-rw-r--r--third_party/dav1d/src/x86/filmgrain16_avx2.asm23
-rw-r--r--third_party/dav1d/src/x86/filmgrain16_sse.asm8
-rw-r--r--third_party/dav1d/src/x86/filmgrain_avx2.asm19
-rw-r--r--third_party/dav1d/src/x86/filmgrain_sse.asm14
-rw-r--r--third_party/dav1d/src/x86/ipred16_avx2.asm18
-rw-r--r--third_party/dav1d/src/x86/ipred_avx2.asm106
-rw-r--r--third_party/dav1d/src/x86/ipred_sse.asm10
-rw-r--r--third_party/dav1d/src/x86/looprestoration_sse.asm8
-rw-r--r--third_party/dav1d/src/x86/mc16_avx2.asm6
-rw-r--r--third_party/dav1d/src/x86/mc16_avx512.asm3
-rw-r--r--third_party/dav1d/src/x86/mc16_sse.asm33
-rw-r--r--third_party/dav1d/src/x86/mc_avx2.asm33
-rw-r--r--third_party/dav1d/src/x86/mc_avx512.asm3
-rw-r--r--third_party/dav1d/src/x86/mc_sse.asm16
-rw-r--r--third_party/dav1d/src/x86/msac.asm172
-rw-r--r--third_party/jpeg-xl/CHANGELOG.md14
-rw-r--r--third_party/jpeg-xl/WORKSPACE2
-rw-r--r--third_party/jpeg-xl/debian/changelog16
-rw-r--r--third_party/jpeg-xl/examples/decode_exif_metadata.cc2
-rw-r--r--third_party/jpeg-xl/examples/decode_oneshot.cc2
-rw-r--r--third_party/jpeg-xl/examples/decode_progressive.cc8
-rw-r--r--third_party/jpeg-xl/examples/encode_oneshot.cc4
-rw-r--r--third_party/jpeg-xl/lib/BUILD5
-rw-r--r--third_party/jpeg-xl/lib/CMakeLists.txt2
-rw-r--r--third_party/jpeg-xl/lib/extras/codec_test.cc7
-rw-r--r--third_party/jpeg-xl/lib/extras/dec/apng.cc34
-rw-r--r--third_party/jpeg-xl/lib/extras/dec/pnm.cc4
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/apng.cc77
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/jpegli.cc2
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/jpg.cc2
-rw-r--r--third_party/jpeg-xl/lib/extras/enc/pnm.cc8
-rw-r--r--third_party/jpeg-xl/lib/extras/mmap.h8
-rw-r--r--third_party/jpeg-xl/lib/jpegli/color_transform.cc6
-rw-r--r--third_party/jpeg-xl/lib/jpegli/decode.h2
-rw-r--r--third_party/jpeg-xl/lib/jpegli/decode_api_test.cc13
-rw-r--r--third_party/jpeg-xl/lib/jpegli/destination_manager.cc4
-rw-r--r--third_party/jpeg-xl/lib/jpegli/encode.h2
-rw-r--r--third_party/jpeg-xl/lib/jpegli/encode_api_test.cc12
-rw-r--r--third_party/jpeg-xl/lib/jpegli/error_handling_test.cc78
-rw-r--r--third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc3
-rw-r--r--third_party/jpeg-xl/lib/jpegli/libjpeg_wrapper.cc4
-rw-r--r--third_party/jpeg-xl/lib/jpegli/source_manager.cc7
-rw-r--r--third_party/jpeg-xl/lib/jpegli/source_manager_test.cc2
-rw-r--r--third_party/jpeg-xl/lib/jpegli/streaming_test.cc3
-rw-r--r--third_party/jpeg-xl/lib/jpegli/test_utils.cc2
-rw-r--r--third_party/jpeg-xl/lib/jpegli/transcode_api_test.cc2
-rw-r--r--third_party/jpeg-xl/lib/jxl.cmake10
-rw-r--r--third_party/jpeg-xl/lib/jxl/alpha_test.cc34
-rw-r--r--third_party/jpeg-xl/lib/jxl/bit_reader_test.cc4
-rw-r--r--third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli.h5
-rw-r--r--third_party/jpeg-xl/lib/jxl/cache_aligned.cc7
-rw-r--r--third_party/jpeg-xl/lib/jxl/cms/jxl_cms_internal.h13
-rw-r--r--third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h6
-rw-r--r--third_party/jpeg-xl/lib/jxl/cms/transfer_functions-inl.h2
-rw-r--r--third_party/jpeg-xl/lib/jxl/color_management_test.cc2
-rw-r--r--third_party/jpeg-xl/lib/jxl/convolve_test.cc8
-rw-r--r--third_party/jpeg-xl/lib/jxl/dct_for_test.h6
-rw-r--r--third_party/jpeg-xl/lib/jxl/dct_test.cc2
-rw-r--r--third_party/jpeg-xl/lib/jxl/dec_cache.cc12
-rw-r--r--third_party/jpeg-xl/lib/jxl/dec_modular.cc2
-rw-r--r--third_party/jpeg-xl/lib/jxl/decode_test.cc16
-rw-r--r--third_party/jpeg-xl/lib/jxl/enc_ar_control_field.cc4
-rw-r--r--third_party/jpeg-xl/lib/jxl/enc_fast_lossless.cc36
-rw-r--r--third_party/jpeg-xl/lib/jxl/enc_frame.cc14
-rw-r--r--third_party/jpeg-xl/lib/jxl/enc_group.cc6
-rw-r--r--third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc20
-rw-r--r--third_party/jpeg-xl/lib/jxl/enc_modular.cc248
-rw-r--r--third_party/jpeg-xl/lib/jxl/enc_modular.h1
-rw-r--r--third_party/jpeg-xl/lib/jxl/enc_params.h3
-rw-r--r--third_party/jpeg-xl/lib/jxl/enc_patch_dictionary.cc18
-rw-r--r--third_party/jpeg-xl/lib/jxl/enc_quant_weights.cc6
-rw-r--r--third_party/jpeg-xl/lib/jxl/enc_splines.cc4
-rw-r--r--third_party/jpeg-xl/lib/jxl/enc_toc.cc6
-rw-r--r--third_party/jpeg-xl/lib/jxl/encode.cc29
-rw-r--r--third_party/jpeg-xl/lib/jxl/encode_test.cc20
-rw-r--r--third_party/jpeg-xl/lib/jxl/entropy_coder_test.cc2
-rw-r--r--third_party/jpeg-xl/lib/jxl/gradient_test.cc4
-rw-r--r--third_party/jpeg-xl/lib/jxl/icc_codec.h2
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc7
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc4
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc19
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc37
-rw-r--r--third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc18
-rw-r--r--third_party/jpeg-xl/lib/jxl/jxl_test.cc143
-rw-r--r--third_party/jpeg-xl/lib/jxl/lehmer_code_test.cc4
-rw-r--r--third_party/jpeg-xl/lib/jxl/modular/encoding/context_predict.h4
-rw-r--r--third_party/jpeg-xl/lib/jxl/modular/encoding/enc_ma.cc8
-rw-r--r--third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc12
-rw-r--r--third_party/jpeg-xl/lib/jxl/modular/transform/enc_palette.cc80
-rw-r--r--third_party/jpeg-xl/lib/jxl/modular/transform/enc_squeeze.cc12
-rw-r--r--third_party/jpeg-xl/lib/jxl/modular/transform/palette.h2
-rw-r--r--third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc22
-rw-r--r--third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.h4
-rw-r--r--third_party/jpeg-xl/lib/jxl/modular/transform/transform.cc2
-rw-r--r--third_party/jpeg-xl/lib/jxl/modular/transform/transform.h2
-rw-r--r--third_party/jpeg-xl/lib/jxl/modular_test.cc15
-rw-r--r--third_party/jpeg-xl/lib/jxl/passes_test.cc47
-rw-r--r--third_party/jpeg-xl/lib/jxl/quant_weights_test.cc2
-rw-r--r--third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.cc2
-rw-r--r--third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.cc10
-rw-r--r--third_party/jpeg-xl/lib/jxl/render_pipeline/stage_cms.cc2
-rw-r--r--third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.cc26
-rw-r--r--third_party/jpeg-xl/lib/jxl/render_pipeline/stage_upsampling.cc2
-rw-r--r--third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.cc11
-rw-r--r--third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.h3
-rw-r--r--third_party/jpeg-xl/lib/jxl/roundtrip_test.cc2
-rw-r--r--third_party/jpeg-xl/lib/jxl/speed_tier_test.cc1
-rw-r--r--third_party/jpeg-xl/lib/jxl/splines.cc12
-rw-r--r--third_party/jpeg-xl/lib/jxl/test_image.cc3
-rw-r--r--third_party/jpeg-xl/lib/jxl/test_utils.h3
-rw-r--r--third_party/jpeg-xl/lib/jxl/testing.h24
-rw-r--r--third_party/jpeg-xl/lib/jxl/tf_gbench.cc4
-rw-r--r--third_party/jpeg-xl/lib/jxl/version.h.in2
-rw-r--r--third_party/jpeg-xl/lib/jxl/xorshift128plus_test.cc4
-rw-r--r--third_party/jpeg-xl/lib/jxl_cms.cmake2
-rw-r--r--third_party/jpeg-xl/lib/jxl_extras.cmake2
-rw-r--r--third_party/jpeg-xl/lib/jxl_lists.bzl2
-rw-r--r--third_party/jpeg-xl/lib/threads/thread_parallel_runner_test.cc6
-rw-r--r--third_party/jpeg-xl/plugins/gimp/file-jxl-load.cc8
-rw-r--r--third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc12
-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
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.1.dist-info/AUTHORS.md (renamed from third_party/python/glean_parser/glean_parser-13.0.0.dist-info/AUTHORS.md)0
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.1.dist-info/LICENSE (renamed from third_party/python/glean_parser/glean_parser-13.0.0.dist-info/LICENSE)0
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.1.dist-info/METADATA (renamed from third_party/python/glean_parser/glean_parser-13.0.0.dist-info/METADATA)6
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.1.dist-info/RECORD (renamed from third_party/python/glean_parser/glean_parser-13.0.0.dist-info/RECORD)24
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.1.dist-info/WHEEL (renamed from third_party/python/glean_parser/glean_parser-13.0.0.dist-info/WHEEL)2
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.1.dist-info/entry_points.txt (renamed from third_party/python/glean_parser/glean_parser-13.0.0.dist-info/entry_points.txt)0
-rw-r--r--third_party/python/glean_parser/glean_parser-13.0.1.dist-info/top_level.txt (renamed from third_party/python/glean_parser/glean_parser-13.0.0.dist-info/top_level.txt)0
-rw-r--r--third_party/python/glean_parser/glean_parser/go_server.py1
-rw-r--r--third_party/python/glean_parser/glean_parser/javascript_server.py1
-rw-r--r--third_party/python/glean_parser/glean_parser/python_server.py1
-rw-r--r--third_party/python/glean_parser/glean_parser/ruby_server.py1
-rw-r--r--third_party/python/glean_parser/glean_parser/util.py7
-rw-r--r--third_party/python/poetry.lock9
-rw-r--r--third_party/python/requirements.in2
-rw-r--r--third_party/python/requirements.txt7
-rw-r--r--third_party/rust/audio_thread_priority/.cargo-checksum.json2
-rw-r--r--third_party/rust/audio_thread_priority/Cargo.toml4
-rw-r--r--third_party/rust/audio_thread_priority/src/lib.rs9
-rw-r--r--third_party/rust/audio_thread_priority/src/rt_win.rs233
-rw-r--r--third_party/rust/audioipc2-client/.cargo-checksum.json2
-rw-r--r--third_party/rust/audioipc2-client/Cargo.toml2
-rw-r--r--third_party/rust/audioipc2-server/.cargo-checksum.json2
-rw-r--r--third_party/rust/audioipc2-server/Cargo.toml2
-rw-r--r--third_party/rust/audioipc2-server/src/lib.rs4
-rw-r--r--third_party/rust/audioipc2-server/src/server.rs2
-rw-r--r--third_party/rust/audioipc2/.cargo-checksum.json2
-rw-r--r--third_party/rust/audioipc2/Cargo.toml2
-rw-r--r--third_party/rust/audioipc2/src/codec.rs2
-rw-r--r--third_party/rust/audioipc2/src/messages.rs2
-rw-r--r--third_party/rust/audioipc2/src/sys/unix/cmsg.rs2
-rw-r--r--third_party/rust/bumpalo/.cargo-checksum.json2
-rw-r--r--third_party/rust/bumpalo/CHANGELOG.md124
-rw-r--r--third_party/rust/bumpalo/Cargo.toml11
-rw-r--r--third_party/rust/bumpalo/README.md23
-rw-r--r--third_party/rust/bumpalo/src/alloc.rs2
-rw-r--r--third_party/rust/bumpalo/src/collections/raw_vec.rs86
-rw-r--r--third_party/rust/bumpalo/src/collections/string.rs17
-rw-r--r--third_party/rust/bumpalo/src/collections/vec.rs148
-rwxr-xr-x[-rw-r--r--]third_party/rust/bumpalo/src/lib.rs192
-rw-r--r--third_party/rust/coreaudio-sys-utils/.cargo-checksum.json2
-rw-r--r--third_party/rust/coreaudio-sys-utils/src/audio_device_extensions.rs2
-rw-r--r--third_party/rust/coreaudio-sys-utils/src/audio_object.rs4
-rw-r--r--third_party/rust/coreaudio-sys-utils/src/audio_unit.rs13
-rw-r--r--third_party/rust/coreaudio-sys-utils/src/dispatch.rs268
-rw-r--r--third_party/rust/cubeb-coreaudio/.cargo-checksum.json2
-rw-r--r--third_party/rust/cubeb-coreaudio/.github/workflows/test.yml30
-rw-r--r--third_party/rust/cubeb-coreaudio/Cargo.toml1
-rwxr-xr-xthird_party/rust/cubeb-coreaudio/run_device_tests.sh2
-rwxr-xr-xthird_party/rust/cubeb-coreaudio/run_tests.sh5
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/aggregate_device.rs45
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/mod.rs834
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/tests/aggregate_device.rs347
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs444
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/tests/device_change.rs36
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/tests/device_property.rs176
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs697
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/tests/manual.rs288
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs18
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs4
-rw-r--r--third_party/rust/cubeb-coreaudio/src/backend/tests/utils.rs507
-rw-r--r--third_party/rust/d3d12/.cargo-checksum.json2
-rw-r--r--third_party/rust/d3d12/Cargo.toml2
-rw-r--r--third_party/rust/embed-manifest/.cargo-checksum.json1
-rw-r--r--third_party/rust/embed-manifest/CHANGELOG.md59
-rw-r--r--third_party/rust/embed-manifest/Cargo.toml38
-rw-r--r--third_party/rust/embed-manifest/LICENSE23
-rw-r--r--third_party/rust/embed-manifest/README.md62
-rw-r--r--third_party/rust/embed-manifest/rustfmt.toml2
-rw-r--r--third_party/rust/embed-manifest/src/embed/coff.rs192
-rw-r--r--third_party/rust/embed-manifest/src/embed/error.rs57
-rw-r--r--third_party/rust/embed-manifest/src/embed/mod.rs139
-rw-r--r--third_party/rust/embed-manifest/src/embed/test.rs173
-rw-r--r--third_party/rust/embed-manifest/src/lib.rs134
-rw-r--r--third_party/rust/embed-manifest/src/manifest/mod.rs882
-rw-r--r--third_party/rust/embed-manifest/src/manifest/test.rs117
-rw-r--r--third_party/rust/embed-manifest/src/manifest/xml.rs140
-rw-r--r--third_party/rust/embed-manifest/testdata/sample.exe.manifest11
-rw-r--r--third_party/rust/error-support/.cargo-checksum.json2
-rw-r--r--third_party/rust/error-support/Cargo.toml4
-rw-r--r--third_party/rust/glean-core/.cargo-checksum.json2
-rw-r--r--third_party/rust/glean-core/Cargo.toml6
-rw-r--r--third_party/rust/glean-core/src/common_metric_data.rs1
-rw-r--r--third_party/rust/glean-core/src/core/mod.rs9
-rw-r--r--third_party/rust/glean-core/src/database/mod.rs1
-rw-r--r--third_party/rust/glean-core/src/debug.rs1
-rw-r--r--third_party/rust/glean-core/src/dispatcher/mod.rs5
-rw-r--r--third_party/rust/glean-core/src/error_recording.rs1
-rw-r--r--third_party/rust/glean-core/src/event_database/mod.rs3
-rw-r--r--third_party/rust/glean-core/src/glean.udl1
-rw-r--r--third_party/rust/glean-core/src/histogram/mod.rs1
-rw-r--r--third_party/rust/glean-core/src/internal_pings.rs11
-rw-r--r--third_party/rust/glean-core/src/lib.rs5
-rw-r--r--third_party/rust/glean-core/src/lib_unit_tests.rs82
-rw-r--r--third_party/rust/glean-core/src/metrics/event.rs16
-rw-r--r--third_party/rust/glean-core/src/metrics/memory_unit.rs2
-rw-r--r--third_party/rust/glean-core/src/metrics/metrics_enabled_config.rs2
-rw-r--r--third_party/rust/glean-core/src/metrics/ping.rs31
-rw-r--r--third_party/rust/glean-core/src/metrics/string.rs2
-rw-r--r--third_party/rust/glean-core/src/metrics/text.rs2
-rw-r--r--third_party/rust/glean-core/src/metrics/time_unit.rs1
-rw-r--r--third_party/rust/glean-core/src/metrics/timespan.rs1
-rw-r--r--third_party/rust/glean-core/src/metrics/timing_distribution.rs2
-rw-r--r--third_party/rust/glean-core/src/metrics/url.rs2
-rw-r--r--third_party/rust/glean-core/src/storage/mod.rs4
-rw-r--r--third_party/rust/glean-core/src/traits/event.rs1
-rw-r--r--third_party/rust/glean-core/src/upload/directory.rs2
-rw-r--r--third_party/rust/glean-core/src/upload/mod.rs4
-rw-r--r--third_party/rust/glean-core/src/upload/request.rs2
-rw-r--r--third_party/rust/glean-core/tests/common/mod.rs1
-rw-r--r--third_party/rust/glean-core/tests/event.rs1
-rw-r--r--third_party/rust/glean-core/tests/ping_maker.rs2
-rw-r--r--third_party/rust/glean/.cargo-checksum.json2
-rw-r--r--third_party/rust/glean/Cargo.toml4
-rw-r--r--third_party/rust/glean/src/common_test.rs1
-rw-r--r--third_party/rust/glean/src/configuration.rs12
-rw-r--r--third_party/rust/glean/src/lib.rs1
-rw-r--r--third_party/rust/glean/tests/schema.rs3
-rw-r--r--third_party/rust/goblin/.cargo-checksum.json2
-rw-r--r--third_party/rust/goblin/CHANGELOG.md27
-rw-r--r--third_party/rust/goblin/Cargo.toml10
-rw-r--r--third_party/rust/goblin/README.md7
-rw-r--r--third_party/rust/goblin/src/elf/reloc.rs2
-rw-r--r--third_party/rust/goblin/src/error.rs7
-rw-r--r--third_party/rust/goblin/src/lib.rs24
-rw-r--r--third_party/rust/goblin/src/mach/load_command.rs3
-rw-r--r--third_party/rust/goblin/src/pe/authenticode.rs200
-rw-r--r--third_party/rust/goblin/src/pe/certificate_table.rs28
-rw-r--r--third_party/rust/goblin/src/pe/data_directories.rs175
-rw-r--r--third_party/rust/goblin/src/pe/header.rs150
-rw-r--r--third_party/rust/goblin/src/pe/mod.rs249
-rw-r--r--third_party/rust/goblin/src/pe/optional_header.rs83
-rw-r--r--third_party/rust/goblin/src/pe/options.rs10
-rw-r--r--third_party/rust/goblin/src/pe/section_table.rs41
-rw-r--r--third_party/rust/goblin/src/pe/symbol.rs9
-rw-r--r--third_party/rust/goblin/src/pe/utils.rs16
-rw-r--r--third_party/rust/goblin/src/strtab.rs5
-rw-r--r--third_party/rust/naga/.cargo-checksum.json2
-rw-r--r--third_party/rust/naga/Cargo.toml4
-rw-r--r--third_party/rust/naga/README.md4
-rw-r--r--third_party/rust/naga/src/back/mod.rs58
-rw-r--r--third_party/rust/naga/src/back/spv/index.rs116
-rw-r--r--third_party/rust/naga/src/back/spv/mod.rs9
-rw-r--r--third_party/rust/naga/src/back/spv/writer.rs70
-rw-r--r--third_party/rust/naga/src/front/glsl/variables.rs2
-rw-r--r--third_party/rust/naga/src/span.rs9
-rw-r--r--third_party/rust/naga/src/valid/analyzer.rs69
-rw-r--r--third_party/rust/naga/src/valid/type.rs2
-rw-r--r--third_party/rust/neqo-common/.cargo-checksum.json2
-rw-r--r--third_party/rust/neqo-common/Cargo.toml28
-rw-r--r--third_party/rust/neqo-common/benches/timer.rs39
-rw-r--r--third_party/rust/neqo-common/src/datagram.rs15
-rw-r--r--third_party/rust/neqo-common/src/lib.rs2
-rw-r--r--third_party/rust/neqo-common/src/log.rs21
-rw-r--r--third_party/rust/neqo-common/src/timer.rs46
-rw-r--r--third_party/rust/neqo-common/src/tos.rs48
-rw-r--r--third_party/rust/neqo-common/src/udp.rs222
-rw-r--r--third_party/rust/neqo-crypto/.cargo-checksum.json2
-rw-r--r--third_party/rust/neqo-crypto/Cargo.toml8
-rw-r--r--third_party/rust/neqo-crypto/bindings/bindings.toml5
-rw-r--r--third_party/rust/neqo-crypto/bindings/mozpkix.hpp1
-rw-r--r--third_party/rust/neqo-crypto/build.rs105
-rw-r--r--third_party/rust/neqo-crypto/min_version.txt1
-rw-r--r--third_party/rust/neqo-crypto/src/aead.rs8
-rw-r--r--third_party/rust/neqo-crypto/src/aead_null.rs (renamed from third_party/rust/neqo-crypto/src/aead_fuzzing.rs)67
-rw-r--r--third_party/rust/neqo-crypto/src/agent.rs11
-rw-r--r--third_party/rust/neqo-crypto/src/err.rs30
-rw-r--r--third_party/rust/neqo-crypto/src/lib.rs68
-rw-r--r--third_party/rust/neqo-crypto/src/min_version.rs9
-rw-r--r--third_party/rust/neqo-crypto/src/selfencrypt.rs2
-rw-r--r--third_party/rust/neqo-crypto/src/time.rs4
-rw-r--r--third_party/rust/neqo-crypto/tests/aead.rs3
-rw-r--r--third_party/rust/neqo-crypto/tests/init.rs51
-rw-r--r--third_party/rust/neqo-crypto/tests/selfencrypt.rs6
-rw-r--r--third_party/rust/neqo-http3/.cargo-checksum.json2
-rw-r--r--third_party/rust/neqo-http3/Cargo.toml8
-rw-r--r--third_party/rust/neqo-http3/src/connection.rs20
-rw-r--r--third_party/rust/neqo-http3/src/connection_client.rs17
-rw-r--r--third_party/rust/neqo-http3/src/connection_server.rs17
-rw-r--r--third_party/rust/neqo-http3/src/recv_message.rs2
-rw-r--r--third_party/rust/neqo-http3/src/send_message.rs6
-rw-r--r--third_party/rust/neqo-http3/src/server_events.rs8
-rw-r--r--third_party/rust/neqo-qpack/.cargo-checksum.json2
-rw-r--r--third_party/rust/neqo-qpack/Cargo.toml2
-rw-r--r--third_party/rust/neqo-qpack/src/table.rs2
-rw-r--r--third_party/rust/neqo-transport/.cargo-checksum.json2
-rw-r--r--third_party/rust/neqo-transport/Cargo.toml4
-rw-r--r--third_party/rust/neqo-transport/benches/range_tracker.rs16
-rw-r--r--third_party/rust/neqo-transport/benches/rx_stream_orderer.rs4
-rw-r--r--third_party/rust/neqo-transport/benches/transfer.rs16
-rw-r--r--third_party/rust/neqo-transport/src/cc/classic_cc.rs27
-rw-r--r--third_party/rust/neqo-transport/src/connection/dump.rs16
-rw-r--r--third_party/rust/neqo-transport/src/connection/mod.rs160
-rw-r--r--third_party/rust/neqo-transport/src/connection/params.rs13
-rw-r--r--third_party/rust/neqo-transport/src/connection/state.rs5
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/handshake.rs11
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/mod.rs85
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/null.rs (renamed from third_party/rust/neqo-transport/src/connection/tests/fuzzing.rs)8
-rw-r--r--third_party/rust/neqo-transport/src/connection/tests/stream.rs6
-rw-r--r--third_party/rust/neqo-transport/src/crypto.rs98
-rw-r--r--third_party/rust/neqo-transport/src/frame.rs86
-rw-r--r--third_party/rust/neqo-transport/src/lib.rs8
-rw-r--r--third_party/rust/neqo-transport/src/packet/mod.rs99
-rw-r--r--third_party/rust/neqo-transport/src/packet/retry.rs1
-rw-r--r--third_party/rust/neqo-transport/src/path.rs2
-rw-r--r--third_party/rust/neqo-transport/src/qlog.rs347
-rw-r--r--third_party/rust/neqo-transport/src/stats.rs6
-rw-r--r--third_party/rust/neqo-transport/tests/common/mod.rs9
-rw-r--r--third_party/rust/neqo-transport/tests/conn_vectors.rs2
-rw-r--r--third_party/rust/neqo-transport/tests/connection.rs70
-rw-r--r--third_party/rust/neqo-transport/tests/retry.rs2
-rw-r--r--third_party/rust/oneshot-uniffi/.cargo-checksum.json2
-rw-r--r--third_party/rust/oneshot-uniffi/CHANGELOG.md7
-rw-r--r--third_party/rust/oneshot-uniffi/Cargo.lock2
-rw-r--r--third_party/rust/oneshot-uniffi/Cargo.toml2
-rw-r--r--third_party/rust/oneshot-uniffi/src/errors.rs15
-rw-r--r--third_party/rust/oneshot-uniffi/src/lib.rs51
-rw-r--r--third_party/rust/oneshot-uniffi/tests/raw.rs46
-rw-r--r--third_party/rust/relevancy/.cargo-checksum.json1
-rw-r--r--third_party/rust/relevancy/Cargo.toml47
-rw-r--r--third_party/rust/relevancy/build.rs8
-rw-r--r--third_party/rust/relevancy/src/bin/generate-test-data.rs43
-rw-r--r--third_party/rust/relevancy/src/db.rs118
-rw-r--r--third_party/rust/relevancy/src/error.rs44
-rw-r--r--third_party/rust/relevancy/src/interest.rs167
-rw-r--r--third_party/rust/relevancy/src/lib.rs81
-rw-r--r--third_party/rust/relevancy/src/populate_interests.rs157
-rw-r--r--third_party/rust/relevancy/src/relevancy.udl106
-rw-r--r--third_party/rust/relevancy/src/schema.rs53
-rw-r--r--third_party/rust/relevancy/src/url_hash.rs63
-rw-r--r--third_party/rust/relevancy/test-databin0 -> 192 bytes
-rw-r--r--third_party/rust/remote_settings/.cargo-checksum.json2
-rw-r--r--third_party/rust/remote_settings/Cargo.toml4
-rw-r--r--third_party/rust/remote_settings/src/client.rs54
-rw-r--r--third_party/rust/rure/src/lib.rs4
-rw-r--r--third_party/rust/scroll/.cargo-checksum.json2
-rw-r--r--third_party/rust/scroll/CHANGELOG.md17
-rw-r--r--third_party/rust/scroll/Cargo.lock205
-rw-r--r--third_party/rust/scroll/Cargo.toml33
-rw-r--r--third_party/rust/scroll/README.md13
-rw-r--r--third_party/rust/scroll/benches/bench.rs157
-rw-r--r--third_party/rust/scroll/examples/data_ctx.rs24
-rw-r--r--third_party/rust/scroll/src/ctx.rs107
-rw-r--r--third_party/rust/scroll/src/endian.rs5
-rw-r--r--third_party/rust/scroll/src/error.rs26
-rw-r--r--third_party/rust/scroll/src/leb128.rs18
-rw-r--r--third_party/rust/scroll/src/lesser.rs7
-rw-r--r--third_party/rust/scroll/src/lib.rs67
-rw-r--r--third_party/rust/scroll/src/pread.rs7
-rw-r--r--third_party/rust/scroll/src/pwrite.rs9
-rw-r--r--third_party/rust/scroll/tests/api.rs292
-rw-r--r--third_party/rust/scroll_derive/.cargo-checksum.json2
-rw-r--r--third_party/rust/scroll_derive/Cargo.toml2
-rw-r--r--third_party/rust/scroll_derive/README.md2
-rw-r--r--third_party/rust/smawk/.cargo-checksum.json1
-rw-r--r--third_party/rust/smawk/Cargo.toml53
-rw-r--r--third_party/rust/smawk/LICENSE21
-rw-r--r--third_party/rust/smawk/README.md151
-rw-r--r--third_party/rust/smawk/dprint.json19
-rw-r--r--third_party/rust/smawk/rustfmt.toml2
-rw-r--r--third_party/rust/smawk/src/brute_force.rs150
-rw-r--r--third_party/rust/smawk/src/lib.rs570
-rw-r--r--third_party/rust/smawk/src/monge.rs121
-rw-r--r--third_party/rust/smawk/src/recursive.rs191
-rw-r--r--third_party/rust/smawk/tests/agreement.rs104
-rw-r--r--third_party/rust/smawk/tests/complexity.rs83
-rw-r--r--third_party/rust/smawk/tests/monge.rs83
-rw-r--r--third_party/rust/smawk/tests/random_monge/mod.rs83
-rw-r--r--third_party/rust/smawk/tests/version-numbers.rs9
-rw-r--r--third_party/rust/sql-support/.cargo-checksum.json2
-rw-r--r--third_party/rust/sql-support/src/open_database.rs6
-rw-r--r--third_party/rust/suggest/.cargo-checksum.json2
-rw-r--r--third_party/rust/suggest/Cargo.toml28
-rw-r--r--third_party/rust/suggest/README.md110
-rw-r--r--third_party/rust/suggest/benches/benchmark_all.rs25
-rw-r--r--third_party/rust/suggest/src/benchmarks/README.md28
-rw-r--r--third_party/rust/suggest/src/benchmarks/client.rs97
-rw-r--r--third_party/rust/suggest/src/benchmarks/ingest.rs116
-rw-r--r--third_party/rust/suggest/src/benchmarks/mod.rs40
-rw-r--r--third_party/rust/suggest/src/bin/debug_ingestion_sizes.rs9
-rw-r--r--third_party/rust/suggest/src/config.rs5
-rw-r--r--third_party/rust/suggest/src/db.rs720
-rw-r--r--third_party/rust/suggest/src/lib.rs4
-rw-r--r--third_party/rust/suggest/src/pocket.rs2
-rw-r--r--third_party/rust/suggest/src/provider.rs48
-rw-r--r--third_party/rust/suggest/src/rs.rs223
-rw-r--r--third_party/rust/suggest/src/schema.rs187
-rw-r--r--third_party/rust/suggest/src/store.rs1113
-rw-r--r--third_party/rust/suggest/src/suggest.udl11
-rw-r--r--third_party/rust/suggest/src/suggestion.rs34
-rw-r--r--third_party/rust/suggest/src/yelp.rs28
-rw-r--r--third_party/rust/sync15/.cargo-checksum.json2
-rw-r--r--third_party/rust/sync15/Cargo.toml4
-rw-r--r--third_party/rust/tabs/.cargo-checksum.json2
-rw-r--r--third_party/rust/tabs/Cargo.toml4
-rw-r--r--third_party/rust/textwrap/.cargo-checksum.json1
-rw-r--r--third_party/rust/textwrap/CHANGELOG.md616
-rw-r--r--third_party/rust/textwrap/Cargo.lock657
-rw-r--r--third_party/rust/textwrap/Cargo.toml91
-rw-r--r--third_party/rust/textwrap/LICENSE21
-rw-r--r--third_party/rust/textwrap/README.md176
-rw-r--r--third_party/rust/textwrap/rustfmt.toml1
-rw-r--r--third_party/rust/textwrap/src/columns.rs193
-rw-r--r--third_party/rust/textwrap/src/core.rs461
-rw-r--r--third_party/rust/textwrap/src/fill.rs298
-rw-r--r--third_party/rust/textwrap/src/fuzzing.rs23
-rw-r--r--third_party/rust/textwrap/src/indentation.rs347
-rw-r--r--third_party/rust/textwrap/src/lib.rs235
-rw-r--r--third_party/rust/textwrap/src/line_ending.rs88
-rw-r--r--third_party/rust/textwrap/src/options.rs300
-rw-r--r--third_party/rust/textwrap/src/refill.rs352
-rw-r--r--third_party/rust/textwrap/src/termwidth.rs52
-rw-r--r--third_party/rust/textwrap/src/word_separators.rs481
-rw-r--r--third_party/rust/textwrap/src/word_splitters.rs314
-rw-r--r--third_party/rust/textwrap/src/wrap.rs686
-rw-r--r--third_party/rust/textwrap/src/wrap_algorithms.rs413
-rw-r--r--third_party/rust/textwrap/src/wrap_algorithms/optimal_fit.rs433
-rw-r--r--third_party/rust/textwrap/tests/indent.rs88
-rw-r--r--third_party/rust/textwrap/tests/version-numbers.rs22
-rw-r--r--third_party/rust/unicode-linebreak/.cargo-checksum.json1
-rw-r--r--third_party/rust/unicode-linebreak/Cargo.toml32
-rw-r--r--third_party/rust/unicode-linebreak/LICENSE201
-rw-r--r--third_party/rust/unicode-linebreak/src/lib.rs160
-rw-r--r--third_party/rust/unicode-linebreak/src/shared.rs134
-rw-r--r--third_party/rust/unicode-linebreak/src/tables.rs10
-rw-r--r--third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi-example-arithmetic/Cargo.toml6
-rw-r--r--third_party/rust/uniffi-example-geometry/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi-example-geometry/Cargo.toml6
-rw-r--r--third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py6
-rw-r--r--third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb6
-rw-r--r--third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi-example-rondpoint/Cargo.toml6
-rw-r--r--third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py2
-rw-r--r--third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb7
-rw-r--r--third_party/rust/uniffi-example-sprites/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi-example-sprites/Cargo.toml6
-rw-r--r--third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py18
-rw-r--r--third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb18
-rw-r--r--third_party/rust/uniffi-example-todolist/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi-example-todolist/Cargo.toml6
-rw-r--r--third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py4
-rw-r--r--third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb6
-rw-r--r--third_party/rust/uniffi/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi/Cargo.toml11
-rw-r--r--third_party/rust/uniffi/README.md81
-rw-r--r--third_party/rust/uniffi/src/cli.rs21
-rw-r--r--third_party/rust/uniffi/src/lib.rs7
-rw-r--r--third_party/rust/uniffi_bindgen/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi_bindgen/Cargo.toml15
-rw-r--r--third_party/rust/uniffi_bindgen/README.md81
-rw-r--r--third_party/rust/uniffi_bindgen/src/backend/filters.rs4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs115
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs24
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs251
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs10
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt107
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt117
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt65
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt140
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt30
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt18
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt83
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt152
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt14
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt61
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt26
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt161
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt343
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md13
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt39
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt10
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt52
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt140
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs16
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs16
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs18
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs177
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py86
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py18
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py5
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py98
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py59
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py112
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py9
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py71
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py63
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py36
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py6
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py45
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py124
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py5
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py68
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py9
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py24
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py18
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py11
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py5
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py24
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py5
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py95
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py17
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/test.rs14
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs79
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb36
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb7
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb42
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb10
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs15
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs5
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs23
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs2
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs209
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs18
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift100
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h48
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift113
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift57
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift145
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift69
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift44
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift201
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift12
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift17
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift4
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift49
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift3
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift125
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs24
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/callbacks.rs207
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/enum_.rs224
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/ffi.rs195
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/function.rs32
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/mod.rs342
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/object.rs199
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/record.rs53
-rw-r--r--third_party/rust/uniffi_bindgen/src/interface/universe.rs5
-rw-r--r--third_party/rust/uniffi_bindgen/src/lib.rs138
-rw-r--r--third_party/rust/uniffi_bindgen/src/library_mode.rs40
-rw-r--r--third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs21
-rw-r--r--third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs2
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs36
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs93
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs13
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs10
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs2
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs34
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs8
-rw-r--r--third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs5
-rw-r--r--third_party/rust/uniffi_build/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi_build/Cargo.toml5
-rw-r--r--third_party/rust/uniffi_build/README.md81
-rw-r--r--third_party/rust/uniffi_checksum_derive/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi_checksum_derive/Cargo.toml3
-rw-r--r--third_party/rust/uniffi_checksum_derive/README.md81
-rw-r--r--third_party/rust/uniffi_core/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi_core/Cargo.toml6
-rw-r--r--third_party/rust/uniffi_core/README.md81
-rw-r--r--third_party/rust/uniffi_core/src/ffi/callbackinterface.rs125
-rw-r--r--third_party/rust/uniffi_core/src/ffi/ffidefault.rs11
-rw-r--r--third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs116
-rw-r--r--third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs487
-rw-r--r--third_party/rust/uniffi_core/src/ffi/foreignfuture.rs241
-rw-r--r--third_party/rust/uniffi_core/src/ffi/handle.rs46
-rw-r--r--third_party/rust/uniffi_core/src/ffi/mod.rs6
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustbuffer.rs122
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustcalls.rs9
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustfuture.rs735
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs320
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs141
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs96
-rw-r--r--third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs223
-rw-r--r--third_party/rust/uniffi_core/src/ffi_converter_impls.rs75
-rw-r--r--third_party/rust/uniffi_core/src/ffi_converter_traits.rs151
-rw-r--r--third_party/rust/uniffi_core/src/lib.rs6
-rw-r--r--third_party/rust/uniffi_core/src/metadata.rs53
-rw-r--r--third_party/rust/uniffi_macros/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi_macros/Cargo.toml10
-rw-r--r--third_party/rust/uniffi_macros/README.md81
-rw-r--r--third_party/rust/uniffi_macros/src/default.rs133
-rw-r--r--third_party/rust/uniffi_macros/src/enum_.rs308
-rw-r--r--third_party/rust/uniffi_macros/src/error.rs101
-rw-r--r--third_party/rust/uniffi_macros/src/export.rs211
-rw-r--r--third_party/rust/uniffi_macros/src/export/attributes.rs306
-rw-r--r--third_party/rust/uniffi_macros/src/export/callback_interface.rs204
-rw-r--r--third_party/rust/uniffi_macros/src/export/item.rs88
-rw-r--r--third_party/rust/uniffi_macros/src/export/scaffolding.rs123
-rw-r--r--third_party/rust/uniffi_macros/src/export/trait_interface.rs183
-rw-r--r--third_party/rust/uniffi_macros/src/export/utrait.rs23
-rw-r--r--third_party/rust/uniffi_macros/src/fnsig.rs248
-rw-r--r--third_party/rust/uniffi_macros/src/lib.rs73
-rw-r--r--third_party/rust/uniffi_macros/src/object.rs76
-rw-r--r--third_party/rust/uniffi_macros/src/record.rs113
-rw-r--r--third_party/rust/uniffi_macros/src/setup_scaffolding.rs57
-rw-r--r--third_party/rust/uniffi_macros/src/util.rs47
-rw-r--r--third_party/rust/uniffi_meta/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi_meta/Cargo.toml5
-rw-r--r--third_party/rust/uniffi_meta/README.md81
-rw-r--r--third_party/rust/uniffi_meta/src/ffi_names.rs13
-rw-r--r--third_party/rust/uniffi_meta/src/group.rs9
-rw-r--r--third_party/rust/uniffi_meta/src/lib.rs65
-rw-r--r--third_party/rust/uniffi_meta/src/metadata.rs13
-rw-r--r--third_party/rust/uniffi_meta/src/reader.rs148
-rw-r--r--third_party/rust/uniffi_meta/src/types.rs22
-rw-r--r--third_party/rust/uniffi_testing/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi_testing/Cargo.toml2
-rw-r--r--third_party/rust/uniffi_udl/.cargo-checksum.json2
-rw-r--r--third_party/rust/uniffi_udl/Cargo.toml12
-rw-r--r--third_party/rust/uniffi_udl/README.md81
-rw-r--r--third_party/rust/uniffi_udl/src/attributes.rs199
-rw-r--r--third_party/rust/uniffi_udl/src/collectors.rs18
-rw-r--r--third_party/rust/uniffi_udl/src/converters/callables.rs16
-rw-r--r--third_party/rust/uniffi_udl/src/converters/enum_.rs79
-rw-r--r--third_party/rust/uniffi_udl/src/converters/interface.rs6
-rw-r--r--third_party/rust/uniffi_udl/src/converters/mod.rs10
-rw-r--r--third_party/rust/uniffi_udl/src/finder.rs60
-rw-r--r--third_party/rust/uniffi_udl/src/lib.rs2
-rw-r--r--third_party/rust/uniffi_udl/src/literal.rs8
-rw-r--r--third_party/rust/uniffi_udl/src/resolver.rs1
-rw-r--r--third_party/rust/wasm-encoder/.cargo-checksum.json2
-rw-r--r--third_party/rust/wasm-encoder/Cargo.toml10
-rw-r--r--third_party/rust/wasm-encoder/src/component/names.rs2
-rw-r--r--third_party/rust/wasm-encoder/src/core/code.rs89
-rw-r--r--third_party/rust/wasm-smith/.cargo-checksum.json2
-rw-r--r--third_party/rust/wasm-smith/Cargo.toml18
-rw-r--r--third_party/rust/wasm-smith/src/component.rs53
-rw-r--r--third_party/rust/wasm-smith/src/config.rs108
-rw-r--r--third_party/rust/wasm-smith/src/core.rs806
-rw-r--r--third_party/rust/wasm-smith/src/core/code_builder.rs141
-rw-r--r--third_party/rust/wasm-smith/src/core/code_builder/no_traps.rs2
-rw-r--r--third_party/rust/wasm-smith/src/core/encode.rs41
-rw-r--r--third_party/rust/wasm-smith/src/core/terminate.rs30
-rw-r--r--third_party/rust/wasm-smith/src/lib.rs7
-rw-r--r--third_party/rust/wasm-smith/tests/available_imports.rs46
-rw-r--r--third_party/rust/wasm-smith/tests/common/mod.rs43
-rw-r--r--third_party/rust/wasm-smith/tests/core.rs3
-rw-r--r--third_party/rust/wasm-smith/tests/exports.rs147
-rw-r--r--third_party/rust/wast/.cargo-checksum.json2
-rw-r--r--third_party/rust/wast/Cargo.toml13
-rw-r--r--third_party/rust/wast/src/component/component.rs1
-rw-r--r--third_party/rust/wast/src/core/binary.rs124
-rw-r--r--third_party/rust/wast/src/core/expr.rs53
-rw-r--r--third_party/rust/wast/src/core/memory.rs2
-rw-r--r--third_party/rust/wast/src/core/module.rs1
-rw-r--r--third_party/rust/wast/src/core/resolve/deinline_import_export.rs2
-rw-r--r--third_party/rust/wast/src/core/table.rs1
-rw-r--r--third_party/rust/wast/src/lib.rs1
-rw-r--r--third_party/rust/wast/src/parser.rs12
-rw-r--r--third_party/rust/wast/src/wat.rs1
-rw-r--r--third_party/rust/webext-storage/.cargo-checksum.json2
-rw-r--r--third_party/rust/webext-storage/Cargo.toml4
-rw-r--r--third_party/rust/weedle2/.cargo-checksum.json2
-rw-r--r--third_party/rust/weedle2/Cargo.toml5
-rw-r--r--third_party/rust/weedle2/README.md2
-rw-r--r--third_party/rust/weedle2/src/common.rs45
-rw-r--r--third_party/rust/weedle2/src/dictionary.rs3
-rw-r--r--third_party/rust/weedle2/src/interface.rs4
-rw-r--r--third_party/rust/weedle2/src/lib.rs16
-rw-r--r--third_party/rust/weedle2/src/namespace.rs4
-rw-r--r--third_party/rust/weedle2/src/whitespace.rs1
-rw-r--r--third_party/rust/wgpu-core/.cargo-checksum.json2
-rw-r--r--third_party/rust/wgpu-core/Cargo.toml4
-rw-r--r--third_party/rust/wgpu-core/src/command/bundle.rs21
-rw-r--r--third_party/rust/wgpu-core/src/command/clear.rs7
-rw-r--r--third_party/rust/wgpu-core/src/command/compute.rs9
-rw-r--r--third_party/rust/wgpu-core/src/command/memory_init.rs11
-rw-r--r--third_party/rust/wgpu-core/src/command/render.rs15
-rw-r--r--third_party/rust/wgpu-core/src/command/transfer.rs19
-rw-r--r--third_party/rust/wgpu-core/src/device/global.rs33
-rw-r--r--third_party/rust/wgpu-core/src/device/life.rs15
-rw-r--r--third_party/rust/wgpu-core/src/device/mod.rs11
-rw-r--r--third_party/rust/wgpu-core/src/device/queue.rs17
-rw-r--r--third_party/rust/wgpu-core/src/device/resource.rs20
-rw-r--r--third_party/rust/wgpu-core/src/snatch.rs35
-rw-r--r--third_party/rust/wgpu-core/src/track/buffer.rs12
-rw-r--r--third_party/rust/wgpu-core/src/track/metadata.rs5
-rw-r--r--third_party/rust/wgpu-core/src/track/mod.rs50
-rw-r--r--third_party/rust/wgpu-core/src/track/texture.rs13
-rw-r--r--third_party/rust/wgpu-hal/.cargo-checksum.json2
-rw-r--r--third_party/rust/wgpu-hal/Cargo.toml12
-rw-r--r--third_party/rust/wgpu-hal/src/dx12/adapter.rs4
-rw-r--r--third_party/rust/wgpu-hal/src/dx12/command.rs4
-rw-r--r--third_party/rust/wgpu-hal/src/dx12/device.rs15
-rw-r--r--third_party/rust/wgpu-hal/src/dx12/instance.rs4
-rw-r--r--third_party/rust/wgpu-hal/src/dx12/mod.rs8
-rw-r--r--third_party/rust/wgpu-hal/src/empty.rs24
-rw-r--r--third_party/rust/wgpu-hal/src/gles/adapter.rs4
-rw-r--r--third_party/rust/wgpu-hal/src/gles/command.rs4
-rw-r--r--third_party/rust/wgpu-hal/src/gles/device.rs21
-rw-r--r--third_party/rust/wgpu-hal/src/gles/egl.rs8
-rw-r--r--third_party/rust/wgpu-hal/src/gles/queue.rs4
-rw-r--r--third_party/rust/wgpu-hal/src/gles/web.rs8
-rw-r--r--third_party/rust/wgpu-hal/src/gles/wgl.rs8
-rw-r--r--third_party/rust/wgpu-hal/src/lib.rs253
-rw-r--r--third_party/rust/wgpu-hal/src/metal/adapter.rs4
-rw-r--r--third_party/rust/wgpu-hal/src/metal/command.rs4
-rw-r--r--third_party/rust/wgpu-hal/src/metal/device.rs15
-rw-r--r--third_party/rust/wgpu-hal/src/metal/mod.rs8
-rw-r--r--third_party/rust/wgpu-hal/src/metal/surface.rs4
-rw-r--r--third_party/rust/wgpu-hal/src/vulkan/adapter.rs181
-rw-r--r--third_party/rust/wgpu-hal/src/vulkan/command.rs4
-rw-r--r--third_party/rust/wgpu-hal/src/vulkan/device.rs4
-rw-r--r--third_party/rust/wgpu-hal/src/vulkan/instance.rs8
-rw-r--r--third_party/rust/wgpu-hal/src/vulkan/mod.rs6
-rw-r--r--third_party/rust/wgpu-types/.cargo-checksum.json2
-rw-r--r--third_party/rust/wgpu-types/Cargo.toml6
-rw-r--r--third_party/rust/wgpu-types/src/lib.rs29
-rw-r--r--third_party/rust/zip/src/read.rs9
-rw-r--r--third_party/rust/zip/src/types.rs2
-rw-r--r--third_party/rust/zip/src/write.rs1
-rw-r--r--third_party/xsimd/include/xsimd/config/xsimd_arch.hpp28
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avx2_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avx512bw_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avx512cd_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avx512dq_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avx512er_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avx512f_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avx512ifma_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avx512pf_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avx512vbmi_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avx512vnni_avx512bw_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avx512vnni_avx512vbmi_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avx_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_avxvnni_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_batch.hpp4
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_fma3_avx2_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_fma3_avx_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_fma3_sse_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_fma4_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_generic_arch.hpp5
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_i8mm_neon64_register.hpp6
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_neon64_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_neon_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_rvv_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_sse2_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_sse3_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_sse4_1_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_sse4_2_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_ssse3_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_sve_register.hpp1
-rw-r--r--third_party/xsimd/include/xsimd/types/xsimd_wasm_register.hpp1
-rw-r--r--third_party/xsimd/moz.yaml4
-rw-r--r--third_party/zstd/COPYING339
-rw-r--r--third_party/zstd/LICENSE30
-rw-r--r--third_party/zstd/lib/.gitignore3
-rw-r--r--third_party/zstd/lib/BUCK232
-rw-r--r--third_party/zstd/lib/Makefile369
-rw-r--r--third_party/zstd/lib/README.md237
-rw-r--r--third_party/zstd/lib/common/allocations.h55
-rw-r--r--third_party/zstd/lib/common/bits.h200
-rw-r--r--third_party/zstd/lib/common/bitstream.h457
-rw-r--r--third_party/zstd/lib/common/compiler.h450
-rw-r--r--third_party/zstd/lib/common/cpu.h249
-rw-r--r--third_party/zstd/lib/common/debug.c30
-rw-r--r--third_party/zstd/lib/common/debug.h116
-rw-r--r--third_party/zstd/lib/common/entropy_common.c340
-rw-r--r--third_party/zstd/lib/common/error_private.c63
-rw-r--r--third_party/zstd/lib/common/error_private.h168
-rw-r--r--third_party/zstd/lib/common/fse.h640
-rw-r--r--third_party/zstd/lib/common/fse_decompress.c313
-rw-r--r--third_party/zstd/lib/common/huf.h286
-rw-r--r--third_party/zstd/lib/common/mem.h426
-rw-r--r--third_party/zstd/lib/common/pool.c371
-rw-r--r--third_party/zstd/lib/common/pool.h90
-rw-r--r--third_party/zstd/lib/common/portability_macros.h158
-rw-r--r--third_party/zstd/lib/common/threading.c182
-rw-r--r--third_party/zstd/lib/common/threading.h150
-rw-r--r--third_party/zstd/lib/common/xxhash.c18
-rw-r--r--third_party/zstd/lib/common/xxhash.h7020
-rw-r--r--third_party/zstd/lib/common/zstd_common.c48
-rw-r--r--third_party/zstd/lib/common/zstd_deps.h111
-rw-r--r--third_party/zstd/lib/common/zstd_internal.h392
-rw-r--r--third_party/zstd/lib/common/zstd_trace.h163
-rw-r--r--third_party/zstd/lib/decompress/huf_decompress.c1944
-rw-r--r--third_party/zstd/lib/decompress/huf_decompress_amd64.S595
-rw-r--r--third_party/zstd/lib/decompress/zstd_ddict.c244
-rw-r--r--third_party/zstd/lib/decompress/zstd_ddict.h44
-rw-r--r--third_party/zstd/lib/decompress/zstd_decompress.c2407
-rw-r--r--third_party/zstd/lib/decompress/zstd_decompress_block.c2215
-rw-r--r--third_party/zstd/lib/decompress/zstd_decompress_block.h73
-rw-r--r--third_party/zstd/lib/decompress/zstd_decompress_internal.h240
-rw-r--r--third_party/zstd/lib/libzstd.mk235
-rw-r--r--third_party/zstd/lib/libzstd.pc.in16
-rw-r--r--third_party/zstd/lib/module.modulemap35
-rw-r--r--third_party/zstd/lib/zdict.h474
-rw-r--r--third_party/zstd/lib/zstd.h3089
-rw-r--r--third_party/zstd/lib/zstd_errors.h114
-rw-r--r--third_party/zstd/moz.build107
-rw-r--r--third_party/zstd/moz.yaml44
-rw-r--r--third_party/zstd/preprocess_asm.py25
1563 files changed, 118693 insertions, 63215 deletions
diff --git a/third_party/aom/CHANGELOG b/third_party/aom/CHANGELOG
index b5c1afbba2..b8a3e4a6a5 100644
--- a/third_party/aom/CHANGELOG
+++ b/third_party/aom/CHANGELOG
@@ -1,3 +1,22 @@
+2024-03-08 v3.8.2
+ This release includes several bug fixes. This release is ABI
+ compatible with the last release. See
+ https://aomedia.googlesource.com/aom/+log/v3.8.1..v3.8.2 for all the
+ commits in this release.
+
+ - Bug Fixes
+ * aomedia:3523: SIGFPE in av1_twopass_postencode_update()
+ pass2_strategy.c:4261.
+ * aomedia:3535, b/317646516: Over reads in aom_convolve_copy_neon().
+ * aomedia:3543: invalid feature modifier when compiling
+ aom_dsp/arm/aom_convolve8_neon_i8mm.c on Debian 10 with arm64
+ architecture.
+ * aomedia:3545: Failed to parse configurations due to inconsistent
+ elements between two arrays "av1_ctrl_args" and "av1_arg_ctrl_map"
+ in aomenc.c.
+ * oss-fuzz:66474, b/319140742: Integer-overflow in search_wiener.
+ * Zero initialize an array in cdef search.
+
2024-01-17 v3.8.1
This release includes several bug fixes. This release is ABI
compatible with the last release. See
diff --git a/third_party/aom/CMakeLists.txt b/third_party/aom/CMakeLists.txt
index a02b220bdb..00a7e2bca9 100644
--- a/third_party/aom/CMakeLists.txt
+++ b/third_party/aom/CMakeLists.txt
@@ -59,7 +59,7 @@ endif()
#
# We set SO_FILE_VERSION = [c-a].a.r
set(LT_CURRENT 11)
-set(LT_REVISION 1)
+set(LT_REVISION 2)
set(LT_AGE 8)
math(EXPR SO_VERSION "${LT_CURRENT} - ${LT_AGE}")
set(SO_FILE_VERSION "${SO_VERSION}.${LT_AGE}.${LT_REVISION}")
@@ -374,6 +374,7 @@ file(WRITE "${AOM_GEN_SRC_DIR}/usage_exit.c"
#
if(ENABLE_EXAMPLES OR ENABLE_TESTS OR ENABLE_TOOLS)
add_library(aom_common_app_util OBJECT ${AOM_COMMON_APP_UTIL_SOURCES})
+ add_library(aom_usage_exit OBJECT "${AOM_GEN_SRC_DIR}/usage_exit.c")
set_property(TARGET ${example} PROPERTY FOLDER examples)
if(CONFIG_AV1_DECODER)
add_library(aom_decoder_app_util OBJECT ${AOM_DECODER_APP_UTIL_SOURCES})
@@ -508,10 +509,10 @@ if(CONFIG_AV1_ENCODER)
# aom_entropy_optimizer.c won't work on macos, but dragging in all the
# helper machinery allows the link to succeed.
add_executable(aom_entropy_optimizer
- "${AOM_GEN_SRC_DIR}/usage_exit.c"
"${AOM_ROOT}/tools/aom_entropy_optimizer.c"
$<TARGET_OBJECTS:aom_common_app_util>
- $<TARGET_OBJECTS:aom_encoder_app_util>)
+ $<TARGET_OBJECTS:aom_encoder_app_util>
+ $<TARGET_OBJECTS:aom_usage_exit>)
# Maintain a list of encoder tool targets.
list(APPEND AOM_ENCODER_TOOL_TARGETS aom_entropy_optimizer)
@@ -661,12 +662,12 @@ endif()
if(ENABLE_TOOLS)
if(CONFIG_AV1_DECODER)
- add_executable(dump_obu "${AOM_GEN_SRC_DIR}/usage_exit.c"
- "${AOM_ROOT}/tools/dump_obu.cc"
+ add_executable(dump_obu "${AOM_ROOT}/tools/dump_obu.cc"
"${AOM_ROOT}/tools/obu_parser.cc"
"${AOM_ROOT}/tools/obu_parser.h"
$<TARGET_OBJECTS:aom_common_app_util>
- $<TARGET_OBJECTS:aom_decoder_app_util>)
+ $<TARGET_OBJECTS:aom_decoder_app_util>
+ $<TARGET_OBJECTS:aom_usage_exit>)
list(APPEND AOM_TOOL_TARGETS dump_obu)
list(APPEND AOM_APP_TARGETS dump_obu)
@@ -825,7 +826,8 @@ if(BUILD_SHARED_LIBS)
# Clang's AddressSanitizer documentation says "When linking shared libraries,
# the AddressSanitizer run-time is not linked, so -Wl,-z,defs may cause link
# errors (don't use it with AddressSanitizer)." See
- # https://clang.llvm.org/docs/AddressSanitizer.html#usage.
+ # https://clang.llvm.org/docs/AddressSanitizer.html#usage. Similarly, see
+ # https://clang.llvm.org/docs/MemorySanitizer.html#usage.
if(NOT WIN32
AND NOT APPLE
AND NOT (CMAKE_C_COMPILER_ID MATCHES "Clang" AND SANITIZE))
@@ -843,12 +845,6 @@ if(BUILD_SHARED_LIBS)
setup_exports_target()
endif()
-# Do not allow implicit vector type conversions on Clang builds (this is already
-# the default on GCC builds).
-if(CMAKE_C_COMPILER_ID MATCHES "Clang")
- append_compiler_flag("-flax-vector-conversions=none")
-endif()
-
# Handle user supplied compile and link flags last to ensure they're obeyed.
set_user_flags()
diff --git a/third_party/aom/README.md b/third_party/aom/README.md
index 4e2eb2756c..f81e13e9bd 100644
--- a/third_party/aom/README.md
+++ b/third_party/aom/README.md
@@ -46,17 +46,23 @@ README.md {#LREADME}
### Prerequisites {#prerequisites}
- 1. [CMake](https://cmake.org). See CMakeLists.txt for the minimum version
- required.
- 2. [Git](https://git-scm.com/).
- 3. [Perl](https://www.perl.org/).
- 4. For x86 targets, [yasm](http://yasm.tortall.net/), which is preferred, or a
- recent version of [nasm](http://www.nasm.us/). If you download yasm with
- the intention to work with Visual Studio, please download win32.exe or
- win64.exe and rename it into yasm.exe. DO NOT download or use vsyasm.exe.
- 5. Building the documentation requires
+1. [CMake](https://cmake.org). See CMakeLists.txt for the minimum version
+ required.
+2. [Git](https://git-scm.com/).
+3. A modern C compiler. gcc 6+, clang 7+, Microsoft Visual Studio 2019+ or
+ the latest version of MinGW-w64 (clang64 or ucrt toolchains) are
+ recommended. A C++ compiler is necessary to build the unit tests and some
+ features contained in the examples.
+4. [Perl](https://www.perl.org/).
+5. For x86 targets, [yasm](http://yasm.tortall.net/) or a recent version (2.14
+ or later) of [nasm](http://www.nasm.us/). (If both yasm and nasm are
+ present, yasm will be used by default. Pass -DENABLE_NASM=ON to cmake to
+ select nasm.) If you download yasm with the intention to work with Visual
+ Studio, please download win32.exe or win64.exe and rename it into yasm.exe.
+ DO NOT download or use vsyasm.exe.
+6. Building the documentation requires
[doxygen version 1.8.10 or newer](http://doxygen.org).
- 6. Emscripten builds require the portable
+7. Emscripten builds require the portable
[EMSDK](https://kripken.github.io/emscripten-site/index.html).
### Get the code {#get-the-code}
diff --git a/third_party/aom/aom/aom_encoder.h b/third_party/aom/aom/aom_encoder.h
index 6a6254dafe..9bdadd6938 100644
--- a/third_party/aom/aom/aom_encoder.h
+++ b/third_party/aom/aom/aom_encoder.h
@@ -1044,6 +1044,11 @@ aom_fixed_buf_t *aom_codec_get_global_headers(aom_codec_ctx_t *ctx);
* Interface is not an encoder interface.
* \retval #AOM_CODEC_INVALID_PARAM
* A parameter was NULL, the image format is unsupported, etc.
+ *
+ * \note
+ * `duration` is of the unsigned long type, which can be 32 or 64 bits.
+ * `duration` must be less than or equal to UINT32_MAX so that its range is
+ * independent of the size of unsigned long.
*/
aom_codec_err_t aom_codec_encode(aom_codec_ctx_t *ctx, const aom_image_t *img,
aom_codec_pts_t pts, unsigned long duration,
diff --git a/third_party/aom/aom/aomdx.h b/third_party/aom/aom/aomdx.h
index 02ea19597c..2dd7bb3375 100644
--- a/third_party/aom/aom/aomdx.h
+++ b/third_party/aom/aom/aomdx.h
@@ -234,8 +234,11 @@ enum aom_dec_control_id {
*/
AV1D_GET_IMG_FORMAT,
- /*!\brief Codec control function to get the size of the tile, unsigned int*
- * parameter
+ /*!\brief Codec control function to get the width and height (in pixels) of
+ * the tiles in a tile list, unsigned int* parameter
+ *
+ * Tile width is in the high 16 bits of the output value, and tile height is
+ * in the low 16 bits of the output value.
*/
AV1D_GET_TILE_SIZE,
diff --git a/third_party/aom/aom/src/aom_encoder.c b/third_party/aom/aom/src/aom_encoder.c
index 70e0b75bcd..f188567b94 100644
--- a/third_party/aom/aom/src/aom_encoder.c
+++ b/third_party/aom/aom/src/aom_encoder.c
@@ -23,6 +23,7 @@
#endif
#include <limits.h>
+#include <stdint.h>
#include <string.h>
#include "aom/aom_encoder.h"
@@ -178,6 +179,10 @@ aom_codec_err_t aom_codec_encode(aom_codec_ctx_t *ctx, const aom_image_t *img,
else if (img && ((img->fmt & AOM_IMG_FMT_HIGHBITDEPTH) != 0) !=
((ctx->init_flags & AOM_CODEC_USE_HIGHBITDEPTH) != 0)) {
res = AOM_CODEC_INVALID_PARAM;
+#if ULONG_MAX > UINT32_MAX
+ } else if (duration > UINT32_MAX) {
+ res = AOM_CODEC_INVALID_PARAM;
+#endif
} else {
/* Execute in a normalized floating point environment, if the platform
* requires it.
diff --git a/third_party/aom/aom/src/aom_image.c b/third_party/aom/aom/src/aom_image.c
index 8e94d5dd4f..3b1c33d056 100644
--- a/third_party/aom/aom/src/aom_image.c
+++ b/third_party/aom/aom/src/aom_image.c
@@ -41,6 +41,8 @@ static aom_image_t *img_alloc_helper(
if (img != NULL) memset(img, 0, sizeof(aom_image_t));
+ if (fmt == AOM_IMG_FMT_NONE) goto fail;
+
/* Treat align==0 like align==1 */
if (!buf_align) buf_align = 1;
diff --git a/third_party/aom/aom_dsp/aom_dsp.cmake b/third_party/aom/aom_dsp/aom_dsp.cmake
index 653f690741..de987cbd23 100644
--- a/third_party/aom/aom_dsp/aom_dsp.cmake
+++ b/third_party/aom/aom_dsp/aom_dsp.cmake
@@ -52,15 +52,12 @@ list(APPEND AOM_DSP_COMMON_SOURCES
list(APPEND AOM_DSP_COMMON_ASM_SSE2
"${AOM_ROOT}/aom_dsp/x86/aom_high_subpixel_8t_sse2.asm"
"${AOM_ROOT}/aom_dsp/x86/aom_high_subpixel_bilinear_sse2.asm"
- "${AOM_ROOT}/aom_dsp/x86/aom_subpixel_8t_sse2.asm"
- "${AOM_ROOT}/aom_dsp/x86/aom_subpixel_bilinear_sse2.asm"
"${AOM_ROOT}/aom_dsp/x86/highbd_intrapred_asm_sse2.asm"
"${AOM_ROOT}/aom_dsp/x86/intrapred_asm_sse2.asm"
"${AOM_ROOT}/aom_dsp/x86/inv_wht_sse2.asm")
list(APPEND AOM_DSP_COMMON_INTRIN_SSE2
"${AOM_ROOT}/aom_dsp/x86/aom_convolve_copy_sse2.c"
- "${AOM_ROOT}/aom_dsp/x86/aom_subpixel_8t_intrin_sse2.c"
"${AOM_ROOT}/aom_dsp/x86/aom_asm_stubs.c"
"${AOM_ROOT}/aom_dsp/x86/convolve.h"
"${AOM_ROOT}/aom_dsp/x86/convolve_sse2.h"
@@ -145,6 +142,9 @@ if(CONFIG_AV1_HIGHBITDEPTH)
"${AOM_ROOT}/aom_dsp/arm/highbd_convolve8_neon.c"
"${AOM_ROOT}/aom_dsp/arm/highbd_intrapred_neon.c"
"${AOM_ROOT}/aom_dsp/arm/highbd_loopfilter_neon.c")
+
+ list(APPEND AOM_DSP_COMMON_INTRIN_SVE
+ "${AOM_ROOT}/aom_dsp/arm/highbd_convolve8_sve.c")
endif()
if(CONFIG_AV1_DECODER)
@@ -200,7 +200,8 @@ if(CONFIG_AV1_ENCODER)
"${AOM_ROOT}/aom_dsp/flow_estimation/x86/disflow_sse4.c")
list(APPEND AOM_DSP_ENCODER_INTRIN_AVX2
- "${AOM_ROOT}/aom_dsp/flow_estimation/x86/corner_match_avx2.c")
+ "${AOM_ROOT}/aom_dsp/flow_estimation/x86/corner_match_avx2.c"
+ "${AOM_ROOT}/aom_dsp/flow_estimation/x86/disflow_avx2.c")
list(APPEND AOM_DSP_ENCODER_INTRIN_NEON
"${AOM_ROOT}/aom_dsp/flow_estimation/arm/disflow_neon.c")
@@ -208,7 +209,6 @@ if(CONFIG_AV1_ENCODER)
list(APPEND AOM_DSP_ENCODER_ASM_SSE2 "${AOM_ROOT}/aom_dsp/x86/sad4d_sse2.asm"
"${AOM_ROOT}/aom_dsp/x86/sad_sse2.asm"
- "${AOM_ROOT}/aom_dsp/x86/subpel_variance_sse2.asm"
"${AOM_ROOT}/aom_dsp/x86/subtract_sse2.asm")
list(APPEND AOM_DSP_ENCODER_ASM_SSE2_X86_64
@@ -227,6 +227,9 @@ if(CONFIG_AV1_ENCODER)
"${AOM_ROOT}/aom_dsp/x86/variance_sse2.c"
"${AOM_ROOT}/aom_dsp/x86/jnt_sad_sse2.c")
+ list(APPEND AOM_DSP_ENCODER_ASM_SSSE3
+ "${AOM_ROOT}/aom_dsp/x86/subpel_variance_ssse3.asm")
+
list(APPEND AOM_DSP_ENCODER_ASM_SSSE3_X86_64
"${AOM_ROOT}/aom_dsp/x86/fwd_txfm_ssse3_x86_64.asm"
"${AOM_ROOT}/aom_dsp/x86/quantize_ssse3_x86_64.asm")
@@ -493,6 +496,8 @@ function(setup_aom_dsp_targets)
endif()
if(HAVE_SVE)
+ add_intrinsics_object_library("${AOM_SVE_FLAG}" "sve" "aom_dsp_common"
+ "AOM_DSP_COMMON_INTRIN_SVE")
if(CONFIG_AV1_ENCODER)
add_intrinsics_object_library("${AOM_SVE_FLAG}" "sve" "aom_dsp_encoder"
"AOM_DSP_ENCODER_INTRIN_SVE")
diff --git a/third_party/aom/aom_dsp/aom_dsp_rtcd_defs.pl b/third_party/aom/aom_dsp/aom_dsp_rtcd_defs.pl
index 7bb156ac59..7e746e9cb9 100755
--- a/third_party/aom/aom_dsp/aom_dsp_rtcd_defs.pl
+++ b/third_party/aom/aom_dsp/aom_dsp_rtcd_defs.pl
@@ -498,8 +498,8 @@ add_proto qw/void aom_convolve8_horiz/, "const uint8_t *src, ptrdiff_t
add_proto qw/void aom_convolve8_vert/, "const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst, ptrdiff_t dst_stride, const int16_t *filter_x, int x_step_q4, const int16_t *filter_y, int y_step_q4, int w, int h";
specialize qw/aom_convolve_copy neon sse2 avx2/;
-specialize qw/aom_convolve8_horiz neon neon_dotprod neon_i8mm sse2 ssse3/, "$avx2_ssse3";
-specialize qw/aom_convolve8_vert neon neon_dotprod neon_i8mm sse2 ssse3/, "$avx2_ssse3";
+specialize qw/aom_convolve8_horiz neon neon_dotprod neon_i8mm ssse3/, "$avx2_ssse3";
+specialize qw/aom_convolve8_vert neon neon_dotprod neon_i8mm ssse3/, "$avx2_ssse3";
add_proto qw/void aom_scaled_2d/, "const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst, ptrdiff_t dst_stride, const InterpKernel *filter, int x0_q4, int x_step_q4, int y0_q4, int y_step_q4, int w, int h";
specialize qw/aom_scaled_2d ssse3 neon/;
@@ -509,10 +509,10 @@ if (aom_config("CONFIG_AV1_HIGHBITDEPTH") eq "yes") {
specialize qw/aom_highbd_convolve_copy sse2 avx2 neon/;
add_proto qw/void aom_highbd_convolve8_horiz/, "const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst, ptrdiff_t dst_stride, const int16_t *filter_x, int x_step_q4, const int16_t *filter_y, int y_step_q4, int w, int h, int bd";
- specialize qw/aom_highbd_convolve8_horiz sse2 avx2 neon/;
+ specialize qw/aom_highbd_convolve8_horiz sse2 avx2 neon sve/;
add_proto qw/void aom_highbd_convolve8_vert/, "const uint8_t *src, ptrdiff_t src_stride, uint8_t *dst, ptrdiff_t dst_stride, const int16_t *filter_x, int x_step_q4, const int16_t *filter_y, int y_step_q4, int w, int h, int bd";
- specialize qw/aom_highbd_convolve8_vert sse2 avx2 neon/;
+ specialize qw/aom_highbd_convolve8_vert sse2 avx2 neon sve/;
}
#
@@ -1087,7 +1087,7 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
specialize qw/aom_sad_skip_16x32x4d avx2 sse2 neon neon_dotprod/;
specialize qw/aom_sad_skip_16x16x4d avx2 sse2 neon neon_dotprod/;
specialize qw/aom_sad_skip_16x8x4d avx2 sse2 neon neon_dotprod/;
- specialize qw/aom_sad_skip_16x4x4d neon neon_dotprod/;
+ specialize qw/aom_sad_skip_16x4x4d avx2 neon neon_dotprod/;
specialize qw/aom_sad_skip_8x32x4d sse2 neon/;
specialize qw/aom_sad_skip_8x16x4d sse2 neon/;
specialize qw/aom_sad_skip_8x8x4d sse2 neon/;
@@ -1116,7 +1116,7 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
specialize qw/aom_sad64x16x3d avx2 neon neon_dotprod/;
specialize qw/aom_sad32x8x3d avx2 neon neon_dotprod/;
specialize qw/aom_sad16x64x3d avx2 neon neon_dotprod/;
- specialize qw/aom_sad16x4x3d neon neon_dotprod/;
+ specialize qw/aom_sad16x4x3d avx2 neon neon_dotprod/;
specialize qw/aom_sad8x32x3d neon/;
specialize qw/aom_sad4x16x3d neon/;
@@ -1264,8 +1264,6 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
add_proto qw/int aom_vector_var/, "const int16_t *ref, const int16_t *src, int bwl";
specialize qw/aom_vector_var avx2 sse4_1 neon sve/;
- # TODO(kyslov@) bring back SSE2 by extending it to 128 block size
- #specialize qw/aom_vector_var neon sse2/;
#
# hamadard transform and satd for implmenting temporal dependency model
@@ -1357,6 +1355,11 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
specialize "aom_highbd_${bd}_mse16x8", qw/neon neon_dotprod/;
specialize "aom_highbd_${bd}_mse8x16", qw/neon neon_dotprod/;
specialize "aom_highbd_${bd}_mse8x8", qw/sse2 neon neon_dotprod/;
+ } elsif ($bd eq 10) {
+ specialize "aom_highbd_${bd}_mse16x16", qw/avx2 sse2 neon sve/;
+ specialize "aom_highbd_${bd}_mse16x8", qw/neon sve/;
+ specialize "aom_highbd_${bd}_mse8x16", qw/neon sve/;
+ specialize "aom_highbd_${bd}_mse8x8", qw/sse2 neon sve/;
} else {
specialize "aom_highbd_${bd}_mse16x16", qw/sse2 neon sve/;
specialize "aom_highbd_${bd}_mse16x8", qw/neon sve/;
@@ -1406,39 +1409,39 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
specialize qw/aom_variance4x8 sse2 neon neon_dotprod/;
specialize qw/aom_variance4x4 sse2 neon neon_dotprod/;
- specialize qw/aom_sub_pixel_variance128x128 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance128x64 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance64x128 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance64x64 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance64x32 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance32x64 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance32x32 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance32x16 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance16x32 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance16x16 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance16x8 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance8x16 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance8x8 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance8x4 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance4x8 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance4x4 neon sse2 ssse3/;
-
- specialize qw/aom_sub_pixel_avg_variance128x128 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance128x64 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance64x128 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance64x64 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance64x32 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance32x64 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance32x32 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance32x16 avx2 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance16x32 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance16x16 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance16x8 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance8x16 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance8x8 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance8x4 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance4x8 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance4x4 neon sse2 ssse3/;
+ specialize qw/aom_sub_pixel_variance128x128 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance128x64 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance64x128 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance64x64 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance64x32 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance32x64 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance32x32 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance32x16 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance16x32 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance16x16 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance16x8 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance8x16 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance8x8 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance8x4 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance4x8 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance4x4 neon ssse3/;
+
+ specialize qw/aom_sub_pixel_avg_variance128x128 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance128x64 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance64x128 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance64x64 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance64x32 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance32x64 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance32x32 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance32x16 avx2 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance16x32 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance16x16 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance16x8 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance8x16 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance8x8 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance8x4 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance4x8 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance4x4 neon ssse3/;
if (aom_config("CONFIG_REALTIME_ONLY") ne "yes") {
specialize qw/aom_variance4x16 neon neon_dotprod sse2/;
@@ -1448,18 +1451,18 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
specialize qw/aom_variance16x64 neon neon_dotprod sse2 avx2/;
specialize qw/aom_variance64x16 neon neon_dotprod sse2 avx2/;
- specialize qw/aom_sub_pixel_variance4x16 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance16x4 neon avx2 sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance8x32 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance32x8 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance16x64 neon avx2 sse2 ssse3/;
- specialize qw/aom_sub_pixel_variance64x16 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance4x16 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance16x4 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance8x32 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance32x8 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance16x64 neon sse2 ssse3/;
- specialize qw/aom_sub_pixel_avg_variance64x16 neon sse2 ssse3/;
+ specialize qw/aom_sub_pixel_variance4x16 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance16x4 neon avx2 ssse3/;
+ specialize qw/aom_sub_pixel_variance8x32 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance32x8 neon ssse3/;
+ specialize qw/aom_sub_pixel_variance16x64 neon avx2 ssse3/;
+ specialize qw/aom_sub_pixel_variance64x16 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance4x16 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance16x4 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance8x32 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance32x8 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance16x64 neon ssse3/;
+ specialize qw/aom_sub_pixel_avg_variance64x16 neon ssse3/;
specialize qw/aom_dist_wtd_sub_pixel_avg_variance4x16 neon ssse3/;
specialize qw/aom_dist_wtd_sub_pixel_avg_variance16x4 neon ssse3/;
@@ -1789,11 +1792,14 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
# Flow estimation library
if (aom_config("CONFIG_REALTIME_ONLY") ne "yes") {
- add_proto qw/double av1_compute_cross_correlation/, "const unsigned char *frame1, int stride1, int x1, int y1, const unsigned char *frame2, int stride2, int x2, int y2";
- specialize qw/av1_compute_cross_correlation sse4_1 avx2/;
+ add_proto qw/bool aom_compute_mean_stddev/, "const unsigned char *frame, int stride, int x, int y, double *mean, double *one_over_stddev";
+ specialize qw/aom_compute_mean_stddev sse4_1 avx2/;
+
+ add_proto qw/double aom_compute_correlation/, "const unsigned char *frame1, int stride1, int x1, int y1, double mean1, double one_over_stddev1, const unsigned char *frame2, int stride2, int x2, int y2, double mean2, double one_over_stddev2";
+ specialize qw/aom_compute_correlation sse4_1 avx2/;
add_proto qw/void aom_compute_flow_at_point/, "const uint8_t *src, const uint8_t *ref, int x, int y, int width, int height, int stride, double *u, double *v";
- specialize qw/aom_compute_flow_at_point sse4_1 neon/;
+ specialize qw/aom_compute_flow_at_point sse4_1 avx2 neon/;
}
} # CONFIG_AV1_ENCODER
diff --git a/third_party/aom/aom_dsp/arm/aom_convolve8_neon_dotprod.c b/third_party/aom/aom_dsp/arm/aom_convolve8_neon_dotprod.c
index ac0a6efd00..c82125ba17 100644
--- a/third_party/aom/aom_dsp/arm/aom_convolve8_neon_dotprod.c
+++ b/third_party/aom/aom_dsp/arm/aom_convolve8_neon_dotprod.c
@@ -267,8 +267,6 @@ void aom_convolve8_vert_neon_dotprod(const uint8_t *src, ptrdiff_t src_stride,
const int32x4_t correction = vdupq_n_s32((int32_t)vaddvq_s16(correct_tmp));
const uint8x8_t range_limit = vdup_n_u8(128);
const uint8x16x3_t merge_block_tbl = vld1q_u8_x3(dot_prod_merge_block_tbl);
- uint8x8_t t0, t1, t2, t3, t4, t5, t6;
- int8x8_t s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10;
int8x16x2_t samples_LUT;
assert((intptr_t)dst % 4 == 0);
@@ -282,46 +280,39 @@ void aom_convolve8_vert_neon_dotprod(const uint8_t *src, ptrdiff_t src_stride,
if (w == 4) {
const uint8x16_t tran_concat_tbl = vld1q_u8(dot_prod_tran_concat_tbl);
- int8x16_t s0123, s1234, s2345, s3456, s4567, s5678, s6789, s78910;
- int16x4_t d0, d1, d2, d3;
- uint8x8_t d01, d23;
+ uint8x8_t t0, t1, t2, t3, t4, t5, t6;
load_u8_8x7(src, src_stride, &t0, &t1, &t2, &t3, &t4, &t5, &t6);
src += 7 * src_stride;
/* Clamp sample range to [-128, 127] for 8-bit signed dot product. */
- s0 = vreinterpret_s8_u8(vsub_u8(t0, range_limit));
- s1 = vreinterpret_s8_u8(vsub_u8(t1, range_limit));
- s2 = vreinterpret_s8_u8(vsub_u8(t2, range_limit));
- s3 = vreinterpret_s8_u8(vsub_u8(t3, range_limit));
- s4 = vreinterpret_s8_u8(vsub_u8(t4, range_limit));
- s5 = vreinterpret_s8_u8(vsub_u8(t5, range_limit));
- s6 = vreinterpret_s8_u8(vsub_u8(t6, range_limit));
- s7 = vdup_n_s8(0);
- s8 = vdup_n_s8(0);
- s9 = vdup_n_s8(0);
+ int8x8_t s0 = vreinterpret_s8_u8(vsub_u8(t0, range_limit));
+ int8x8_t s1 = vreinterpret_s8_u8(vsub_u8(t1, range_limit));
+ int8x8_t s2 = vreinterpret_s8_u8(vsub_u8(t2, range_limit));
+ int8x8_t s3 = vreinterpret_s8_u8(vsub_u8(t3, range_limit));
+ int8x8_t s4 = vreinterpret_s8_u8(vsub_u8(t4, range_limit));
+ int8x8_t s5 = vreinterpret_s8_u8(vsub_u8(t5, range_limit));
+ int8x8_t s6 = vreinterpret_s8_u8(vsub_u8(t6, range_limit));
/* This operation combines a conventional transpose and the sample permute
* (see horizontal case) required before computing the dot product.
*/
+ int8x16_t s0123, s1234, s2345, s3456;
transpose_concat_4x4(s0, s1, s2, s3, &s0123, tran_concat_tbl);
transpose_concat_4x4(s1, s2, s3, s4, &s1234, tran_concat_tbl);
transpose_concat_4x4(s2, s3, s4, s5, &s2345, tran_concat_tbl);
transpose_concat_4x4(s3, s4, s5, s6, &s3456, tran_concat_tbl);
- transpose_concat_4x4(s4, s5, s6, s7, &s4567, tran_concat_tbl);
- transpose_concat_4x4(s5, s6, s7, s8, &s5678, tran_concat_tbl);
- transpose_concat_4x4(s6, s7, s8, s9, &s6789, tran_concat_tbl);
do {
uint8x8_t t7, t8, t9, t10;
-
load_u8_8x4(src, src_stride, &t7, &t8, &t9, &t10);
- s7 = vreinterpret_s8_u8(vsub_u8(t7, range_limit));
- s8 = vreinterpret_s8_u8(vsub_u8(t8, range_limit));
- s9 = vreinterpret_s8_u8(vsub_u8(t9, range_limit));
- s10 = vreinterpret_s8_u8(vsub_u8(t10, range_limit));
+ int8x8_t s7 = vreinterpret_s8_u8(vsub_u8(t7, range_limit));
+ int8x8_t s8 = vreinterpret_s8_u8(vsub_u8(t8, range_limit));
+ int8x8_t s9 = vreinterpret_s8_u8(vsub_u8(t9, range_limit));
+ int8x8_t s10 = vreinterpret_s8_u8(vsub_u8(t10, range_limit));
+ int8x16_t s4567, s5678, s6789, s78910;
transpose_concat_4x4(s7, s8, s9, s10, &s78910, tran_concat_tbl);
/* Merge new data into block from previous iteration. */
@@ -331,12 +322,13 @@ void aom_convolve8_vert_neon_dotprod(const uint8_t *src, ptrdiff_t src_stride,
s5678 = vqtbl2q_s8(samples_LUT, merge_block_tbl.val[1]);
s6789 = vqtbl2q_s8(samples_LUT, merge_block_tbl.val[2]);
- d0 = convolve8_4_sdot_partial(s0123, s4567, correction, filter);
- d1 = convolve8_4_sdot_partial(s1234, s5678, correction, filter);
- d2 = convolve8_4_sdot_partial(s2345, s6789, correction, filter);
- d3 = convolve8_4_sdot_partial(s3456, s78910, correction, filter);
- d01 = vqrshrun_n_s16(vcombine_s16(d0, d1), FILTER_BITS);
- d23 = vqrshrun_n_s16(vcombine_s16(d2, d3), FILTER_BITS);
+ int16x4_t d0 = convolve8_4_sdot_partial(s0123, s4567, correction, filter);
+ int16x4_t d1 = convolve8_4_sdot_partial(s1234, s5678, correction, filter);
+ int16x4_t d2 = convolve8_4_sdot_partial(s2345, s6789, correction, filter);
+ int16x4_t d3 =
+ convolve8_4_sdot_partial(s3456, s78910, correction, filter);
+ uint8x8_t d01 = vqrshrun_n_s16(vcombine_s16(d0, d1), FILTER_BITS);
+ uint8x8_t d23 = vqrshrun_n_s16(vcombine_s16(d2, d3), FILTER_BITS);
store_u8x4_strided_x2(dst + 0 * dst_stride, dst_stride, d01);
store_u8x4_strided_x2(dst + 2 * dst_stride, dst_stride, d23);
@@ -354,37 +346,30 @@ void aom_convolve8_vert_neon_dotprod(const uint8_t *src, ptrdiff_t src_stride,
} while (h != 0);
} else {
const uint8x16x2_t tran_concat_tbl = vld1q_u8_x2(dot_prod_tran_concat_tbl);
- int8x16_t s0123_lo, s0123_hi, s1234_lo, s1234_hi, s2345_lo, s2345_hi,
- s3456_lo, s3456_hi, s4567_lo, s4567_hi, s5678_lo, s5678_hi, s6789_lo,
- s6789_hi, s78910_lo, s78910_hi;
- uint8x8_t d0, d1, d2, d3;
- const uint8_t *s;
- uint8_t *d;
- int height;
do {
- height = h;
- s = src;
- d = dst;
+ int height = h;
+ const uint8_t *s = src;
+ uint8_t *d = dst;
+ uint8x8_t t0, t1, t2, t3, t4, t5, t6;
load_u8_8x7(s, src_stride, &t0, &t1, &t2, &t3, &t4, &t5, &t6);
s += 7 * src_stride;
/* Clamp sample range to [-128, 127] for 8-bit signed dot product. */
- s0 = vreinterpret_s8_u8(vsub_u8(t0, range_limit));
- s1 = vreinterpret_s8_u8(vsub_u8(t1, range_limit));
- s2 = vreinterpret_s8_u8(vsub_u8(t2, range_limit));
- s3 = vreinterpret_s8_u8(vsub_u8(t3, range_limit));
- s4 = vreinterpret_s8_u8(vsub_u8(t4, range_limit));
- s5 = vreinterpret_s8_u8(vsub_u8(t5, range_limit));
- s6 = vreinterpret_s8_u8(vsub_u8(t6, range_limit));
- s7 = vdup_n_s8(0);
- s8 = vdup_n_s8(0);
- s9 = vdup_n_s8(0);
+ int8x8_t s0 = vreinterpret_s8_u8(vsub_u8(t0, range_limit));
+ int8x8_t s1 = vreinterpret_s8_u8(vsub_u8(t1, range_limit));
+ int8x8_t s2 = vreinterpret_s8_u8(vsub_u8(t2, range_limit));
+ int8x8_t s3 = vreinterpret_s8_u8(vsub_u8(t3, range_limit));
+ int8x8_t s4 = vreinterpret_s8_u8(vsub_u8(t4, range_limit));
+ int8x8_t s5 = vreinterpret_s8_u8(vsub_u8(t5, range_limit));
+ int8x8_t s6 = vreinterpret_s8_u8(vsub_u8(t6, range_limit));
/* This operation combines a conventional transpose and the sample permute
* (see horizontal case) required before computing the dot product.
*/
+ int8x16_t s0123_lo, s0123_hi, s1234_lo, s1234_hi, s2345_lo, s2345_hi,
+ s3456_lo, s3456_hi;
transpose_concat_8x4(s0, s1, s2, s3, &s0123_lo, &s0123_hi,
tran_concat_tbl);
transpose_concat_8x4(s1, s2, s3, s4, &s1234_lo, &s1234_hi,
@@ -393,23 +378,18 @@ void aom_convolve8_vert_neon_dotprod(const uint8_t *src, ptrdiff_t src_stride,
tran_concat_tbl);
transpose_concat_8x4(s3, s4, s5, s6, &s3456_lo, &s3456_hi,
tran_concat_tbl);
- transpose_concat_8x4(s4, s5, s6, s7, &s4567_lo, &s4567_hi,
- tran_concat_tbl);
- transpose_concat_8x4(s5, s6, s7, s8, &s5678_lo, &s5678_hi,
- tran_concat_tbl);
- transpose_concat_8x4(s6, s7, s8, s9, &s6789_lo, &s6789_hi,
- tran_concat_tbl);
do {
uint8x8_t t7, t8, t9, t10;
-
load_u8_8x4(s, src_stride, &t7, &t8, &t9, &t10);
- s7 = vreinterpret_s8_u8(vsub_u8(t7, range_limit));
- s8 = vreinterpret_s8_u8(vsub_u8(t8, range_limit));
- s9 = vreinterpret_s8_u8(vsub_u8(t9, range_limit));
- s10 = vreinterpret_s8_u8(vsub_u8(t10, range_limit));
+ int8x8_t s7 = vreinterpret_s8_u8(vsub_u8(t7, range_limit));
+ int8x8_t s8 = vreinterpret_s8_u8(vsub_u8(t8, range_limit));
+ int8x8_t s9 = vreinterpret_s8_u8(vsub_u8(t9, range_limit));
+ int8x8_t s10 = vreinterpret_s8_u8(vsub_u8(t10, range_limit));
+ int8x16_t s4567_lo, s4567_hi, s5678_lo, s5678_hi, s6789_lo, s6789_hi,
+ s78910_lo, s78910_hi;
transpose_concat_8x4(s7, s8, s9, s10, &s78910_lo, &s78910_hi,
tran_concat_tbl);
@@ -426,14 +406,14 @@ void aom_convolve8_vert_neon_dotprod(const uint8_t *src, ptrdiff_t src_stride,
s5678_hi = vqtbl2q_s8(samples_LUT, merge_block_tbl.val[1]);
s6789_hi = vqtbl2q_s8(samples_LUT, merge_block_tbl.val[2]);
- d0 = convolve8_8_sdot_partial(s0123_lo, s4567_lo, s0123_hi, s4567_hi,
- correction, filter);
- d1 = convolve8_8_sdot_partial(s1234_lo, s5678_lo, s1234_hi, s5678_hi,
- correction, filter);
- d2 = convolve8_8_sdot_partial(s2345_lo, s6789_lo, s2345_hi, s6789_hi,
- correction, filter);
- d3 = convolve8_8_sdot_partial(s3456_lo, s78910_lo, s3456_hi, s78910_hi,
- correction, filter);
+ uint8x8_t d0 = convolve8_8_sdot_partial(s0123_lo, s4567_lo, s0123_hi,
+ s4567_hi, correction, filter);
+ uint8x8_t d1 = convolve8_8_sdot_partial(s1234_lo, s5678_lo, s1234_hi,
+ s5678_hi, correction, filter);
+ uint8x8_t d2 = convolve8_8_sdot_partial(s2345_lo, s6789_lo, s2345_hi,
+ s6789_hi, correction, filter);
+ uint8x8_t d3 = convolve8_8_sdot_partial(s3456_lo, s78910_lo, s3456_hi,
+ s78910_hi, correction, filter);
store_u8_8x4(d, dst_stride, d0, d1, d2, d3);
diff --git a/third_party/aom/aom_dsp/arm/aom_convolve8_neon_i8mm.c b/third_party/aom/aom_dsp/arm/aom_convolve8_neon_i8mm.c
index c314c0a192..df6e4d2ab5 100644
--- a/third_party/aom/aom_dsp/arm/aom_convolve8_neon_i8mm.c
+++ b/third_party/aom/aom_dsp/arm/aom_convolve8_neon_i8mm.c
@@ -15,7 +15,6 @@
#include <string.h>
#include "config/aom_config.h"
-#include "config/aom_dsp_rtcd.h"
#include "aom/aom_integer.h"
#include "aom_dsp/aom_dsp_common.h"
@@ -246,7 +245,6 @@ void aom_convolve8_vert_neon_i8mm(const uint8_t *src, ptrdiff_t src_stride,
int h) {
const int8x8_t filter = vmovn_s16(vld1q_s16(filter_y));
const uint8x16x3_t merge_block_tbl = vld1q_u8_x3(dot_prod_merge_block_tbl);
- uint8x8_t s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10;
uint8x16x2_t samples_LUT;
assert((intptr_t)dst % 4 == 0);
@@ -260,31 +258,25 @@ void aom_convolve8_vert_neon_i8mm(const uint8_t *src, ptrdiff_t src_stride,
if (w == 4) {
const uint8x16_t tran_concat_tbl = vld1q_u8(dot_prod_tran_concat_tbl);
- uint8x16_t s0123, s1234, s2345, s3456, s4567, s5678, s6789, s78910;
- int16x4_t d0, d1, d2, d3;
- uint8x8_t d01, d23;
+ uint8x8_t s0, s1, s2, s3, s4, s5, s6;
load_u8_8x7(src, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
src += 7 * src_stride;
- s7 = vdup_n_u8(0);
- s8 = vdup_n_u8(0);
- s9 = vdup_n_u8(0);
-
/* This operation combines a conventional transpose and the sample permute
* (see horizontal case) required before computing the dot product.
*/
+ uint8x16_t s0123, s1234, s2345, s3456;
transpose_concat_4x4(s0, s1, s2, s3, &s0123, tran_concat_tbl);
transpose_concat_4x4(s1, s2, s3, s4, &s1234, tran_concat_tbl);
transpose_concat_4x4(s2, s3, s4, s5, &s2345, tran_concat_tbl);
transpose_concat_4x4(s3, s4, s5, s6, &s3456, tran_concat_tbl);
- transpose_concat_4x4(s4, s5, s6, s7, &s4567, tran_concat_tbl);
- transpose_concat_4x4(s5, s6, s7, s8, &s5678, tran_concat_tbl);
- transpose_concat_4x4(s6, s7, s8, s9, &s6789, tran_concat_tbl);
do {
+ uint8x8_t s7, s8, s9, s10;
load_u8_8x4(src, src_stride, &s7, &s8, &s9, &s10);
+ uint8x16_t s4567, s5678, s6789, s78910;
transpose_concat_4x4(s7, s8, s9, s10, &s78910, tran_concat_tbl);
/* Merge new data into block from previous iteration. */
@@ -294,12 +286,12 @@ void aom_convolve8_vert_neon_i8mm(const uint8_t *src, ptrdiff_t src_stride,
s5678 = vqtbl2q_u8(samples_LUT, merge_block_tbl.val[1]);
s6789 = vqtbl2q_u8(samples_LUT, merge_block_tbl.val[2]);
- d0 = convolve8_4_usdot_partial(s0123, s4567, filter);
- d1 = convolve8_4_usdot_partial(s1234, s5678, filter);
- d2 = convolve8_4_usdot_partial(s2345, s6789, filter);
- d3 = convolve8_4_usdot_partial(s3456, s78910, filter);
- d01 = vqrshrun_n_s16(vcombine_s16(d0, d1), FILTER_BITS);
- d23 = vqrshrun_n_s16(vcombine_s16(d2, d3), FILTER_BITS);
+ int16x4_t d0 = convolve8_4_usdot_partial(s0123, s4567, filter);
+ int16x4_t d1 = convolve8_4_usdot_partial(s1234, s5678, filter);
+ int16x4_t d2 = convolve8_4_usdot_partial(s2345, s6789, filter);
+ int16x4_t d3 = convolve8_4_usdot_partial(s3456, s78910, filter);
+ uint8x8_t d01 = vqrshrun_n_s16(vcombine_s16(d0, d1), FILTER_BITS);
+ uint8x8_t d23 = vqrshrun_n_s16(vcombine_s16(d2, d3), FILTER_BITS);
store_u8x4_strided_x2(dst + 0 * dst_stride, dst_stride, d01);
store_u8x4_strided_x2(dst + 2 * dst_stride, dst_stride, d23);
@@ -317,29 +309,21 @@ void aom_convolve8_vert_neon_i8mm(const uint8_t *src, ptrdiff_t src_stride,
} while (h != 0);
} else {
const uint8x16x2_t tran_concat_tbl = vld1q_u8_x2(dot_prod_tran_concat_tbl);
- uint8x16_t s0123_lo, s0123_hi, s1234_lo, s1234_hi, s2345_lo, s2345_hi,
- s3456_lo, s3456_hi, s4567_lo, s4567_hi, s5678_lo, s5678_hi, s6789_lo,
- s6789_hi, s78910_lo, s78910_hi;
- uint8x8_t d0, d1, d2, d3;
- const uint8_t *s;
- uint8_t *d;
- int height;
do {
- height = h;
- s = src;
- d = dst;
+ int height = h;
+ const uint8_t *s = src;
+ uint8_t *d = dst;
+ uint8x8_t s0, s1, s2, s3, s4, s5, s6;
load_u8_8x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
s += 7 * src_stride;
- s7 = vdup_n_u8(0);
- s8 = vdup_n_u8(0);
- s9 = vdup_n_u8(0);
-
/* This operation combines a conventional transpose and the sample permute
* (see horizontal case) required before computing the dot product.
*/
+ uint8x16_t s0123_lo, s0123_hi, s1234_lo, s1234_hi, s2345_lo, s2345_hi,
+ s3456_lo, s3456_hi;
transpose_concat_8x4(s0, s1, s2, s3, &s0123_lo, &s0123_hi,
tran_concat_tbl);
transpose_concat_8x4(s1, s2, s3, s4, &s1234_lo, &s1234_hi,
@@ -348,16 +332,13 @@ void aom_convolve8_vert_neon_i8mm(const uint8_t *src, ptrdiff_t src_stride,
tran_concat_tbl);
transpose_concat_8x4(s3, s4, s5, s6, &s3456_lo, &s3456_hi,
tran_concat_tbl);
- transpose_concat_8x4(s4, s5, s6, s7, &s4567_lo, &s4567_hi,
- tran_concat_tbl);
- transpose_concat_8x4(s5, s6, s7, s8, &s5678_lo, &s5678_hi,
- tran_concat_tbl);
- transpose_concat_8x4(s6, s7, s8, s9, &s6789_lo, &s6789_hi,
- tran_concat_tbl);
do {
+ uint8x8_t s7, s8, s9, s10;
load_u8_8x4(s, src_stride, &s7, &s8, &s9, &s10);
+ uint8x16_t s4567_lo, s4567_hi, s5678_lo, s5678_hi, s6789_lo, s6789_hi,
+ s78910_lo, s78910_hi;
transpose_concat_8x4(s7, s8, s9, s10, &s78910_lo, &s78910_hi,
tran_concat_tbl);
@@ -374,14 +355,14 @@ void aom_convolve8_vert_neon_i8mm(const uint8_t *src, ptrdiff_t src_stride,
s5678_hi = vqtbl2q_u8(samples_LUT, merge_block_tbl.val[1]);
s6789_hi = vqtbl2q_u8(samples_LUT, merge_block_tbl.val[2]);
- d0 = convolve8_8_usdot_partial(s0123_lo, s4567_lo, s0123_hi, s4567_hi,
- filter);
- d1 = convolve8_8_usdot_partial(s1234_lo, s5678_lo, s1234_hi, s5678_hi,
- filter);
- d2 = convolve8_8_usdot_partial(s2345_lo, s6789_lo, s2345_hi, s6789_hi,
- filter);
- d3 = convolve8_8_usdot_partial(s3456_lo, s78910_lo, s3456_hi, s78910_hi,
- filter);
+ uint8x8_t d0 = convolve8_8_usdot_partial(s0123_lo, s4567_lo, s0123_hi,
+ s4567_hi, filter);
+ uint8x8_t d1 = convolve8_8_usdot_partial(s1234_lo, s5678_lo, s1234_hi,
+ s5678_hi, filter);
+ uint8x8_t d2 = convolve8_8_usdot_partial(s2345_lo, s6789_lo, s2345_hi,
+ s6789_hi, filter);
+ uint8x8_t d3 = convolve8_8_usdot_partial(s3456_lo, s78910_lo, s3456_hi,
+ s78910_hi, filter);
store_u8_8x4(d, dst_stride, d0, d1, d2, d3);
diff --git a/third_party/aom/aom_dsp/arm/aom_filter.h b/third_party/aom/aom_dsp/arm/aom_filter.h
new file mode 100644
index 0000000000..9972d064fc
--- /dev/null
+++ b/third_party/aom/aom_dsp/arm/aom_filter.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2024, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#ifndef AOM_AOM_DSP_ARM_AOM_FILTER_H_
+#define AOM_AOM_DSP_ARM_AOM_FILTER_H_
+
+#include <stdint.h>
+
+#include "config/aom_config.h"
+#include "config/aom_dsp_rtcd.h"
+
+static INLINE int get_filter_taps_convolve8(const int16_t *filter) {
+ if (filter[0] | filter[7]) {
+ return 8;
+ }
+ if (filter[1] | filter[6]) {
+ return 6;
+ }
+ if (filter[2] | filter[5]) {
+ return 4;
+ }
+ return 2;
+}
+
+#endif // AOM_AOM_DSP_ARM_AOM_FILTER_H_
diff --git a/third_party/aom/aom_dsp/arm/aom_neon_sve2_bridge.h b/third_party/aom/aom_dsp/arm/aom_neon_sve2_bridge.h
new file mode 100644
index 0000000000..6e7d2d6365
--- /dev/null
+++ b/third_party/aom/aom_dsp/arm/aom_neon_sve2_bridge.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2024, Alliance for Open Media. 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 AOM_AOM_DSP_ARM_AOM_NEON_SVE2_BRIDGE_H_
+#define AOM_AOM_DSP_ARM_AOM_NEON_SVE2_BRIDGE_H_
+
+#include <arm_neon_sve_bridge.h>
+
+#include "config/aom_dsp_rtcd.h"
+#include "config/aom_config.h"
+
+// We can access instructions exclusive to the SVE2 instruction set from a
+// predominantly Neon context by making use of the Neon-SVE bridge intrinsics
+// to reinterpret Neon vectors as SVE vectors - with the high part of the SVE
+// vector (if it's longer than 128 bits) being "don't care".
+
+// While sub-optimal on machines that have SVE vector length > 128-bit - as the
+// remainder of the vector is unused - this approach is still beneficial when
+// compared to a Neon-only solution.
+
+static INLINE int16x8_t aom_tbl2_s16(int16x8_t s0, int16x8_t s1,
+ uint16x8_t tbl) {
+ svint16x2_t samples = svcreate2_s16(svset_neonq_s16(svundef_s16(), s0),
+ svset_neonq_s16(svundef_s16(), s1));
+ return svget_neonq_s16(
+ svtbl2_s16(samples, svset_neonq_u16(svundef_u16(), tbl)));
+}
+
+#endif // AOM_AOM_DSP_ARM_AOM_NEON_SVE2_BRIDGE_H_
diff --git a/third_party/aom/aom_dsp/arm/dot_sve.h b/third_party/aom/aom_dsp/arm/aom_neon_sve_bridge.h
index cf49f23606..3da80e22ba 100644
--- a/third_party/aom/aom_dsp/arm/dot_sve.h
+++ b/third_party/aom/aom_dsp/arm/aom_neon_sve_bridge.h
@@ -8,16 +8,15 @@
* be found in the AUTHORS file in the root of the source tree.
*/
-#ifndef AOM_AOM_DSP_ARM_DOT_SVE_H_
-#define AOM_AOM_DSP_ARM_DOT_SVE_H_
+#ifndef AOM_AOM_DSP_ARM_AOM_NEON_SVE_BRIDGE_H_
+#define AOM_AOM_DSP_ARM_AOM_NEON_SVE_BRIDGE_H_
#include <arm_neon_sve_bridge.h>
#include "config/aom_dsp_rtcd.h"
#include "config/aom_config.h"
-// Dot product instructions operating on 16-bit input elements are exclusive to
-// the SVE instruction set. However, we can access these instructions from a
+// We can access instructions exclusive to the SVE instruction set from a
// predominantly Neon context by making use of the Neon-SVE bridge intrinsics
// to reinterpret Neon vectors as SVE vectors - with the high part of the SVE
// vector (if it's longer than 128 bits) being "don't care".
@@ -39,4 +38,19 @@ static INLINE int64x2_t aom_sdotq_s16(int64x2_t acc, int16x8_t x, int16x8_t y) {
svset_neonq_s16(svundef_s16(), y)));
}
-#endif // AOM_AOM_DSP_ARM_DOT_SVE_H_
+#define aom_svdot_lane_s16(sum, s0, f, lane) \
+ svget_neonq_s64(svdot_lane_s64(svset_neonq_s64(svundef_s64(), sum), \
+ svset_neonq_s16(svundef_s16(), s0), \
+ svset_neonq_s16(svundef_s16(), f), lane))
+
+static INLINE uint16x8_t aom_tbl_u16(uint16x8_t s, uint16x8_t tbl) {
+ return svget_neonq_u16(svtbl_u16(svset_neonq_u16(svundef_u16(), s),
+ svset_neonq_u16(svundef_u16(), tbl)));
+}
+
+static INLINE int16x8_t aom_tbl_s16(int16x8_t s, uint16x8_t tbl) {
+ return svget_neonq_s16(svtbl_s16(svset_neonq_s16(svundef_s16(), s),
+ svset_neonq_u16(svundef_u16(), tbl)));
+}
+
+#endif // AOM_AOM_DSP_ARM_AOM_NEON_SVE_BRIDGE_H_
diff --git a/third_party/aom/aom_dsp/arm/avg_sve.c b/third_party/aom/aom_dsp/arm/avg_sve.c
index bbf5a9447c..57a546501a 100644
--- a/third_party/aom/aom_dsp/arm/avg_sve.c
+++ b/third_party/aom/aom_dsp/arm/avg_sve.c
@@ -14,7 +14,7 @@
#include "config/aom_config.h"
#include "config/aom_dsp_rtcd.h"
#include "aom/aom_integer.h"
-#include "aom_dsp/arm/dot_sve.h"
+#include "aom_dsp/arm/aom_neon_sve_bridge.h"
#include "aom_dsp/arm/mem_neon.h"
#include "aom_ports/mem.h"
diff --git a/third_party/aom/aom_dsp/arm/blk_sse_sum_sve.c b/third_party/aom/aom_dsp/arm/blk_sse_sum_sve.c
index 18bdc5dbfe..f538346d8b 100644
--- a/third_party/aom/aom_dsp/arm/blk_sse_sum_sve.c
+++ b/third_party/aom/aom_dsp/arm/blk_sse_sum_sve.c
@@ -15,7 +15,7 @@
#include "config/aom_dsp_rtcd.h"
#include "config/aom_config.h"
-#include "aom_dsp/arm/dot_sve.h"
+#include "aom_dsp/arm/aom_neon_sve_bridge.h"
#include "aom_dsp/arm/mem_neon.h"
static INLINE void get_blk_sse_sum_4xh_sve(const int16_t *data, int stride,
diff --git a/third_party/aom/aom_dsp/arm/highbd_convolve8_sve.c b/third_party/aom/aom_dsp/arm/highbd_convolve8_sve.c
new file mode 100644
index 0000000000..e57c41a0b0
--- /dev/null
+++ b/third_party/aom/aom_dsp/arm/highbd_convolve8_sve.c
@@ -0,0 +1,681 @@
+/*
+ * Copyright (c) 2024, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#include <arm_neon.h>
+#include <assert.h>
+#include <stdint.h>
+
+#include "config/aom_config.h"
+#include "config/aom_dsp_rtcd.h"
+
+#include "aom_dsp/arm/aom_neon_sve_bridge.h"
+#include "aom_dsp/arm/aom_filter.h"
+#include "aom_dsp/arm/mem_neon.h"
+
+static INLINE uint16x4_t highbd_convolve8_4_h(int16x8_t s[4], int16x8_t filter,
+ uint16x4_t max) {
+ int64x2_t sum[4];
+
+ sum[0] = aom_sdotq_s16(vdupq_n_s64(0), s[0], filter);
+ sum[1] = aom_sdotq_s16(vdupq_n_s64(0), s[1], filter);
+ sum[2] = aom_sdotq_s16(vdupq_n_s64(0), s[2], filter);
+ sum[3] = aom_sdotq_s16(vdupq_n_s64(0), s[3], filter);
+
+ int64x2_t sum01 = vpaddq_s64(sum[0], sum[1]);
+ int64x2_t sum23 = vpaddq_s64(sum[2], sum[3]);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+
+ uint16x4_t res = vqrshrun_n_s32(sum0123, FILTER_BITS);
+ return vmin_u16(res, max);
+}
+
+static INLINE uint16x8_t highbd_convolve8_8_h(int16x8_t s[8], int16x8_t filter,
+ uint16x8_t max) {
+ int64x2_t sum[8];
+
+ sum[0] = aom_sdotq_s16(vdupq_n_s64(0), s[0], filter);
+ sum[1] = aom_sdotq_s16(vdupq_n_s64(0), s[1], filter);
+ sum[2] = aom_sdotq_s16(vdupq_n_s64(0), s[2], filter);
+ sum[3] = aom_sdotq_s16(vdupq_n_s64(0), s[3], filter);
+ sum[4] = aom_sdotq_s16(vdupq_n_s64(0), s[4], filter);
+ sum[5] = aom_sdotq_s16(vdupq_n_s64(0), s[5], filter);
+ sum[6] = aom_sdotq_s16(vdupq_n_s64(0), s[6], filter);
+ sum[7] = aom_sdotq_s16(vdupq_n_s64(0), s[7], filter);
+
+ int64x2_t sum01 = vpaddq_s64(sum[0], sum[1]);
+ int64x2_t sum23 = vpaddq_s64(sum[2], sum[3]);
+ int64x2_t sum45 = vpaddq_s64(sum[4], sum[5]);
+ int64x2_t sum67 = vpaddq_s64(sum[6], sum[7]);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum45), vmovn_s64(sum67));
+
+ uint16x8_t res = vcombine_u16(vqrshrun_n_s32(sum0123, FILTER_BITS),
+ vqrshrun_n_s32(sum4567, FILTER_BITS));
+ return vminq_u16(res, max);
+}
+
+static INLINE void highbd_convolve8_horiz_8tap_sve(
+ const uint16_t *src, ptrdiff_t src_stride, uint16_t *dst,
+ ptrdiff_t dst_stride, const int16_t *filter_x, int width, int height,
+ int bd) {
+ const int16x8_t filter = vld1q_s16(filter_x);
+
+ if (width == 4) {
+ const uint16x4_t max = vdup_n_u16((1 << bd) - 1);
+ const int16_t *s = (const int16_t *)src;
+ uint16_t *d = dst;
+
+ do {
+ int16x8_t s0[4], s1[4], s2[4], s3[4];
+ load_s16_8x4(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3]);
+ load_s16_8x4(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3]);
+ load_s16_8x4(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3]);
+ load_s16_8x4(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3]);
+
+ uint16x4_t d0 = highbd_convolve8_4_h(s0, filter, max);
+ uint16x4_t d1 = highbd_convolve8_4_h(s1, filter, max);
+ uint16x4_t d2 = highbd_convolve8_4_h(s2, filter, max);
+ uint16x4_t d3 = highbd_convolve8_4_h(s3, filter, max);
+
+ store_u16_4x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ height -= 4;
+ } while (height > 0);
+ } else {
+ do {
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+ const int16_t *s = (const int16_t *)src;
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[8], s1[8], s2[8], s3[8];
+ load_s16_8x8(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3],
+ &s0[4], &s0[5], &s0[6], &s0[7]);
+ load_s16_8x8(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3],
+ &s1[4], &s1[5], &s1[6], &s1[7]);
+ load_s16_8x8(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3],
+ &s2[4], &s2[5], &s2[6], &s2[7]);
+ load_s16_8x8(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3],
+ &s3[4], &s3[5], &s3[6], &s3[7]);
+
+ uint16x8_t d0 = highbd_convolve8_8_h(s0, filter, max);
+ uint16x8_t d1 = highbd_convolve8_8_h(s1, filter, max);
+ uint16x8_t d2 = highbd_convolve8_8_h(s2, filter, max);
+ uint16x8_t d3 = highbd_convolve8_8_h(s3, filter, max);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height > 0);
+ }
+}
+
+// clang-format off
+DECLARE_ALIGNED(16, static const uint16_t, kDotProdTbl[16]) = {
+ 0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6,
+};
+
+DECLARE_ALIGNED(16, static const uint16_t, kDeinterleaveTbl[8]) = {
+ 0, 2, 4, 6, 1, 3, 5, 7,
+};
+// clang-format on
+
+static INLINE uint16x4_t highbd_convolve4_4_h(int16x8_t s, int16x8_t filter,
+ uint16x8x2_t permute_tbl,
+ uint16x4_t max) {
+ int16x8_t permuted_samples0 = aom_tbl_s16(s, permute_tbl.val[0]);
+ int16x8_t permuted_samples1 = aom_tbl_s16(s, permute_tbl.val[1]);
+
+ int64x2_t sum0 =
+ aom_svdot_lane_s16(vdupq_n_s64(0), permuted_samples0, filter, 0);
+ int64x2_t sum1 =
+ aom_svdot_lane_s16(vdupq_n_s64(0), permuted_samples1, filter, 0);
+
+ int32x4_t res_s32 = vcombine_s32(vmovn_s64(sum0), vmovn_s64(sum1));
+ uint16x4_t res = vqrshrun_n_s32(res_s32, FILTER_BITS);
+
+ return vmin_u16(res, max);
+}
+
+static INLINE uint16x8_t highbd_convolve4_8_h(int16x8_t s[4], int16x8_t filter,
+ uint16x8_t idx, uint16x8_t max) {
+ int64x2_t sum04 = aom_svdot_lane_s16(vdupq_n_s64(0), s[0], filter, 0);
+ int64x2_t sum15 = aom_svdot_lane_s16(vdupq_n_s64(0), s[1], filter, 0);
+ int64x2_t sum26 = aom_svdot_lane_s16(vdupq_n_s64(0), s[2], filter, 0);
+ int64x2_t sum37 = aom_svdot_lane_s16(vdupq_n_s64(0), s[3], filter, 0);
+
+ int32x4_t res0 = vcombine_s32(vmovn_s64(sum04), vmovn_s64(sum15));
+ int32x4_t res1 = vcombine_s32(vmovn_s64(sum26), vmovn_s64(sum37));
+
+ uint16x8_t res = vcombine_u16(vqrshrun_n_s32(res0, FILTER_BITS),
+ vqrshrun_n_s32(res1, FILTER_BITS));
+
+ res = aom_tbl_u16(res, idx);
+
+ return vminq_u16(res, max);
+}
+
+static INLINE void highbd_convolve8_horiz_4tap_sve(
+ const uint16_t *src, ptrdiff_t src_stride, uint16_t *dst,
+ ptrdiff_t dst_stride, const int16_t *filter_x, int width, int height,
+ int bd) {
+ const int16x8_t filter = vcombine_s16(vld1_s16(filter_x + 2), vdup_n_s16(0));
+
+ if (width == 4) {
+ const uint16x4_t max = vdup_n_u16((1 << bd) - 1);
+ uint16x8x2_t permute_tbl = vld1q_u16_x2(kDotProdTbl);
+
+ const int16_t *s = (const int16_t *)src;
+ uint16_t *d = dst;
+
+ do {
+ int16x8_t s0, s1, s2, s3;
+ load_s16_8x4(s, src_stride, &s0, &s1, &s2, &s3);
+
+ uint16x4_t d0 = highbd_convolve4_4_h(s0, filter, permute_tbl, max);
+ uint16x4_t d1 = highbd_convolve4_4_h(s1, filter, permute_tbl, max);
+ uint16x4_t d2 = highbd_convolve4_4_h(s2, filter, permute_tbl, max);
+ uint16x4_t d3 = highbd_convolve4_4_h(s3, filter, permute_tbl, max);
+
+ store_u16_4x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ height -= 4;
+ } while (height > 0);
+ } else {
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+ uint16x8_t idx = vld1q_u16(kDeinterleaveTbl);
+
+ do {
+ const int16_t *s = (const int16_t *)src;
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[4], s1[4], s2[4], s3[4];
+ load_s16_8x4(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3]);
+ load_s16_8x4(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3]);
+ load_s16_8x4(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3]);
+ load_s16_8x4(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3]);
+
+ uint16x8_t d0 = highbd_convolve4_8_h(s0, filter, idx, max);
+ uint16x8_t d1 = highbd_convolve4_8_h(s1, filter, idx, max);
+ uint16x8_t d2 = highbd_convolve4_8_h(s2, filter, idx, max);
+ uint16x8_t d3 = highbd_convolve4_8_h(s3, filter, idx, max);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height > 0);
+ }
+}
+
+void aom_highbd_convolve8_horiz_sve(const uint8_t *src8, ptrdiff_t src_stride,
+ uint8_t *dst8, ptrdiff_t dst_stride,
+ const int16_t *filter_x, int x_step_q4,
+ const int16_t *filter_y, int y_step_q4,
+ int width, int height, int bd) {
+ assert(x_step_q4 == 16);
+ assert(width >= 4 && height >= 4);
+ (void)filter_y;
+ (void)x_step_q4;
+ (void)y_step_q4;
+
+ const uint16_t *src = CONVERT_TO_SHORTPTR(src8);
+ uint16_t *dst = CONVERT_TO_SHORTPTR(dst8);
+
+ src -= SUBPEL_TAPS / 2 - 1;
+
+ if (get_filter_taps_convolve8(filter_x) <= 4) {
+ highbd_convolve8_horiz_4tap_sve(src + 2, src_stride, dst, dst_stride,
+ filter_x, width, height, bd);
+ } else {
+ highbd_convolve8_horiz_8tap_sve(src, src_stride, dst, dst_stride, filter_x,
+ width, height, bd);
+ }
+}
+
+DECLARE_ALIGNED(16, static const uint8_t, kDotProdMergeBlockTbl[48]) = {
+ // Shift left and insert new last column in transposed 4x4 block.
+ 2, 3, 4, 5, 6, 7, 16, 17, 10, 11, 12, 13, 14, 15, 24, 25,
+ // Shift left and insert two new columns in transposed 4x4 block.
+ 4, 5, 6, 7, 16, 17, 18, 19, 12, 13, 14, 15, 24, 25, 26, 27,
+ // Shift left and insert three new columns in transposed 4x4 block.
+ 6, 7, 16, 17, 18, 19, 20, 21, 14, 15, 24, 25, 26, 27, 28, 29
+};
+
+static INLINE void transpose_concat_4x4(int16x4_t s0, int16x4_t s1,
+ int16x4_t s2, int16x4_t s3,
+ int16x8_t res[2]) {
+ // Transpose 16-bit elements and concatenate result rows as follows:
+ // s0: 00, 01, 02, 03
+ // s1: 10, 11, 12, 13
+ // s2: 20, 21, 22, 23
+ // s3: 30, 31, 32, 33
+ //
+ // res[0]: 00 10 20 30 01 11 21 31
+ // res[1]: 02 12 22 32 03 13 23 33
+
+ int16x8_t s0q = vcombine_s16(s0, vdup_n_s16(0));
+ int16x8_t s1q = vcombine_s16(s1, vdup_n_s16(0));
+ int16x8_t s2q = vcombine_s16(s2, vdup_n_s16(0));
+ int16x8_t s3q = vcombine_s16(s3, vdup_n_s16(0));
+
+ int32x4_t s01 = vreinterpretq_s32_s16(vzip1q_s16(s0q, s1q));
+ int32x4_t s23 = vreinterpretq_s32_s16(vzip1q_s16(s2q, s3q));
+
+ int32x4x2_t s0123 = vzipq_s32(s01, s23);
+
+ res[0] = vreinterpretq_s16_s32(s0123.val[0]);
+ res[1] = vreinterpretq_s16_s32(s0123.val[1]);
+}
+
+static INLINE void transpose_concat_8x4(int16x8_t s0, int16x8_t s1,
+ int16x8_t s2, int16x8_t s3,
+ int16x8_t res[4]) {
+ // Transpose 16-bit elements and concatenate result rows as follows:
+ // s0: 00, 01, 02, 03, 04, 05, 06, 07
+ // s1: 10, 11, 12, 13, 14, 15, 16, 17
+ // s2: 20, 21, 22, 23, 24, 25, 26, 27
+ // s3: 30, 31, 32, 33, 34, 35, 36, 37
+ //
+ // res_lo[0]: 00 10 20 30 01 11 21 31
+ // res_lo[1]: 02 12 22 32 03 13 23 33
+ // res_hi[0]: 04 14 24 34 05 15 25 35
+ // res_hi[1]: 06 16 26 36 07 17 27 37
+
+ int16x8x2_t tr01_16 = vzipq_s16(s0, s1);
+ int16x8x2_t tr23_16 = vzipq_s16(s2, s3);
+
+ int32x4x2_t tr01_32 = vzipq_s32(vreinterpretq_s32_s16(tr01_16.val[0]),
+ vreinterpretq_s32_s16(tr23_16.val[0]));
+ int32x4x2_t tr23_32 = vzipq_s32(vreinterpretq_s32_s16(tr01_16.val[1]),
+ vreinterpretq_s32_s16(tr23_16.val[1]));
+
+ res[0] = vreinterpretq_s16_s32(tr01_32.val[0]);
+ res[1] = vreinterpretq_s16_s32(tr01_32.val[1]);
+ res[2] = vreinterpretq_s16_s32(tr23_32.val[0]);
+ res[3] = vreinterpretq_s16_s32(tr23_32.val[1]);
+}
+
+static INLINE void aom_tbl2x4_s16(int16x8_t t0[4], int16x8_t t1[4],
+ uint8x16_t tbl, int16x8_t res[4]) {
+ int8x16x2_t samples0 = { vreinterpretq_s8_s16(t0[0]),
+ vreinterpretq_s8_s16(t1[0]) };
+ int8x16x2_t samples1 = { vreinterpretq_s8_s16(t0[1]),
+ vreinterpretq_s8_s16(t1[1]) };
+ int8x16x2_t samples2 = { vreinterpretq_s8_s16(t0[2]),
+ vreinterpretq_s8_s16(t1[2]) };
+ int8x16x2_t samples3 = { vreinterpretq_s8_s16(t0[3]),
+ vreinterpretq_s8_s16(t1[3]) };
+
+ res[0] = vreinterpretq_s16_s8(vqtbl2q_s8(samples0, tbl));
+ res[1] = vreinterpretq_s16_s8(vqtbl2q_s8(samples1, tbl));
+ res[2] = vreinterpretq_s16_s8(vqtbl2q_s8(samples2, tbl));
+ res[3] = vreinterpretq_s16_s8(vqtbl2q_s8(samples3, tbl));
+}
+
+static INLINE void aom_tbl2x2_s16(int16x8_t t0[2], int16x8_t t1[2],
+ uint8x16_t tbl, int16x8_t res[2]) {
+ int8x16x2_t samples0 = { vreinterpretq_s8_s16(t0[0]),
+ vreinterpretq_s8_s16(t1[0]) };
+ int8x16x2_t samples1 = { vreinterpretq_s8_s16(t0[1]),
+ vreinterpretq_s8_s16(t1[1]) };
+
+ res[0] = vreinterpretq_s16_s8(vqtbl2q_s8(samples0, tbl));
+ res[1] = vreinterpretq_s16_s8(vqtbl2q_s8(samples1, tbl));
+}
+
+static INLINE uint16x4_t highbd_convolve8_4_v(int16x8_t samples_lo[2],
+ int16x8_t samples_hi[2],
+ int16x8_t filter,
+ uint16x4_t max) {
+ int64x2_t sum[2];
+
+ sum[0] = aom_svdot_lane_s16(vdupq_n_s64(0), samples_lo[0], filter, 0);
+ sum[0] = aom_svdot_lane_s16(sum[0], samples_hi[0], filter, 1);
+
+ sum[1] = aom_svdot_lane_s16(vdupq_n_s64(0), samples_lo[1], filter, 0);
+ sum[1] = aom_svdot_lane_s16(sum[1], samples_hi[1], filter, 1);
+
+ int32x4_t res_s32 = vcombine_s32(vmovn_s64(sum[0]), vmovn_s64(sum[1]));
+
+ uint16x4_t res = vqrshrun_n_s32(res_s32, FILTER_BITS);
+
+ return vmin_u16(res, max);
+}
+
+static INLINE uint16x8_t highbd_convolve8_8_v(int16x8_t samples_lo[4],
+ int16x8_t samples_hi[4],
+ int16x8_t filter,
+ uint16x8_t max) {
+ int64x2_t sum[4];
+
+ sum[0] = aom_svdot_lane_s16(vdupq_n_s64(0), samples_lo[0], filter, 0);
+ sum[0] = aom_svdot_lane_s16(sum[0], samples_hi[0], filter, 1);
+
+ sum[1] = aom_svdot_lane_s16(vdupq_n_s64(0), samples_lo[1], filter, 0);
+ sum[1] = aom_svdot_lane_s16(sum[1], samples_hi[1], filter, 1);
+
+ sum[2] = aom_svdot_lane_s16(vdupq_n_s64(0), samples_lo[2], filter, 0);
+ sum[2] = aom_svdot_lane_s16(sum[2], samples_hi[2], filter, 1);
+
+ sum[3] = aom_svdot_lane_s16(vdupq_n_s64(0), samples_lo[3], filter, 0);
+ sum[3] = aom_svdot_lane_s16(sum[3], samples_hi[3], filter, 1);
+
+ int32x4_t res0 = vcombine_s32(vmovn_s64(sum[0]), vmovn_s64(sum[1]));
+ int32x4_t res1 = vcombine_s32(vmovn_s64(sum[2]), vmovn_s64(sum[3]));
+
+ uint16x8_t res = vcombine_u16(vqrshrun_n_s32(res0, FILTER_BITS),
+ vqrshrun_n_s32(res1, FILTER_BITS));
+
+ return vminq_u16(res, max);
+}
+
+static INLINE void highbd_convolve8_vert_8tap_sve(
+ const uint16_t *src, ptrdiff_t src_stride, uint16_t *dst,
+ ptrdiff_t dst_stride, const int16_t *filter_y, int width, int height,
+ int bd) {
+ const int16x8_t y_filter = vld1q_s16(filter_y);
+
+ uint8x16_t merge_block_tbl[3];
+ merge_block_tbl[0] = vld1q_u8(kDotProdMergeBlockTbl);
+ merge_block_tbl[1] = vld1q_u8(kDotProdMergeBlockTbl + 16);
+ merge_block_tbl[2] = vld1q_u8(kDotProdMergeBlockTbl + 32);
+
+ if (width == 4) {
+ const uint16x4_t max = vdup_n_u16((1 << bd) - 1);
+ int16_t *s = (int16_t *)src;
+
+ int16x4_t s0, s1, s2, s3, s4, s5, s6;
+ load_s16_4x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
+ s += 7 * src_stride;
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[2], s1234[2], s2345[2], s3456[2];
+ transpose_concat_4x4(s0, s1, s2, s3, s0123);
+ transpose_concat_4x4(s1, s2, s3, s4, s1234);
+ transpose_concat_4x4(s2, s3, s4, s5, s2345);
+ transpose_concat_4x4(s3, s4, s5, s6, s3456);
+
+ do {
+ int16x4_t s7, s8, s9, s10;
+ load_s16_4x4(s, src_stride, &s7, &s8, &s9, &s10);
+
+ int16x8_t s4567[2], s5678[2], s6789[2], s78910[2];
+
+ // Transpose and shuffle the 4 lines that were loaded.
+ transpose_concat_4x4(s7, s8, s9, s10, s78910);
+
+ // Merge new data into block from previous iteration.
+ aom_tbl2x2_s16(s3456, s78910, merge_block_tbl[0], s4567);
+ aom_tbl2x2_s16(s3456, s78910, merge_block_tbl[1], s5678);
+ aom_tbl2x2_s16(s3456, s78910, merge_block_tbl[2], s6789);
+
+ uint16x4_t d0 = highbd_convolve8_4_v(s0123, s4567, y_filter, max);
+ uint16x4_t d1 = highbd_convolve8_4_v(s1234, s5678, y_filter, max);
+ uint16x4_t d2 = highbd_convolve8_4_v(s2345, s6789, y_filter, max);
+ uint16x4_t d3 = highbd_convolve8_4_v(s3456, s78910, y_filter, max);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s3456[0] = s78910[0];
+ s3456[1] = s78910[1];
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+ do {
+ int h = height;
+ int16_t *s = (int16_t *)src;
+ uint16_t *d = dst;
+
+ int16x8_t s0, s1, s2, s3, s4, s5, s6;
+ load_s16_8x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
+ s += 7 * src_stride;
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[4], s1234[4], s2345[4], s3456[4];
+ transpose_concat_8x4(s0, s1, s2, s3, s0123);
+ transpose_concat_8x4(s1, s2, s3, s4, s1234);
+ transpose_concat_8x4(s2, s3, s4, s5, s2345);
+ transpose_concat_8x4(s3, s4, s5, s6, s3456);
+
+ do {
+ int16x8_t s7, s8, s9, s10;
+ load_s16_8x4(s, src_stride, &s7, &s8, &s9, &s10);
+
+ int16x8_t s4567[4], s5678[4], s6789[4], s78910[4];
+
+ // Transpose and shuffle the 4 lines that were loaded.
+ transpose_concat_8x4(s7, s8, s9, s10, s78910);
+
+ // Merge new data into block from previous iteration.
+ aom_tbl2x4_s16(s3456, s78910, merge_block_tbl[0], s4567);
+ aom_tbl2x4_s16(s3456, s78910, merge_block_tbl[1], s5678);
+ aom_tbl2x4_s16(s3456, s78910, merge_block_tbl[2], s6789);
+
+ uint16x8_t d0 = highbd_convolve8_8_v(s0123, s4567, y_filter, max);
+ uint16x8_t d1 = highbd_convolve8_8_v(s1234, s5678, y_filter, max);
+ uint16x8_t d2 = highbd_convolve8_8_v(s2345, s6789, y_filter, max);
+ uint16x8_t d3 = highbd_convolve8_8_v(s3456, s78910, y_filter, max);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s0123[2] = s4567[2];
+ s0123[3] = s4567[3];
+
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s1234[2] = s5678[2];
+ s1234[3] = s5678[3];
+
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s2345[2] = s6789[2];
+ s2345[3] = s6789[3];
+
+ s3456[0] = s78910[0];
+ s3456[1] = s78910[1];
+ s3456[2] = s78910[2];
+ s3456[3] = s78910[3];
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ src += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ }
+}
+
+static INLINE uint16x4_t highbd_convolve4_4_v(int16x8_t s[2], int16x8_t filter,
+ uint16x4_t max) {
+ int64x2_t sum01 = aom_svdot_lane_s16(vdupq_n_s64(0), s[0], filter, 0);
+ int64x2_t sum23 = aom_svdot_lane_s16(vdupq_n_s64(0), s[1], filter, 0);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ uint16x4_t res = vqrshrun_n_s32(sum0123, FILTER_BITS);
+
+ return vmin_u16(res, max);
+}
+
+static INLINE uint16x8_t highbd_convolve4_8_v(int16x8_t s[4], int16x8_t filter,
+ uint16x8_t max) {
+ int64x2_t sum01 = aom_svdot_lane_s16(vdupq_n_s64(0), s[0], filter, 0);
+ int64x2_t sum23 = aom_svdot_lane_s16(vdupq_n_s64(0), s[1], filter, 0);
+ int64x2_t sum45 = aom_svdot_lane_s16(vdupq_n_s64(0), s[2], filter, 0);
+ int64x2_t sum67 = aom_svdot_lane_s16(vdupq_n_s64(0), s[3], filter, 0);
+
+ int32x4_t s0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ int32x4_t s4567 = vcombine_s32(vmovn_s64(sum45), vmovn_s64(sum67));
+
+ uint16x8_t res = vcombine_u16(vqrshrun_n_s32(s0123, FILTER_BITS),
+ vqrshrun_n_s32(s4567, FILTER_BITS));
+
+ return vminq_u16(res, max);
+}
+
+static INLINE void highbd_convolve8_vert_4tap_sve(
+ const uint16_t *src, ptrdiff_t src_stride, uint16_t *dst,
+ ptrdiff_t dst_stride, const int16_t *filter_y, int width, int height,
+ int bd) {
+ const int16x8_t y_filter =
+ vcombine_s16(vld1_s16(filter_y + 2), vdup_n_s16(0));
+
+ uint8x16_t merge_block_tbl[3];
+ merge_block_tbl[0] = vld1q_u8(kDotProdMergeBlockTbl);
+ merge_block_tbl[1] = vld1q_u8(kDotProdMergeBlockTbl + 16);
+ merge_block_tbl[2] = vld1q_u8(kDotProdMergeBlockTbl + 32);
+
+ if (width == 4) {
+ const uint16x4_t max = vdup_n_u16((1 << bd) - 1);
+ int16_t *s = (int16_t *)src;
+
+ int16x4_t s0, s1, s2;
+ load_s16_4x3(s, src_stride, &s0, &s1, &s2);
+ s += 3 * src_stride;
+
+ do {
+ int16x4_t s3, s4, s5, s6;
+ load_s16_4x4(s, src_stride, &s3, &s4, &s5, &s6);
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[2], s1234[2], s2345[2], s3456[2];
+ transpose_concat_4x4(s0, s1, s2, s3, s0123);
+ transpose_concat_4x4(s1, s2, s3, s4, s1234);
+ transpose_concat_4x4(s2, s3, s4, s5, s2345);
+ transpose_concat_4x4(s3, s4, s5, s6, s3456);
+
+ uint16x4_t d0 = highbd_convolve4_4_v(s0123, y_filter, max);
+ uint16x4_t d1 = highbd_convolve4_4_v(s1234, y_filter, max);
+ uint16x4_t d2 = highbd_convolve4_4_v(s2345, y_filter, max);
+ uint16x4_t d3 = highbd_convolve4_4_v(s3456, y_filter, max);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ // Shuffle everything up four rows.
+ s0 = s4;
+ s1 = s5;
+ s2 = s6;
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+ do {
+ int h = height;
+ int16_t *s = (int16_t *)src;
+ uint16_t *d = dst;
+
+ int16x8_t s0, s1, s2;
+ load_s16_8x3(s, src_stride, &s0, &s1, &s2);
+ s += 3 * src_stride;
+
+ do {
+ int16x8_t s3, s4, s5, s6;
+ load_s16_8x4(s, src_stride, &s3, &s4, &s5, &s6);
+
+ // This operation combines a conventional transpose and the sample
+ // permute required before computing the dot product.
+ int16x8_t s0123[4], s1234[4], s2345[4], s3456[4];
+ transpose_concat_8x4(s0, s1, s2, s3, s0123);
+ transpose_concat_8x4(s1, s2, s3, s4, s1234);
+ transpose_concat_8x4(s2, s3, s4, s5, s2345);
+ transpose_concat_8x4(s3, s4, s5, s6, s3456);
+
+ uint16x8_t d0 = highbd_convolve4_8_v(s0123, y_filter, max);
+ uint16x8_t d1 = highbd_convolve4_8_v(s1234, y_filter, max);
+ uint16x8_t d2 = highbd_convolve4_8_v(s2345, y_filter, max);
+ uint16x8_t d3 = highbd_convolve4_8_v(s3456, y_filter, max);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ // Shuffle everything up four rows.
+ s0 = s4;
+ s1 = s5;
+ s2 = s6;
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ src += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ }
+}
+
+void aom_highbd_convolve8_vert_sve(const uint8_t *src8, ptrdiff_t src_stride,
+ uint8_t *dst8, ptrdiff_t dst_stride,
+ const int16_t *filter_x, int x_step_q4,
+ const int16_t *filter_y, int y_step_q4,
+ int width, int height, int bd) {
+ assert(y_step_q4 == 16);
+ assert(w >= 4 && h >= 4);
+ (void)filter_x;
+ (void)y_step_q4;
+ (void)x_step_q4;
+
+ const uint16_t *src = CONVERT_TO_SHORTPTR(src8);
+ uint16_t *dst = CONVERT_TO_SHORTPTR(dst8);
+
+ src -= (SUBPEL_TAPS / 2 - 1) * src_stride;
+
+ if (get_filter_taps_convolve8(filter_y) <= 4) {
+ highbd_convolve8_vert_4tap_sve(src + 2 * src_stride, src_stride, dst,
+ dst_stride, filter_y, width, height, bd);
+ } else {
+ highbd_convolve8_vert_8tap_sve(src, src_stride, dst, dst_stride, filter_y,
+ width, height, bd);
+ }
+}
diff --git a/third_party/aom/aom_dsp/arm/highbd_sse_sve.c b/third_party/aom/aom_dsp/arm/highbd_sse_sve.c
index b267da5cfb..9ea13ab67a 100644
--- a/third_party/aom/aom_dsp/arm/highbd_sse_sve.c
+++ b/third_party/aom/aom_dsp/arm/highbd_sse_sve.c
@@ -10,7 +10,7 @@
#include <arm_neon.h>
-#include "aom_dsp/arm/dot_sve.h"
+#include "aom_dsp/arm/aom_neon_sve_bridge.h"
#include "aom_dsp/arm/mem_neon.h"
#include "config/aom_dsp_rtcd.h"
diff --git a/third_party/aom/aom_dsp/arm/highbd_variance_sve.c b/third_party/aom/aom_dsp/arm/highbd_variance_sve.c
index a2c30a1688..ad1f55e367 100644
--- a/third_party/aom/aom_dsp/arm/highbd_variance_sve.c
+++ b/third_party/aom/aom_dsp/arm/highbd_variance_sve.c
@@ -16,7 +16,7 @@
#include "config/aom_dsp_rtcd.h"
#include "aom_dsp/aom_filter.h"
-#include "aom_dsp/arm/dot_sve.h"
+#include "aom_dsp/arm/aom_neon_sve_bridge.h"
#include "aom_dsp/arm/mem_neon.h"
#include "aom_dsp/variance.h"
diff --git a/third_party/aom/aom_dsp/arm/mem_neon.h b/third_party/aom/aom_dsp/arm/mem_neon.h
index 52c7a34e3e..32a462a186 100644
--- a/third_party/aom/aom_dsp/arm/mem_neon.h
+++ b/third_party/aom/aom_dsp/arm/mem_neon.h
@@ -56,17 +56,10 @@ static INLINE uint16x8x4_t vld1q_u16_x4(const uint16_t *ptr) {
#elif defined(__GNUC__) && !defined(__clang__) // GCC 64-bit.
#if __GNUC__ < 8
-
static INLINE uint8x16x2_t vld1q_u8_x2(const uint8_t *ptr) {
uint8x16x2_t res = { { vld1q_u8(ptr + 0 * 16), vld1q_u8(ptr + 1 * 16) } };
return res;
}
-
-static INLINE uint16x8x4_t vld1q_u16_x4(const uint16_t *ptr) {
- uint16x8x4_t res = { { vld1q_u16(ptr + 0 * 8), vld1q_u16(ptr + 1 * 8),
- vld1q_u16(ptr + 2 * 8), vld1q_u16(ptr + 3 * 8) } };
- return res;
-}
#endif // __GNUC__ < 8
#if __GNUC__ < 9
@@ -76,6 +69,15 @@ static INLINE uint8x16x3_t vld1q_u8_x3(const uint8_t *ptr) {
return res;
}
#endif // __GNUC__ < 9
+
+// vld1q_u16_x4 is defined from GCC 8.5.0 and onwards.
+#if ((__GNUC__ << 8) | __GNUC_MINOR__) < 0x805
+static INLINE uint16x8x4_t vld1q_u16_x4(const uint16_t *ptr) {
+ uint16x8x4_t res = { { vld1q_u16(ptr + 0 * 8), vld1q_u16(ptr + 1 * 8),
+ vld1q_u16(ptr + 2 * 8), vld1q_u16(ptr + 3 * 8) } };
+ return res;
+}
+#endif // ((__GNUC__ << 8) | __GNUC_MINOR__) < 0x805
#endif // defined(__GNUC__) && !defined(__clang__)
static INLINE void store_u8_8x2(uint8_t *s, ptrdiff_t p, const uint8x8_t s0,
@@ -457,6 +459,16 @@ static INLINE void load_s16_4x4(const int16_t *s, ptrdiff_t p,
*s3 = vld1_s16(s);
}
+static INLINE void load_s16_4x3(const int16_t *s, ptrdiff_t p,
+ int16x4_t *const s0, int16x4_t *const s1,
+ int16x4_t *const s2) {
+ *s0 = vld1_s16(s);
+ s += p;
+ *s1 = vld1_s16(s);
+ s += p;
+ *s2 = vld1_s16(s);
+}
+
static INLINE void store_u8_8x8(uint8_t *s, ptrdiff_t p, const uint8x8_t s0,
const uint8x8_t s1, const uint8x8_t s2,
const uint8x8_t s3, const uint8x8_t s4,
@@ -525,6 +537,16 @@ static INLINE void store_u16_8x8(uint16_t *s, ptrdiff_t dst_stride,
vst1q_u16(s, s7);
}
+static INLINE void store_u16_4x3(uint16_t *s, ptrdiff_t dst_stride,
+ const uint16x4_t s0, const uint16x4_t s1,
+ const uint16x4_t s2) {
+ vst1_u16(s, s0);
+ s += dst_stride;
+ vst1_u16(s, s1);
+ s += dst_stride;
+ vst1_u16(s, s2);
+}
+
static INLINE void store_u16_4x4(uint16_t *s, ptrdiff_t dst_stride,
const uint16x4_t s0, const uint16x4_t s1,
const uint16x4_t s2, const uint16x4_t s3) {
@@ -544,6 +566,16 @@ static INLINE void store_u16_8x2(uint16_t *s, ptrdiff_t dst_stride,
vst1q_u16(s, s1);
}
+static INLINE void store_u16_8x3(uint16_t *s, ptrdiff_t dst_stride,
+ const uint16x8_t s0, const uint16x8_t s1,
+ const uint16x8_t s2) {
+ vst1q_u16(s, s0);
+ s += dst_stride;
+ vst1q_u16(s, s1);
+ s += dst_stride;
+ vst1q_u16(s, s2);
+}
+
static INLINE void store_u16_8x4(uint16_t *s, ptrdiff_t dst_stride,
const uint16x8_t s0, const uint16x8_t s1,
const uint16x8_t s2, const uint16x8_t s3) {
@@ -857,6 +889,16 @@ static INLINE void load_s16_8x4(const int16_t *s, ptrdiff_t p,
*s3 = vld1q_s16(s);
}
+static INLINE void load_s16_8x3(const int16_t *s, ptrdiff_t p,
+ int16x8_t *const s0, int16x8_t *const s1,
+ int16x8_t *const s2) {
+ *s0 = vld1q_s16(s);
+ s += p;
+ *s1 = vld1q_s16(s);
+ s += p;
+ *s2 = vld1q_s16(s);
+}
+
// Load 2 sets of 4 bytes when alignment is not guaranteed.
static INLINE uint8x8_t load_unaligned_u8(const uint8_t *buf, int stride) {
uint32_t a;
diff --git a/third_party/aom/aom_dsp/arm/sum_squares_sve.c b/third_party/aom/aom_dsp/arm/sum_squares_sve.c
index 724e43859e..c7e6dfcb02 100644
--- a/third_party/aom/aom_dsp/arm/sum_squares_sve.c
+++ b/third_party/aom/aom_dsp/arm/sum_squares_sve.c
@@ -11,7 +11,7 @@
#include <arm_neon.h>
-#include "aom_dsp/arm/dot_sve.h"
+#include "aom_dsp/arm/aom_neon_sve_bridge.h"
#include "aom_dsp/arm/mem_neon.h"
#include "config/aom_dsp_rtcd.h"
diff --git a/third_party/aom/aom_dsp/flow_estimation/corner_detect.c b/third_party/aom/aom_dsp/flow_estimation/corner_detect.c
index 284d1bd7b8..44d423dcdf 100644
--- a/third_party/aom/aom_dsp/flow_estimation/corner_detect.c
+++ b/third_party/aom/aom_dsp/flow_estimation/corner_detect.c
@@ -20,6 +20,7 @@
#include "aom_dsp/aom_dsp_common.h"
#include "aom_dsp/flow_estimation/corner_detect.h"
#include "aom_mem/aom_mem.h"
+#include "aom_util/aom_pthread.h"
#include "av1/common/common.h"
#define FAST_BARRIER 18
@@ -39,11 +40,24 @@ CornerList *av1_alloc_corner_list(void) {
return corners;
}
-static bool compute_corner_list(const ImagePyramid *pyr, CornerList *corners) {
- const uint8_t *buf = pyr->layers[0].buffer;
- int width = pyr->layers[0].width;
- int height = pyr->layers[0].height;
- int stride = pyr->layers[0].stride;
+static bool compute_corner_list(const YV12_BUFFER_CONFIG *frame, int bit_depth,
+ int downsample_level, CornerList *corners) {
+ ImagePyramid *pyr = frame->y_pyramid;
+ const int layers =
+ aom_compute_pyramid(frame, bit_depth, downsample_level + 1, pyr);
+
+ if (layers < 0) {
+ return false;
+ }
+
+ // Clamp downsampling ratio base on max number of layers allowed
+ // for this frame size
+ downsample_level = layers - 1;
+
+ const uint8_t *buf = pyr->layers[downsample_level].buffer;
+ int width = pyr->layers[downsample_level].width;
+ int height = pyr->layers[downsample_level].height;
+ int stride = pyr->layers[downsample_level].stride;
int *scores = NULL;
int num_corners;
@@ -53,9 +67,11 @@ static bool compute_corner_list(const ImagePyramid *pyr, CornerList *corners) {
if (num_corners <= MAX_CORNERS) {
// Use all detected corners
- if (num_corners != 0) {
- memcpy(corners->corners, frame_corners_xy,
- sizeof(*frame_corners_xy) * num_corners);
+ for (int i = 0; i < num_corners; i++) {
+ corners->corners[2 * i + 0] =
+ frame_corners_xy[i].x * (1 << downsample_level);
+ corners->corners[2 * i + 1] =
+ frame_corners_xy[i].y * (1 << downsample_level);
}
corners->num_corners = num_corners;
} else {
@@ -85,8 +101,10 @@ static bool compute_corner_list(const ImagePyramid *pyr, CornerList *corners) {
for (int i = 0; i < num_corners; i++) {
if (scores[i] > threshold) {
assert(copied_corners < MAX_CORNERS);
- corners->corners[2 * copied_corners + 0] = frame_corners_xy[i].x;
- corners->corners[2 * copied_corners + 1] = frame_corners_xy[i].y;
+ corners->corners[2 * copied_corners + 0] =
+ frame_corners_xy[i].x * (1 << downsample_level);
+ corners->corners[2 * copied_corners + 1] =
+ frame_corners_xy[i].y * (1 << downsample_level);
copied_corners += 1;
}
}
@@ -99,7 +117,8 @@ static bool compute_corner_list(const ImagePyramid *pyr, CornerList *corners) {
return true;
}
-bool av1_compute_corner_list(const ImagePyramid *pyr, CornerList *corners) {
+bool av1_compute_corner_list(const YV12_BUFFER_CONFIG *frame, int bit_depth,
+ int downsample_level, CornerList *corners) {
assert(corners);
#if CONFIG_MULTITHREAD
@@ -107,7 +126,8 @@ bool av1_compute_corner_list(const ImagePyramid *pyr, CornerList *corners) {
#endif // CONFIG_MULTITHREAD
if (!corners->valid) {
- corners->valid = compute_corner_list(pyr, corners);
+ corners->valid =
+ compute_corner_list(frame, bit_depth, downsample_level, corners);
}
bool valid = corners->valid;
diff --git a/third_party/aom/aom_dsp/flow_estimation/corner_detect.h b/third_party/aom/aom_dsp/flow_estimation/corner_detect.h
index d05846ce5d..54d94309ed 100644
--- a/third_party/aom/aom_dsp/flow_estimation/corner_detect.h
+++ b/third_party/aom/aom_dsp/flow_estimation/corner_detect.h
@@ -18,7 +18,7 @@
#include <memory.h>
#include "aom_dsp/pyramid.h"
-#include "aom_util/aom_thread.h"
+#include "aom_util/aom_pthread.h"
#ifdef __cplusplus
extern "C" {
@@ -57,7 +57,8 @@ size_t av1_get_corner_list_size(void);
CornerList *av1_alloc_corner_list(void);
-bool av1_compute_corner_list(const ImagePyramid *pyr, CornerList *corners);
+bool av1_compute_corner_list(const YV12_BUFFER_CONFIG *frame, int bit_depth,
+ int downsample_level, CornerList *corners);
#ifndef NDEBUG
// Check if a corner list has already been computed.
diff --git a/third_party/aom/aom_dsp/flow_estimation/corner_match.c b/third_party/aom/aom_dsp/flow_estimation/corner_match.c
index dc7589a8c6..c78edb8910 100644
--- a/third_party/aom/aom_dsp/flow_estimation/corner_match.c
+++ b/third_party/aom/aom_dsp/flow_estimation/corner_match.c
@@ -17,62 +17,84 @@
#include "aom_dsp/flow_estimation/corner_detect.h"
#include "aom_dsp/flow_estimation/corner_match.h"
+#include "aom_dsp/flow_estimation/disflow.h"
#include "aom_dsp/flow_estimation/flow_estimation.h"
#include "aom_dsp/flow_estimation/ransac.h"
#include "aom_dsp/pyramid.h"
#include "aom_scale/yv12config.h"
-#define SEARCH_SZ 9
-#define SEARCH_SZ_BY2 ((SEARCH_SZ - 1) / 2)
-
#define THRESHOLD_NCC 0.75
-/* Compute var(frame) * MATCH_SZ_SQ over a MATCH_SZ by MATCH_SZ window of frame,
- centered at (x, y).
+/* Compute mean and standard deviation of pixels in a window of size
+ MATCH_SZ by MATCH_SZ centered at (x, y).
+ Store results into *mean and *one_over_stddev
+
+ Note: The output of this function is scaled by MATCH_SZ, as in
+ *mean = MATCH_SZ * <true mean> and
+ *one_over_stddev = 1 / (MATCH_SZ * <true stddev>)
+
+ Combined with the fact that we return 1/stddev rather than the standard
+ deviation itself, this allows us to completely avoid divisions in
+ aom_compute_correlation, which is much hotter than this function is.
+
+ Returns true if this feature point is usable, false otherwise.
*/
-static double compute_variance(const unsigned char *frame, int stride, int x,
- int y) {
+bool aom_compute_mean_stddev_c(const unsigned char *frame, int stride, int x,
+ int y, double *mean, double *one_over_stddev) {
int sum = 0;
int sumsq = 0;
- int var;
- int i, j;
- for (i = 0; i < MATCH_SZ; ++i)
- for (j = 0; j < MATCH_SZ; ++j) {
+ for (int i = 0; i < MATCH_SZ; ++i) {
+ for (int j = 0; j < MATCH_SZ; ++j) {
sum += frame[(i + y - MATCH_SZ_BY2) * stride + (j + x - MATCH_SZ_BY2)];
sumsq += frame[(i + y - MATCH_SZ_BY2) * stride + (j + x - MATCH_SZ_BY2)] *
frame[(i + y - MATCH_SZ_BY2) * stride + (j + x - MATCH_SZ_BY2)];
}
- var = sumsq * MATCH_SZ_SQ - sum * sum;
- return (double)var;
+ }
+ *mean = (double)sum / MATCH_SZ;
+ const double variance = sumsq - (*mean) * (*mean);
+ if (variance < MIN_FEATURE_VARIANCE) {
+ *one_over_stddev = 0.0;
+ return false;
+ }
+ *one_over_stddev = 1.0 / sqrt(variance);
+ return true;
}
-/* Compute corr(frame1, frame2) * MATCH_SZ * stddev(frame1), where the
- correlation/standard deviation are taken over MATCH_SZ by MATCH_SZ windows
- of each image, centered at (x1, y1) and (x2, y2) respectively.
+/* Compute corr(frame1, frame2) over a window of size MATCH_SZ by MATCH_SZ.
+ To save on computation, the mean and (1 divided by the) standard deviation
+ of the window in each frame are precomputed and passed into this function
+ as arguments.
*/
-double av1_compute_cross_correlation_c(const unsigned char *frame1, int stride1,
- int x1, int y1,
- const unsigned char *frame2, int stride2,
- int x2, int y2) {
+double aom_compute_correlation_c(const unsigned char *frame1, int stride1,
+ int x1, int y1, double mean1,
+ double one_over_stddev1,
+ const unsigned char *frame2, int stride2,
+ int x2, int y2, double mean2,
+ double one_over_stddev2) {
int v1, v2;
- int sum1 = 0;
- int sum2 = 0;
- int sumsq2 = 0;
int cross = 0;
- int var2, cov;
- int i, j;
- for (i = 0; i < MATCH_SZ; ++i)
- for (j = 0; j < MATCH_SZ; ++j) {
+ for (int i = 0; i < MATCH_SZ; ++i) {
+ for (int j = 0; j < MATCH_SZ; ++j) {
v1 = frame1[(i + y1 - MATCH_SZ_BY2) * stride1 + (j + x1 - MATCH_SZ_BY2)];
v2 = frame2[(i + y2 - MATCH_SZ_BY2) * stride2 + (j + x2 - MATCH_SZ_BY2)];
- sum1 += v1;
- sum2 += v2;
- sumsq2 += v2 * v2;
cross += v1 * v2;
}
- var2 = sumsq2 * MATCH_SZ_SQ - sum2 * sum2;
- cov = cross * MATCH_SZ_SQ - sum1 * sum2;
- return cov / sqrt((double)var2);
+ }
+
+ // Note: In theory, the calculations here "should" be
+ // covariance = cross / N^2 - mean1 * mean2
+ // correlation = covariance / (stddev1 * stddev2).
+ //
+ // However, because of the scaling in aom_compute_mean_stddev, the
+ // lines below actually calculate
+ // covariance * N^2 = cross - (mean1 * N) * (mean2 * N)
+ // correlation = (covariance * N^2) / ((stddev1 * N) * (stddev2 * N))
+ //
+ // ie. we have removed the need for a division, and still end up with the
+ // correct unscaled correlation (ie, in the range [-1, +1])
+ double covariance = cross - mean1 * mean2;
+ double correlation = covariance * (one_over_stddev1 * one_over_stddev2);
+ return correlation;
}
static int is_eligible_point(int pointx, int pointy, int width, int height) {
@@ -87,65 +109,14 @@ static int is_eligible_distance(int point1x, int point1y, int point2x,
(point1y - point2y) * (point1y - point2y)) <= thresh * thresh;
}
-static void improve_correspondence(const unsigned char *src,
- const unsigned char *ref, int width,
- int height, int src_stride, int ref_stride,
- Correspondence *correspondences,
- int num_correspondences) {
- int i;
- for (i = 0; i < num_correspondences; ++i) {
- int x, y, best_x = 0, best_y = 0;
- double best_match_ncc = 0.0;
- // For this algorithm, all points have integer coordinates.
- // It's a little more efficient to convert them to ints once,
- // before the inner loops
- int x0 = (int)correspondences[i].x;
- int y0 = (int)correspondences[i].y;
- int rx0 = (int)correspondences[i].rx;
- int ry0 = (int)correspondences[i].ry;
- for (y = -SEARCH_SZ_BY2; y <= SEARCH_SZ_BY2; ++y) {
- for (x = -SEARCH_SZ_BY2; x <= SEARCH_SZ_BY2; ++x) {
- double match_ncc;
- if (!is_eligible_point(rx0 + x, ry0 + y, width, height)) continue;
- if (!is_eligible_distance(x0, y0, rx0 + x, ry0 + y, width, height))
- continue;
- match_ncc = av1_compute_cross_correlation(src, src_stride, x0, y0, ref,
- ref_stride, rx0 + x, ry0 + y);
- if (match_ncc > best_match_ncc) {
- best_match_ncc = match_ncc;
- best_y = y;
- best_x = x;
- }
- }
- }
- correspondences[i].rx += best_x;
- correspondences[i].ry += best_y;
- }
- for (i = 0; i < num_correspondences; ++i) {
- int x, y, best_x = 0, best_y = 0;
- double best_match_ncc = 0.0;
- int x0 = (int)correspondences[i].x;
- int y0 = (int)correspondences[i].y;
- int rx0 = (int)correspondences[i].rx;
- int ry0 = (int)correspondences[i].ry;
- for (y = -SEARCH_SZ_BY2; y <= SEARCH_SZ_BY2; ++y)
- for (x = -SEARCH_SZ_BY2; x <= SEARCH_SZ_BY2; ++x) {
- double match_ncc;
- if (!is_eligible_point(x0 + x, y0 + y, width, height)) continue;
- if (!is_eligible_distance(x0 + x, y0 + y, rx0, ry0, width, height))
- continue;
- match_ncc = av1_compute_cross_correlation(
- ref, ref_stride, rx0, ry0, src, src_stride, x0 + x, y0 + y);
- if (match_ncc > best_match_ncc) {
- best_match_ncc = match_ncc;
- best_y = y;
- best_x = x;
- }
- }
- correspondences[i].x += best_x;
- correspondences[i].y += best_y;
- }
-}
+typedef struct {
+ int x;
+ int y;
+ double mean;
+ double one_over_stddev;
+ int best_match_idx;
+ double best_match_corr;
+} PointInfo;
static int determine_correspondence(const unsigned char *src,
const int *src_corners, int num_src_corners,
@@ -154,56 +125,136 @@ static int determine_correspondence(const unsigned char *src,
int width, int height, int src_stride,
int ref_stride,
Correspondence *correspondences) {
- // TODO(sarahparker) Improve this to include 2-way match
- int i, j;
+ PointInfo *src_point_info = NULL;
+ PointInfo *ref_point_info = NULL;
int num_correspondences = 0;
- for (i = 0; i < num_src_corners; ++i) {
- double best_match_ncc = 0.0;
- double template_norm;
- int best_match_j = -1;
- if (!is_eligible_point(src_corners[2 * i], src_corners[2 * i + 1], width,
- height))
+
+ src_point_info =
+ (PointInfo *)aom_calloc(num_src_corners, sizeof(*src_point_info));
+ if (!src_point_info) {
+ goto finished;
+ }
+
+ ref_point_info =
+ (PointInfo *)aom_calloc(num_ref_corners, sizeof(*ref_point_info));
+ if (!ref_point_info) {
+ goto finished;
+ }
+
+ // First pass (linear):
+ // Filter corner lists and compute per-patch means and standard deviations,
+ // for the src and ref frames independently
+ int src_point_count = 0;
+ for (int i = 0; i < num_src_corners; i++) {
+ int src_x = src_corners[2 * i];
+ int src_y = src_corners[2 * i + 1];
+ if (!is_eligible_point(src_x, src_y, width, height)) continue;
+
+ PointInfo *point = &src_point_info[src_point_count];
+ point->x = src_x;
+ point->y = src_y;
+ point->best_match_corr = THRESHOLD_NCC;
+ if (!aom_compute_mean_stddev(src, src_stride, src_x, src_y, &point->mean,
+ &point->one_over_stddev))
continue;
- for (j = 0; j < num_ref_corners; ++j) {
- double match_ncc;
- if (!is_eligible_point(ref_corners[2 * j], ref_corners[2 * j + 1], width,
- height))
- continue;
- if (!is_eligible_distance(src_corners[2 * i], src_corners[2 * i + 1],
- ref_corners[2 * j], ref_corners[2 * j + 1],
- width, height))
+ src_point_count++;
+ }
+ if (src_point_count == 0) {
+ goto finished;
+ }
+
+ int ref_point_count = 0;
+ for (int j = 0; j < num_ref_corners; j++) {
+ int ref_x = ref_corners[2 * j];
+ int ref_y = ref_corners[2 * j + 1];
+ if (!is_eligible_point(ref_x, ref_y, width, height)) continue;
+
+ PointInfo *point = &ref_point_info[ref_point_count];
+ point->x = ref_x;
+ point->y = ref_y;
+ point->best_match_corr = THRESHOLD_NCC;
+ if (!aom_compute_mean_stddev(ref, ref_stride, ref_x, ref_y, &point->mean,
+ &point->one_over_stddev))
+ continue;
+ ref_point_count++;
+ }
+ if (ref_point_count == 0) {
+ goto finished;
+ }
+
+ // Second pass (quadratic):
+ // For each pair of points, compute correlation, and use this to determine
+ // the best match of each corner, in both directions
+ for (int i = 0; i < src_point_count; ++i) {
+ PointInfo *src_point = &src_point_info[i];
+ for (int j = 0; j < ref_point_count; ++j) {
+ PointInfo *ref_point = &ref_point_info[j];
+ if (!is_eligible_distance(src_point->x, src_point->y, ref_point->x,
+ ref_point->y, width, height))
continue;
- match_ncc = av1_compute_cross_correlation(
- src, src_stride, src_corners[2 * i], src_corners[2 * i + 1], ref,
- ref_stride, ref_corners[2 * j], ref_corners[2 * j + 1]);
- if (match_ncc > best_match_ncc) {
- best_match_ncc = match_ncc;
- best_match_j = j;
+
+ double corr = aom_compute_correlation(
+ src, src_stride, src_point->x, src_point->y, src_point->mean,
+ src_point->one_over_stddev, ref, ref_stride, ref_point->x,
+ ref_point->y, ref_point->mean, ref_point->one_over_stddev);
+
+ if (corr > src_point->best_match_corr) {
+ src_point->best_match_idx = j;
+ src_point->best_match_corr = corr;
+ }
+ if (corr > ref_point->best_match_corr) {
+ ref_point->best_match_idx = i;
+ ref_point->best_match_corr = corr;
}
}
- // Note: We want to test if the best correlation is >= THRESHOLD_NCC,
- // but need to account for the normalization in
- // av1_compute_cross_correlation.
- template_norm = compute_variance(src, src_stride, src_corners[2 * i],
- src_corners[2 * i + 1]);
- if (best_match_ncc > THRESHOLD_NCC * sqrt(template_norm)) {
- correspondences[num_correspondences].x = src_corners[2 * i];
- correspondences[num_correspondences].y = src_corners[2 * i + 1];
- correspondences[num_correspondences].rx = ref_corners[2 * best_match_j];
- correspondences[num_correspondences].ry =
- ref_corners[2 * best_match_j + 1];
+ }
+
+ // Third pass (linear):
+ // Scan through source corners, generating a correspondence for each corner
+ // iff ref_best_match[src_best_match[i]] == i
+ // Then refine the generated correspondences using optical flow
+ for (int i = 0; i < src_point_count; i++) {
+ PointInfo *point = &src_point_info[i];
+
+ // Skip corners which were not matched, or which didn't find
+ // a good enough match
+ if (point->best_match_corr < THRESHOLD_NCC) continue;
+
+ PointInfo *match_point = &ref_point_info[point->best_match_idx];
+ if (match_point->best_match_idx == i) {
+ // Refine match using optical flow and store
+ const int sx = point->x;
+ const int sy = point->y;
+ const int rx = match_point->x;
+ const int ry = match_point->y;
+ double u = (double)(rx - sx);
+ double v = (double)(ry - sy);
+
+ const int patch_tl_x = sx - DISFLOW_PATCH_CENTER;
+ const int patch_tl_y = sy - DISFLOW_PATCH_CENTER;
+
+ aom_compute_flow_at_point(src, ref, patch_tl_x, patch_tl_y, width, height,
+ src_stride, &u, &v);
+
+ Correspondence *correspondence = &correspondences[num_correspondences];
+ correspondence->x = (double)sx;
+ correspondence->y = (double)sy;
+ correspondence->rx = (double)sx + u;
+ correspondence->ry = (double)sy + v;
num_correspondences++;
}
}
- improve_correspondence(src, ref, width, height, src_stride, ref_stride,
- correspondences, num_correspondences);
+
+finished:
+ aom_free(src_point_info);
+ aom_free(ref_point_info);
return num_correspondences;
}
bool av1_compute_global_motion_feature_match(
TransformationType type, YV12_BUFFER_CONFIG *src, YV12_BUFFER_CONFIG *ref,
- int bit_depth, MotionModel *motion_models, int num_motion_models,
- bool *mem_alloc_failed) {
+ int bit_depth, int downsample_level, MotionModel *motion_models,
+ int num_motion_models, bool *mem_alloc_failed) {
int num_correspondences;
Correspondence *correspondences;
ImagePyramid *src_pyramid = src->y_pyramid;
@@ -212,19 +263,19 @@ bool av1_compute_global_motion_feature_match(
CornerList *ref_corners = ref->corners;
// Precompute information we will need about each frame
- if (!aom_compute_pyramid(src, bit_depth, src_pyramid)) {
+ if (aom_compute_pyramid(src, bit_depth, 1, src_pyramid) < 0) {
*mem_alloc_failed = true;
return false;
}
- if (!av1_compute_corner_list(src_pyramid, src_corners)) {
+ if (!av1_compute_corner_list(src, bit_depth, downsample_level, src_corners)) {
*mem_alloc_failed = true;
return false;
}
- if (!aom_compute_pyramid(ref, bit_depth, ref_pyramid)) {
+ if (aom_compute_pyramid(ref, bit_depth, 1, ref_pyramid) < 0) {
*mem_alloc_failed = true;
return false;
}
- if (!av1_compute_corner_list(ref_pyramid, ref_corners)) {
+ if (!av1_compute_corner_list(src, bit_depth, downsample_level, ref_corners)) {
*mem_alloc_failed = true;
return false;
}
diff --git a/third_party/aom/aom_dsp/flow_estimation/corner_match.h b/third_party/aom/aom_dsp/flow_estimation/corner_match.h
index 4435d2c767..77ebee2ea3 100644
--- a/third_party/aom/aom_dsp/flow_estimation/corner_match.h
+++ b/third_party/aom/aom_dsp/flow_estimation/corner_match.h
@@ -25,14 +25,20 @@
extern "C" {
#endif
-#define MATCH_SZ 13
+#define MATCH_SZ 16
#define MATCH_SZ_BY2 ((MATCH_SZ - 1) / 2)
#define MATCH_SZ_SQ (MATCH_SZ * MATCH_SZ)
+// Minimum threshold for the variance of a patch, in order for it to be
+// considered useful for matching.
+// This is evaluated against the scaled variance MATCH_SZ_SQ * sigma^2,
+// so a setting of 1 * MATCH_SZ_SQ corresponds to an unscaled variance of 1
+#define MIN_FEATURE_VARIANCE (1 * MATCH_SZ_SQ)
+
bool av1_compute_global_motion_feature_match(
TransformationType type, YV12_BUFFER_CONFIG *src, YV12_BUFFER_CONFIG *ref,
- int bit_depth, MotionModel *motion_models, int num_motion_models,
- bool *mem_alloc_failed);
+ int bit_depth, int downsample_level, MotionModel *motion_models,
+ int num_motion_models, bool *mem_alloc_failed);
#ifdef __cplusplus
}
diff --git a/third_party/aom/aom_dsp/flow_estimation/disflow.c b/third_party/aom/aom_dsp/flow_estimation/disflow.c
index 82b531c729..f511a6eb49 100644
--- a/third_party/aom/aom_dsp/flow_estimation/disflow.c
+++ b/third_party/aom/aom_dsp/flow_estimation/disflow.c
@@ -603,9 +603,9 @@ static void upscale_flow_component(double *flow, int cur_width, int cur_height,
// make sure flow_u and flow_v start at 0
static bool compute_flow_field(const ImagePyramid *src_pyr,
- const ImagePyramid *ref_pyr, FlowField *flow) {
+ const ImagePyramid *ref_pyr, int n_levels,
+ FlowField *flow) {
bool mem_status = true;
- assert(src_pyr->n_levels == ref_pyr->n_levels);
double *flow_u = flow->u;
double *flow_v = flow->v;
@@ -613,7 +613,7 @@ static bool compute_flow_field(const ImagePyramid *src_pyr,
double *tmpbuf0;
double *tmpbuf;
- if (src_pyr->n_levels < 2) {
+ if (n_levels < 2) {
// tmpbuf not needed
tmpbuf0 = NULL;
tmpbuf = NULL;
@@ -639,7 +639,7 @@ static bool compute_flow_field(const ImagePyramid *src_pyr,
// correspondences by interpolating this flow field, and then refine the
// correspondences themselves. This is both faster and gives better output
// compared to refining the flow field at level 0 and then interpolating.
- for (int level = src_pyr->n_levels - 1; level >= 1; --level) {
+ for (int level = n_levels - 1; level >= 1; --level) {
const PyramidLayer *cur_layer = &src_pyr->layers[level];
const int cur_width = cur_layer->width;
const int cur_height = cur_layer->height;
@@ -762,29 +762,31 @@ static void free_flow_field(FlowField *flow) {
// Following the convention in flow_estimation.h, the flow vectors are computed
// at fixed points in `src` and point to the corresponding locations in `ref`,
// regardless of the temporal ordering of the frames.
-bool av1_compute_global_motion_disflow(TransformationType type,
- YV12_BUFFER_CONFIG *src,
- YV12_BUFFER_CONFIG *ref, int bit_depth,
- MotionModel *motion_models,
- int num_motion_models,
- bool *mem_alloc_failed) {
+bool av1_compute_global_motion_disflow(
+ TransformationType type, YV12_BUFFER_CONFIG *src, YV12_BUFFER_CONFIG *ref,
+ int bit_depth, int downsample_level, MotionModel *motion_models,
+ int num_motion_models, bool *mem_alloc_failed) {
// Precompute information we will need about each frame
ImagePyramid *src_pyramid = src->y_pyramid;
CornerList *src_corners = src->corners;
ImagePyramid *ref_pyramid = ref->y_pyramid;
- if (!aom_compute_pyramid(src, bit_depth, src_pyramid)) {
- *mem_alloc_failed = true;
- return false;
- }
- if (!av1_compute_corner_list(src_pyramid, src_corners)) {
+
+ const int src_layers =
+ aom_compute_pyramid(src, bit_depth, DISFLOW_PYRAMID_LEVELS, src_pyramid);
+ const int ref_layers =
+ aom_compute_pyramid(ref, bit_depth, DISFLOW_PYRAMID_LEVELS, ref_pyramid);
+
+ if (src_layers < 0 || ref_layers < 0) {
*mem_alloc_failed = true;
return false;
}
- if (!aom_compute_pyramid(ref, bit_depth, ref_pyramid)) {
+ if (!av1_compute_corner_list(src, bit_depth, downsample_level, src_corners)) {
*mem_alloc_failed = true;
return false;
}
+ assert(src_layers == ref_layers);
+
const int src_width = src_pyramid->layers[0].width;
const int src_height = src_pyramid->layers[0].height;
assert(ref_pyramid->layers[0].width == src_width);
@@ -796,7 +798,7 @@ bool av1_compute_global_motion_disflow(TransformationType type,
return false;
}
- if (!compute_flow_field(src_pyramid, ref_pyramid, flow)) {
+ if (!compute_flow_field(src_pyramid, ref_pyramid, src_layers, flow)) {
*mem_alloc_failed = true;
free_flow_field(flow);
return false;
diff --git a/third_party/aom/aom_dsp/flow_estimation/disflow.h b/third_party/aom/aom_dsp/flow_estimation/disflow.h
index ef877b638c..ac3680004d 100644
--- a/third_party/aom/aom_dsp/flow_estimation/disflow.h
+++ b/third_party/aom/aom_dsp/flow_estimation/disflow.h
@@ -15,7 +15,6 @@
#include <stdbool.h>
#include "aom_dsp/flow_estimation/flow_estimation.h"
-#include "aom_dsp/rect.h"
#include "aom_scale/yv12config.h"
#ifdef __cplusplus
@@ -92,12 +91,10 @@ typedef struct {
int stride;
} FlowField;
-bool av1_compute_global_motion_disflow(TransformationType type,
- YV12_BUFFER_CONFIG *src,
- YV12_BUFFER_CONFIG *ref, int bit_depth,
- MotionModel *motion_models,
- int num_motion_models,
- bool *mem_alloc_failed);
+bool av1_compute_global_motion_disflow(
+ TransformationType type, YV12_BUFFER_CONFIG *src, YV12_BUFFER_CONFIG *ref,
+ int bit_depth, int downsample_level, MotionModel *motion_models,
+ int num_motion_models, bool *mem_alloc_failed);
#ifdef __cplusplus
}
diff --git a/third_party/aom/aom_dsp/flow_estimation/flow_estimation.c b/third_party/aom/aom_dsp/flow_estimation/flow_estimation.c
index 0f47f86f55..96624eb863 100644
--- a/third_party/aom/aom_dsp/flow_estimation/flow_estimation.c
+++ b/third_party/aom/aom_dsp/flow_estimation/flow_estimation.c
@@ -18,14 +18,6 @@
#include "aom_ports/mem.h"
#include "aom_scale/yv12config.h"
-// For each global motion method, how many pyramid levels should we allocate?
-// Note that this is a maximum, and fewer levels will be allocated if the frame
-// is not large enough to need all of the specified levels
-const int global_motion_pyr_levels[GLOBAL_MOTION_METHODS] = {
- 1, // GLOBAL_MOTION_METHOD_FEATURE_MATCH
- 16, // GLOBAL_MOTION_METHOD_DISFLOW
-};
-
// clang-format off
const double kIdentityParams[MAX_PARAMDIM] = {
0.0, 0.0, 1.0, 0.0, 0.0, 1.0
@@ -43,17 +35,17 @@ const double kIdentityParams[MAX_PARAMDIM] = {
bool aom_compute_global_motion(TransformationType type, YV12_BUFFER_CONFIG *src,
YV12_BUFFER_CONFIG *ref, int bit_depth,
GlobalMotionMethod gm_method,
- MotionModel *motion_models,
+ int downsample_level, MotionModel *motion_models,
int num_motion_models, bool *mem_alloc_failed) {
switch (gm_method) {
case GLOBAL_MOTION_METHOD_FEATURE_MATCH:
return av1_compute_global_motion_feature_match(
- type, src, ref, bit_depth, motion_models, num_motion_models,
- mem_alloc_failed);
+ type, src, ref, bit_depth, downsample_level, motion_models,
+ num_motion_models, mem_alloc_failed);
case GLOBAL_MOTION_METHOD_DISFLOW:
- return av1_compute_global_motion_disflow(type, src, ref, bit_depth,
- motion_models, num_motion_models,
- mem_alloc_failed);
+ return av1_compute_global_motion_disflow(
+ type, src, ref, bit_depth, downsample_level, motion_models,
+ num_motion_models, mem_alloc_failed);
default: assert(0 && "Unknown global motion estimation type");
}
return false;
diff --git a/third_party/aom/aom_dsp/flow_estimation/flow_estimation.h b/third_party/aom/aom_dsp/flow_estimation/flow_estimation.h
index 2dfae24980..a38b03fc4e 100644
--- a/third_party/aom/aom_dsp/flow_estimation/flow_estimation.h
+++ b/third_party/aom/aom_dsp/flow_estimation/flow_estimation.h
@@ -61,11 +61,6 @@ typedef struct {
double rx, ry;
} Correspondence;
-// For each global motion method, how many pyramid levels should we allocate?
-// Note that this is a maximum, and fewer levels will be allocated if the frame
-// is not large enough to need all of the specified levels
-extern const int global_motion_pyr_levels[GLOBAL_MOTION_METHODS];
-
// Which global motion method should we use in practice?
// Disflow is both faster and gives better results than feature matching in
// practically all cases, so we use disflow by default
@@ -85,7 +80,7 @@ extern const double kIdentityParams[MAX_PARAMDIM];
bool aom_compute_global_motion(TransformationType type, YV12_BUFFER_CONFIG *src,
YV12_BUFFER_CONFIG *ref, int bit_depth,
GlobalMotionMethod gm_method,
- MotionModel *motion_models,
+ int downsample_level, MotionModel *motion_models,
int num_motion_models, bool *mem_alloc_failed);
#ifdef __cplusplus
diff --git a/third_party/aom/aom_dsp/flow_estimation/ransac.c b/third_party/aom/aom_dsp/flow_estimation/ransac.c
index b88a07b023..7c7bebdda4 100644
--- a/third_party/aom/aom_dsp/flow_estimation/ransac.c
+++ b/third_party/aom/aom_dsp/flow_estimation/ransac.c
@@ -29,8 +29,13 @@
#define INLIER_THRESHOLD 1.25
#define INLIER_THRESHOLD_SQUARED (INLIER_THRESHOLD * INLIER_THRESHOLD)
+
+// Number of initial models to generate
#define NUM_TRIALS 20
+// Number of times to refine the best model found
+#define NUM_REFINES 5
+
// Flag to enable functions for finding TRANSLATION type models.
//
// These modes are not considered currently due to a spec bug (see comments
@@ -39,63 +44,110 @@
// but disabled, for completeness.
#define ALLOW_TRANSLATION_MODELS 0
+typedef struct {
+ int num_inliers;
+ double sse; // Sum of squared errors of inliers
+ int *inlier_indices;
+} RANSAC_MOTION;
+
////////////////////////////////////////////////////////////////////////////////
// ransac
-typedef bool (*IsDegenerateFunc)(double *p);
-typedef bool (*FindTransformationFunc)(int points, const double *points1,
- const double *points2, double *params);
-typedef void (*ProjectPointsFunc)(const double *mat, const double *points,
- double *proj, int n, int stride_points,
- int stride_proj);
+typedef bool (*FindTransformationFunc)(const Correspondence *points,
+ const int *indices, int num_indices,
+ double *params);
+typedef void (*ScoreModelFunc)(const double *mat, const Correspondence *points,
+ int num_points, RANSAC_MOTION *model);
// vtable-like structure which stores all of the information needed by RANSAC
// for a particular model type
typedef struct {
- IsDegenerateFunc is_degenerate;
FindTransformationFunc find_transformation;
- ProjectPointsFunc project_points;
+ ScoreModelFunc score_model;
+
+ // The minimum number of points which can be passed to find_transformation
+ // to generate a model.
+ //
+ // This should be set as small as possible. This is due to an observation
+ // from section 4 of "Optimal Ransac" by A. Hast, J. Nysjö and
+ // A. Marchetti (https://dspace5.zcu.cz/bitstream/11025/6869/1/Hast.pdf):
+ // using the minimum possible number of points in the initial model maximizes
+ // the chance that all of the selected points are inliers.
+ //
+ // That paper proposes a method which can deal with models which are
+ // contaminated by outliers, which helps in cases where the inlier fraction
+ // is low. However, for our purposes, global motion only gives significant
+ // gains when the inlier fraction is high.
+ //
+ // So we do not use the method from this paper, but we do find that
+ // minimizing the number of points used for initial model fitting helps
+ // make the best use of the limited number of models we consider.
int minpts;
} RansacModelInfo;
#if ALLOW_TRANSLATION_MODELS
-static void project_points_translation(const double *mat, const double *points,
- double *proj, int n, int stride_points,
- int stride_proj) {
- int i;
- for (i = 0; i < n; ++i) {
- const double x = *(points++), y = *(points++);
- *(proj++) = x + mat[0];
- *(proj++) = y + mat[1];
- points += stride_points - 2;
- proj += stride_proj - 2;
+static void score_translation(const double *mat, const Correspondence *points,
+ int num_points, RANSAC_MOTION *model) {
+ model->num_inliers = 0;
+ model->sse = 0.0;
+
+ for (int i = 0; i < num_points; ++i) {
+ const double x1 = points[i].x;
+ const double y1 = points[i].y;
+ const double x2 = points[i].rx;
+ const double y2 = points[i].ry;
+
+ const double proj_x = x1 + mat[0];
+ const double proj_y = y1 + mat[1];
+
+ const double dx = proj_x - x2;
+ const double dy = proj_y - y2;
+ const double sse = dx * dx + dy * dy;
+
+ if (sse < INLIER_THRESHOLD_SQUARED) {
+ model->inlier_indices[model->num_inliers++] = i;
+ model->sse += sse;
+ }
}
}
#endif // ALLOW_TRANSLATION_MODELS
-static void project_points_affine(const double *mat, const double *points,
- double *proj, int n, int stride_points,
- int stride_proj) {
- int i;
- for (i = 0; i < n; ++i) {
- const double x = *(points++), y = *(points++);
- *(proj++) = mat[2] * x + mat[3] * y + mat[0];
- *(proj++) = mat[4] * x + mat[5] * y + mat[1];
- points += stride_points - 2;
- proj += stride_proj - 2;
+static void score_affine(const double *mat, const Correspondence *points,
+ int num_points, RANSAC_MOTION *model) {
+ model->num_inliers = 0;
+ model->sse = 0.0;
+
+ for (int i = 0; i < num_points; ++i) {
+ const double x1 = points[i].x;
+ const double y1 = points[i].y;
+ const double x2 = points[i].rx;
+ const double y2 = points[i].ry;
+
+ const double proj_x = mat[2] * x1 + mat[3] * y1 + mat[0];
+ const double proj_y = mat[4] * x1 + mat[5] * y1 + mat[1];
+
+ const double dx = proj_x - x2;
+ const double dy = proj_y - y2;
+ const double sse = dx * dx + dy * dy;
+
+ if (sse < INLIER_THRESHOLD_SQUARED) {
+ model->inlier_indices[model->num_inliers++] = i;
+ model->sse += sse;
+ }
}
}
#if ALLOW_TRANSLATION_MODELS
-static bool find_translation(int np, const double *pts1, const double *pts2,
- double *params) {
+static bool find_translation(const Correspondence *points, const int *indices,
+ int num_indices, double *params) {
double sumx = 0;
double sumy = 0;
- for (int i = 0; i < np; ++i) {
- double dx = *(pts2++);
- double dy = *(pts2++);
- double sx = *(pts1++);
- double sy = *(pts1++);
+ for (int i = 0; i < num_indices; ++i) {
+ int index = indices[i];
+ const double sx = points[index].x;
+ const double sy = points[index].y;
+ const double dx = points[index].rx;
+ const double dy = points[index].ry;
sumx += dx - sx;
sumy += dy - sy;
@@ -111,8 +163,8 @@ static bool find_translation(int np, const double *pts1, const double *pts2,
}
#endif // ALLOW_TRANSLATION_MODELS
-static bool find_rotzoom(int np, const double *pts1, const double *pts2,
- double *params) {
+static bool find_rotzoom(const Correspondence *points, const int *indices,
+ int num_indices, double *params) {
const int n = 4; // Size of least-squares problem
double mat[4 * 4]; // Accumulator for A'A
double y[4]; // Accumulator for A'b
@@ -120,11 +172,12 @@ static bool find_rotzoom(int np, const double *pts1, const double *pts2,
double b; // Single element of b
least_squares_init(mat, y, n);
- for (int i = 0; i < np; ++i) {
- double dx = *(pts2++);
- double dy = *(pts2++);
- double sx = *(pts1++);
- double sy = *(pts1++);
+ for (int i = 0; i < num_indices; ++i) {
+ int index = indices[i];
+ const double sx = points[index].x;
+ const double sy = points[index].y;
+ const double dx = points[index].rx;
+ const double dy = points[index].ry;
a[0] = 1;
a[1] = 0;
@@ -153,8 +206,8 @@ static bool find_rotzoom(int np, const double *pts1, const double *pts2,
return true;
}
-static bool find_affine(int np, const double *pts1, const double *pts2,
- double *params) {
+static bool find_affine(const Correspondence *points, const int *indices,
+ int num_indices, double *params) {
// Note: The least squares problem for affine models is 6-dimensional,
// but it splits into two independent 3-dimensional subproblems.
// Solving these two subproblems separately and recombining at the end
@@ -174,11 +227,12 @@ static bool find_affine(int np, const double *pts1, const double *pts2,
least_squares_init(mat[0], y[0], n);
least_squares_init(mat[1], y[1], n);
- for (int i = 0; i < np; ++i) {
- double dx = *(pts2++);
- double dy = *(pts2++);
- double sx = *(pts1++);
- double sy = *(pts1++);
+ for (int i = 0; i < num_indices; ++i) {
+ int index = indices[i];
+ const double sx = points[index].x;
+ const double sy = points[index].y;
+ const double dx = points[index].rx;
+ const double dy = points[index].ry;
a[0][0] = 1;
a[0][1] = sx;
@@ -211,12 +265,6 @@ static bool find_affine(int np, const double *pts1, const double *pts2,
return true;
}
-typedef struct {
- int num_inliers;
- double sse; // Sum of squared errors of inliers
- int *inlier_indices;
-} RANSAC_MOTION;
-
// Return -1 if 'a' is a better motion, 1 if 'b' is better, 0 otherwise.
static int compare_motions(const void *arg_a, const void *arg_b) {
const RANSAC_MOTION *motion_a = (RANSAC_MOTION *)arg_a;
@@ -234,15 +282,6 @@ static bool is_better_motion(const RANSAC_MOTION *motion_a,
return compare_motions(motion_a, motion_b) < 0;
}
-static void copy_points_at_indices(double *dest, const double *src,
- const int *indices, int num_points) {
- for (int i = 0; i < num_points; ++i) {
- const int index = indices[i];
- dest[i * 2] = src[index * 2];
- dest[i * 2 + 1] = src[index * 2 + 1];
- }
-}
-
// Returns true on success, false on error
static bool ransac_internal(const Correspondence *matched_points, int npoints,
MotionModel *motion_models, int num_desired_motions,
@@ -257,10 +296,6 @@ static bool ransac_internal(const Correspondence *matched_points, int npoints,
int indices[MAX_MINPTS] = { 0 };
- double *points1, *points2;
- double *corners1, *corners2;
- double *projected_corners;
-
// Store information for the num_desired_motions best transformations found
// and the worst motion among them, as well as the motion currently under
// consideration.
@@ -271,18 +306,19 @@ static bool ransac_internal(const Correspondence *matched_points, int npoints,
// currently under consideration.
double params_this_motion[MAX_PARAMDIM];
+ // Initialize output models, as a fallback in case we can't find a model
+ for (i = 0; i < num_desired_motions; i++) {
+ memcpy(motion_models[i].params, kIdentityParams,
+ MAX_PARAMDIM * sizeof(*(motion_models[i].params)));
+ motion_models[i].num_inliers = 0;
+ }
+
if (npoints < minpts * MINPTS_MULTIPLIER || npoints == 0) {
return false;
}
int min_inliers = AOMMAX((int)(MIN_INLIER_PROB * npoints), minpts);
- points1 = (double *)aom_malloc(sizeof(*points1) * npoints * 2);
- points2 = (double *)aom_malloc(sizeof(*points2) * npoints * 2);
- corners1 = (double *)aom_malloc(sizeof(*corners1) * npoints * 2);
- corners2 = (double *)aom_malloc(sizeof(*corners2) * npoints * 2);
- projected_corners =
- (double *)aom_malloc(sizeof(*projected_corners) * npoints * 2);
motions =
(RANSAC_MOTION *)aom_calloc(num_desired_motions, sizeof(RANSAC_MOTION));
@@ -295,8 +331,7 @@ static bool ransac_internal(const Correspondence *matched_points, int npoints,
int *inlier_buffer = (int *)aom_malloc(sizeof(*inlier_buffer) * npoints *
(num_desired_motions + 1));
- if (!(points1 && points2 && corners1 && corners2 && projected_corners &&
- motions && inlier_buffer)) {
+ if (!(motions && inlier_buffer)) {
ret_val = false;
*mem_alloc_failed = true;
goto finish_ransac;
@@ -311,50 +346,22 @@ static bool ransac_internal(const Correspondence *matched_points, int npoints,
memset(&current_motion, 0, sizeof(current_motion));
current_motion.inlier_indices = inlier_buffer + num_desired_motions * npoints;
- for (i = 0; i < npoints; ++i) {
- corners1[2 * i + 0] = matched_points[i].x;
- corners1[2 * i + 1] = matched_points[i].y;
- corners2[2 * i + 0] = matched_points[i].rx;
- corners2[2 * i + 1] = matched_points[i].ry;
- }
-
for (int trial_count = 0; trial_count < NUM_TRIALS; trial_count++) {
lcg_pick(npoints, minpts, indices, &seed);
- copy_points_at_indices(points1, corners1, indices, minpts);
- copy_points_at_indices(points2, corners2, indices, minpts);
-
- if (model_info->is_degenerate(points1)) {
- continue;
- }
-
- if (!model_info->find_transformation(minpts, points1, points2,
+ if (!model_info->find_transformation(matched_points, indices, minpts,
params_this_motion)) {
continue;
}
- model_info->project_points(params_this_motion, corners1, projected_corners,
- npoints, 2, 2);
-
- current_motion.num_inliers = 0;
- double sse = 0.0;
- for (i = 0; i < npoints; ++i) {
- double dx = projected_corners[i * 2] - corners2[i * 2];
- double dy = projected_corners[i * 2 + 1] - corners2[i * 2 + 1];
- double squared_error = dx * dx + dy * dy;
-
- if (squared_error < INLIER_THRESHOLD_SQUARED) {
- current_motion.inlier_indices[current_motion.num_inliers++] = i;
- sse += squared_error;
- }
- }
+ model_info->score_model(params_this_motion, matched_points, npoints,
+ &current_motion);
if (current_motion.num_inliers < min_inliers) {
// Reject models with too few inliers
continue;
}
- current_motion.sse = sse;
if (is_better_motion(&current_motion, worst_kept_motion)) {
// This motion is better than the worst currently kept motion. Remember
// the inlier points and sse. The parameters for each kept motion
@@ -386,86 +393,98 @@ static bool ransac_internal(const Correspondence *matched_points, int npoints,
// Sort the motions, best first.
qsort(motions, num_desired_motions, sizeof(RANSAC_MOTION), compare_motions);
- // Recompute the motions using only the inliers.
+ // Refine each of the best N models using iterative estimation.
+ //
+ // The idea here is loosely based on the iterative method from
+ // "Locally Optimized RANSAC" by O. Chum, J. Matas and Josef Kittler:
+ // https://cmp.felk.cvut.cz/ftp/articles/matas/chum-dagm03.pdf
+ //
+ // However, we implement a simpler version than their proposal, and simply
+ // refit the model repeatedly until the number of inliers stops increasing,
+ // with a cap on the number of iterations to defend against edge cases which
+ // only improve very slowly.
for (i = 0; i < num_desired_motions; ++i) {
- int num_inliers = motions[i].num_inliers;
- if (num_inliers > 0) {
- assert(num_inliers >= minpts);
-
- copy_points_at_indices(points1, corners1, motions[i].inlier_indices,
- num_inliers);
- copy_points_at_indices(points2, corners2, motions[i].inlier_indices,
- num_inliers);
-
- if (!model_info->find_transformation(num_inliers, points1, points2,
- motion_models[i].params)) {
- // In the unlikely event that this model fitting fails,
- // we don't have a good fallback. So just clear the output
- // model and move on
- memcpy(motion_models[i].params, kIdentityParams,
- MAX_PARAMDIM * sizeof(*(motion_models[i].params)));
- motion_models[i].num_inliers = 0;
- continue;
+ if (motions[i].num_inliers <= 0) {
+ // Output model has already been initialized to the identity model,
+ // so just skip setup
+ continue;
+ }
+
+ bool bad_model = false;
+ for (int refine_count = 0; refine_count < NUM_REFINES; refine_count++) {
+ int num_inliers = motions[i].num_inliers;
+ assert(num_inliers >= min_inliers);
+
+ if (!model_info->find_transformation(matched_points,
+ motions[i].inlier_indices,
+ num_inliers, params_this_motion)) {
+ // In the unlikely event that this model fitting fails, we don't have a
+ // good fallback. So leave this model set to the identity model
+ bad_model = true;
+ break;
}
- // Populate inliers array
- for (int j = 0; j < num_inliers; j++) {
- int index = motions[i].inlier_indices[j];
- const Correspondence *corr = &matched_points[index];
- motion_models[i].inliers[2 * j + 0] = (int)rint(corr->x);
- motion_models[i].inliers[2 * j + 1] = (int)rint(corr->y);
+ // Score the newly generated model
+ model_info->score_model(params_this_motion, matched_points, npoints,
+ &current_motion);
+
+ // At this point, there are three possibilities:
+ // 1) If we found more inliers, keep refining.
+ // 2) If we found the same number of inliers but a lower SSE, we want to
+ // keep the new model, but further refinement is unlikely to gain much.
+ // So commit to this new model
+ // 3) It is possible, but very unlikely, that the new model will have
+ // fewer inliers. If it does happen, we probably just lost a few
+ // borderline inliers. So treat the same as case (2).
+ if (current_motion.num_inliers > motions[i].num_inliers) {
+ motions[i].num_inliers = current_motion.num_inliers;
+ motions[i].sse = current_motion.sse;
+ int *tmp = motions[i].inlier_indices;
+ motions[i].inlier_indices = current_motion.inlier_indices;
+ current_motion.inlier_indices = tmp;
+ } else {
+ // Refined model is no better, so stop
+ // This shouldn't be significantly worse than the previous model,
+ // so it's fine to use the parameters in params_this_motion.
+ // This saves us from having to cache the previous iteration's params.
+ break;
}
- motion_models[i].num_inliers = num_inliers;
- } else {
- memcpy(motion_models[i].params, kIdentityParams,
- MAX_PARAMDIM * sizeof(*(motion_models[i].params)));
- motion_models[i].num_inliers = 0;
}
+
+ if (bad_model) continue;
+
+ // Fill in output struct
+ memcpy(motion_models[i].params, params_this_motion,
+ MAX_PARAMDIM * sizeof(*motion_models[i].params));
+ for (int j = 0; j < motions[i].num_inliers; j++) {
+ int index = motions[i].inlier_indices[j];
+ const Correspondence *corr = &matched_points[index];
+ motion_models[i].inliers[2 * j + 0] = (int)rint(corr->x);
+ motion_models[i].inliers[2 * j + 1] = (int)rint(corr->y);
+ }
+ motion_models[i].num_inliers = motions[i].num_inliers;
}
finish_ransac:
aom_free(inlier_buffer);
aom_free(motions);
- aom_free(projected_corners);
- aom_free(corners2);
- aom_free(corners1);
- aom_free(points2);
- aom_free(points1);
return ret_val;
}
-static bool is_collinear3(double *p1, double *p2, double *p3) {
- static const double collinear_eps = 1e-3;
- const double v =
- (p2[0] - p1[0]) * (p3[1] - p1[1]) - (p2[1] - p1[1]) * (p3[0] - p1[0]);
- return fabs(v) < collinear_eps;
-}
-
-#if ALLOW_TRANSLATION_MODELS
-static bool is_degenerate_translation(double *p) {
- return (p[0] - p[2]) * (p[0] - p[2]) + (p[1] - p[3]) * (p[1] - p[3]) <= 2;
-}
-#endif // ALLOW_TRANSLATION_MODELS
-
-static bool is_degenerate_affine(double *p) {
- return is_collinear3(p, p + 2, p + 4);
-}
-
static const RansacModelInfo ransac_model_info[TRANS_TYPES] = {
// IDENTITY
- { NULL, NULL, NULL, 0 },
+ { NULL, NULL, 0 },
// TRANSLATION
#if ALLOW_TRANSLATION_MODELS
- { is_degenerate_translation, find_translation, project_points_translation,
- 3 },
+ { find_translation, score_translation, 1 },
#else
- { NULL, NULL, NULL, 0 },
+ { NULL, NULL, 0 },
#endif
// ROTZOOM
- { is_degenerate_affine, find_rotzoom, project_points_affine, 3 },
+ { find_rotzoom, score_affine, 2 },
// AFFINE
- { is_degenerate_affine, find_affine, project_points_affine, 3 },
+ { find_affine, score_affine, 3 },
};
// Returns true on success, false on error
diff --git a/third_party/aom/aom_dsp/flow_estimation/x86/corner_match_avx2.c b/third_party/aom/aom_dsp/flow_estimation/x86/corner_match_avx2.c
index 87c76fa13b..ff69ae75f5 100644
--- a/third_party/aom/aom_dsp/flow_estimation/x86/corner_match_avx2.c
+++ b/third_party/aom/aom_dsp/flow_estimation/x86/corner_match_avx2.c
@@ -17,64 +17,112 @@
#include "aom_ports/mem.h"
#include "aom_dsp/flow_estimation/corner_match.h"
-DECLARE_ALIGNED(16, static const uint8_t,
- byte_mask[16]) = { 255, 255, 255, 255, 255, 255, 255, 255,
- 255, 255, 255, 255, 255, 0, 0, 0 };
-#if MATCH_SZ != 13
-#error "Need to change byte_mask in corner_match_sse4.c if MATCH_SZ != 13"
+DECLARE_ALIGNED(32, static const uint16_t, ones_array[16]) = { 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1 };
+
+#if MATCH_SZ != 16
+#error "Need to apply pixel mask in corner_match_avx2.c if MATCH_SZ != 16"
#endif
-/* Compute corr(frame1, frame2) * MATCH_SZ * stddev(frame1), where the
-correlation/standard deviation are taken over MATCH_SZ by MATCH_SZ windows
-of each image, centered at (x1, y1) and (x2, y2) respectively.
+/* Compute mean and standard deviation of pixels in a window of size
+ MATCH_SZ by MATCH_SZ centered at (x, y).
+ Store results into *mean and *one_over_stddev
+
+ Note: The output of this function is scaled by MATCH_SZ, as in
+ *mean = MATCH_SZ * <true mean> and
+ *one_over_stddev = 1 / (MATCH_SZ * <true stddev>)
+
+ Combined with the fact that we return 1/stddev rather than the standard
+ deviation itself, this allows us to completely avoid divisions in
+ aom_compute_correlation, which is much hotter than this function is.
+
+ Returns true if this feature point is usable, false otherwise.
*/
-double av1_compute_cross_correlation_avx2(const unsigned char *frame1,
- int stride1, int x1, int y1,
- const unsigned char *frame2,
- int stride2, int x2, int y2) {
- int i, stride1_i = 0, stride2_i = 0;
- __m256i temp1, sum_vec, sumsq2_vec, cross_vec, v, v1_1, v2_1;
- const __m128i mask = _mm_load_si128((__m128i *)byte_mask);
- const __m256i zero = _mm256_setzero_si256();
- __m128i v1, v2;
-
- sum_vec = zero;
- sumsq2_vec = zero;
- cross_vec = zero;
+bool aom_compute_mean_stddev_avx2(const unsigned char *frame, int stride, int x,
+ int y, double *mean,
+ double *one_over_stddev) {
+ __m256i sum_vec = _mm256_setzero_si256();
+ __m256i sumsq_vec = _mm256_setzero_si256();
+
+ frame += (y - MATCH_SZ_BY2) * stride + (x - MATCH_SZ_BY2);
+
+ for (int i = 0; i < MATCH_SZ; ++i) {
+ const __m256i v = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i *)frame));
+
+ sum_vec = _mm256_add_epi16(sum_vec, v);
+ sumsq_vec = _mm256_add_epi32(sumsq_vec, _mm256_madd_epi16(v, v));
+
+ frame += stride;
+ }
+
+ // Reduce sum_vec and sumsq_vec into single values
+ // Start by reducing each vector to 8x32-bit values, hadd() to perform 8
+ // additions, sum vertically to do 4 more, then the last 2 in scalar code.
+ const __m256i ones = _mm256_load_si256((__m256i *)ones_array);
+ const __m256i partial_sum = _mm256_madd_epi16(sum_vec, ones);
+ const __m256i tmp_8x32 = _mm256_hadd_epi32(partial_sum, sumsq_vec);
+ const __m128i tmp_4x32 = _mm_add_epi32(_mm256_extracti128_si256(tmp_8x32, 0),
+ _mm256_extracti128_si256(tmp_8x32, 1));
+ const int sum =
+ _mm_extract_epi32(tmp_4x32, 0) + _mm_extract_epi32(tmp_4x32, 1);
+ const int sumsq =
+ _mm_extract_epi32(tmp_4x32, 2) + _mm_extract_epi32(tmp_4x32, 3);
+
+ *mean = (double)sum / MATCH_SZ;
+ const double variance = sumsq - (*mean) * (*mean);
+ if (variance < MIN_FEATURE_VARIANCE) {
+ *one_over_stddev = 0.0;
+ return false;
+ }
+ *one_over_stddev = 1.0 / sqrt(variance);
+ return true;
+}
+
+/* Compute corr(frame1, frame2) over a window of size MATCH_SZ by MATCH_SZ.
+ To save on computation, the mean and (1 divided by the) standard deviation
+ of the window in each frame are precomputed and passed into this function
+ as arguments.
+*/
+double aom_compute_correlation_avx2(const unsigned char *frame1, int stride1,
+ int x1, int y1, double mean1,
+ double one_over_stddev1,
+ const unsigned char *frame2, int stride2,
+ int x2, int y2, double mean2,
+ double one_over_stddev2) {
+ __m256i cross_vec = _mm256_setzero_si256();
frame1 += (y1 - MATCH_SZ_BY2) * stride1 + (x1 - MATCH_SZ_BY2);
frame2 += (y2 - MATCH_SZ_BY2) * stride2 + (x2 - MATCH_SZ_BY2);
- for (i = 0; i < MATCH_SZ; ++i) {
- v1 = _mm_and_si128(_mm_loadu_si128((__m128i *)&frame1[stride1_i]), mask);
- v1_1 = _mm256_cvtepu8_epi16(v1);
- v2 = _mm_and_si128(_mm_loadu_si128((__m128i *)&frame2[stride2_i]), mask);
- v2_1 = _mm256_cvtepu8_epi16(v2);
+ for (int i = 0; i < MATCH_SZ; ++i) {
+ const __m256i v1 = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i *)frame1));
+ const __m256i v2 = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i *)frame2));
- v = _mm256_insertf128_si256(_mm256_castsi128_si256(v1), v2, 1);
- sumsq2_vec = _mm256_add_epi32(sumsq2_vec, _mm256_madd_epi16(v2_1, v2_1));
+ cross_vec = _mm256_add_epi32(cross_vec, _mm256_madd_epi16(v1, v2));
- sum_vec = _mm256_add_epi16(sum_vec, _mm256_sad_epu8(v, zero));
- cross_vec = _mm256_add_epi32(cross_vec, _mm256_madd_epi16(v1_1, v2_1));
- stride1_i += stride1;
- stride2_i += stride2;
+ frame1 += stride1;
+ frame2 += stride2;
}
- __m256i sum_vec1 = _mm256_srli_si256(sum_vec, 8);
- sum_vec = _mm256_add_epi32(sum_vec, sum_vec1);
- int sum1_acc = _mm_cvtsi128_si32(_mm256_castsi256_si128(sum_vec));
- int sum2_acc = _mm256_extract_epi32(sum_vec, 4);
-
- __m256i unp_low = _mm256_unpacklo_epi64(sumsq2_vec, cross_vec);
- __m256i unp_hig = _mm256_unpackhi_epi64(sumsq2_vec, cross_vec);
- temp1 = _mm256_add_epi32(unp_low, unp_hig);
-
- __m128i low_sumsq = _mm256_castsi256_si128(temp1);
- low_sumsq = _mm_add_epi32(low_sumsq, _mm256_extractf128_si256(temp1, 1));
- low_sumsq = _mm_add_epi32(low_sumsq, _mm_srli_epi64(low_sumsq, 32));
- int sumsq2_acc = _mm_cvtsi128_si32(low_sumsq);
- int cross_acc = _mm_extract_epi32(low_sumsq, 2);
-
- int var2 = sumsq2_acc * MATCH_SZ_SQ - sum2_acc * sum2_acc;
- int cov = cross_acc * MATCH_SZ_SQ - sum1_acc * sum2_acc;
- return cov / sqrt((double)var2);
+
+ // Sum cross_vec into a single value
+ const __m128i tmp = _mm_add_epi32(_mm256_extracti128_si256(cross_vec, 0),
+ _mm256_extracti128_si256(cross_vec, 1));
+ const int cross = _mm_extract_epi32(tmp, 0) + _mm_extract_epi32(tmp, 1) +
+ _mm_extract_epi32(tmp, 2) + _mm_extract_epi32(tmp, 3);
+
+ // Note: In theory, the calculations here "should" be
+ // covariance = cross / N^2 - mean1 * mean2
+ // correlation = covariance / (stddev1 * stddev2).
+ //
+ // However, because of the scaling in aom_compute_mean_stddev, the
+ // lines below actually calculate
+ // covariance * N^2 = cross - (mean1 * N) * (mean2 * N)
+ // correlation = (covariance * N^2) / ((stddev1 * N) * (stddev2 * N))
+ //
+ // ie. we have removed the need for a division, and still end up with the
+ // correct unscaled correlation (ie, in the range [-1, +1])
+ const double covariance = cross - mean1 * mean2;
+ const double correlation = covariance * (one_over_stddev1 * one_over_stddev2);
+ return correlation;
}
diff --git a/third_party/aom/aom_dsp/flow_estimation/x86/corner_match_sse4.c b/third_party/aom/aom_dsp/flow_estimation/x86/corner_match_sse4.c
index b3cb5bc5fd..bff7db6d2f 100644
--- a/third_party/aom/aom_dsp/flow_estimation/x86/corner_match_sse4.c
+++ b/third_party/aom/aom_dsp/flow_estimation/x86/corner_match_sse4.c
@@ -21,84 +21,125 @@
#include "aom_ports/mem.h"
#include "aom_dsp/flow_estimation/corner_match.h"
-DECLARE_ALIGNED(16, static const uint8_t,
- byte_mask[16]) = { 255, 255, 255, 255, 255, 255, 255, 255,
- 255, 255, 255, 255, 255, 0, 0, 0 };
-#if MATCH_SZ != 13
-#error "Need to change byte_mask in corner_match_sse4.c if MATCH_SZ != 13"
+DECLARE_ALIGNED(16, static const uint16_t, ones_array[8]) = { 1, 1, 1, 1,
+ 1, 1, 1, 1 };
+
+#if MATCH_SZ != 16
+#error "Need to apply pixel mask in corner_match_sse4.c if MATCH_SZ != 16"
#endif
-/* Compute corr(frame1, frame2) * MATCH_SZ * stddev(frame1), where the
- correlation/standard deviation are taken over MATCH_SZ by MATCH_SZ windows
- of each image, centered at (x1, y1) and (x2, y2) respectively.
+/* Compute mean and standard deviation of pixels in a window of size
+ MATCH_SZ by MATCH_SZ centered at (x, y).
+ Store results into *mean and *one_over_stddev
+
+ Note: The output of this function is scaled by MATCH_SZ, as in
+ *mean = MATCH_SZ * <true mean> and
+ *one_over_stddev = 1 / (MATCH_SZ * <true stddev>)
+
+ Combined with the fact that we return 1/stddev rather than the standard
+ deviation itself, this allows us to completely avoid divisions in
+ aom_compute_correlation, which is much hotter than this function is.
+
+ Returns true if this feature point is usable, false otherwise.
+*/
+bool aom_compute_mean_stddev_sse4_1(const unsigned char *frame, int stride,
+ int x, int y, double *mean,
+ double *one_over_stddev) {
+ // 8 16-bit partial sums of pixels
+ // Each lane sums at most 2*MATCH_SZ pixels, which can have values up to 255,
+ // and is therefore at most 2*MATCH_SZ*255, which is > 2^8 but < 2^16.
+ // Thus this value is safe to store in 16 bits.
+ __m128i sum_vec = _mm_setzero_si128();
+
+ // 8 32-bit partial sums of squares
+ __m128i sumsq_vec_l = _mm_setzero_si128();
+ __m128i sumsq_vec_r = _mm_setzero_si128();
+
+ frame += (y - MATCH_SZ_BY2) * stride + (x - MATCH_SZ_BY2);
+
+ for (int i = 0; i < MATCH_SZ; ++i) {
+ const __m128i v = _mm_loadu_si128((__m128i *)frame);
+ const __m128i v_l = _mm_cvtepu8_epi16(v);
+ const __m128i v_r = _mm_cvtepu8_epi16(_mm_srli_si128(v, 8));
+
+ sum_vec = _mm_add_epi16(sum_vec, _mm_add_epi16(v_l, v_r));
+ sumsq_vec_l = _mm_add_epi32(sumsq_vec_l, _mm_madd_epi16(v_l, v_l));
+ sumsq_vec_r = _mm_add_epi32(sumsq_vec_r, _mm_madd_epi16(v_r, v_r));
+
+ frame += stride;
+ }
+
+ // Reduce sum_vec and sumsq_vec into single values
+ // Start by reducing each vector to 4x32-bit values, hadd() to perform four
+ // additions, then perform the last two additions in scalar code.
+ const __m128i ones = _mm_load_si128((__m128i *)ones_array);
+ const __m128i partial_sum = _mm_madd_epi16(sum_vec, ones);
+ const __m128i partial_sumsq = _mm_add_epi32(sumsq_vec_l, sumsq_vec_r);
+ const __m128i tmp = _mm_hadd_epi32(partial_sum, partial_sumsq);
+ const int sum = _mm_extract_epi32(tmp, 0) + _mm_extract_epi32(tmp, 1);
+ const int sumsq = _mm_extract_epi32(tmp, 2) + _mm_extract_epi32(tmp, 3);
+
+ *mean = (double)sum / MATCH_SZ;
+ const double variance = sumsq - (*mean) * (*mean);
+ if (variance < MIN_FEATURE_VARIANCE) {
+ *one_over_stddev = 0.0;
+ return false;
+ }
+ *one_over_stddev = 1.0 / sqrt(variance);
+ return true;
+}
+
+/* Compute corr(frame1, frame2) over a window of size MATCH_SZ by MATCH_SZ.
+ To save on computation, the mean and (1 divided by the) standard deviation
+ of the window in each frame are precomputed and passed into this function
+ as arguments.
*/
-double av1_compute_cross_correlation_sse4_1(const unsigned char *frame1,
- int stride1, int x1, int y1,
- const unsigned char *frame2,
- int stride2, int x2, int y2) {
- int i;
- // 2 16-bit partial sums in lanes 0, 4 (== 2 32-bit partial sums in lanes 0,
- // 2)
- __m128i sum1_vec = _mm_setzero_si128();
- __m128i sum2_vec = _mm_setzero_si128();
- // 4 32-bit partial sums of squares
- __m128i sumsq2_vec = _mm_setzero_si128();
- __m128i cross_vec = _mm_setzero_si128();
-
- const __m128i mask = _mm_load_si128((__m128i *)byte_mask);
- const __m128i zero = _mm_setzero_si128();
+double aom_compute_correlation_sse4_1(const unsigned char *frame1, int stride1,
+ int x1, int y1, double mean1,
+ double one_over_stddev1,
+ const unsigned char *frame2, int stride2,
+ int x2, int y2, double mean2,
+ double one_over_stddev2) {
+ // 8 32-bit partial sums of products
+ __m128i cross_vec_l = _mm_setzero_si128();
+ __m128i cross_vec_r = _mm_setzero_si128();
frame1 += (y1 - MATCH_SZ_BY2) * stride1 + (x1 - MATCH_SZ_BY2);
frame2 += (y2 - MATCH_SZ_BY2) * stride2 + (x2 - MATCH_SZ_BY2);
- for (i = 0; i < MATCH_SZ; ++i) {
- const __m128i v1 =
- _mm_and_si128(_mm_loadu_si128((__m128i *)&frame1[i * stride1]), mask);
- const __m128i v2 =
- _mm_and_si128(_mm_loadu_si128((__m128i *)&frame2[i * stride2]), mask);
-
- // Using the 'sad' intrinsic here is a bit faster than adding
- // v1_l + v1_r and v2_l + v2_r, plus it avoids the need for a 16->32 bit
- // conversion step later, for a net speedup of ~10%
- sum1_vec = _mm_add_epi16(sum1_vec, _mm_sad_epu8(v1, zero));
- sum2_vec = _mm_add_epi16(sum2_vec, _mm_sad_epu8(v2, zero));
+ for (int i = 0; i < MATCH_SZ; ++i) {
+ const __m128i v1 = _mm_loadu_si128((__m128i *)frame1);
+ const __m128i v2 = _mm_loadu_si128((__m128i *)frame2);
const __m128i v1_l = _mm_cvtepu8_epi16(v1);
const __m128i v1_r = _mm_cvtepu8_epi16(_mm_srli_si128(v1, 8));
const __m128i v2_l = _mm_cvtepu8_epi16(v2);
const __m128i v2_r = _mm_cvtepu8_epi16(_mm_srli_si128(v2, 8));
- sumsq2_vec = _mm_add_epi32(
- sumsq2_vec,
- _mm_add_epi32(_mm_madd_epi16(v2_l, v2_l), _mm_madd_epi16(v2_r, v2_r)));
- cross_vec = _mm_add_epi32(
- cross_vec,
- _mm_add_epi32(_mm_madd_epi16(v1_l, v2_l), _mm_madd_epi16(v1_r, v2_r)));
+ cross_vec_l = _mm_add_epi32(cross_vec_l, _mm_madd_epi16(v1_l, v2_l));
+ cross_vec_r = _mm_add_epi32(cross_vec_r, _mm_madd_epi16(v1_r, v2_r));
+
+ frame1 += stride1;
+ frame2 += stride2;
}
- // Now we can treat the four registers (sum1_vec, sum2_vec, sumsq2_vec,
- // cross_vec)
- // as holding 4 32-bit elements each, which we want to sum horizontally.
- // We do this by transposing and then summing vertically.
- __m128i tmp_0 = _mm_unpacklo_epi32(sum1_vec, sum2_vec);
- __m128i tmp_1 = _mm_unpackhi_epi32(sum1_vec, sum2_vec);
- __m128i tmp_2 = _mm_unpacklo_epi32(sumsq2_vec, cross_vec);
- __m128i tmp_3 = _mm_unpackhi_epi32(sumsq2_vec, cross_vec);
-
- __m128i tmp_4 = _mm_unpacklo_epi64(tmp_0, tmp_2);
- __m128i tmp_5 = _mm_unpackhi_epi64(tmp_0, tmp_2);
- __m128i tmp_6 = _mm_unpacklo_epi64(tmp_1, tmp_3);
- __m128i tmp_7 = _mm_unpackhi_epi64(tmp_1, tmp_3);
-
- __m128i res =
- _mm_add_epi32(_mm_add_epi32(tmp_4, tmp_5), _mm_add_epi32(tmp_6, tmp_7));
-
- int sum1 = _mm_extract_epi32(res, 0);
- int sum2 = _mm_extract_epi32(res, 1);
- int sumsq2 = _mm_extract_epi32(res, 2);
- int cross = _mm_extract_epi32(res, 3);
-
- int var2 = sumsq2 * MATCH_SZ_SQ - sum2 * sum2;
- int cov = cross * MATCH_SZ_SQ - sum1 * sum2;
- return cov / sqrt((double)var2);
+ // Sum cross_vec into a single value
+ const __m128i tmp = _mm_add_epi32(cross_vec_l, cross_vec_r);
+ const int cross = _mm_extract_epi32(tmp, 0) + _mm_extract_epi32(tmp, 1) +
+ _mm_extract_epi32(tmp, 2) + _mm_extract_epi32(tmp, 3);
+
+ // Note: In theory, the calculations here "should" be
+ // covariance = cross / N^2 - mean1 * mean2
+ // correlation = covariance / (stddev1 * stddev2).
+ //
+ // However, because of the scaling in aom_compute_mean_stddev, the
+ // lines below actually calculate
+ // covariance * N^2 = cross - (mean1 * N) * (mean2 * N)
+ // correlation = (covariance * N^2) / ((stddev1 * N) * (stddev2 * N))
+ //
+ // ie. we have removed the need for a division, and still end up with the
+ // correct unscaled correlation (ie, in the range [-1, +1])
+ const double covariance = cross - mean1 * mean2;
+ const double correlation = covariance * (one_over_stddev1 * one_over_stddev2);
+ return correlation;
}
diff --git a/third_party/aom/aom_dsp/flow_estimation/x86/disflow_avx2.c b/third_party/aom/aom_dsp/flow_estimation/x86/disflow_avx2.c
new file mode 100644
index 0000000000..ad5a1bd7c6
--- /dev/null
+++ b/third_party/aom/aom_dsp/flow_estimation/x86/disflow_avx2.c
@@ -0,0 +1,417 @@
+/*
+ * Copyright (c) 2024, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#include <assert.h>
+#include <math.h>
+#include <immintrin.h>
+
+#include "aom_dsp/aom_dsp_common.h"
+#include "aom_dsp/flow_estimation/disflow.h"
+#include "aom_dsp/x86/synonyms.h"
+#include "aom_dsp/x86/synonyms_avx2.h"
+
+#include "config/aom_dsp_rtcd.h"
+
+#if DISFLOW_PATCH_SIZE != 8
+#error "Need to change disflow_avx2.c if DISFLOW_PATCH_SIZE != 8"
+#endif
+
+// Compute horizontal and vertical kernels and return them packed into a
+// register. The coefficient ordering is:
+// h0, h1, v0, v1, h2, h3, v2, v3
+// This is chosen because it takes less work than fully separating the kernels,
+// but it is separated enough that we can pick out each coefficient pair in the
+// main compute_flow_at_point function
+static INLINE __m128i compute_cubic_kernels(double u, double v) {
+ const __m128d x = _mm_set_pd(v, u);
+
+ const __m128d x2 = _mm_mul_pd(x, x);
+ const __m128d x3 = _mm_mul_pd(x2, x);
+
+ // Macro to multiply a value v by a constant coefficient c
+#define MULC(c, v) _mm_mul_pd(_mm_set1_pd(c), v)
+
+ // Compute floating-point kernel
+ // Note: To ensure results are bit-identical to the C code, we need to perform
+ // exactly the same sequence of operations here as in the C code.
+ __m128d k0 = _mm_sub_pd(_mm_add_pd(MULC(-0.5, x), x2), MULC(0.5, x3));
+ __m128d k1 =
+ _mm_add_pd(_mm_sub_pd(_mm_set1_pd(1.0), MULC(2.5, x2)), MULC(1.5, x3));
+ __m128d k2 =
+ _mm_sub_pd(_mm_add_pd(MULC(0.5, x), MULC(2.0, x2)), MULC(1.5, x3));
+ __m128d k3 = _mm_add_pd(MULC(-0.5, x2), MULC(0.5, x3));
+#undef MULC
+
+ // Integerize
+ __m128d prec = _mm_set1_pd((double)(1 << DISFLOW_INTERP_BITS));
+
+ k0 = _mm_round_pd(_mm_mul_pd(k0, prec),
+ _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC);
+ k1 = _mm_round_pd(_mm_mul_pd(k1, prec),
+ _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC);
+ k2 = _mm_round_pd(_mm_mul_pd(k2, prec),
+ _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC);
+ k3 = _mm_round_pd(_mm_mul_pd(k3, prec),
+ _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC);
+
+ const __m128i c0 = _mm_cvtpd_epi32(k0);
+ const __m128i c1 = _mm_cvtpd_epi32(k1);
+ const __m128i c2 = _mm_cvtpd_epi32(k2);
+ const __m128i c3 = _mm_cvtpd_epi32(k3);
+
+ // Rearrange results and convert down to 16 bits, giving the target output
+ // ordering
+ const __m128i c01 = _mm_unpacklo_epi32(c0, c1);
+ const __m128i c23 = _mm_unpacklo_epi32(c2, c3);
+ return _mm_packs_epi32(c01, c23);
+}
+
+// Compare two regions of width x height pixels, one rooted at position
+// (x, y) in src and the other at (x + u, y + v) in ref.
+// This function returns the sum of squared pixel differences between
+// the two regions.
+//
+// TODO(rachelbarker): Test speed/quality impact of using bilinear interpolation
+// instad of bicubic interpolation
+static INLINE void compute_flow_vector(const uint8_t *src, const uint8_t *ref,
+ int width, int height, int stride, int x,
+ int y, double u, double v,
+ const int16_t *dx, const int16_t *dy,
+ int *b) {
+ const __m256i zero = _mm256_setzero_si256();
+
+ // Accumulate 8 32-bit partial sums for each element of b
+ // These will be flattened at the end.
+ __m256i b0_acc = _mm256_setzero_si256();
+ __m256i b1_acc = _mm256_setzero_si256();
+
+ // Split offset into integer and fractional parts, and compute cubic
+ // interpolation kernels
+ const int u_int = (int)floor(u);
+ const int v_int = (int)floor(v);
+ const double u_frac = u - floor(u);
+ const double v_frac = v - floor(v);
+
+ const __m128i kernels = compute_cubic_kernels(u_frac, v_frac);
+
+ // Storage for intermediate values between the two convolution directions
+ // In the AVX2 implementation, this needs a dummy row at the end, because
+ // we generate 2 rows at a time but the total number of rows is odd.
+ // So we generate one more row than we actually need.
+ DECLARE_ALIGNED(32, int16_t,
+ tmp_[DISFLOW_PATCH_SIZE * (DISFLOW_PATCH_SIZE + 4)]);
+ int16_t *tmp = tmp_ + DISFLOW_PATCH_SIZE; // Offset by one row
+
+ // Clamp coordinates so that all pixels we fetch will remain within the
+ // allocated border region, but allow them to go far enough out that
+ // the border pixels' values do not change.
+ // Since we are calculating an 8x8 block, the bottom-right pixel
+ // in the block has coordinates (x0 + 7, y0 + 7). Then, the cubic
+ // interpolation has 4 taps, meaning that the output of pixel
+ // (x_w, y_w) depends on the pixels in the range
+ // ([x_w - 1, x_w + 2], [y_w - 1, y_w + 2]).
+ //
+ // Thus the most extreme coordinates which will be fetched are
+ // (x0 - 1, y0 - 1) and (x0 + 9, y0 + 9).
+ const int x0 = clamp(x + u_int, -9, width);
+ const int y0 = clamp(y + v_int, -9, height);
+
+ // Horizontal convolution
+
+ // Prepare the kernel vectors
+ // We split the kernel into two vectors with kernel indices:
+ // 0, 1, 0, 1, 0, 1, 0, 1, and
+ // 2, 3, 2, 3, 2, 3, 2, 3
+ __m256i h_kernel_01 = _mm256_broadcastd_epi32(kernels);
+ __m256i h_kernel_23 = _mm256_broadcastd_epi32(_mm_srli_si128(kernels, 8));
+
+ __m256i round_const_h = _mm256_set1_epi32(1 << (DISFLOW_INTERP_BITS - 6 - 1));
+
+ for (int i = -1; i < DISFLOW_PATCH_SIZE + 2; i += 2) {
+ const int y_w = y0 + i;
+ const uint8_t *ref_row = &ref[y_w * stride + (x0 - 1)];
+ int16_t *tmp_row = &tmp[i * DISFLOW_PATCH_SIZE];
+
+ // Load this row of pixels.
+ // For an 8x8 patch, we need to load the 8 image pixels + 3 extras,
+ // for a total of 11 pixels. Here we load 16 pixels, but only use
+ // the first 11.
+ __m256i row =
+ yy_loadu2_128((__m128i *)(ref_row + stride), (__m128i *)ref_row);
+
+ // Expand pixels to int16s
+ // We must use unpacks here, as we have one row in each 128-bit lane
+ // and want to handle each of those independently.
+ // This is in contrast to _mm256_cvtepu8_epi16(), which takes a single
+ // 128-bit input and widens it to 256 bits.
+ __m256i px_0to7_i16 = _mm256_unpacklo_epi8(row, zero);
+ __m256i px_4to10_i16 =
+ _mm256_unpacklo_epi8(_mm256_srli_si256(row, 4), zero);
+
+ // Compute first four outputs
+ // input pixels 0, 1, 1, 2, 2, 3, 3, 4
+ // * kernel 0, 1, 0, 1, 0, 1, 0, 1
+ __m256i px0 =
+ _mm256_unpacklo_epi16(px_0to7_i16, _mm256_srli_si256(px_0to7_i16, 2));
+ // input pixels 2, 3, 3, 4, 4, 5, 5, 6
+ // * kernel 2, 3, 2, 3, 2, 3, 2, 3
+ __m256i px1 = _mm256_unpacklo_epi16(_mm256_srli_si256(px_0to7_i16, 4),
+ _mm256_srli_si256(px_0to7_i16, 6));
+ // Convolve with kernel and sum 2x2 boxes to form first 4 outputs
+ __m256i sum0 = _mm256_add_epi32(_mm256_madd_epi16(px0, h_kernel_01),
+ _mm256_madd_epi16(px1, h_kernel_23));
+
+ __m256i out0 = _mm256_srai_epi32(_mm256_add_epi32(sum0, round_const_h),
+ DISFLOW_INTERP_BITS - 6);
+
+ // Compute second four outputs
+ __m256i px2 =
+ _mm256_unpacklo_epi16(px_4to10_i16, _mm256_srli_si256(px_4to10_i16, 2));
+ __m256i px3 = _mm256_unpacklo_epi16(_mm256_srli_si256(px_4to10_i16, 4),
+ _mm256_srli_si256(px_4to10_i16, 6));
+ __m256i sum1 = _mm256_add_epi32(_mm256_madd_epi16(px2, h_kernel_01),
+ _mm256_madd_epi16(px3, h_kernel_23));
+
+ // Round by just enough bits that the result is
+ // guaranteed to fit into an i16. Then the next stage can use 16 x 16 -> 32
+ // bit multiplies, which should be a fair bit faster than 32 x 32 -> 32
+ // as it does now
+ // This means shifting down so we have 6 extra bits, for a maximum value
+ // of +18360, which can occur if u_frac == 0.5 and the input pixels are
+ // {0, 255, 255, 0}.
+ __m256i out1 = _mm256_srai_epi32(_mm256_add_epi32(sum1, round_const_h),
+ DISFLOW_INTERP_BITS - 6);
+
+ _mm256_storeu_si256((__m256i *)tmp_row, _mm256_packs_epi32(out0, out1));
+ }
+
+ // Vertical convolution
+ const int round_bits = DISFLOW_INTERP_BITS + 6 - DISFLOW_DERIV_SCALE_LOG2;
+ __m256i round_const_v = _mm256_set1_epi32(1 << (round_bits - 1));
+
+ __m256i v_kernel_01 = _mm256_broadcastd_epi32(_mm_srli_si128(kernels, 4));
+ __m256i v_kernel_23 = _mm256_broadcastd_epi32(_mm_srli_si128(kernels, 12));
+
+ for (int i = 0; i < DISFLOW_PATCH_SIZE; i += 2) {
+ int16_t *tmp_row = &tmp[i * DISFLOW_PATCH_SIZE];
+
+ // Load 5 rows of 8 x 16-bit values, and pack into 4 registers
+ // holding rows {0, 1}, {1, 2}, {2, 3}, {3, 4}
+ __m128i row0 = _mm_loadu_si128((__m128i *)(tmp_row - DISFLOW_PATCH_SIZE));
+ __m128i row1 = _mm_loadu_si128((__m128i *)tmp_row);
+ __m128i row2 = _mm_loadu_si128((__m128i *)(tmp_row + DISFLOW_PATCH_SIZE));
+ __m128i row3 =
+ _mm_loadu_si128((__m128i *)(tmp_row + 2 * DISFLOW_PATCH_SIZE));
+ __m128i row4 =
+ _mm_loadu_si128((__m128i *)(tmp_row + 3 * DISFLOW_PATCH_SIZE));
+
+ __m256i px0 = _mm256_set_m128i(row1, row0);
+ __m256i px1 = _mm256_set_m128i(row2, row1);
+ __m256i px2 = _mm256_set_m128i(row3, row2);
+ __m256i px3 = _mm256_set_m128i(row4, row3);
+
+ // We want to calculate px0 * v_kernel[0] + px1 * v_kernel[1] + ... ,
+ // but each multiply expands its output to 32 bits. So we need to be
+ // a little clever about how we do this
+ __m256i sum0 = _mm256_add_epi32(
+ _mm256_madd_epi16(_mm256_unpacklo_epi16(px0, px1), v_kernel_01),
+ _mm256_madd_epi16(_mm256_unpacklo_epi16(px2, px3), v_kernel_23));
+ __m256i sum1 = _mm256_add_epi32(
+ _mm256_madd_epi16(_mm256_unpackhi_epi16(px0, px1), v_kernel_01),
+ _mm256_madd_epi16(_mm256_unpackhi_epi16(px2, px3), v_kernel_23));
+
+ __m256i sum0_rounded =
+ _mm256_srai_epi32(_mm256_add_epi32(sum0, round_const_v), round_bits);
+ __m256i sum1_rounded =
+ _mm256_srai_epi32(_mm256_add_epi32(sum1, round_const_v), round_bits);
+
+ __m256i warped = _mm256_packs_epi32(sum0_rounded, sum1_rounded);
+ __m128i src_pixels_u8 = xx_loadu_2x64(&src[(y + i + 1) * stride + x],
+ &src[(y + i) * stride + x]);
+ __m256i src_pixels =
+ _mm256_slli_epi16(_mm256_cvtepu8_epi16(src_pixels_u8), 3);
+
+ // Calculate delta from the target patch
+ __m256i dt = _mm256_sub_epi16(warped, src_pixels);
+
+ // Load 2x8 elements each of dx and dt, to pair with the 2x8 elements of dt
+ // that we have just computed. Then compute 2x8 partial sums of dx * dt
+ // and dy * dt, implicitly sum to give 2x4 partial sums of each, and
+ // accumulate.
+ __m256i dx_row = _mm256_loadu_si256((__m256i *)&dx[i * DISFLOW_PATCH_SIZE]);
+ __m256i dy_row = _mm256_loadu_si256((__m256i *)&dy[i * DISFLOW_PATCH_SIZE]);
+ b0_acc = _mm256_add_epi32(b0_acc, _mm256_madd_epi16(dx_row, dt));
+ b1_acc = _mm256_add_epi32(b1_acc, _mm256_madd_epi16(dy_row, dt));
+ }
+
+ // Flatten the two sets of partial sums to find the final value of b
+ // We need to set b[0] = sum(b0_acc), b[1] = sum(b1_acc).
+ // We need to do 14 additions in total; a `hadd` instruction can take care
+ // of eight of them, then a vertical sum can do four more, leaving two
+ // scalar additions.
+ __m256i partial_sum_256 = _mm256_hadd_epi32(b0_acc, b1_acc);
+ __m128i partial_sum =
+ _mm_add_epi32(_mm256_extracti128_si256(partial_sum_256, 0),
+ _mm256_extracti128_si256(partial_sum_256, 1));
+ b[0] = _mm_extract_epi32(partial_sum, 0) + _mm_extract_epi32(partial_sum, 1);
+ b[1] = _mm_extract_epi32(partial_sum, 2) + _mm_extract_epi32(partial_sum, 3);
+}
+
+// Compute the x and y gradients of the source patch in a single pass,
+// and store into dx and dy respectively.
+static INLINE void sobel_filter(const uint8_t *src, int src_stride, int16_t *dx,
+ int16_t *dy) {
+ const __m256i zero = _mm256_setzero_si256();
+
+ // Loop setup: Load the first two rows (of 10 input rows) and apply
+ // the horizontal parts of the two filters
+ __m256i row_m1_0 =
+ yy_loadu2_128((__m128i *)(src - 1), (__m128i *)(src - src_stride - 1));
+ __m256i row_m1_0_a = _mm256_unpacklo_epi8(row_m1_0, zero);
+ __m256i row_m1_0_b =
+ _mm256_unpacklo_epi8(_mm256_srli_si256(row_m1_0, 1), zero);
+ __m256i row_m1_0_c =
+ _mm256_unpacklo_epi8(_mm256_srli_si256(row_m1_0, 2), zero);
+
+ __m256i row_m1_0_hsmooth =
+ _mm256_add_epi16(_mm256_add_epi16(row_m1_0_a, row_m1_0_c),
+ _mm256_slli_epi16(row_m1_0_b, 1));
+ __m256i row_m1_0_hdiff = _mm256_sub_epi16(row_m1_0_a, row_m1_0_c);
+
+ // Main loop: For each pair of output rows (i, i+1):
+ // * Load rows (i+1, i+2) and apply both horizontal filters
+ // * Apply vertical filters and store results
+ // * Shift rows for next iteration
+ for (int i = 0; i < DISFLOW_PATCH_SIZE; i += 2) {
+ // Load rows (i+1, i+2) and apply both horizontal filters
+ const __m256i row_p1_p2 =
+ yy_loadu2_128((__m128i *)(src + (i + 2) * src_stride - 1),
+ (__m128i *)(src + (i + 1) * src_stride - 1));
+ const __m256i row_p1_p2_a = _mm256_unpacklo_epi8(row_p1_p2, zero);
+ const __m256i row_p1_p2_b =
+ _mm256_unpacklo_epi8(_mm256_srli_si256(row_p1_p2, 1), zero);
+ const __m256i row_p1_p2_c =
+ _mm256_unpacklo_epi8(_mm256_srli_si256(row_p1_p2, 2), zero);
+
+ const __m256i row_p1_p2_hsmooth =
+ _mm256_add_epi16(_mm256_add_epi16(row_p1_p2_a, row_p1_p2_c),
+ _mm256_slli_epi16(row_p1_p2_b, 1));
+ const __m256i row_p1_p2_hdiff = _mm256_sub_epi16(row_p1_p2_a, row_p1_p2_c);
+
+ // Apply vertical filters and store results
+ // dx = vertical smooth(horizontal diff(input))
+ // dy = vertical diff(horizontal smooth(input))
+ const __m256i row_0_p1_hdiff =
+ _mm256_permute2x128_si256(row_m1_0_hdiff, row_p1_p2_hdiff, 0x21);
+ const __m256i dx_row =
+ _mm256_add_epi16(_mm256_add_epi16(row_m1_0_hdiff, row_p1_p2_hdiff),
+ _mm256_slli_epi16(row_0_p1_hdiff, 1));
+ const __m256i dy_row =
+ _mm256_sub_epi16(row_m1_0_hsmooth, row_p1_p2_hsmooth);
+
+ _mm256_storeu_si256((__m256i *)(dx + i * DISFLOW_PATCH_SIZE), dx_row);
+ _mm256_storeu_si256((__m256i *)(dy + i * DISFLOW_PATCH_SIZE), dy_row);
+
+ // Shift rows for next iteration
+ // This allows a lot of work to be reused, reducing the number of
+ // horizontal filtering operations from 2*3*8 = 48 to 2*10 = 20
+ row_m1_0_hsmooth = row_p1_p2_hsmooth;
+ row_m1_0_hdiff = row_p1_p2_hdiff;
+ }
+}
+
+static INLINE void compute_flow_matrix(const int16_t *dx, int dx_stride,
+ const int16_t *dy, int dy_stride,
+ double *M) {
+ __m256i acc[4] = { 0 };
+
+ for (int i = 0; i < DISFLOW_PATCH_SIZE; i += 2) {
+ __m256i dx_row = _mm256_loadu_si256((__m256i *)&dx[i * dx_stride]);
+ __m256i dy_row = _mm256_loadu_si256((__m256i *)&dy[i * dy_stride]);
+
+ acc[0] = _mm256_add_epi32(acc[0], _mm256_madd_epi16(dx_row, dx_row));
+ acc[1] = _mm256_add_epi32(acc[1], _mm256_madd_epi16(dx_row, dy_row));
+ // Don't compute acc[2], as it should be equal to acc[1]
+ acc[3] = _mm256_add_epi32(acc[3], _mm256_madd_epi16(dy_row, dy_row));
+ }
+
+ // Condense sums
+ __m256i partial_sum_0 = _mm256_hadd_epi32(acc[0], acc[1]);
+ __m256i partial_sum_1 = _mm256_hadd_epi32(acc[1], acc[3]);
+ __m256i result_256 = _mm256_hadd_epi32(partial_sum_0, partial_sum_1);
+ __m128i result = _mm_add_epi32(_mm256_extracti128_si256(result_256, 0),
+ _mm256_extracti128_si256(result_256, 1));
+
+ // Apply regularization
+ // We follow the standard regularization method of adding `k * I` before
+ // inverting. This ensures that the matrix will be invertible.
+ //
+ // Setting the regularization strength k to 1 seems to work well here, as
+ // typical values coming from the other equations are very large (1e5 to
+ // 1e6, with an upper limit of around 6e7, at the time of writing).
+ // It also preserves the property that all matrix values are whole numbers,
+ // which is convenient for integerized SIMD implementation.
+ result = _mm_add_epi32(result, _mm_set_epi32(1, 0, 0, 1));
+
+ // Convert results to doubles and store
+ _mm256_storeu_pd(M, _mm256_cvtepi32_pd(result));
+}
+
+// Try to invert the matrix M
+// Note: Due to the nature of how a least-squares matrix is constructed, all of
+// the eigenvalues will be >= 0, and therefore det M >= 0 as well.
+// The regularization term `+ k * I` further ensures that det M >= k^2.
+// As mentioned in compute_flow_matrix(), here we use k = 1, so det M >= 1.
+// So we don't have to worry about non-invertible matrices here.
+static INLINE void invert_2x2(const double *M, double *M_inv) {
+ double det = (M[0] * M[3]) - (M[1] * M[2]);
+ assert(det >= 1);
+ const double det_inv = 1 / det;
+
+ M_inv[0] = M[3] * det_inv;
+ M_inv[1] = -M[1] * det_inv;
+ M_inv[2] = -M[2] * det_inv;
+ M_inv[3] = M[0] * det_inv;
+}
+
+void aom_compute_flow_at_point_avx2(const uint8_t *src, const uint8_t *ref,
+ int x, int y, int width, int height,
+ int stride, double *u, double *v) {
+ DECLARE_ALIGNED(32, double, M[4]);
+ DECLARE_ALIGNED(32, double, M_inv[4]);
+ DECLARE_ALIGNED(32, int16_t, dx[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE]);
+ DECLARE_ALIGNED(32, int16_t, dy[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE]);
+ int b[2];
+
+ // Compute gradients within this patch
+ const uint8_t *src_patch = &src[y * stride + x];
+ sobel_filter(src_patch, stride, dx, dy);
+
+ compute_flow_matrix(dx, DISFLOW_PATCH_SIZE, dy, DISFLOW_PATCH_SIZE, M);
+ invert_2x2(M, M_inv);
+
+ for (int itr = 0; itr < DISFLOW_MAX_ITR; itr++) {
+ compute_flow_vector(src, ref, width, height, stride, x, y, *u, *v, dx, dy,
+ b);
+
+ // Solve flow equations to find a better estimate for the flow vector
+ // at this point
+ const double step_u = M_inv[0] * b[0] + M_inv[1] * b[1];
+ const double step_v = M_inv[2] * b[0] + M_inv[3] * b[1];
+ *u += fclamp(step_u * DISFLOW_STEP_SIZE, -2, 2);
+ *v += fclamp(step_v * DISFLOW_STEP_SIZE, -2, 2);
+
+ if (fabs(step_u) + fabs(step_v) < DISFLOW_STEP_SIZE_THRESOLD) {
+ // Stop iteration when we're close to convergence
+ break;
+ }
+ }
+}
diff --git a/third_party/aom/aom_dsp/flow_estimation/x86/disflow_sse4.c b/third_party/aom/aom_dsp/flow_estimation/x86/disflow_sse4.c
index 2c5effd638..e0a4bd040c 100644
--- a/third_party/aom/aom_dsp/flow_estimation/x86/disflow_sse4.c
+++ b/third_party/aom/aom_dsp/flow_estimation/x86/disflow_sse4.c
@@ -1,13 +1,12 @@
/*
- * Copyright (c) 2022, Alliance for Open Media. All rights reserved
+ * Copyright (c) 2024, Alliance for Open Media. All rights reserved
*
- * This source code is subject to the terms of the BSD 3-Clause Clear License
- * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
- * License was not distributed with this source code in the LICENSE file, you
- * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. If the
- * Alliance for Open Media Patent License 1.0 was not distributed with this
- * source code in the PATENTS file, you can obtain it at
- * aomedia.org/license/patent-license/.
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
*/
#include <assert.h>
@@ -20,46 +19,59 @@
#include "config/aom_dsp_rtcd.h"
-// Internal cross-check against C code
-// If you set this to 1 and compile in debug mode, then the outputs of the two
-// convolution stages will be checked against the plain C version of the code,
-// and an assertion will be fired if the results differ.
-#define CHECK_RESULTS 0
-
-// Note: Max sum(+ve coefficients) = 1.125 * scale
-static INLINE void get_cubic_kernel_dbl(double x, double kernel[4]) {
- // Check that the fractional position is in range.
- //
- // Note: x is calculated from, e.g., `u_frac = u - floor(u)`.
- // Mathematically, this implies that 0 <= x < 1. However, in practice it is
- // possible to have x == 1 due to floating point rounding. This is fine,
- // and we still interpolate correctly if we allow x = 1.
- assert(0 <= x && x <= 1);
-
- double x2 = x * x;
- double x3 = x2 * x;
- kernel[0] = -0.5 * x + x2 - 0.5 * x3;
- kernel[1] = 1.0 - 2.5 * x2 + 1.5 * x3;
- kernel[2] = 0.5 * x + 2.0 * x2 - 1.5 * x3;
- kernel[3] = -0.5 * x2 + 0.5 * x3;
-}
-
-static INLINE void get_cubic_kernel_int(double x, int16_t kernel[4]) {
- double kernel_dbl[4];
- get_cubic_kernel_dbl(x, kernel_dbl);
-
- kernel[0] = (int16_t)rint(kernel_dbl[0] * (1 << DISFLOW_INTERP_BITS));
- kernel[1] = (int16_t)rint(kernel_dbl[1] * (1 << DISFLOW_INTERP_BITS));
- kernel[2] = (int16_t)rint(kernel_dbl[2] * (1 << DISFLOW_INTERP_BITS));
- kernel[3] = (int16_t)rint(kernel_dbl[3] * (1 << DISFLOW_INTERP_BITS));
-}
-
-#if CHECK_RESULTS
-static INLINE int get_cubic_value_int(const int *p, const int16_t kernel[4]) {
- return kernel[0] * p[0] + kernel[1] * p[1] + kernel[2] * p[2] +
- kernel[3] * p[3];
+#if DISFLOW_PATCH_SIZE != 8
+#error "Need to change disflow_sse4.c if DISFLOW_PATCH_SIZE != 8"
+#endif
+
+// Compute horizontal and vertical kernels and return them packed into a
+// register. The coefficient ordering is:
+// h0, h1, v0, v1, h2, h3, v2, v3
+// This is chosen because it takes less work than fully separating the kernels,
+// but it is separated enough that we can pick out each coefficient pair in the
+// main compute_flow_at_point function
+static INLINE __m128i compute_cubic_kernels(double u, double v) {
+ const __m128d x = _mm_set_pd(v, u);
+
+ const __m128d x2 = _mm_mul_pd(x, x);
+ const __m128d x3 = _mm_mul_pd(x2, x);
+
+ // Macro to multiply a value v by a constant coefficient c
+#define MULC(c, v) _mm_mul_pd(_mm_set1_pd(c), v)
+
+ // Compute floating-point kernel
+ // Note: To ensure results are bit-identical to the C code, we need to perform
+ // exactly the same sequence of operations here as in the C code.
+ __m128d k0 = _mm_sub_pd(_mm_add_pd(MULC(-0.5, x), x2), MULC(0.5, x3));
+ __m128d k1 =
+ _mm_add_pd(_mm_sub_pd(_mm_set1_pd(1.0), MULC(2.5, x2)), MULC(1.5, x3));
+ __m128d k2 =
+ _mm_sub_pd(_mm_add_pd(MULC(0.5, x), MULC(2.0, x2)), MULC(1.5, x3));
+ __m128d k3 = _mm_add_pd(MULC(-0.5, x2), MULC(0.5, x3));
+#undef MULC
+
+ // Integerize
+ __m128d prec = _mm_set1_pd((double)(1 << DISFLOW_INTERP_BITS));
+
+ k0 = _mm_round_pd(_mm_mul_pd(k0, prec),
+ _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC);
+ k1 = _mm_round_pd(_mm_mul_pd(k1, prec),
+ _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC);
+ k2 = _mm_round_pd(_mm_mul_pd(k2, prec),
+ _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC);
+ k3 = _mm_round_pd(_mm_mul_pd(k3, prec),
+ _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC);
+
+ const __m128i c0 = _mm_cvtpd_epi32(k0);
+ const __m128i c1 = _mm_cvtpd_epi32(k1);
+ const __m128i c2 = _mm_cvtpd_epi32(k2);
+ const __m128i c3 = _mm_cvtpd_epi32(k3);
+
+ // Rearrange results and convert down to 16 bits, giving the target output
+ // ordering
+ const __m128i c01 = _mm_unpacklo_epi32(c0, c1);
+ const __m128i c23 = _mm_unpacklo_epi32(c2, c3);
+ return _mm_packs_epi32(c01, c23);
}
-#endif // CHECK_RESULTS
// Compare two regions of width x height pixels, one rooted at position
// (x, y) in src and the other at (x + u, y + v) in ref.
@@ -80,10 +92,6 @@ static INLINE void compute_flow_vector(const uint8_t *src, const uint8_t *ref,
// These will be flattened at the end.
__m128i b0_acc = _mm_setzero_si128();
__m128i b1_acc = _mm_setzero_si128();
-#if CHECK_RESULTS
- // Also keep a running sum using the C algorithm, for cross-checking
- int c_result[2] = { 0 };
-#endif // CHECK_RESULTS
// Split offset into integer and fractional parts, and compute cubic
// interpolation kernels
@@ -92,13 +100,11 @@ static INLINE void compute_flow_vector(const uint8_t *src, const uint8_t *ref,
const double u_frac = u - floor(u);
const double v_frac = v - floor(v);
- int16_t h_kernel[4];
- int16_t v_kernel[4];
- get_cubic_kernel_int(u_frac, h_kernel);
- get_cubic_kernel_int(v_frac, v_kernel);
+ const __m128i kernels = compute_cubic_kernels(u_frac, v_frac);
// Storage for intermediate values between the two convolution directions
- int16_t tmp_[DISFLOW_PATCH_SIZE * (DISFLOW_PATCH_SIZE + 3)];
+ DECLARE_ALIGNED(16, int16_t,
+ tmp_[DISFLOW_PATCH_SIZE * (DISFLOW_PATCH_SIZE + 3)]);
int16_t *tmp = tmp_ + DISFLOW_PATCH_SIZE; // Offset by one row
// Clamp coordinates so that all pixels we fetch will remain within the
@@ -121,8 +127,8 @@ static INLINE void compute_flow_vector(const uint8_t *src, const uint8_t *ref,
// We split the kernel into two vectors with kernel indices:
// 0, 1, 0, 1, 0, 1, 0, 1, and
// 2, 3, 2, 3, 2, 3, 2, 3
- __m128i h_kernel_01 = xx_set2_epi16(h_kernel[0], h_kernel[1]);
- __m128i h_kernel_23 = xx_set2_epi16(h_kernel[2], h_kernel[3]);
+ __m128i h_kernel_01 = _mm_set1_epi32(_mm_extract_epi32(kernels, 0));
+ __m128i h_kernel_23 = _mm_set1_epi32(_mm_extract_epi32(kernels, 2));
__m128i round_const_h = _mm_set1_epi32(1 << (DISFLOW_INTERP_BITS - 6 - 1));
@@ -141,10 +147,6 @@ static INLINE void compute_flow_vector(const uint8_t *src, const uint8_t *ref,
__m128i px_0to7_i16 = _mm_cvtepu8_epi16(row);
__m128i px_4to10_i16 = _mm_cvtepu8_epi16(_mm_srli_si128(row, 4));
- // Relevant multiply instruction
- // This multiplies pointwise, then sums in pairs.
- //_mm_madd_epi16();
-
// Compute first four outputs
// input pixels 0, 1, 1, 2, 2, 3, 3, 4
// * kernel 0, 1, 0, 1, 0, 1, 0, 1
@@ -180,43 +182,14 @@ static INLINE void compute_flow_vector(const uint8_t *src, const uint8_t *ref,
DISFLOW_INTERP_BITS - 6);
_mm_storeu_si128((__m128i *)tmp_row, _mm_packs_epi32(out0, out1));
-
-#if CHECK_RESULTS && !defined(NDEBUG)
- // Cross-check
- for (int j = 0; j < DISFLOW_PATCH_SIZE; ++j) {
- const int x_w = x0 + j;
- int arr[4];
-
- arr[0] = (int)ref[y_w * stride + (x_w - 1)];
- arr[1] = (int)ref[y_w * stride + (x_w + 0)];
- arr[2] = (int)ref[y_w * stride + (x_w + 1)];
- arr[3] = (int)ref[y_w * stride + (x_w + 2)];
-
- // Apply kernel and round, keeping 6 extra bits of precision.
- //
- // 6 is the maximum allowable number of extra bits which will avoid
- // the intermediate values overflowing an int16_t. The most extreme
- // intermediate value occurs when:
- // * The input pixels are [0, 255, 255, 0]
- // * u_frac = 0.5
- // In this case, the un-scaled output is 255 * 1.125 = 286.875.
- // As an integer with 6 fractional bits, that is 18360, which fits
- // in an int16_t. But with 7 fractional bits it would be 36720,
- // which is too large.
- const int c_value = ROUND_POWER_OF_TWO(get_cubic_value_int(arr, h_kernel),
- DISFLOW_INTERP_BITS - 6);
- (void)c_value; // Suppress warnings
- assert(tmp_row[j] == c_value);
- }
-#endif // CHECK_RESULTS
}
// Vertical convolution
const int round_bits = DISFLOW_INTERP_BITS + 6 - DISFLOW_DERIV_SCALE_LOG2;
__m128i round_const_v = _mm_set1_epi32(1 << (round_bits - 1));
- __m128i v_kernel_01 = xx_set2_epi16(v_kernel[0], v_kernel[1]);
- __m128i v_kernel_23 = xx_set2_epi16(v_kernel[2], v_kernel[3]);
+ __m128i v_kernel_01 = _mm_set1_epi32(_mm_extract_epi32(kernels, 1));
+ __m128i v_kernel_23 = _mm_set1_epi32(_mm_extract_epi32(kernels, 3));
for (int i = 0; i < DISFLOW_PATCH_SIZE; ++i) {
int16_t *tmp_row = &tmp[i * DISFLOW_PATCH_SIZE];
@@ -259,30 +232,6 @@ static INLINE void compute_flow_vector(const uint8_t *src, const uint8_t *ref,
__m128i dy_row = _mm_loadu_si128((__m128i *)&dy[i * DISFLOW_PATCH_SIZE]);
b0_acc = _mm_add_epi32(b0_acc, _mm_madd_epi16(dx_row, dt));
b1_acc = _mm_add_epi32(b1_acc, _mm_madd_epi16(dy_row, dt));
-
-#if CHECK_RESULTS
- int16_t dt_arr[8];
- memcpy(dt_arr, &dt, 8 * sizeof(*dt_arr));
- for (int j = 0; j < DISFLOW_PATCH_SIZE; ++j) {
- int16_t *p = &tmp[i * DISFLOW_PATCH_SIZE + j];
- int arr[4] = { p[-DISFLOW_PATCH_SIZE], p[0], p[DISFLOW_PATCH_SIZE],
- p[2 * DISFLOW_PATCH_SIZE] };
- const int result = get_cubic_value_int(arr, v_kernel);
-
- // Apply kernel and round.
- // This time, we have to round off the 6 extra bits which were kept
- // earlier, but we also want to keep DISFLOW_DERIV_SCALE_LOG2 extra bits
- // of precision to match the scale of the dx and dy arrays.
- const int c_warped = ROUND_POWER_OF_TWO(result, round_bits);
- const int c_src_px = src[(x + j) + (y + i) * stride] << 3;
- const int c_dt = c_warped - c_src_px;
-
- assert(dt_arr[j] == c_dt);
-
- c_result[0] += dx[i * DISFLOW_PATCH_SIZE + j] * c_dt;
- c_result[1] += dy[i * DISFLOW_PATCH_SIZE + j] * c_dt;
- }
-#endif // CHECK_RESULTS
}
// Flatten the two sets of partial sums to find the final value of b
@@ -292,156 +241,66 @@ static INLINE void compute_flow_vector(const uint8_t *src, const uint8_t *ref,
__m128i partial_sum = _mm_hadd_epi32(b0_acc, b1_acc);
b[0] = _mm_extract_epi32(partial_sum, 0) + _mm_extract_epi32(partial_sum, 1);
b[1] = _mm_extract_epi32(partial_sum, 2) + _mm_extract_epi32(partial_sum, 3);
-
-#if CHECK_RESULTS
- assert(b[0] == c_result[0]);
- assert(b[1] == c_result[1]);
-#endif // CHECK_RESULTS
}
-static INLINE void sobel_filter_x(const uint8_t *src, int src_stride,
- int16_t *dst, int dst_stride) {
- int16_t tmp_[DISFLOW_PATCH_SIZE * (DISFLOW_PATCH_SIZE + 2)];
- int16_t *tmp = tmp_ + DISFLOW_PATCH_SIZE;
-#if CHECK_RESULTS
- const int taps = 3;
-#endif // CHECK_RESULTS
-
- // Horizontal filter
- // As the kernel is simply {1, 0, -1}, we implement this as simply
- // out[x] = image[x-1] - image[x+1]
- // rather than doing a "proper" convolution operation
- for (int y = -1; y < DISFLOW_PATCH_SIZE + 1; ++y) {
- const uint8_t *src_row = src + y * src_stride;
- int16_t *tmp_row = tmp + y * DISFLOW_PATCH_SIZE;
-
- // Load pixels and expand to 16 bits
- __m128i row = _mm_loadu_si128((__m128i *)(src_row - 1));
- __m128i px0 = _mm_cvtepu8_epi16(row);
- __m128i px2 = _mm_cvtepu8_epi16(_mm_srli_si128(row, 2));
-
- __m128i out = _mm_sub_epi16(px0, px2);
-
- // Store to intermediate array
- _mm_storeu_si128((__m128i *)tmp_row, out);
-
-#if CHECK_RESULTS
- // Cross-check
- static const int16_t h_kernel[3] = { 1, 0, -1 };
- for (int x = 0; x < DISFLOW_PATCH_SIZE; ++x) {
- int sum = 0;
- for (int k = 0; k < taps; ++k) {
- sum += h_kernel[k] * src_row[x + k - 1];
- }
- (void)sum;
- assert(tmp_row[x] == sum);
- }
-#endif // CHECK_RESULTS
- }
-
- // Vertical filter
- // Here the kernel is {1, 2, 1}, which can be implemented
- // with simple sums rather than multiplies and adds.
- // In order to minimize dependency chains, we evaluate in the order
- // (image[y - 1] + image[y + 1]) + (image[y] << 1)
- // This way, the first addition and the shift can happen in parallel
- for (int y = 0; y < DISFLOW_PATCH_SIZE; ++y) {
- const int16_t *tmp_row = tmp + y * DISFLOW_PATCH_SIZE;
- int16_t *dst_row = dst + y * dst_stride;
-
- __m128i px0 = _mm_loadu_si128((__m128i *)(tmp_row - DISFLOW_PATCH_SIZE));
- __m128i px1 = _mm_loadu_si128((__m128i *)tmp_row);
- __m128i px2 = _mm_loadu_si128((__m128i *)(tmp_row + DISFLOW_PATCH_SIZE));
-
- __m128i out =
- _mm_add_epi16(_mm_add_epi16(px0, px2), _mm_slli_epi16(px1, 1));
-
- _mm_storeu_si128((__m128i *)dst_row, out);
-
-#if CHECK_RESULTS
- static const int16_t v_kernel[3] = { 1, 2, 1 };
- for (int x = 0; x < DISFLOW_PATCH_SIZE; ++x) {
- int sum = 0;
- for (int k = 0; k < taps; ++k) {
- sum += v_kernel[k] * tmp[(y + k - 1) * DISFLOW_PATCH_SIZE + x];
- }
- (void)sum;
- assert(dst_row[x] == sum);
- }
-#endif // CHECK_RESULTS
- }
-}
-
-static INLINE void sobel_filter_y(const uint8_t *src, int src_stride,
- int16_t *dst, int dst_stride) {
- int16_t tmp_[DISFLOW_PATCH_SIZE * (DISFLOW_PATCH_SIZE + 2)];
- int16_t *tmp = tmp_ + DISFLOW_PATCH_SIZE;
-#if CHECK_RESULTS
- const int taps = 3;
-#endif // CHECK_RESULTS
-
- // Horizontal filter
- // Here the kernel is {1, 2, 1}, which can be implemented
- // with simple sums rather than multiplies and adds.
- // In order to minimize dependency chains, we evaluate in the order
- // (image[y - 1] + image[y + 1]) + (image[y] << 1)
- // This way, the first addition and the shift can happen in parallel
- for (int y = -1; y < DISFLOW_PATCH_SIZE + 1; ++y) {
- const uint8_t *src_row = src + y * src_stride;
- int16_t *tmp_row = tmp + y * DISFLOW_PATCH_SIZE;
-
- // Load pixels and expand to 16 bits
- __m128i row = _mm_loadu_si128((__m128i *)(src_row - 1));
- __m128i px0 = _mm_cvtepu8_epi16(row);
- __m128i px1 = _mm_cvtepu8_epi16(_mm_srli_si128(row, 1));
- __m128i px2 = _mm_cvtepu8_epi16(_mm_srli_si128(row, 2));
-
- __m128i out =
- _mm_add_epi16(_mm_add_epi16(px0, px2), _mm_slli_epi16(px1, 1));
-
- // Store to intermediate array
- _mm_storeu_si128((__m128i *)tmp_row, out);
-
-#if CHECK_RESULTS
- // Cross-check
- static const int16_t h_kernel[3] = { 1, 2, 1 };
- for (int x = 0; x < DISFLOW_PATCH_SIZE; ++x) {
- int sum = 0;
- for (int k = 0; k < taps; ++k) {
- sum += h_kernel[k] * src_row[x + k - 1];
- }
- (void)sum;
- assert(tmp_row[x] == sum);
- }
-#endif // CHECK_RESULTS
- }
-
- // Vertical filter
- // As the kernel is simply {1, 0, -1}, we implement this as simply
- // out[x] = image[x-1] - image[x+1]
- // rather than doing a "proper" convolution operation
- for (int y = 0; y < DISFLOW_PATCH_SIZE; ++y) {
- const int16_t *tmp_row = tmp + y * DISFLOW_PATCH_SIZE;
- int16_t *dst_row = dst + y * dst_stride;
-
- __m128i px0 = _mm_loadu_si128((__m128i *)(tmp_row - DISFLOW_PATCH_SIZE));
- __m128i px2 = _mm_loadu_si128((__m128i *)(tmp_row + DISFLOW_PATCH_SIZE));
-
- __m128i out = _mm_sub_epi16(px0, px2);
-
- _mm_storeu_si128((__m128i *)dst_row, out);
-
-#if CHECK_RESULTS
- static const int16_t v_kernel[3] = { 1, 0, -1 };
- for (int x = 0; x < DISFLOW_PATCH_SIZE; ++x) {
- int sum = 0;
- for (int k = 0; k < taps; ++k) {
- sum += v_kernel[k] * tmp[(y + k - 1) * DISFLOW_PATCH_SIZE + x];
- }
- (void)sum;
- assert(dst_row[x] == sum);
- }
-#endif // CHECK_RESULTS
+// Compute the x and y gradients of the source patch in a single pass,
+// and store into dx and dy respectively.
+static INLINE void sobel_filter(const uint8_t *src, int src_stride, int16_t *dx,
+ int16_t *dy) {
+ // Loop setup: Load the first two rows (of 10 input rows) and apply
+ // the horizontal parts of the two filters
+ __m128i row_m1 = _mm_loadu_si128((__m128i *)(src - src_stride - 1));
+ __m128i row_m1_a = _mm_cvtepu8_epi16(row_m1);
+ __m128i row_m1_b = _mm_cvtepu8_epi16(_mm_srli_si128(row_m1, 1));
+ __m128i row_m1_c = _mm_cvtepu8_epi16(_mm_srli_si128(row_m1, 2));
+
+ __m128i row_m1_hsmooth = _mm_add_epi16(_mm_add_epi16(row_m1_a, row_m1_c),
+ _mm_slli_epi16(row_m1_b, 1));
+ __m128i row_m1_hdiff = _mm_sub_epi16(row_m1_a, row_m1_c);
+
+ __m128i row = _mm_loadu_si128((__m128i *)(src - 1));
+ __m128i row_a = _mm_cvtepu8_epi16(row);
+ __m128i row_b = _mm_cvtepu8_epi16(_mm_srli_si128(row, 1));
+ __m128i row_c = _mm_cvtepu8_epi16(_mm_srli_si128(row, 2));
+
+ __m128i row_hsmooth =
+ _mm_add_epi16(_mm_add_epi16(row_a, row_c), _mm_slli_epi16(row_b, 1));
+ __m128i row_hdiff = _mm_sub_epi16(row_a, row_c);
+
+ // Main loop: For each of the 8 output rows:
+ // * Load row i+1 and apply both horizontal filters
+ // * Apply vertical filters and store results
+ // * Shift rows for next iteration
+ for (int i = 0; i < DISFLOW_PATCH_SIZE; i++) {
+ // Load row i+1 and apply both horizontal filters
+ const __m128i row_p1 =
+ _mm_loadu_si128((__m128i *)(src + (i + 1) * src_stride - 1));
+ const __m128i row_p1_a = _mm_cvtepu8_epi16(row_p1);
+ const __m128i row_p1_b = _mm_cvtepu8_epi16(_mm_srli_si128(row_p1, 1));
+ const __m128i row_p1_c = _mm_cvtepu8_epi16(_mm_srli_si128(row_p1, 2));
+
+ const __m128i row_p1_hsmooth = _mm_add_epi16(
+ _mm_add_epi16(row_p1_a, row_p1_c), _mm_slli_epi16(row_p1_b, 1));
+ const __m128i row_p1_hdiff = _mm_sub_epi16(row_p1_a, row_p1_c);
+
+ // Apply vertical filters and store results
+ // dx = vertical smooth(horizontal diff(input))
+ // dy = vertical diff(horizontal smooth(input))
+ const __m128i dx_row =
+ _mm_add_epi16(_mm_add_epi16(row_m1_hdiff, row_p1_hdiff),
+ _mm_slli_epi16(row_hdiff, 1));
+ const __m128i dy_row = _mm_sub_epi16(row_m1_hsmooth, row_p1_hsmooth);
+
+ _mm_storeu_si128((__m128i *)(dx + i * DISFLOW_PATCH_SIZE), dx_row);
+ _mm_storeu_si128((__m128i *)(dy + i * DISFLOW_PATCH_SIZE), dy_row);
+
+ // Shift rows for next iteration
+ // This allows a lot of work to be reused, reducing the number of
+ // horizontal filtering operations from 2*3*8 = 48 to 2*10 = 20
+ row_m1_hsmooth = row_hsmooth;
+ row_m1_hdiff = row_hdiff;
+ row_hsmooth = row_p1_hsmooth;
+ row_hdiff = row_p1_hdiff;
}
}
@@ -476,30 +335,6 @@ static INLINE void compute_flow_matrix(const int16_t *dx, int dx_stride,
// which is convenient for integerized SIMD implementation.
result = _mm_add_epi32(result, _mm_set_epi32(1, 0, 0, 1));
-#if CHECK_RESULTS
- int tmp[4] = { 0 };
-
- for (int i = 0; i < DISFLOW_PATCH_SIZE; i++) {
- for (int j = 0; j < DISFLOW_PATCH_SIZE; j++) {
- tmp[0] += dx[i * dx_stride + j] * dx[i * dx_stride + j];
- tmp[1] += dx[i * dx_stride + j] * dy[i * dy_stride + j];
- // Don't compute tmp[2], as it should be equal to tmp[1]
- tmp[3] += dy[i * dy_stride + j] * dy[i * dy_stride + j];
- }
- }
-
- // Apply regularization
- tmp[0] += 1;
- tmp[3] += 1;
-
- tmp[2] = tmp[1];
-
- assert(tmp[0] == _mm_extract_epi32(result, 0));
- assert(tmp[1] == _mm_extract_epi32(result, 1));
- assert(tmp[2] == _mm_extract_epi32(result, 2));
- assert(tmp[3] == _mm_extract_epi32(result, 3));
-#endif // CHECK_RESULTS
-
// Convert results to doubles and store
_mm_storeu_pd(M, _mm_cvtepi32_pd(result));
_mm_storeu_pd(M + 2, _mm_cvtepi32_pd(_mm_srli_si128(result, 8)));
@@ -525,16 +360,15 @@ static INLINE void invert_2x2(const double *M, double *M_inv) {
void aom_compute_flow_at_point_sse4_1(const uint8_t *src, const uint8_t *ref,
int x, int y, int width, int height,
int stride, double *u, double *v) {
- double M[4];
- double M_inv[4];
+ DECLARE_ALIGNED(16, double, M[4]);
+ DECLARE_ALIGNED(16, double, M_inv[4]);
+ DECLARE_ALIGNED(16, int16_t, dx[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE]);
+ DECLARE_ALIGNED(16, int16_t, dy[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE]);
int b[2];
- int16_t dx[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE];
- int16_t dy[DISFLOW_PATCH_SIZE * DISFLOW_PATCH_SIZE];
// Compute gradients within this patch
const uint8_t *src_patch = &src[y * stride + x];
- sobel_filter_x(src_patch, stride, dx, DISFLOW_PATCH_SIZE);
- sobel_filter_y(src_patch, stride, dy, DISFLOW_PATCH_SIZE);
+ sobel_filter(src_patch, stride, dx, dy);
compute_flow_matrix(dx, DISFLOW_PATCH_SIZE, dy, DISFLOW_PATCH_SIZE, M);
invert_2x2(M, M_inv);
diff --git a/third_party/aom/aom_dsp/mathutils.h b/third_party/aom/aom_dsp/mathutils.h
index cbb6cf491f..26635fc4d1 100644
--- a/third_party/aom/aom_dsp/mathutils.h
+++ b/third_party/aom/aom_dsp/mathutils.h
@@ -17,7 +17,6 @@
#include <string.h>
#include "aom_dsp/aom_dsp_common.h"
-#include "aom_mem/aom_mem.h"
static const double TINY_NEAR_ZERO = 1.0E-16;
diff --git a/third_party/aom/aom_dsp/noise_model.c b/third_party/aom/aom_dsp/noise_model.c
index 065ec9a106..947dfd3c7a 100644
--- a/third_party/aom/aom_dsp/noise_model.c
+++ b/third_party/aom/aom_dsp/noise_model.c
@@ -19,6 +19,8 @@
#include "aom_dsp/noise_model.h"
#include "aom_dsp/noise_util.h"
#include "aom_mem/aom_mem.h"
+#include "aom_ports/mem.h"
+#include "aom_scale/yv12config.h"
#define kLowPolyNumParams 3
@@ -1555,7 +1557,7 @@ void aom_denoise_and_model_free(struct aom_denoise_and_model_t *ctx) {
}
static int denoise_and_model_realloc_if_necessary(
- struct aom_denoise_and_model_t *ctx, YV12_BUFFER_CONFIG *sd) {
+ struct aom_denoise_and_model_t *ctx, const YV12_BUFFER_CONFIG *sd) {
if (ctx->width == sd->y_width && ctx->height == sd->y_height &&
ctx->y_stride == sd->y_stride && ctx->uv_stride == sd->uv_stride)
return 1;
@@ -1624,7 +1626,7 @@ static int denoise_and_model_realloc_if_necessary(
// TODO(aomedia:3151): Handle a monochrome image (sd->u_buffer and sd->v_buffer
// are null pointers) correctly.
int aom_denoise_and_model_run(struct aom_denoise_and_model_t *ctx,
- YV12_BUFFER_CONFIG *sd,
+ const YV12_BUFFER_CONFIG *sd,
aom_film_grain_t *film_grain, int apply_denoise) {
const int block_size = ctx->block_size;
const int use_highbd = (sd->flags & YV12_FLAG_HIGHBITDEPTH) != 0;
diff --git a/third_party/aom/aom_dsp/noise_model.h b/third_party/aom/aom_dsp/noise_model.h
index 8228aeacfc..5b2d7efe29 100644
--- a/third_party/aom/aom_dsp/noise_model.h
+++ b/third_party/aom/aom_dsp/noise_model.h
@@ -297,14 +297,14 @@ struct aom_denoise_and_model_t;
* aom_denoise_and_model_alloc that holds some
* buffers for denoising and the current noise
* estimate.
- * \param[in,out] buf The raw input buffer to be denoised.
+ * \param[in,out] sd The raw input buffer to be denoised.
* \param[out] grain Output film grain parameters
* \param[in] apply_denoise Whether or not to apply the denoising to the
* frame that will be encoded
*/
int aom_denoise_and_model_run(struct aom_denoise_and_model_t *ctx,
- YV12_BUFFER_CONFIG *buf, aom_film_grain_t *grain,
- int apply_denoise);
+ const YV12_BUFFER_CONFIG *sd,
+ aom_film_grain_t *grain, int apply_denoise);
/*!\brief Allocates a context that can be used for denoising and noise modeling.
*
diff --git a/third_party/aom/aom_dsp/pyramid.c b/third_party/aom/aom_dsp/pyramid.c
index 324a18baea..5de001dbd5 100644
--- a/third_party/aom/aom_dsp/pyramid.c
+++ b/third_party/aom/aom_dsp/pyramid.c
@@ -12,7 +12,7 @@
#include "aom_dsp/pyramid.h"
#include "aom_mem/aom_mem.h"
#include "aom_ports/bitops.h"
-#include "aom_util/aom_thread.h"
+#include "aom_util/aom_pthread.h"
// TODO(rachelbarker): Move needed code from av1/ to aom_dsp/
#include "av1/common/resize.h"
@@ -26,18 +26,16 @@
// levels. This is counted in the size checked against the max allocation
// limit
// * Then calls aom_alloc_pyramid() to actually create the pyramid
-// * Pyramid is initially marked as invalid (no data)
-// * Whenever pyramid is needed, we check the valid flag. If set, use existing
-// data. If not set, compute full pyramid
-// * Whenever frame buffer is reused, clear the valid flag
+// * Pyramid is initially marked as containing no valid data
+// * Each pyramid layer is computed on-demand, the first time it is requested
+// * Whenever frame buffer is reused, reset the counter of filled levels.
+// This invalidates all of the existing pyramid levels.
// * Whenever frame buffer is resized, reallocate pyramid
-size_t aom_get_pyramid_alloc_size(int width, int height, int n_levels,
- bool image_is_16bit) {
- // Limit number of levels on small frames
+size_t aom_get_pyramid_alloc_size(int width, int height, bool image_is_16bit) {
+ // Allocate the maximum possible number of layers for this width and height
const int msb = get_msb(AOMMIN(width, height));
- const int max_levels = AOMMAX(msb - MIN_PYRAMID_SIZE_LOG2, 1);
- n_levels = AOMMIN(n_levels, max_levels);
+ const int n_levels = AOMMAX(msb - MIN_PYRAMID_SIZE_LOG2, 1);
size_t alloc_size = 0;
alloc_size += sizeof(ImagePyramid);
@@ -100,12 +98,10 @@ size_t aom_get_pyramid_alloc_size(int width, int height, int n_levels,
return alloc_size;
}
-ImagePyramid *aom_alloc_pyramid(int width, int height, int n_levels,
- bool image_is_16bit) {
- // Limit number of levels on small frames
+ImagePyramid *aom_alloc_pyramid(int width, int height, bool image_is_16bit) {
+ // Allocate the maximum possible number of layers for this width and height
const int msb = get_msb(AOMMIN(width, height));
- const int max_levels = AOMMAX(msb - MIN_PYRAMID_SIZE_LOG2, 1);
- n_levels = AOMMIN(n_levels, max_levels);
+ const int n_levels = AOMMAX(msb - MIN_PYRAMID_SIZE_LOG2, 1);
ImagePyramid *pyr = aom_calloc(1, sizeof(*pyr));
if (!pyr) {
@@ -118,8 +114,8 @@ ImagePyramid *aom_alloc_pyramid(int width, int height, int n_levels,
return NULL;
}
- pyr->valid = false;
- pyr->n_levels = n_levels;
+ pyr->max_levels = n_levels;
+ pyr->filled_levels = 0;
// Compute sizes and offsets for each pyramid level
// These are gathered up first, so that we can allocate all pyramid levels
@@ -248,46 +244,67 @@ static INLINE void fill_border(uint8_t *img_buf, const int width,
}
}
-// Compute coarse to fine pyramids for a frame
+// Compute downsampling pyramid for a frame
+//
+// This function will ensure that the first `n_levels` levels of the pyramid
+// are filled, unless the frame is too small to have this many levels.
+// In that case, we will fill all available levels and then stop.
+//
+// Returns the actual number of levels filled, capped at n_levels,
+// or -1 on error.
+//
// This must only be called while holding frame_pyr->mutex
-static INLINE bool fill_pyramid(const YV12_BUFFER_CONFIG *frame, int bit_depth,
- ImagePyramid *frame_pyr) {
- int n_levels = frame_pyr->n_levels;
+static INLINE int fill_pyramid(const YV12_BUFFER_CONFIG *frame, int bit_depth,
+ int n_levels, ImagePyramid *frame_pyr) {
+ int already_filled_levels = frame_pyr->filled_levels;
+
+ // This condition should already be enforced by aom_compute_pyramid
+ assert(n_levels <= frame_pyr->max_levels);
+
+ if (already_filled_levels >= n_levels) {
+ return n_levels;
+ }
+
const int frame_width = frame->y_crop_width;
const int frame_height = frame->y_crop_height;
const int frame_stride = frame->y_stride;
assert((frame_width >> n_levels) >= 0);
assert((frame_height >> n_levels) >= 0);
- PyramidLayer *first_layer = &frame_pyr->layers[0];
- if (frame->flags & YV12_FLAG_HIGHBITDEPTH) {
- // For frames stored in a 16-bit buffer, we need to downconvert to 8 bits
- assert(first_layer->width == frame_width);
- assert(first_layer->height == frame_height);
-
- uint16_t *frame_buffer = CONVERT_TO_SHORTPTR(frame->y_buffer);
- uint8_t *pyr_buffer = first_layer->buffer;
- int pyr_stride = first_layer->stride;
- for (int y = 0; y < frame_height; y++) {
- uint16_t *frame_row = frame_buffer + y * frame_stride;
- uint8_t *pyr_row = pyr_buffer + y * pyr_stride;
- for (int x = 0; x < frame_width; x++) {
- pyr_row[x] = frame_row[x] >> (bit_depth - 8);
+ if (already_filled_levels == 0) {
+ // Fill in largest level from the original image
+ PyramidLayer *first_layer = &frame_pyr->layers[0];
+ if (frame->flags & YV12_FLAG_HIGHBITDEPTH) {
+ // For frames stored in a 16-bit buffer, we need to downconvert to 8 bits
+ assert(first_layer->width == frame_width);
+ assert(first_layer->height == frame_height);
+
+ uint16_t *frame_buffer = CONVERT_TO_SHORTPTR(frame->y_buffer);
+ uint8_t *pyr_buffer = first_layer->buffer;
+ int pyr_stride = first_layer->stride;
+ for (int y = 0; y < frame_height; y++) {
+ uint16_t *frame_row = frame_buffer + y * frame_stride;
+ uint8_t *pyr_row = pyr_buffer + y * pyr_stride;
+ for (int x = 0; x < frame_width; x++) {
+ pyr_row[x] = frame_row[x] >> (bit_depth - 8);
+ }
}
+
+ fill_border(pyr_buffer, frame_width, frame_height, pyr_stride);
+ } else {
+ // For frames stored in an 8-bit buffer, we don't need to copy anything -
+ // we can just reference the original image buffer
+ first_layer->buffer = frame->y_buffer;
+ first_layer->width = frame_width;
+ first_layer->height = frame_height;
+ first_layer->stride = frame_stride;
}
- fill_border(pyr_buffer, frame_width, frame_height, pyr_stride);
- } else {
- // For frames stored in an 8-bit buffer, we need to configure the first
- // pyramid layer to point at the original image buffer
- first_layer->buffer = frame->y_buffer;
- first_layer->width = frame_width;
- first_layer->height = frame_height;
- first_layer->stride = frame_stride;
+ already_filled_levels = 1;
}
// Fill in the remaining levels through progressive downsampling
- for (int level = 1; level < n_levels; ++level) {
+ for (int level = already_filled_levels; level < n_levels; ++level) {
PyramidLayer *prev_layer = &frame_pyr->layers[level - 1];
uint8_t *prev_buffer = prev_layer->buffer;
int prev_stride = prev_layer->stride;
@@ -314,11 +331,16 @@ static INLINE bool fill_pyramid(const YV12_BUFFER_CONFIG *frame, int bit_depth,
// TODO(rachelbarker): Use optimized downsample-by-2 function
if (!av1_resize_plane(prev_buffer, this_height << 1, this_width << 1,
prev_stride, this_buffer, this_height, this_width,
- this_stride))
- return false;
+ this_stride)) {
+ // If we can't allocate memory, we'll have to terminate early
+ frame_pyr->filled_levels = n_levels;
+ return -1;
+ }
fill_border(this_buffer, this_width, this_height, this_stride);
}
- return true;
+
+ frame_pyr->filled_levels = n_levels;
+ return n_levels;
}
// Fill out a downsampling pyramid for a given frame.
@@ -327,63 +349,72 @@ static INLINE bool fill_pyramid(const YV12_BUFFER_CONFIG *frame, int bit_depth,
// regardless of the input bit depth. Additional levels are then downscaled
// by powers of 2.
//
-// For small input frames, the number of levels actually constructed
-// will be limited so that the smallest image is at least MIN_PYRAMID_SIZE
-// pixels along each side.
+// This function will ensure that the first `n_levels` levels of the pyramid
+// are filled, unless the frame is too small to have this many levels.
+// In that case, we will fill all available levels and then stop.
+// No matter how small the frame is, at least one level is guaranteed
+// to be filled.
//
-// However, if the input frame has a side of length < MIN_PYRAMID_SIZE,
-// we will still construct the top level.
-bool aom_compute_pyramid(const YV12_BUFFER_CONFIG *frame, int bit_depth,
- ImagePyramid *pyr) {
+// Returns the actual number of levels filled, capped at n_levels,
+// or -1 on error.
+int aom_compute_pyramid(const YV12_BUFFER_CONFIG *frame, int bit_depth,
+ int n_levels, ImagePyramid *pyr) {
assert(pyr);
// Per the comments in the ImagePyramid struct, we must take this mutex
- // before reading or writing the "valid" flag, and hold it while computing
- // the pyramid, to ensure proper behaviour if multiple threads call this
- // function simultaneously
+ // before reading or writing the filled_levels field, and hold it while
+ // computing any additional pyramid levels, to ensure proper behaviour
+ // when multithreading is used
#if CONFIG_MULTITHREAD
pthread_mutex_lock(&pyr->mutex);
#endif // CONFIG_MULTITHREAD
- if (!pyr->valid) {
- pyr->valid = fill_pyramid(frame, bit_depth, pyr);
+ n_levels = AOMMIN(n_levels, pyr->max_levels);
+ int result = n_levels;
+ if (pyr->filled_levels < n_levels) {
+ // Compute any missing levels that we need
+ result = fill_pyramid(frame, bit_depth, n_levels, pyr);
}
- bool valid = pyr->valid;
-
- // At this point, the pyramid is guaranteed to be valid, and can be safely
- // read from without holding the mutex any more
+ // At this point, as long as result >= 0, the requested number of pyramid
+ // levels are guaranteed to be valid, and can be safely read from without
+ // holding the mutex any further
+ assert(IMPLIES(result >= 0, pyr->filled_levels >= n_levels));
#if CONFIG_MULTITHREAD
pthread_mutex_unlock(&pyr->mutex);
#endif // CONFIG_MULTITHREAD
- return valid;
+ return result;
}
#ifndef NDEBUG
-// Check if a pyramid has already been computed.
+// Check if a pyramid has already been computed to at least n levels
// This is mostly a debug helper - as it is necessary to hold pyr->mutex
-// while reading the valid flag, we cannot just write:
-// assert(pyr->valid);
+// while reading the number of already-computed levels, we cannot just write:
+// assert(pyr->filled_levels >= n_levels);
// This function allows the check to be correctly written as:
-// assert(aom_is_pyramid_valid(pyr));
-bool aom_is_pyramid_valid(ImagePyramid *pyr) {
+// assert(aom_is_pyramid_valid(pyr, n_levels));
+//
+// Note: This deliberately does not restrict n_levels based on the maximum
+// number of permitted levels for the frame size. This allows the check to
+// catch cases where the caller forgets to handle the case where
+// max_levels is less than the requested number of levels
+bool aom_is_pyramid_valid(ImagePyramid *pyr, int n_levels) {
assert(pyr);
// Per the comments in the ImagePyramid struct, we must take this mutex
- // before reading or writing the "valid" flag, and hold it while computing
- // the pyramid, to ensure proper behaviour if multiple threads call this
- // function simultaneously
+ // before reading or writing the filled_levels field, to ensure proper
+ // behaviour when multithreading is used
#if CONFIG_MULTITHREAD
pthread_mutex_lock(&pyr->mutex);
#endif // CONFIG_MULTITHREAD
- bool valid = pyr->valid;
+ bool result = (pyr->filled_levels >= n_levels);
#if CONFIG_MULTITHREAD
pthread_mutex_unlock(&pyr->mutex);
#endif // CONFIG_MULTITHREAD
- return valid;
+ return result;
}
#endif
@@ -394,7 +425,7 @@ void aom_invalidate_pyramid(ImagePyramid *pyr) {
#if CONFIG_MULTITHREAD
pthread_mutex_lock(&pyr->mutex);
#endif // CONFIG_MULTITHREAD
- pyr->valid = false;
+ pyr->filled_levels = 0;
#if CONFIG_MULTITHREAD
pthread_mutex_unlock(&pyr->mutex);
#endif // CONFIG_MULTITHREAD
diff --git a/third_party/aom/aom_dsp/pyramid.h b/third_party/aom/aom_dsp/pyramid.h
index 9442a1ff08..745bb7e525 100644
--- a/third_party/aom/aom_dsp/pyramid.h
+++ b/third_party/aom/aom_dsp/pyramid.h
@@ -19,7 +19,7 @@
#include "config/aom_config.h"
#include "aom_scale/yv12config.h"
-#include "aom_util/aom_thread.h"
+#include "aom_util/aom_pthread.h"
#ifdef __cplusplus
extern "C" {
@@ -57,23 +57,31 @@ typedef struct image_pyramid {
// same time
//
// Semantics:
- // * This mutex must be held whenever reading or writing the `valid` flag
+ // * This mutex must be held whenever reading or writing the
+ // `filled_levels` field
//
// * This mutex must also be held while computing the image pyramid,
// to ensure that only one thread may do so at a time.
//
- // * However, once you have read the valid flag and seen a true value,
- // it is safe to drop the mutex and read from the remaining fields.
- // This is because, once the image pyramid is computed, its contents
+ // * However, once you have read the filled_levels field and observed
+ // a value N, it is safe to drop the mutex and read from the remaining
+ // fields, including the first N pyramid levels (but no higher).
+ // Note that filled_levels must be read once and cached in a local variable
+ // in order for this to be safe - it cannot be re-read without retaking
+ // the mutex.
+ //
+ // This works because, once the image pyramid is computed, its contents
// will not be changed until the parent frame buffer is recycled,
// which will not happen until there are no more outstanding references
// to the frame buffer.
pthread_mutex_t mutex;
#endif
- // Flag indicating whether the pyramid contains valid data
- bool valid;
- // Number of allocated/filled levels in this pyramid
- int n_levels;
+ // Maximum number of levels for the given frame size
+ // We always allocate enough memory for this many levels, as the memory
+ // cost of higher levels of the pyramid is minimal.
+ int max_levels;
+ // Number of levels which currently hold valid data
+ int filled_levels;
// Pointer to allocated buffer
uint8_t *buffer_alloc;
// Data for each level
@@ -82,11 +90,9 @@ typedef struct image_pyramid {
PyramidLayer *layers;
} ImagePyramid;
-size_t aom_get_pyramid_alloc_size(int width, int height, int n_levels,
- bool image_is_16bit);
+size_t aom_get_pyramid_alloc_size(int width, int height, bool image_is_16bit);
-ImagePyramid *aom_alloc_pyramid(int width, int height, int n_levels,
- bool image_is_16bit);
+ImagePyramid *aom_alloc_pyramid(int width, int height, bool image_is_16bit);
// Fill out a downsampling pyramid for a given frame.
//
@@ -94,23 +100,28 @@ ImagePyramid *aom_alloc_pyramid(int width, int height, int n_levels,
// regardless of the input bit depth. Additional levels are then downscaled
// by powers of 2.
//
-// For small input frames, the number of levels actually constructed
-// will be limited so that the smallest image is at least MIN_PYRAMID_SIZE
-// pixels along each side.
+// This function will ensure that the first `n_levels` levels of the pyramid
+// are filled, unless the frame is too small to have this many levels.
+// In that case, we will fill all available levels and then stop.
//
-// However, if the input frame has a side of length < MIN_PYRAMID_SIZE,
-// we will still construct the top level.
-bool aom_compute_pyramid(const YV12_BUFFER_CONFIG *frame, int bit_depth,
- ImagePyramid *pyr);
+// Returns the actual number of levels filled, capped at n_levels,
+// or -1 on error.
+int aom_compute_pyramid(const YV12_BUFFER_CONFIG *frame, int bit_depth,
+ int n_levels, ImagePyramid *pyr);
#ifndef NDEBUG
-// Check if a pyramid has already been computed.
+// Check if a pyramid has already been computed to at least n levels
// This is mostly a debug helper - as it is necessary to hold pyr->mutex
-// while reading the valid flag, we cannot just write:
-// assert(pyr->valid);
+// while reading the number of already-computed levels, we cannot just write:
+// assert(pyr->filled_levels >= n_levels);
// This function allows the check to be correctly written as:
-// assert(aom_is_pyramid_valid(pyr));
-bool aom_is_pyramid_valid(ImagePyramid *pyr);
+// assert(aom_is_pyramid_valid(pyr, n_levels));
+//
+// Note: This deliberately does not restrict n_levels based on the maximum
+// number of permitted levels for the frame size. This allows the check to
+// catch cases where the caller forgets to handle the case where
+// max_levels is less than the requested number of levels
+bool aom_is_pyramid_valid(ImagePyramid *pyr, int n_levels);
#endif
// Mark a pyramid as no longer containing valid data.
diff --git a/third_party/aom/aom_dsp/rect.h b/third_party/aom/aom_dsp/rect.h
deleted file mode 100644
index 11bdaca979..0000000000
--- a/third_party/aom/aom_dsp/rect.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (c) 2022, Alliance for Open Media. All rights reserved
- *
- * This source code is subject to the terms of the BSD 2 Clause License and
- * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
- * was not distributed with this source code in the LICENSE file, you can
- * obtain it at www.aomedia.org/license/software. If the Alliance for Open
- * Media Patent License 1.0 was not distributed with this source code in the
- * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
- */
-
-#ifndef AOM_AOM_DSP_RECT_H_
-#define AOM_AOM_DSP_RECT_H_
-
-#include "config/aom_config.h"
-
-#include <stdbool.h>
-
-// Struct representing a rectangle of pixels.
-// The axes are inclusive-exclusive, ie. the point (top, left) is included
-// in the rectangle but (bottom, right) is not.
-typedef struct {
- int left, right, top, bottom;
-} PixelRect;
-
-static INLINE int rect_width(const PixelRect *r) { return r->right - r->left; }
-
-static INLINE int rect_height(const PixelRect *r) { return r->bottom - r->top; }
-
-static INLINE bool is_inside_rect(const int x, const int y,
- const PixelRect *r) {
- return (r->left <= x && x < r->right) && (r->top <= y && y < r->bottom);
-}
-
-#endif // AOM_AOM_DSP_RECT_H_
diff --git a/third_party/aom/aom_dsp/variance.c b/third_party/aom/aom_dsp/variance.c
index f02c3077ae..6cdd58492a 100644
--- a/third_party/aom/aom_dsp/variance.c
+++ b/third_party/aom/aom_dsp/variance.c
@@ -10,7 +10,6 @@
*/
#include <assert.h>
#include <stdlib.h>
-#include <string.h>
#include "config/aom_config.h"
#include "config/aom_dsp_rtcd.h"
@@ -70,12 +69,10 @@ uint32_t aom_sse_odd_size(const uint8_t *a, int a_stride, const uint8_t *b,
// taps should sum to FILTER_WEIGHT. pixel_step defines whether the filter is
// applied horizontally (pixel_step = 1) or vertically (pixel_step = stride).
// It defines the offset required to move from one input to the next.
-void aom_var_filter_block2d_bil_first_pass_c(const uint8_t *a, uint16_t *b,
- unsigned int src_pixels_per_line,
- unsigned int pixel_step,
- unsigned int output_height,
- unsigned int output_width,
- const uint8_t *filter) {
+static void var_filter_block2d_bil_first_pass_c(
+ const uint8_t *a, uint16_t *b, unsigned int src_pixels_per_line,
+ unsigned int pixel_step, unsigned int output_height,
+ unsigned int output_width, const uint8_t *filter) {
unsigned int i, j;
for (i = 0; i < output_height; ++i) {
@@ -100,12 +97,10 @@ void aom_var_filter_block2d_bil_first_pass_c(const uint8_t *a, uint16_t *b,
// filter is applied horizontally (pixel_step = 1) or vertically
// (pixel_step = stride). It defines the offset required to move from one input
// to the next. Output is 8-bit.
-void aom_var_filter_block2d_bil_second_pass_c(const uint16_t *a, uint8_t *b,
- unsigned int src_pixels_per_line,
- unsigned int pixel_step,
- unsigned int output_height,
- unsigned int output_width,
- const uint8_t *filter) {
+static void var_filter_block2d_bil_second_pass_c(
+ const uint16_t *a, uint8_t *b, unsigned int src_pixels_per_line,
+ unsigned int pixel_step, unsigned int output_height,
+ unsigned int output_width, const uint8_t *filter) {
unsigned int i, j;
for (i = 0; i < output_height; ++i) {
@@ -129,19 +124,19 @@ void aom_var_filter_block2d_bil_second_pass_c(const uint16_t *a, uint8_t *b,
return *sse - (uint32_t)(((int64_t)sum * sum) / (W * H)); \
}
-#define SUBPIX_VAR(W, H) \
- uint32_t aom_sub_pixel_variance##W##x##H##_c( \
- const uint8_t *a, int a_stride, int xoffset, int yoffset, \
- const uint8_t *b, int b_stride, uint32_t *sse) { \
- uint16_t fdata3[(H + 1) * W]; \
- uint8_t temp2[H * W]; \
- \
- aom_var_filter_block2d_bil_first_pass_c(a, fdata3, a_stride, 1, H + 1, W, \
- bilinear_filters_2t[xoffset]); \
- aom_var_filter_block2d_bil_second_pass_c(fdata3, temp2, W, W, H, W, \
- bilinear_filters_2t[yoffset]); \
- \
- return aom_variance##W##x##H##_c(temp2, W, b, b_stride, sse); \
+#define SUBPIX_VAR(W, H) \
+ uint32_t aom_sub_pixel_variance##W##x##H##_c( \
+ const uint8_t *a, int a_stride, int xoffset, int yoffset, \
+ const uint8_t *b, int b_stride, uint32_t *sse) { \
+ uint16_t fdata3[(H + 1) * W]; \
+ uint8_t temp2[H * W]; \
+ \
+ var_filter_block2d_bil_first_pass_c(a, fdata3, a_stride, 1, H + 1, W, \
+ bilinear_filters_2t[xoffset]); \
+ var_filter_block2d_bil_second_pass_c(fdata3, temp2, W, W, H, W, \
+ bilinear_filters_2t[yoffset]); \
+ \
+ return aom_variance##W##x##H##_c(temp2, W, b, b_stride, sse); \
}
#define SUBPIX_AVG_VAR(W, H) \
@@ -153,10 +148,10 @@ void aom_var_filter_block2d_bil_second_pass_c(const uint16_t *a, uint8_t *b,
uint8_t temp2[H * W]; \
DECLARE_ALIGNED(16, uint8_t, temp3[H * W]); \
\
- aom_var_filter_block2d_bil_first_pass_c(a, fdata3, a_stride, 1, H + 1, W, \
- bilinear_filters_2t[xoffset]); \
- aom_var_filter_block2d_bil_second_pass_c(fdata3, temp2, W, W, H, W, \
- bilinear_filters_2t[yoffset]); \
+ var_filter_block2d_bil_first_pass_c(a, fdata3, a_stride, 1, H + 1, W, \
+ bilinear_filters_2t[xoffset]); \
+ var_filter_block2d_bil_second_pass_c(fdata3, temp2, W, W, H, W, \
+ bilinear_filters_2t[yoffset]); \
\
aom_comp_avg_pred(temp3, second_pred, W, H, temp2, W); \
\
@@ -170,10 +165,10 @@ void aom_var_filter_block2d_bil_second_pass_c(const uint16_t *a, uint8_t *b,
uint8_t temp2[H * W]; \
DECLARE_ALIGNED(16, uint8_t, temp3[H * W]); \
\
- aom_var_filter_block2d_bil_first_pass_c(a, fdata3, a_stride, 1, H + 1, W, \
- bilinear_filters_2t[xoffset]); \
- aom_var_filter_block2d_bil_second_pass_c(fdata3, temp2, W, W, H, W, \
- bilinear_filters_2t[yoffset]); \
+ var_filter_block2d_bil_first_pass_c(a, fdata3, a_stride, 1, H + 1, W, \
+ bilinear_filters_2t[xoffset]); \
+ var_filter_block2d_bil_second_pass_c(fdata3, temp2, W, W, H, W, \
+ bilinear_filters_2t[yoffset]); \
\
aom_dist_wtd_comp_avg_pred(temp3, second_pred, W, H, temp2, W, jcp_param); \
\
@@ -730,24 +725,24 @@ void aom_comp_mask_pred_c(uint8_t *comp_pred, const uint8_t *pred, int width,
}
}
-#define MASK_SUBPIX_VAR(W, H) \
- unsigned int aom_masked_sub_pixel_variance##W##x##H##_c( \
- const uint8_t *src, int src_stride, int xoffset, int yoffset, \
- const uint8_t *ref, int ref_stride, const uint8_t *second_pred, \
- const uint8_t *msk, int msk_stride, int invert_mask, \
- unsigned int *sse) { \
- uint16_t fdata3[(H + 1) * W]; \
- uint8_t temp2[H * W]; \
- DECLARE_ALIGNED(16, uint8_t, temp3[H * W]); \
- \
- aom_var_filter_block2d_bil_first_pass_c(src, fdata3, src_stride, 1, H + 1, \
- W, bilinear_filters_2t[xoffset]); \
- aom_var_filter_block2d_bil_second_pass_c(fdata3, temp2, W, W, H, W, \
- bilinear_filters_2t[yoffset]); \
- \
- aom_comp_mask_pred_c(temp3, second_pred, W, H, temp2, W, msk, msk_stride, \
- invert_mask); \
- return aom_variance##W##x##H##_c(temp3, W, ref, ref_stride, sse); \
+#define MASK_SUBPIX_VAR(W, H) \
+ unsigned int aom_masked_sub_pixel_variance##W##x##H##_c( \
+ const uint8_t *src, int src_stride, int xoffset, int yoffset, \
+ const uint8_t *ref, int ref_stride, const uint8_t *second_pred, \
+ const uint8_t *msk, int msk_stride, int invert_mask, \
+ unsigned int *sse) { \
+ uint16_t fdata3[(H + 1) * W]; \
+ uint8_t temp2[H * W]; \
+ DECLARE_ALIGNED(16, uint8_t, temp3[H * W]); \
+ \
+ var_filter_block2d_bil_first_pass_c(src, fdata3, src_stride, 1, H + 1, W, \
+ bilinear_filters_2t[xoffset]); \
+ var_filter_block2d_bil_second_pass_c(fdata3, temp2, W, W, H, W, \
+ bilinear_filters_2t[yoffset]); \
+ \
+ aom_comp_mask_pred_c(temp3, second_pred, W, H, temp2, W, msk, msk_stride, \
+ invert_mask); \
+ return aom_variance##W##x##H##_c(temp3, W, ref, ref_stride, sse); \
}
MASK_SUBPIX_VAR(4, 4)
@@ -924,19 +919,19 @@ static INLINE void obmc_variance(const uint8_t *pre, int pre_stride,
return *sse - (unsigned int)(((int64_t)sum * sum) / (W * H)); \
}
-#define OBMC_SUBPIX_VAR(W, H) \
- unsigned int aom_obmc_sub_pixel_variance##W##x##H##_c( \
- const uint8_t *pre, int pre_stride, int xoffset, int yoffset, \
- const int32_t *wsrc, const int32_t *mask, unsigned int *sse) { \
- uint16_t fdata3[(H + 1) * W]; \
- uint8_t temp2[H * W]; \
- \
- aom_var_filter_block2d_bil_first_pass_c(pre, fdata3, pre_stride, 1, H + 1, \
- W, bilinear_filters_2t[xoffset]); \
- aom_var_filter_block2d_bil_second_pass_c(fdata3, temp2, W, W, H, W, \
- bilinear_filters_2t[yoffset]); \
- \
- return aom_obmc_variance##W##x##H##_c(temp2, W, wsrc, mask, sse); \
+#define OBMC_SUBPIX_VAR(W, H) \
+ unsigned int aom_obmc_sub_pixel_variance##W##x##H##_c( \
+ const uint8_t *pre, int pre_stride, int xoffset, int yoffset, \
+ const int32_t *wsrc, const int32_t *mask, unsigned int *sse) { \
+ uint16_t fdata3[(H + 1) * W]; \
+ uint8_t temp2[H * W]; \
+ \
+ var_filter_block2d_bil_first_pass_c(pre, fdata3, pre_stride, 1, H + 1, W, \
+ bilinear_filters_2t[xoffset]); \
+ var_filter_block2d_bil_second_pass_c(fdata3, temp2, W, W, H, W, \
+ bilinear_filters_2t[yoffset]); \
+ \
+ return aom_obmc_variance##W##x##H##_c(temp2, W, wsrc, mask, sse); \
}
OBMC_VAR(4, 4)
diff --git a/third_party/aom/aom_dsp/x86/aom_asm_stubs.c b/third_party/aom/aom_dsp/x86/aom_asm_stubs.c
index b08ec2546b..6c7fdd6eb1 100644
--- a/third_party/aom/aom_dsp/x86/aom_asm_stubs.c
+++ b/third_party/aom/aom_dsp/x86/aom_asm_stubs.c
@@ -15,40 +15,6 @@
#include "aom_dsp/x86/convolve.h"
#if HAVE_SSE2
-filter8_1dfunction aom_filter_block1d16_v8_sse2;
-filter8_1dfunction aom_filter_block1d16_h8_sse2;
-filter8_1dfunction aom_filter_block1d8_v8_sse2;
-filter8_1dfunction aom_filter_block1d8_h8_sse2;
-filter8_1dfunction aom_filter_block1d4_v8_sse2;
-filter8_1dfunction aom_filter_block1d4_h8_sse2;
-filter8_1dfunction aom_filter_block1d16_v4_sse2;
-filter8_1dfunction aom_filter_block1d16_h4_sse2;
-
-filter8_1dfunction aom_filter_block1d8_h4_sse2;
-filter8_1dfunction aom_filter_block1d8_v4_sse2;
-filter8_1dfunction aom_filter_block1d4_h4_sse2;
-filter8_1dfunction aom_filter_block1d4_v4_sse2;
-
-filter8_1dfunction aom_filter_block1d16_v2_sse2;
-filter8_1dfunction aom_filter_block1d16_h2_sse2;
-filter8_1dfunction aom_filter_block1d8_v2_sse2;
-filter8_1dfunction aom_filter_block1d8_h2_sse2;
-filter8_1dfunction aom_filter_block1d4_v2_sse2;
-filter8_1dfunction aom_filter_block1d4_h2_sse2;
-
-// void aom_convolve8_horiz_sse2(const uint8_t *src, ptrdiff_t src_stride,
-// uint8_t *dst, ptrdiff_t dst_stride,
-// const int16_t *filter_x, int x_step_q4,
-// const int16_t *filter_y, int y_step_q4,
-// int w, int h);
-// void aom_convolve8_vert_sse2(const uint8_t *src, ptrdiff_t src_stride,
-// uint8_t *dst, ptrdiff_t dst_stride,
-// const int16_t *filter_x, int x_step_q4,
-// const int16_t *filter_y, int y_step_q4,
-// int w, int h);
-FUN_CONV_1D(horiz, x_step_q4, filter_x, h, src, , sse2)
-FUN_CONV_1D(vert, y_step_q4, filter_y, v, src - src_stride * 3, , sse2)
-
#if CONFIG_AV1_HIGHBITDEPTH
highbd_filter8_1dfunction aom_highbd_filter_block1d16_v8_sse2;
highbd_filter8_1dfunction aom_highbd_filter_block1d16_h8_sse2;
diff --git a/third_party/aom/aom_dsp/x86/aom_subpixel_8t_intrin_sse2.c b/third_party/aom/aom_dsp/x86/aom_subpixel_8t_intrin_sse2.c
deleted file mode 100644
index 5c36b68727..0000000000
--- a/third_party/aom/aom_dsp/x86/aom_subpixel_8t_intrin_sse2.c
+++ /dev/null
@@ -1,569 +0,0 @@
-/*
- * Copyright (c) 2018, Alliance for Open Media. All rights reserved
- *
- * This source code is subject to the terms of the BSD 2 Clause License and
- * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
- * was not distributed with this source code in the LICENSE file, you can
- * obtain it at www.aomedia.org/license/software. If the Alliance for Open
- * Media Patent License 1.0 was not distributed with this source code in the
- * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
- */
-
-#include <emmintrin.h> // SSE2
-
-#include "config/aom_dsp_rtcd.h"
-#include "aom_dsp/x86/convolve.h"
-#include "aom_ports/mem.h"
-
-void aom_filter_block1d16_h4_sse2(const uint8_t *src_ptr,
- ptrdiff_t src_pixels_per_line,
- uint8_t *output_ptr, ptrdiff_t output_pitch,
- uint32_t output_height,
- const int16_t *filter) {
- __m128i filtersReg;
- __m128i addFilterReg32;
- __m128i secondFilters, thirdFilters;
- __m128i srcRegFilt32b1_1, srcRegFilt32b1_2, srcRegFilt32b2_1,
- srcRegFilt32b2_2;
- __m128i srcReg32b1, srcReg32b2;
- unsigned int i;
- src_ptr -= 3;
- addFilterReg32 = _mm_set1_epi16(32);
- filtersReg = _mm_loadu_si128((const __m128i *)filter);
- filtersReg = _mm_srai_epi16(filtersReg, 1);
-
- // coeffs 0 1 0 1 2 3 2 3
- const __m128i tmp_0 = _mm_unpacklo_epi32(filtersReg, filtersReg);
- // coeffs 4 5 4 5 6 7 6 7
- const __m128i tmp_1 = _mm_unpackhi_epi32(filtersReg, filtersReg);
-
- secondFilters = _mm_unpackhi_epi64(tmp_0, tmp_0); // coeffs 2 3 2 3 2 3 2 3
- thirdFilters = _mm_unpacklo_epi64(tmp_1, tmp_1); // coeffs 4 5 4 5 4 5 4 5
-
- for (i = output_height; i > 0; i -= 1) {
- srcReg32b1 = _mm_loadu_si128((const __m128i *)src_ptr);
-
- __m128i ss_2 = _mm_srli_si128(srcReg32b1, 2);
- __m128i ss_4 = _mm_srli_si128(srcReg32b1, 4);
- __m128i ss_1_1 = _mm_unpacklo_epi8(ss_2, _mm_setzero_si128());
- __m128i ss_2_1 = _mm_unpacklo_epi8(ss_4, _mm_setzero_si128());
- __m128i d1 = _mm_madd_epi16(ss_1_1, secondFilters);
- __m128i d2 = _mm_madd_epi16(ss_2_1, thirdFilters);
- srcRegFilt32b1_1 = _mm_add_epi32(d1, d2);
-
- __m128i ss_1 = _mm_srli_si128(srcReg32b1, 3);
- __m128i ss_3 = _mm_srli_si128(srcReg32b1, 5);
- __m128i ss_1_2 = _mm_unpacklo_epi8(ss_1, _mm_setzero_si128());
- __m128i ss_2_2 = _mm_unpacklo_epi8(ss_3, _mm_setzero_si128());
- d1 = _mm_madd_epi16(ss_1_2, secondFilters);
- d2 = _mm_madd_epi16(ss_2_2, thirdFilters);
- srcRegFilt32b1_2 = _mm_add_epi32(d1, d2);
-
- __m128i res_lo = _mm_unpacklo_epi32(srcRegFilt32b1_1, srcRegFilt32b1_2);
- __m128i res_hi = _mm_unpackhi_epi32(srcRegFilt32b1_1, srcRegFilt32b1_2);
- srcRegFilt32b1_1 = _mm_packs_epi32(res_lo, res_hi);
-
- // reading stride of the next 16 bytes
- // (part of it was being read by earlier read)
- srcReg32b2 = _mm_loadu_si128((const __m128i *)(src_ptr + 8));
-
- ss_2 = _mm_srli_si128(srcReg32b2, 2);
- ss_4 = _mm_srli_si128(srcReg32b2, 4);
- ss_1_1 = _mm_unpacklo_epi8(ss_2, _mm_setzero_si128());
- ss_2_1 = _mm_unpacklo_epi8(ss_4, _mm_setzero_si128());
- d1 = _mm_madd_epi16(ss_1_1, secondFilters);
- d2 = _mm_madd_epi16(ss_2_1, thirdFilters);
- srcRegFilt32b2_1 = _mm_add_epi32(d1, d2);
-
- ss_1 = _mm_srli_si128(srcReg32b2, 3);
- ss_3 = _mm_srli_si128(srcReg32b2, 5);
- ss_1_2 = _mm_unpacklo_epi8(ss_1, _mm_setzero_si128());
- ss_2_2 = _mm_unpacklo_epi8(ss_3, _mm_setzero_si128());
- d1 = _mm_madd_epi16(ss_1_2, secondFilters);
- d2 = _mm_madd_epi16(ss_2_2, thirdFilters);
- srcRegFilt32b2_2 = _mm_add_epi32(d1, d2);
-
- res_lo = _mm_unpacklo_epi32(srcRegFilt32b2_1, srcRegFilt32b2_2);
- res_hi = _mm_unpackhi_epi32(srcRegFilt32b2_1, srcRegFilt32b2_2);
- srcRegFilt32b2_1 = _mm_packs_epi32(res_lo, res_hi);
-
- // shift by 6 bit each 16 bit
- srcRegFilt32b1_1 = _mm_adds_epi16(srcRegFilt32b1_1, addFilterReg32);
- srcRegFilt32b2_1 = _mm_adds_epi16(srcRegFilt32b2_1, addFilterReg32);
- srcRegFilt32b1_1 = _mm_srai_epi16(srcRegFilt32b1_1, 6);
- srcRegFilt32b2_1 = _mm_srai_epi16(srcRegFilt32b2_1, 6);
-
- // shrink to 8 bit each 16 bits, the first lane contain the first
- // convolve result and the second lane contain the second convolve result
- srcRegFilt32b1_1 = _mm_packus_epi16(srcRegFilt32b1_1, srcRegFilt32b2_1);
-
- src_ptr += src_pixels_per_line;
-
- _mm_store_si128((__m128i *)output_ptr, srcRegFilt32b1_1);
-
- output_ptr += output_pitch;
- }
-}
-
-void aom_filter_block1d16_v4_sse2(const uint8_t *src_ptr, ptrdiff_t src_pitch,
- uint8_t *output_ptr, ptrdiff_t out_pitch,
- uint32_t output_height,
- const int16_t *filter) {
- __m128i filtersReg;
- __m128i srcReg2, srcReg3, srcReg4, srcReg5, srcReg6;
- __m128i srcReg23_lo, srcReg23_hi, srcReg34_lo, srcReg34_hi;
- __m128i srcReg45_lo, srcReg45_hi, srcReg56_lo, srcReg56_hi;
- __m128i resReg23_lo, resReg34_lo, resReg45_lo, resReg56_lo;
- __m128i resReg23_hi, resReg34_hi, resReg45_hi, resReg56_hi;
- __m128i resReg23_45_lo, resReg34_56_lo, resReg23_45_hi, resReg34_56_hi;
- __m128i resReg23_45, resReg34_56;
- __m128i addFilterReg32, secondFilters, thirdFilters;
- __m128i tmp_0, tmp_1;
- unsigned int i;
- ptrdiff_t src_stride, dst_stride;
-
- addFilterReg32 = _mm_set1_epi16(32);
- filtersReg = _mm_loadu_si128((const __m128i *)filter);
- filtersReg = _mm_srai_epi16(filtersReg, 1);
-
- // coeffs 0 1 0 1 2 3 2 3
- const __m128i tmp0 = _mm_unpacklo_epi32(filtersReg, filtersReg);
- // coeffs 4 5 4 5 6 7 6 7
- const __m128i tmp1 = _mm_unpackhi_epi32(filtersReg, filtersReg);
-
- secondFilters = _mm_unpackhi_epi64(tmp0, tmp0); // coeffs 2 3 2 3 2 3 2 3
- thirdFilters = _mm_unpacklo_epi64(tmp1, tmp1); // coeffs 4 5 4 5 4 5 4 5
-
- // multiply the size of the source and destination stride by two
- src_stride = src_pitch << 1;
- dst_stride = out_pitch << 1;
-
- srcReg2 = _mm_loadu_si128((const __m128i *)(src_ptr + src_pitch * 2));
- srcReg3 = _mm_loadu_si128((const __m128i *)(src_ptr + src_pitch * 3));
- srcReg23_lo = _mm_unpacklo_epi8(srcReg2, srcReg3);
- srcReg23_hi = _mm_unpackhi_epi8(srcReg2, srcReg3);
- __m128i resReg23_lo_1 = _mm_unpacklo_epi8(srcReg23_lo, _mm_setzero_si128());
- __m128i resReg23_lo_2 = _mm_unpackhi_epi8(srcReg23_lo, _mm_setzero_si128());
- __m128i resReg23_hi_1 = _mm_unpacklo_epi8(srcReg23_hi, _mm_setzero_si128());
- __m128i resReg23_hi_2 = _mm_unpackhi_epi8(srcReg23_hi, _mm_setzero_si128());
-
- srcReg4 = _mm_loadu_si128((const __m128i *)(src_ptr + src_pitch * 4));
- srcReg34_lo = _mm_unpacklo_epi8(srcReg3, srcReg4);
- srcReg34_hi = _mm_unpackhi_epi8(srcReg3, srcReg4);
- __m128i resReg34_lo_1 = _mm_unpacklo_epi8(srcReg34_lo, _mm_setzero_si128());
- __m128i resReg34_lo_2 = _mm_unpackhi_epi8(srcReg34_lo, _mm_setzero_si128());
- __m128i resReg34_hi_1 = _mm_unpacklo_epi8(srcReg34_hi, _mm_setzero_si128());
- __m128i resReg34_hi_2 = _mm_unpackhi_epi8(srcReg34_hi, _mm_setzero_si128());
-
- for (i = output_height; i > 1; i -= 2) {
- srcReg5 = _mm_loadu_si128((const __m128i *)(src_ptr + src_pitch * 5));
-
- srcReg45_lo = _mm_unpacklo_epi8(srcReg4, srcReg5);
- srcReg45_hi = _mm_unpackhi_epi8(srcReg4, srcReg5);
-
- srcReg6 = _mm_loadu_si128((const __m128i *)(src_ptr + src_pitch * 6));
-
- srcReg56_lo = _mm_unpacklo_epi8(srcReg5, srcReg6);
- srcReg56_hi = _mm_unpackhi_epi8(srcReg5, srcReg6);
-
- // multiply 2 adjacent elements with the filter and add the result
-
- tmp_0 = _mm_madd_epi16(resReg23_lo_1, secondFilters);
- tmp_1 = _mm_madd_epi16(resReg23_lo_2, secondFilters);
- resReg23_lo = _mm_packs_epi32(tmp_0, tmp_1);
-
- tmp_0 = _mm_madd_epi16(resReg34_lo_1, secondFilters);
- tmp_1 = _mm_madd_epi16(resReg34_lo_2, secondFilters);
- resReg34_lo = _mm_packs_epi32(tmp_0, tmp_1);
-
- __m128i resReg45_lo_1 = _mm_unpacklo_epi8(srcReg45_lo, _mm_setzero_si128());
- __m128i resReg45_lo_2 = _mm_unpackhi_epi8(srcReg45_lo, _mm_setzero_si128());
- tmp_0 = _mm_madd_epi16(resReg45_lo_1, thirdFilters);
- tmp_1 = _mm_madd_epi16(resReg45_lo_2, thirdFilters);
- resReg45_lo = _mm_packs_epi32(tmp_0, tmp_1);
-
- __m128i resReg56_lo_1 = _mm_unpacklo_epi8(srcReg56_lo, _mm_setzero_si128());
- __m128i resReg56_lo_2 = _mm_unpackhi_epi8(srcReg56_lo, _mm_setzero_si128());
- tmp_0 = _mm_madd_epi16(resReg56_lo_1, thirdFilters);
- tmp_1 = _mm_madd_epi16(resReg56_lo_2, thirdFilters);
- resReg56_lo = _mm_packs_epi32(tmp_0, tmp_1);
-
- // add and saturate the results together
- resReg23_45_lo = _mm_adds_epi16(resReg23_lo, resReg45_lo);
- resReg34_56_lo = _mm_adds_epi16(resReg34_lo, resReg56_lo);
-
- // multiply 2 adjacent elements with the filter and add the result
-
- tmp_0 = _mm_madd_epi16(resReg23_hi_1, secondFilters);
- tmp_1 = _mm_madd_epi16(resReg23_hi_2, secondFilters);
- resReg23_hi = _mm_packs_epi32(tmp_0, tmp_1);
-
- tmp_0 = _mm_madd_epi16(resReg34_hi_1, secondFilters);
- tmp_1 = _mm_madd_epi16(resReg34_hi_2, secondFilters);
- resReg34_hi = _mm_packs_epi32(tmp_0, tmp_1);
-
- __m128i resReg45_hi_1 = _mm_unpacklo_epi8(srcReg45_hi, _mm_setzero_si128());
- __m128i resReg45_hi_2 = _mm_unpackhi_epi8(srcReg45_hi, _mm_setzero_si128());
- tmp_0 = _mm_madd_epi16(resReg45_hi_1, thirdFilters);
- tmp_1 = _mm_madd_epi16(resReg45_hi_2, thirdFilters);
- resReg45_hi = _mm_packs_epi32(tmp_0, tmp_1);
-
- __m128i resReg56_hi_1 = _mm_unpacklo_epi8(srcReg56_hi, _mm_setzero_si128());
- __m128i resReg56_hi_2 = _mm_unpackhi_epi8(srcReg56_hi, _mm_setzero_si128());
- tmp_0 = _mm_madd_epi16(resReg56_hi_1, thirdFilters);
- tmp_1 = _mm_madd_epi16(resReg56_hi_2, thirdFilters);
- resReg56_hi = _mm_packs_epi32(tmp_0, tmp_1);
-
- // add and saturate the results together
- resReg23_45_hi = _mm_adds_epi16(resReg23_hi, resReg45_hi);
- resReg34_56_hi = _mm_adds_epi16(resReg34_hi, resReg56_hi);
-
- // shift by 6 bit each 16 bit
- resReg23_45_lo = _mm_adds_epi16(resReg23_45_lo, addFilterReg32);
- resReg34_56_lo = _mm_adds_epi16(resReg34_56_lo, addFilterReg32);
- resReg23_45_hi = _mm_adds_epi16(resReg23_45_hi, addFilterReg32);
- resReg34_56_hi = _mm_adds_epi16(resReg34_56_hi, addFilterReg32);
- resReg23_45_lo = _mm_srai_epi16(resReg23_45_lo, 6);
- resReg34_56_lo = _mm_srai_epi16(resReg34_56_lo, 6);
- resReg23_45_hi = _mm_srai_epi16(resReg23_45_hi, 6);
- resReg34_56_hi = _mm_srai_epi16(resReg34_56_hi, 6);
-
- // shrink to 8 bit each 16 bits, the first lane contain the first
- // convolve result and the second lane contain the second convolve
- // result
- resReg23_45 = _mm_packus_epi16(resReg23_45_lo, resReg23_45_hi);
- resReg34_56 = _mm_packus_epi16(resReg34_56_lo, resReg34_56_hi);
-
- src_ptr += src_stride;
-
- _mm_store_si128((__m128i *)output_ptr, (resReg23_45));
- _mm_store_si128((__m128i *)(output_ptr + out_pitch), (resReg34_56));
-
- output_ptr += dst_stride;
-
- // save part of the registers for next strides
- resReg23_lo_1 = resReg45_lo_1;
- resReg23_lo_2 = resReg45_lo_2;
- resReg23_hi_1 = resReg45_hi_1;
- resReg23_hi_2 = resReg45_hi_2;
- resReg34_lo_1 = resReg56_lo_1;
- resReg34_lo_2 = resReg56_lo_2;
- resReg34_hi_1 = resReg56_hi_1;
- resReg34_hi_2 = resReg56_hi_2;
- srcReg4 = srcReg6;
- }
-}
-
-void aom_filter_block1d8_h4_sse2(const uint8_t *src_ptr,
- ptrdiff_t src_pixels_per_line,
- uint8_t *output_ptr, ptrdiff_t output_pitch,
- uint32_t output_height,
- const int16_t *filter) {
- __m128i filtersReg;
- __m128i addFilterReg32;
- __m128i secondFilters, thirdFilters;
- __m128i srcRegFilt32b1_1, srcRegFilt32b1_2;
- __m128i srcReg32b1;
- unsigned int i;
- src_ptr -= 3;
- addFilterReg32 = _mm_set1_epi16(32);
- filtersReg = _mm_loadu_si128((const __m128i *)filter);
- filtersReg = _mm_srai_epi16(filtersReg, 1);
-
- // coeffs 0 1 0 1 2 3 2 3
- const __m128i tmp_0 = _mm_unpacklo_epi32(filtersReg, filtersReg);
- // coeffs 4 5 4 5 6 7 6 7
- const __m128i tmp_1 = _mm_unpackhi_epi32(filtersReg, filtersReg);
-
- secondFilters = _mm_unpackhi_epi64(tmp_0, tmp_0); // coeffs 2 3 2 3 2 3 2 3
- thirdFilters = _mm_unpacklo_epi64(tmp_1, tmp_1); // coeffs 4 5 4 5 4 5 4 5
-
- for (i = output_height; i > 0; i -= 1) {
- srcReg32b1 = _mm_loadu_si128((const __m128i *)src_ptr);
-
- __m128i ss_2 = _mm_srli_si128(srcReg32b1, 2);
- __m128i ss_4 = _mm_srli_si128(srcReg32b1, 4);
- ss_2 = _mm_unpacklo_epi8(ss_2, _mm_setzero_si128());
- ss_4 = _mm_unpacklo_epi8(ss_4, _mm_setzero_si128());
- __m128i d1 = _mm_madd_epi16(ss_2, secondFilters);
- __m128i d2 = _mm_madd_epi16(ss_4, thirdFilters);
- srcRegFilt32b1_1 = _mm_add_epi32(d1, d2);
-
- __m128i ss_3 = _mm_srli_si128(srcReg32b1, 3);
- __m128i ss_5 = _mm_srli_si128(srcReg32b1, 5);
- ss_3 = _mm_unpacklo_epi8(ss_3, _mm_setzero_si128());
- ss_5 = _mm_unpacklo_epi8(ss_5, _mm_setzero_si128());
- d1 = _mm_madd_epi16(ss_3, secondFilters);
- d2 = _mm_madd_epi16(ss_5, thirdFilters);
- srcRegFilt32b1_2 = _mm_add_epi32(d1, d2);
-
- __m128i res_lo = _mm_unpacklo_epi32(srcRegFilt32b1_1, srcRegFilt32b1_2);
- __m128i res_hi = _mm_unpackhi_epi32(srcRegFilt32b1_1, srcRegFilt32b1_2);
- srcRegFilt32b1_1 = _mm_packs_epi32(res_lo, res_hi);
-
- // shift by 6 bit each 16 bit
- srcRegFilt32b1_1 = _mm_adds_epi16(srcRegFilt32b1_1, addFilterReg32);
- srcRegFilt32b1_1 = _mm_srai_epi16(srcRegFilt32b1_1, 6);
-
- // shrink to 8 bit each 16 bits, the first lane contain the first
- // convolve result and the second lane contain the second convolve result
- srcRegFilt32b1_1 = _mm_packus_epi16(srcRegFilt32b1_1, _mm_setzero_si128());
-
- src_ptr += src_pixels_per_line;
-
- _mm_storel_epi64((__m128i *)output_ptr, srcRegFilt32b1_1);
-
- output_ptr += output_pitch;
- }
-}
-
-void aom_filter_block1d8_v4_sse2(const uint8_t *src_ptr, ptrdiff_t src_pitch,
- uint8_t *output_ptr, ptrdiff_t out_pitch,
- uint32_t output_height,
- const int16_t *filter) {
- __m128i filtersReg;
- __m128i srcReg2, srcReg3, srcReg4, srcReg5, srcReg6;
- __m128i srcReg23_lo, srcReg34_lo;
- __m128i srcReg45_lo, srcReg56_lo;
- __m128i resReg23_lo, resReg34_lo, resReg45_lo, resReg56_lo;
- __m128i resReg23_45_lo, resReg34_56_lo;
- __m128i resReg23_45, resReg34_56;
- __m128i addFilterReg32, secondFilters, thirdFilters;
- __m128i tmp_0, tmp_1;
- unsigned int i;
- ptrdiff_t src_stride, dst_stride;
-
- addFilterReg32 = _mm_set1_epi16(32);
- filtersReg = _mm_loadu_si128((const __m128i *)filter);
- filtersReg = _mm_srai_epi16(filtersReg, 1);
-
- // coeffs 0 1 0 1 2 3 2 3
- const __m128i tmp0 = _mm_unpacklo_epi32(filtersReg, filtersReg);
- // coeffs 4 5 4 5 6 7 6 7
- const __m128i tmp1 = _mm_unpackhi_epi32(filtersReg, filtersReg);
-
- secondFilters = _mm_unpackhi_epi64(tmp0, tmp0); // coeffs 2 3 2 3 2 3 2 3
- thirdFilters = _mm_unpacklo_epi64(tmp1, tmp1); // coeffs 4 5 4 5 4 5 4 5
-
- // multiply the size of the source and destination stride by two
- src_stride = src_pitch << 1;
- dst_stride = out_pitch << 1;
-
- srcReg2 = _mm_loadu_si128((const __m128i *)(src_ptr + src_pitch * 2));
- srcReg3 = _mm_loadu_si128((const __m128i *)(src_ptr + src_pitch * 3));
- srcReg23_lo = _mm_unpacklo_epi8(srcReg2, srcReg3);
- __m128i resReg23_lo_1 = _mm_unpacklo_epi8(srcReg23_lo, _mm_setzero_si128());
- __m128i resReg23_lo_2 = _mm_unpackhi_epi8(srcReg23_lo, _mm_setzero_si128());
-
- srcReg4 = _mm_loadu_si128((const __m128i *)(src_ptr + src_pitch * 4));
- srcReg34_lo = _mm_unpacklo_epi8(srcReg3, srcReg4);
- __m128i resReg34_lo_1 = _mm_unpacklo_epi8(srcReg34_lo, _mm_setzero_si128());
- __m128i resReg34_lo_2 = _mm_unpackhi_epi8(srcReg34_lo, _mm_setzero_si128());
-
- for (i = output_height; i > 1; i -= 2) {
- srcReg5 = _mm_loadu_si128((const __m128i *)(src_ptr + src_pitch * 5));
- srcReg45_lo = _mm_unpacklo_epi8(srcReg4, srcReg5);
-
- srcReg6 = _mm_loadu_si128((const __m128i *)(src_ptr + src_pitch * 6));
- srcReg56_lo = _mm_unpacklo_epi8(srcReg5, srcReg6);
-
- // multiply 2 adjacent elements with the filter and add the result
-
- tmp_0 = _mm_madd_epi16(resReg23_lo_1, secondFilters);
- tmp_1 = _mm_madd_epi16(resReg23_lo_2, secondFilters);
- resReg23_lo = _mm_packs_epi32(tmp_0, tmp_1);
-
- tmp_0 = _mm_madd_epi16(resReg34_lo_1, secondFilters);
- tmp_1 = _mm_madd_epi16(resReg34_lo_2, secondFilters);
- resReg34_lo = _mm_packs_epi32(tmp_0, tmp_1);
-
- __m128i resReg45_lo_1 = _mm_unpacklo_epi8(srcReg45_lo, _mm_setzero_si128());
- __m128i resReg45_lo_2 = _mm_unpackhi_epi8(srcReg45_lo, _mm_setzero_si128());
- tmp_0 = _mm_madd_epi16(resReg45_lo_1, thirdFilters);
- tmp_1 = _mm_madd_epi16(resReg45_lo_2, thirdFilters);
- resReg45_lo = _mm_packs_epi32(tmp_0, tmp_1);
-
- __m128i resReg56_lo_1 = _mm_unpacklo_epi8(srcReg56_lo, _mm_setzero_si128());
- __m128i resReg56_lo_2 = _mm_unpackhi_epi8(srcReg56_lo, _mm_setzero_si128());
- tmp_0 = _mm_madd_epi16(resReg56_lo_1, thirdFilters);
- tmp_1 = _mm_madd_epi16(resReg56_lo_2, thirdFilters);
- resReg56_lo = _mm_packs_epi32(tmp_0, tmp_1);
-
- // add and saturate the results together
- resReg23_45_lo = _mm_adds_epi16(resReg23_lo, resReg45_lo);
- resReg34_56_lo = _mm_adds_epi16(resReg34_lo, resReg56_lo);
-
- // shift by 6 bit each 16 bit
- resReg23_45_lo = _mm_adds_epi16(resReg23_45_lo, addFilterReg32);
- resReg34_56_lo = _mm_adds_epi16(resReg34_56_lo, addFilterReg32);
- resReg23_45_lo = _mm_srai_epi16(resReg23_45_lo, 6);
- resReg34_56_lo = _mm_srai_epi16(resReg34_56_lo, 6);
-
- // shrink to 8 bit each 16 bits, the first lane contain the first
- // convolve result and the second lane contain the second convolve
- // result
- resReg23_45 = _mm_packus_epi16(resReg23_45_lo, _mm_setzero_si128());
- resReg34_56 = _mm_packus_epi16(resReg34_56_lo, _mm_setzero_si128());
-
- src_ptr += src_stride;
-
- _mm_storel_epi64((__m128i *)output_ptr, (resReg23_45));
- _mm_storel_epi64((__m128i *)(output_ptr + out_pitch), (resReg34_56));
-
- output_ptr += dst_stride;
-
- // save part of the registers for next strides
- resReg23_lo_1 = resReg45_lo_1;
- resReg23_lo_2 = resReg45_lo_2;
- resReg34_lo_1 = resReg56_lo_1;
- resReg34_lo_2 = resReg56_lo_2;
- srcReg4 = srcReg6;
- }
-}
-
-void aom_filter_block1d4_h4_sse2(const uint8_t *src_ptr,
- ptrdiff_t src_pixels_per_line,
- uint8_t *output_ptr, ptrdiff_t output_pitch,
- uint32_t output_height,
- const int16_t *filter) {
- __m128i filtersReg;
- __m128i addFilterReg32;
- __m128i secondFilters, thirdFilters;
- __m128i srcRegFilt32b1_1;
- __m128i srcReg32b1;
- unsigned int i;
- src_ptr -= 3;
- addFilterReg32 = _mm_set1_epi16(32);
- filtersReg = _mm_loadu_si128((const __m128i *)filter);
- filtersReg = _mm_srai_epi16(filtersReg, 1);
-
- // coeffs 0 1 0 1 2 3 2 3
- const __m128i tmp_0 = _mm_unpacklo_epi32(filtersReg, filtersReg);
- // coeffs 4 5 4 5 6 7 6 7
- const __m128i tmp_1 = _mm_unpackhi_epi32(filtersReg, filtersReg);
-
- secondFilters = _mm_unpackhi_epi64(tmp_0, tmp_0); // coeffs 2 3 2 3 2 3 2 3
- thirdFilters = _mm_unpacklo_epi64(tmp_1, tmp_1); // coeffs 4 5 4 5 4 5 4 5
-
- for (i = output_height; i > 0; i -= 1) {
- srcReg32b1 = _mm_loadu_si128((const __m128i *)src_ptr);
-
- __m128i ss_2 = _mm_srli_si128(srcReg32b1, 2);
- __m128i ss_3 = _mm_srli_si128(srcReg32b1, 3);
- __m128i ss_4 = _mm_srli_si128(srcReg32b1, 4);
- __m128i ss_5 = _mm_srli_si128(srcReg32b1, 5);
-
- ss_2 = _mm_unpacklo_epi8(ss_2, _mm_setzero_si128());
- ss_3 = _mm_unpacklo_epi8(ss_3, _mm_setzero_si128());
- ss_4 = _mm_unpacklo_epi8(ss_4, _mm_setzero_si128());
- ss_5 = _mm_unpacklo_epi8(ss_5, _mm_setzero_si128());
-
- __m128i ss_1_1 = _mm_unpacklo_epi32(ss_2, ss_3);
- __m128i ss_1_2 = _mm_unpacklo_epi32(ss_4, ss_5);
-
- __m128i d1 = _mm_madd_epi16(ss_1_1, secondFilters);
- __m128i d2 = _mm_madd_epi16(ss_1_2, thirdFilters);
- srcRegFilt32b1_1 = _mm_add_epi32(d1, d2);
-
- srcRegFilt32b1_1 = _mm_packs_epi32(srcRegFilt32b1_1, _mm_setzero_si128());
-
- // shift by 6 bit each 16 bit
- srcRegFilt32b1_1 = _mm_adds_epi16(srcRegFilt32b1_1, addFilterReg32);
- srcRegFilt32b1_1 = _mm_srai_epi16(srcRegFilt32b1_1, 6);
-
- // shrink to 8 bit each 16 bits, the first lane contain the first
- // convolve result and the second lane contain the second convolve result
- srcRegFilt32b1_1 = _mm_packus_epi16(srcRegFilt32b1_1, _mm_setzero_si128());
-
- src_ptr += src_pixels_per_line;
-
- *((int *)(output_ptr)) = _mm_cvtsi128_si32(srcRegFilt32b1_1);
-
- output_ptr += output_pitch;
- }
-}
-
-void aom_filter_block1d4_v4_sse2(const uint8_t *src_ptr, ptrdiff_t src_pitch,
- uint8_t *output_ptr, ptrdiff_t out_pitch,
- uint32_t output_height,
- const int16_t *filter) {
- __m128i filtersReg;
- __m128i srcReg2, srcReg3, srcReg4, srcReg5, srcReg6;
- __m128i srcReg23, srcReg34, srcReg45, srcReg56;
- __m128i resReg23_34, resReg45_56;
- __m128i resReg23_34_45_56;
- __m128i addFilterReg32, secondFilters, thirdFilters;
- __m128i tmp_0, tmp_1;
- unsigned int i;
- ptrdiff_t src_stride, dst_stride;
-
- addFilterReg32 = _mm_set1_epi16(32);
- filtersReg = _mm_loadu_si128((const __m128i *)filter);
- filtersReg = _mm_srai_epi16(filtersReg, 1);
-
- // coeffs 0 1 0 1 2 3 2 3
- const __m128i tmp0 = _mm_unpacklo_epi32(filtersReg, filtersReg);
- // coeffs 4 5 4 5 6 7 6 7
- const __m128i tmp1 = _mm_unpackhi_epi32(filtersReg, filtersReg);
-
- secondFilters = _mm_unpackhi_epi64(tmp0, tmp0); // coeffs 2 3 2 3 2 3 2 3
- thirdFilters = _mm_unpacklo_epi64(tmp1, tmp1); // coeffs 4 5 4 5 4 5 4 5
-
- // multiply the size of the source and destination stride by two
- src_stride = src_pitch << 1;
- dst_stride = out_pitch << 1;
-
- srcReg2 = _mm_loadl_epi64((const __m128i *)(src_ptr + src_pitch * 2));
- srcReg3 = _mm_loadl_epi64((const __m128i *)(src_ptr + src_pitch * 3));
- srcReg23 = _mm_unpacklo_epi8(srcReg2, srcReg3);
- __m128i resReg23 = _mm_unpacklo_epi8(srcReg23, _mm_setzero_si128());
-
- srcReg4 = _mm_loadl_epi64((const __m128i *)(src_ptr + src_pitch * 4));
- srcReg34 = _mm_unpacklo_epi8(srcReg3, srcReg4);
- __m128i resReg34 = _mm_unpacklo_epi8(srcReg34, _mm_setzero_si128());
-
- for (i = output_height; i > 1; i -= 2) {
- srcReg5 = _mm_loadl_epi64((const __m128i *)(src_ptr + src_pitch * 5));
- srcReg45 = _mm_unpacklo_epi8(srcReg4, srcReg5);
- srcReg6 = _mm_loadl_epi64((const __m128i *)(src_ptr + src_pitch * 6));
- srcReg56 = _mm_unpacklo_epi8(srcReg5, srcReg6);
-
- // multiply 2 adjacent elements with the filter and add the result
- tmp_0 = _mm_madd_epi16(resReg23, secondFilters);
- tmp_1 = _mm_madd_epi16(resReg34, secondFilters);
- resReg23_34 = _mm_packs_epi32(tmp_0, tmp_1);
-
- __m128i resReg45 = _mm_unpacklo_epi8(srcReg45, _mm_setzero_si128());
- __m128i resReg56 = _mm_unpacklo_epi8(srcReg56, _mm_setzero_si128());
-
- tmp_0 = _mm_madd_epi16(resReg45, thirdFilters);
- tmp_1 = _mm_madd_epi16(resReg56, thirdFilters);
- resReg45_56 = _mm_packs_epi32(tmp_0, tmp_1);
-
- // add and saturate the results together
- resReg23_34_45_56 = _mm_adds_epi16(resReg23_34, resReg45_56);
-
- // shift by 6 bit each 16 bit
- resReg23_34_45_56 = _mm_adds_epi16(resReg23_34_45_56, addFilterReg32);
- resReg23_34_45_56 = _mm_srai_epi16(resReg23_34_45_56, 6);
-
- // shrink to 8 bit each 16 bits, the first lane contain the first
- // convolve result and the second lane contain the second convolve
- // result
- resReg23_34_45_56 =
- _mm_packus_epi16(resReg23_34_45_56, _mm_setzero_si128());
-
- src_ptr += src_stride;
-
- *((int *)(output_ptr)) = _mm_cvtsi128_si32(resReg23_34_45_56);
- *((int *)(output_ptr + out_pitch)) =
- _mm_cvtsi128_si32(_mm_srli_si128(resReg23_34_45_56, 4));
-
- output_ptr += dst_stride;
-
- // save part of the registers for next strides
- resReg23 = resReg45;
- resReg34 = resReg56;
- srcReg4 = srcReg6;
- }
-}
diff --git a/third_party/aom/aom_dsp/x86/aom_subpixel_8t_sse2.asm b/third_party/aom/aom_dsp/x86/aom_subpixel_8t_sse2.asm
deleted file mode 100644
index 640c5b2416..0000000000
--- a/third_party/aom/aom_dsp/x86/aom_subpixel_8t_sse2.asm
+++ /dev/null
@@ -1,615 +0,0 @@
-;
-; Copyright (c) 2016, Alliance for Open Media. All rights reserved
-;
-; This source code is subject to the terms of the BSD 2 Clause License and
-; the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
-; was not distributed with this source code in the LICENSE file, you can
-; obtain it at www.aomedia.org/license/software. If the Alliance for Open
-; Media Patent License 1.0 was not distributed with this source code in the
-; PATENTS file, you can obtain it at www.aomedia.org/license/patent.
-;
-
-;
-
-
-%include "aom_ports/x86_abi_support.asm"
-
-;Note: tap3 and tap4 have to be applied and added after other taps to avoid
-;overflow.
-
-%macro GET_FILTERS_4 0
- mov rdx, arg(5) ;filter ptr
- mov rcx, 0x0400040
-
- movdqa xmm7, [rdx] ;load filters
- pshuflw xmm0, xmm7, 0b ;k0
- pshuflw xmm1, xmm7, 01010101b ;k1
- pshuflw xmm2, xmm7, 10101010b ;k2
- pshuflw xmm3, xmm7, 11111111b ;k3
- psrldq xmm7, 8
- pshuflw xmm4, xmm7, 0b ;k4
- pshuflw xmm5, xmm7, 01010101b ;k5
- pshuflw xmm6, xmm7, 10101010b ;k6
- pshuflw xmm7, xmm7, 11111111b ;k7
-
- punpcklqdq xmm0, xmm1
- punpcklqdq xmm2, xmm3
- punpcklqdq xmm5, xmm4
- punpcklqdq xmm6, xmm7
-
- movdqa k0k1, xmm0
- movdqa k2k3, xmm2
- movdqa k5k4, xmm5
- movdqa k6k7, xmm6
-
- movq xmm6, rcx
- pshufd xmm6, xmm6, 0
- movdqa krd, xmm6
-
- pxor xmm7, xmm7
- movdqa zero, xmm7
-%endm
-
-%macro APPLY_FILTER_4 1
- punpckldq xmm0, xmm1 ;two row in one register
- punpckldq xmm6, xmm7
- punpckldq xmm2, xmm3
- punpckldq xmm5, xmm4
-
- punpcklbw xmm0, zero ;unpack to word
- punpcklbw xmm6, zero
- punpcklbw xmm2, zero
- punpcklbw xmm5, zero
-
- pmullw xmm0, k0k1 ;multiply the filter factors
- pmullw xmm6, k6k7
- pmullw xmm2, k2k3
- pmullw xmm5, k5k4
-
- paddsw xmm0, xmm6 ;sum
- movdqa xmm1, xmm0
- psrldq xmm1, 8
- paddsw xmm0, xmm1
- paddsw xmm0, xmm2
- psrldq xmm2, 8
- paddsw xmm0, xmm5
- psrldq xmm5, 8
- paddsw xmm0, xmm2
- paddsw xmm0, xmm5
-
- paddsw xmm0, krd ;rounding
- psraw xmm0, 7 ;shift
- packuswb xmm0, xmm0 ;pack to byte
-
-%if %1
- movd xmm1, [rdi]
- pavgb xmm0, xmm1
-%endif
- movd [rdi], xmm0
-%endm
-
-%macro GET_FILTERS 0
- mov rdx, arg(5) ;filter ptr
- mov rsi, arg(0) ;src_ptr
- mov rdi, arg(2) ;output_ptr
- mov rcx, 0x0400040
-
- movdqa xmm7, [rdx] ;load filters
- pshuflw xmm0, xmm7, 0b ;k0
- pshuflw xmm1, xmm7, 01010101b ;k1
- pshuflw xmm2, xmm7, 10101010b ;k2
- pshuflw xmm3, xmm7, 11111111b ;k3
- pshufhw xmm4, xmm7, 0b ;k4
- pshufhw xmm5, xmm7, 01010101b ;k5
- pshufhw xmm6, xmm7, 10101010b ;k6
- pshufhw xmm7, xmm7, 11111111b ;k7
-
- punpcklwd xmm0, xmm0
- punpcklwd xmm1, xmm1
- punpcklwd xmm2, xmm2
- punpcklwd xmm3, xmm3
- punpckhwd xmm4, xmm4
- punpckhwd xmm5, xmm5
- punpckhwd xmm6, xmm6
- punpckhwd xmm7, xmm7
-
- movdqa k0, xmm0 ;store filter factors on stack
- movdqa k1, xmm1
- movdqa k2, xmm2
- movdqa k3, xmm3
- movdqa k4, xmm4
- movdqa k5, xmm5
- movdqa k6, xmm6
- movdqa k7, xmm7
-
- movq xmm6, rcx
- pshufd xmm6, xmm6, 0
- movdqa krd, xmm6 ;rounding
-
- pxor xmm7, xmm7
- movdqa zero, xmm7
-%endm
-
-%macro LOAD_VERT_8 1
- movq xmm0, [rsi + %1] ;0
- movq xmm1, [rsi + rax + %1] ;1
- movq xmm6, [rsi + rdx * 2 + %1] ;6
- lea rsi, [rsi + rax]
- movq xmm7, [rsi + rdx * 2 + %1] ;7
- movq xmm2, [rsi + rax + %1] ;2
- movq xmm3, [rsi + rax * 2 + %1] ;3
- movq xmm4, [rsi + rdx + %1] ;4
- movq xmm5, [rsi + rax * 4 + %1] ;5
-%endm
-
-%macro APPLY_FILTER_8 2
- punpcklbw xmm0, zero
- punpcklbw xmm1, zero
- punpcklbw xmm6, zero
- punpcklbw xmm7, zero
- punpcklbw xmm2, zero
- punpcklbw xmm5, zero
- punpcklbw xmm3, zero
- punpcklbw xmm4, zero
-
- pmullw xmm0, k0
- pmullw xmm1, k1
- pmullw xmm6, k6
- pmullw xmm7, k7
- pmullw xmm2, k2
- pmullw xmm5, k5
- pmullw xmm3, k3
- pmullw xmm4, k4
-
- paddsw xmm0, xmm1
- paddsw xmm0, xmm6
- paddsw xmm0, xmm7
- paddsw xmm0, xmm2
- paddsw xmm0, xmm5
- paddsw xmm0, xmm3
- paddsw xmm0, xmm4
-
- paddsw xmm0, krd ;rounding
- psraw xmm0, 7 ;shift
- packuswb xmm0, xmm0 ;pack back to byte
-%if %1
- movq xmm1, [rdi + %2]
- pavgb xmm0, xmm1
-%endif
- movq [rdi + %2], xmm0
-%endm
-
-SECTION .text
-
-;void aom_filter_block1d4_v8_sse2
-;(
-; unsigned char *src_ptr,
-; unsigned int src_pitch,
-; unsigned char *output_ptr,
-; unsigned int out_pitch,
-; unsigned int output_height,
-; short *filter
-;)
-globalsym(aom_filter_block1d4_v8_sse2)
-sym(aom_filter_block1d4_v8_sse2):
- push rbp
- mov rbp, rsp
- SHADOW_ARGS_TO_STACK 6
- SAVE_XMM 7
- push rsi
- push rdi
- push rbx
- ; end prolog
-
- ALIGN_STACK 16, rax
- sub rsp, 16 * 6
- %define k0k1 [rsp + 16 * 0]
- %define k2k3 [rsp + 16 * 1]
- %define k5k4 [rsp + 16 * 2]
- %define k6k7 [rsp + 16 * 3]
- %define krd [rsp + 16 * 4]
- %define zero [rsp + 16 * 5]
-
- GET_FILTERS_4
-
- mov rsi, arg(0) ;src_ptr
- mov rdi, arg(2) ;output_ptr
-
- movsxd rax, DWORD PTR arg(1) ;pixels_per_line
- movsxd rbx, DWORD PTR arg(3) ;out_pitch
- lea rdx, [rax + rax * 2]
- movsxd rcx, DWORD PTR arg(4) ;output_height
-
-.loop:
- movd xmm0, [rsi] ;load src: row 0
- movd xmm1, [rsi + rax] ;1
- movd xmm6, [rsi + rdx * 2] ;6
- lea rsi, [rsi + rax]
- movd xmm7, [rsi + rdx * 2] ;7
- movd xmm2, [rsi + rax] ;2
- movd xmm3, [rsi + rax * 2] ;3
- movd xmm4, [rsi + rdx] ;4
- movd xmm5, [rsi + rax * 4] ;5
-
- APPLY_FILTER_4 0
-
- lea rdi, [rdi + rbx]
- dec rcx
- jnz .loop
-
- add rsp, 16 * 6
- pop rsp
- pop rbx
- ; begin epilog
- pop rdi
- pop rsi
- RESTORE_XMM
- UNSHADOW_ARGS
- pop rbp
- ret
-
-;void aom_filter_block1d8_v8_sse2
-;(
-; unsigned char *src_ptr,
-; unsigned int src_pitch,
-; unsigned char *output_ptr,
-; unsigned int out_pitch,
-; unsigned int output_height,
-; short *filter
-;)
-globalsym(aom_filter_block1d8_v8_sse2)
-sym(aom_filter_block1d8_v8_sse2):
- push rbp
- mov rbp, rsp
- SHADOW_ARGS_TO_STACK 6
- SAVE_XMM 7
- push rsi
- push rdi
- push rbx
- ; end prolog
-
- ALIGN_STACK 16, rax
- sub rsp, 16 * 10
- %define k0 [rsp + 16 * 0]
- %define k1 [rsp + 16 * 1]
- %define k2 [rsp + 16 * 2]
- %define k3 [rsp + 16 * 3]
- %define k4 [rsp + 16 * 4]
- %define k5 [rsp + 16 * 5]
- %define k6 [rsp + 16 * 6]
- %define k7 [rsp + 16 * 7]
- %define krd [rsp + 16 * 8]
- %define zero [rsp + 16 * 9]
-
- GET_FILTERS
-
- movsxd rax, DWORD PTR arg(1) ;pixels_per_line
- movsxd rbx, DWORD PTR arg(3) ;out_pitch
- lea rdx, [rax + rax * 2]
- movsxd rcx, DWORD PTR arg(4) ;output_height
-
-.loop:
- LOAD_VERT_8 0
- APPLY_FILTER_8 0, 0
-
- lea rdi, [rdi + rbx]
- dec rcx
- jnz .loop
-
- add rsp, 16 * 10
- pop rsp
- pop rbx
- ; begin epilog
- pop rdi
- pop rsi
- RESTORE_XMM
- UNSHADOW_ARGS
- pop rbp
- ret
-
-;void aom_filter_block1d16_v8_sse2
-;(
-; unsigned char *src_ptr,
-; unsigned int src_pitch,
-; unsigned char *output_ptr,
-; unsigned int out_pitch,
-; unsigned int output_height,
-; short *filter
-;)
-globalsym(aom_filter_block1d16_v8_sse2)
-sym(aom_filter_block1d16_v8_sse2):
- push rbp
- mov rbp, rsp
- SHADOW_ARGS_TO_STACK 6
- SAVE_XMM 7
- push rsi
- push rdi
- push rbx
- ; end prolog
-
- ALIGN_STACK 16, rax
- sub rsp, 16 * 10
- %define k0 [rsp + 16 * 0]
- %define k1 [rsp + 16 * 1]
- %define k2 [rsp + 16 * 2]
- %define k3 [rsp + 16 * 3]
- %define k4 [rsp + 16 * 4]
- %define k5 [rsp + 16 * 5]
- %define k6 [rsp + 16 * 6]
- %define k7 [rsp + 16 * 7]
- %define krd [rsp + 16 * 8]
- %define zero [rsp + 16 * 9]
-
- GET_FILTERS
-
- movsxd rax, DWORD PTR arg(1) ;pixels_per_line
- movsxd rbx, DWORD PTR arg(3) ;out_pitch
- lea rdx, [rax + rax * 2]
- movsxd rcx, DWORD PTR arg(4) ;output_height
-
-.loop:
- LOAD_VERT_8 0
- APPLY_FILTER_8 0, 0
- sub rsi, rax
-
- LOAD_VERT_8 8
- APPLY_FILTER_8 0, 8
- add rdi, rbx
-
- dec rcx
- jnz .loop
-
- add rsp, 16 * 10
- pop rsp
- pop rbx
- ; begin epilog
- pop rdi
- pop rsi
- RESTORE_XMM
- UNSHADOW_ARGS
- pop rbp
- ret
-
-;void aom_filter_block1d4_h8_sse2
-;(
-; unsigned char *src_ptr,
-; unsigned int src_pixels_per_line,
-; unsigned char *output_ptr,
-; unsigned int output_pitch,
-; unsigned int output_height,
-; short *filter
-;)
-globalsym(aom_filter_block1d4_h8_sse2)
-sym(aom_filter_block1d4_h8_sse2):
- push rbp
- mov rbp, rsp
- SHADOW_ARGS_TO_STACK 6
- SAVE_XMM 7
- push rsi
- push rdi
- ; end prolog
-
- ALIGN_STACK 16, rax
- sub rsp, 16 * 6
- %define k0k1 [rsp + 16 * 0]
- %define k2k3 [rsp + 16 * 1]
- %define k5k4 [rsp + 16 * 2]
- %define k6k7 [rsp + 16 * 3]
- %define krd [rsp + 16 * 4]
- %define zero [rsp + 16 * 5]
-
- GET_FILTERS_4
-
- mov rsi, arg(0) ;src_ptr
- mov rdi, arg(2) ;output_ptr
-
- movsxd rax, DWORD PTR arg(1) ;pixels_per_line
- movsxd rdx, DWORD PTR arg(3) ;out_pitch
- movsxd rcx, DWORD PTR arg(4) ;output_height
-
-.loop:
- movdqu xmm0, [rsi - 3] ;load src
-
- movdqa xmm1, xmm0
- movdqa xmm6, xmm0
- movdqa xmm7, xmm0
- movdqa xmm2, xmm0
- movdqa xmm3, xmm0
- movdqa xmm5, xmm0
- movdqa xmm4, xmm0
-
- psrldq xmm1, 1
- psrldq xmm6, 6
- psrldq xmm7, 7
- psrldq xmm2, 2
- psrldq xmm3, 3
- psrldq xmm5, 5
- psrldq xmm4, 4
-
- APPLY_FILTER_4 0
-
- lea rsi, [rsi + rax]
- lea rdi, [rdi + rdx]
- dec rcx
- jnz .loop
-
- add rsp, 16 * 6
- pop rsp
-
- ; begin epilog
- pop rdi
- pop rsi
- RESTORE_XMM
- UNSHADOW_ARGS
- pop rbp
- ret
-
-;void aom_filter_block1d8_h8_sse2
-;(
-; unsigned char *src_ptr,
-; unsigned int src_pixels_per_line,
-; unsigned char *output_ptr,
-; unsigned int output_pitch,
-; unsigned int output_height,
-; short *filter
-;)
-globalsym(aom_filter_block1d8_h8_sse2)
-sym(aom_filter_block1d8_h8_sse2):
- push rbp
- mov rbp, rsp
- SHADOW_ARGS_TO_STACK 6
- SAVE_XMM 7
- push rsi
- push rdi
- ; end prolog
-
- ALIGN_STACK 16, rax
- sub rsp, 16 * 10
- %define k0 [rsp + 16 * 0]
- %define k1 [rsp + 16 * 1]
- %define k2 [rsp + 16 * 2]
- %define k3 [rsp + 16 * 3]
- %define k4 [rsp + 16 * 4]
- %define k5 [rsp + 16 * 5]
- %define k6 [rsp + 16 * 6]
- %define k7 [rsp + 16 * 7]
- %define krd [rsp + 16 * 8]
- %define zero [rsp + 16 * 9]
-
- GET_FILTERS
-
- movsxd rax, DWORD PTR arg(1) ;pixels_per_line
- movsxd rdx, DWORD PTR arg(3) ;out_pitch
- movsxd rcx, DWORD PTR arg(4) ;output_height
-
-.loop:
- movdqu xmm0, [rsi - 3] ;load src
-
- movdqa xmm1, xmm0
- movdqa xmm6, xmm0
- movdqa xmm7, xmm0
- movdqa xmm2, xmm0
- movdqa xmm5, xmm0
- movdqa xmm3, xmm0
- movdqa xmm4, xmm0
-
- psrldq xmm1, 1
- psrldq xmm6, 6
- psrldq xmm7, 7
- psrldq xmm2, 2
- psrldq xmm5, 5
- psrldq xmm3, 3
- psrldq xmm4, 4
-
- APPLY_FILTER_8 0, 0
-
- lea rsi, [rsi + rax]
- lea rdi, [rdi + rdx]
- dec rcx
- jnz .loop
-
- add rsp, 16 * 10
- pop rsp
-
- ; begin epilog
- pop rdi
- pop rsi
- RESTORE_XMM
- UNSHADOW_ARGS
- pop rbp
- ret
-
-;void aom_filter_block1d16_h8_sse2
-;(
-; unsigned char *src_ptr,
-; unsigned int src_pixels_per_line,
-; unsigned char *output_ptr,
-; unsigned int output_pitch,
-; unsigned int output_height,
-; short *filter
-;)
-globalsym(aom_filter_block1d16_h8_sse2)
-sym(aom_filter_block1d16_h8_sse2):
- push rbp
- mov rbp, rsp
- SHADOW_ARGS_TO_STACK 6
- SAVE_XMM 7
- push rsi
- push rdi
- ; end prolog
-
- ALIGN_STACK 16, rax
- sub rsp, 16 * 10
- %define k0 [rsp + 16 * 0]
- %define k1 [rsp + 16 * 1]
- %define k2 [rsp + 16 * 2]
- %define k3 [rsp + 16 * 3]
- %define k4 [rsp + 16 * 4]
- %define k5 [rsp + 16 * 5]
- %define k6 [rsp + 16 * 6]
- %define k7 [rsp + 16 * 7]
- %define krd [rsp + 16 * 8]
- %define zero [rsp + 16 * 9]
-
- GET_FILTERS
-
- movsxd rax, DWORD PTR arg(1) ;pixels_per_line
- movsxd rdx, DWORD PTR arg(3) ;out_pitch
- movsxd rcx, DWORD PTR arg(4) ;output_height
-
-.loop:
- movdqu xmm0, [rsi - 3] ;load src
-
- movdqa xmm1, xmm0
- movdqa xmm6, xmm0
- movdqa xmm7, xmm0
- movdqa xmm2, xmm0
- movdqa xmm5, xmm0
- movdqa xmm3, xmm0
- movdqa xmm4, xmm0
-
- psrldq xmm1, 1
- psrldq xmm6, 6
- psrldq xmm7, 7
- psrldq xmm2, 2
- psrldq xmm5, 5
- psrldq xmm3, 3
- psrldq xmm4, 4
-
- APPLY_FILTER_8 0, 0
-
- movdqu xmm0, [rsi + 5] ;load src
-
- movdqa xmm1, xmm0
- movdqa xmm6, xmm0
- movdqa xmm7, xmm0
- movdqa xmm2, xmm0
- movdqa xmm5, xmm0
- movdqa xmm3, xmm0
- movdqa xmm4, xmm0
-
- psrldq xmm1, 1
- psrldq xmm6, 6
- psrldq xmm7, 7
- psrldq xmm2, 2
- psrldq xmm5, 5
- psrldq xmm3, 3
- psrldq xmm4, 4
-
- APPLY_FILTER_8 0, 8
-
- lea rsi, [rsi + rax]
- lea rdi, [rdi + rdx]
- dec rcx
- jnz .loop
-
- add rsp, 16 * 10
- pop rsp
-
- ; begin epilog
- pop rdi
- pop rsi
- RESTORE_XMM
- UNSHADOW_ARGS
- pop rbp
- ret
diff --git a/third_party/aom/aom_dsp/x86/aom_subpixel_bilinear_sse2.asm b/third_party/aom/aom_dsp/x86/aom_subpixel_bilinear_sse2.asm
deleted file mode 100644
index 90dd55a4be..0000000000
--- a/third_party/aom/aom_dsp/x86/aom_subpixel_bilinear_sse2.asm
+++ /dev/null
@@ -1,295 +0,0 @@
-;
-; Copyright (c) 2016, Alliance for Open Media. All rights reserved
-;
-; This source code is subject to the terms of the BSD 2 Clause License and
-; the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
-; was not distributed with this source code in the LICENSE file, you can
-; obtain it at www.aomedia.org/license/software. If the Alliance for Open
-; Media Patent License 1.0 was not distributed with this source code in the
-; PATENTS file, you can obtain it at www.aomedia.org/license/patent.
-;
-
-;
-
-%include "aom_ports/x86_abi_support.asm"
-
-%macro GET_PARAM_4 0
- mov rdx, arg(5) ;filter ptr
- mov rsi, arg(0) ;src_ptr
- mov rdi, arg(2) ;output_ptr
- mov rcx, 0x0400040
-
- movdqa xmm3, [rdx] ;load filters
- pshuflw xmm4, xmm3, 11111111b ;k3
- psrldq xmm3, 8
- pshuflw xmm3, xmm3, 0b ;k4
- punpcklqdq xmm4, xmm3 ;k3k4
-
- movq xmm3, rcx ;rounding
- pshufd xmm3, xmm3, 0
-
- pxor xmm2, xmm2
-
- movsxd rax, DWORD PTR arg(1) ;pixels_per_line
- movsxd rdx, DWORD PTR arg(3) ;out_pitch
- movsxd rcx, DWORD PTR arg(4) ;output_height
-%endm
-
-%macro APPLY_FILTER_4 1
-
- punpckldq xmm0, xmm1 ;two row in one register
- punpcklbw xmm0, xmm2 ;unpack to word
- pmullw xmm0, xmm4 ;multiply the filter factors
-
- movdqa xmm1, xmm0
- psrldq xmm1, 8
- paddsw xmm0, xmm1
-
- paddsw xmm0, xmm3 ;rounding
- psraw xmm0, 7 ;shift
- packuswb xmm0, xmm0 ;pack to byte
-
-%if %1
- movd xmm1, [rdi]
- pavgb xmm0, xmm1
-%endif
-
- movd [rdi], xmm0
- lea rsi, [rsi + rax]
- lea rdi, [rdi + rdx]
- dec rcx
-%endm
-
-%macro GET_PARAM 0
- mov rdx, arg(5) ;filter ptr
- mov rsi, arg(0) ;src_ptr
- mov rdi, arg(2) ;output_ptr
- mov rcx, 0x0400040
-
- movdqa xmm7, [rdx] ;load filters
-
- pshuflw xmm6, xmm7, 11111111b ;k3
- pshufhw xmm7, xmm7, 0b ;k4
- punpcklwd xmm6, xmm6
- punpckhwd xmm7, xmm7
-
- movq xmm4, rcx ;rounding
- pshufd xmm4, xmm4, 0
-
- pxor xmm5, xmm5
-
- movsxd rax, DWORD PTR arg(1) ;pixels_per_line
- movsxd rdx, DWORD PTR arg(3) ;out_pitch
- movsxd rcx, DWORD PTR arg(4) ;output_height
-%endm
-
-%macro APPLY_FILTER_8 1
- punpcklbw xmm0, xmm5
- punpcklbw xmm1, xmm5
-
- pmullw xmm0, xmm6
- pmullw xmm1, xmm7
- paddsw xmm0, xmm1
- paddsw xmm0, xmm4 ;rounding
- psraw xmm0, 7 ;shift
- packuswb xmm0, xmm0 ;pack back to byte
-%if %1
- movq xmm1, [rdi]
- pavgb xmm0, xmm1
-%endif
- movq [rdi], xmm0 ;store the result
-
- lea rsi, [rsi + rax]
- lea rdi, [rdi + rdx]
- dec rcx
-%endm
-
-%macro APPLY_FILTER_16 1
- punpcklbw xmm0, xmm5
- punpcklbw xmm1, xmm5
- punpckhbw xmm2, xmm5
- punpckhbw xmm3, xmm5
-
- pmullw xmm0, xmm6
- pmullw xmm1, xmm7
- pmullw xmm2, xmm6
- pmullw xmm3, xmm7
-
- paddsw xmm0, xmm1
- paddsw xmm2, xmm3
-
- paddsw xmm0, xmm4 ;rounding
- paddsw xmm2, xmm4
- psraw xmm0, 7 ;shift
- psraw xmm2, 7
- packuswb xmm0, xmm2 ;pack back to byte
-%if %1
- movdqu xmm1, [rdi]
- pavgb xmm0, xmm1
-%endif
- movdqu [rdi], xmm0 ;store the result
-
- lea rsi, [rsi + rax]
- lea rdi, [rdi + rdx]
- dec rcx
-%endm
-
-SECTION .text
-
-globalsym(aom_filter_block1d4_v2_sse2)
-sym(aom_filter_block1d4_v2_sse2):
- push rbp
- mov rbp, rsp
- SHADOW_ARGS_TO_STACK 6
- push rsi
- push rdi
- ; end prolog
-
- GET_PARAM_4
-.loop:
- movd xmm0, [rsi] ;load src
- movd xmm1, [rsi + rax]
-
- APPLY_FILTER_4 0
- jnz .loop
-
- ; begin epilog
- pop rdi
- pop rsi
- UNSHADOW_ARGS
- pop rbp
- ret
-
-globalsym(aom_filter_block1d8_v2_sse2)
-sym(aom_filter_block1d8_v2_sse2):
- push rbp
- mov rbp, rsp
- SHADOW_ARGS_TO_STACK 6
- SAVE_XMM 7
- push rsi
- push rdi
- ; end prolog
-
- GET_PARAM
-.loop:
- movq xmm0, [rsi] ;0
- movq xmm1, [rsi + rax] ;1
-
- APPLY_FILTER_8 0
- jnz .loop
-
- ; begin epilog
- pop rdi
- pop rsi
- RESTORE_XMM
- UNSHADOW_ARGS
- pop rbp
- ret
-
-globalsym(aom_filter_block1d16_v2_sse2)
-sym(aom_filter_block1d16_v2_sse2):
- push rbp
- mov rbp, rsp
- SHADOW_ARGS_TO_STACK 6
- SAVE_XMM 7
- push rsi
- push rdi
- ; end prolog
-
- GET_PARAM
-.loop:
- movdqu xmm0, [rsi] ;0
- movdqu xmm1, [rsi + rax] ;1
- movdqa xmm2, xmm0
- movdqa xmm3, xmm1
-
- APPLY_FILTER_16 0
- jnz .loop
-
- ; begin epilog
- pop rdi
- pop rsi
- RESTORE_XMM
- UNSHADOW_ARGS
- pop rbp
- ret
-
-globalsym(aom_filter_block1d4_h2_sse2)
-sym(aom_filter_block1d4_h2_sse2):
- push rbp
- mov rbp, rsp
- SHADOW_ARGS_TO_STACK 6
- push rsi
- push rdi
- ; end prolog
-
- GET_PARAM_4
-.loop:
- movdqu xmm0, [rsi] ;load src
- movdqa xmm1, xmm0
- psrldq xmm1, 1
-
- APPLY_FILTER_4 0
- jnz .loop
-
- ; begin epilog
- pop rdi
- pop rsi
- UNSHADOW_ARGS
- pop rbp
- ret
-
-globalsym(aom_filter_block1d8_h2_sse2)
-sym(aom_filter_block1d8_h2_sse2):
- push rbp
- mov rbp, rsp
- SHADOW_ARGS_TO_STACK 6
- SAVE_XMM 7
- push rsi
- push rdi
- ; end prolog
-
- GET_PARAM
-.loop:
- movdqu xmm0, [rsi] ;load src
- movdqa xmm1, xmm0
- psrldq xmm1, 1
-
- APPLY_FILTER_8 0
- jnz .loop
-
- ; begin epilog
- pop rdi
- pop rsi
- RESTORE_XMM
- UNSHADOW_ARGS
- pop rbp
- ret
-
-globalsym(aom_filter_block1d16_h2_sse2)
-sym(aom_filter_block1d16_h2_sse2):
- push rbp
- mov rbp, rsp
- SHADOW_ARGS_TO_STACK 6
- SAVE_XMM 7
- push rsi
- push rdi
- ; end prolog
-
- GET_PARAM
-.loop:
- movdqu xmm0, [rsi] ;load src
- movdqu xmm1, [rsi + 1]
- movdqa xmm2, xmm0
- movdqa xmm3, xmm1
-
- APPLY_FILTER_16 0
- jnz .loop
-
- ; begin epilog
- pop rdi
- pop rsi
- RESTORE_XMM
- UNSHADOW_ARGS
- pop rbp
- ret
diff --git a/third_party/aom/aom_dsp/x86/avg_intrin_sse2.c b/third_party/aom/aom_dsp/x86/avg_intrin_sse2.c
index 9ab9143eee..0b552b704b 100644
--- a/third_party/aom/aom_dsp/x86/avg_intrin_sse2.c
+++ b/third_party/aom/aom_dsp/x86/avg_intrin_sse2.c
@@ -133,7 +133,7 @@ unsigned int aom_avg_8x8_sse2(const uint8_t *s, int p) {
return (avg + 32) >> 6;
}
-void calc_avg_8x8_dual_sse2(const uint8_t *s, int p, int *avg) {
+static void calc_avg_8x8_dual_sse2(const uint8_t *s, int p, int *avg) {
__m128i sum0, sum1, s0, s1, s2, s3, u0;
u0 = _mm_setzero_si128();
s0 = _mm_sad_epu8(_mm_loadu_si128((const __m128i *)(s)), u0);
diff --git a/third_party/aom/aom_dsp/x86/fwd_txfm_impl_sse2.h b/third_party/aom/aom_dsp/x86/fwd_txfm_impl_sse2.h
index 7ee8ba330e..e1db3b950c 100644
--- a/third_party/aom/aom_dsp/x86/fwd_txfm_impl_sse2.h
+++ b/third_party/aom/aom_dsp/x86/fwd_txfm_impl_sse2.h
@@ -30,6 +30,7 @@
#define SUB_EPI16 _mm_sub_epi16
#endif
+#if defined(FDCT4x4_2D_HELPER)
static void FDCT4x4_2D_HELPER(const int16_t *input, int stride, __m128i *in0,
__m128i *in1) {
// Constants
@@ -185,7 +186,9 @@ static void FDCT4x4_2D_HELPER(const int16_t *input, int stride, __m128i *in0,
}
}
}
+#endif // defined(FDCT4x4_2D_HELPER)
+#if defined(FDCT4x4_2D)
void FDCT4x4_2D(const int16_t *input, tran_low_t *output, int stride) {
// This 2D transform implements 4 vertical 1D transforms followed
// by 4 horizontal 1D transforms. The multiplies and adds are as given
@@ -205,13 +208,16 @@ void FDCT4x4_2D(const int16_t *input, tran_low_t *output, int stride) {
storeu_output(&in0, output + 0 * 4);
storeu_output(&in1, output + 2 * 4);
}
+#endif // defined(FDCT4x4_2D)
+#if defined(FDCT4x4_2D_LP)
void FDCT4x4_2D_LP(const int16_t *input, int16_t *output, int stride) {
__m128i in0, in1;
FDCT4x4_2D_HELPER(input, stride, &in0, &in1);
_mm_storeu_si128((__m128i *)(output + 0 * 4), in0);
_mm_storeu_si128((__m128i *)(output + 2 * 4), in1);
}
+#endif // defined(FDCT4x4_2D_LP)
#if CONFIG_INTERNAL_STATS
void FDCT8x8_2D(const int16_t *input, tran_low_t *output, int stride) {
diff --git a/third_party/aom/aom_dsp/x86/highbd_variance_avx2.c b/third_party/aom/aom_dsp/x86/highbd_variance_avx2.c
index b4ff91d856..21e9e8b282 100644
--- a/third_party/aom/aom_dsp/x86/highbd_variance_avx2.c
+++ b/third_party/aom/aom_dsp/x86/highbd_variance_avx2.c
@@ -618,9 +618,9 @@ static uint32_t aom_highbd_var_filter_block2d_bil_avx2(
return (var > 0) ? var : 0;
}
-void aom_highbd_calc8x8var_avx2(const uint16_t *src, int src_stride,
- const uint16_t *ref, int ref_stride,
- uint32_t *sse, int *sum) {
+static void highbd_calc8x8var_avx2(const uint16_t *src, int src_stride,
+ const uint16_t *ref, int ref_stride,
+ uint32_t *sse, int *sum) {
__m256i v_sum_d = _mm256_setzero_si256();
__m256i v_sse_d = _mm256_setzero_si256();
for (int i = 0; i < 8; i += 2) {
@@ -653,9 +653,9 @@ void aom_highbd_calc8x8var_avx2(const uint16_t *src, int src_stride,
*sse = _mm_extract_epi32(v_d, 1);
}
-void aom_highbd_calc16x16var_avx2(const uint16_t *src, int src_stride,
- const uint16_t *ref, int ref_stride,
- uint32_t *sse, int *sum) {
+static void highbd_calc16x16var_avx2(const uint16_t *src, int src_stride,
+ const uint16_t *ref, int ref_stride,
+ uint32_t *sse, int *sum) {
__m256i v_sum_d = _mm256_setzero_si256();
__m256i v_sse_d = _mm256_setzero_si256();
const __m256i one = _mm256_set1_epi16(1);
@@ -703,19 +703,19 @@ static void highbd_10_variance_avx2(const uint16_t *src, int src_stride,
*sse = (uint32_t)ROUND_POWER_OF_TWO(sse_long, 4);
}
-#define VAR_FN(w, h, block_size, shift) \
- uint32_t aom_highbd_10_variance##w##x##h##_avx2( \
- const uint8_t *src8, int src_stride, const uint8_t *ref8, \
- int ref_stride, uint32_t *sse) { \
- int sum; \
- int64_t var; \
- uint16_t *src = CONVERT_TO_SHORTPTR(src8); \
- uint16_t *ref = CONVERT_TO_SHORTPTR(ref8); \
- highbd_10_variance_avx2( \
- src, src_stride, ref, ref_stride, w, h, sse, &sum, \
- aom_highbd_calc##block_size##x##block_size##var_avx2, block_size); \
- var = (int64_t)(*sse) - (((int64_t)sum * sum) >> shift); \
- return (var >= 0) ? (uint32_t)var : 0; \
+#define VAR_FN(w, h, block_size, shift) \
+ uint32_t aom_highbd_10_variance##w##x##h##_avx2( \
+ const uint8_t *src8, int src_stride, const uint8_t *ref8, \
+ int ref_stride, uint32_t *sse) { \
+ int sum; \
+ int64_t var; \
+ uint16_t *src = CONVERT_TO_SHORTPTR(src8); \
+ uint16_t *ref = CONVERT_TO_SHORTPTR(ref8); \
+ highbd_10_variance_avx2(src, src_stride, ref, ref_stride, w, h, sse, &sum, \
+ highbd_calc##block_size##x##block_size##var_avx2, \
+ block_size); \
+ var = (int64_t)(*sse) - (((int64_t)sum * sum) >> shift); \
+ return (var >= 0) ? (uint32_t)var : 0; \
}
VAR_FN(128, 128, 16, 14)
@@ -741,6 +741,17 @@ VAR_FN(8, 32, 8, 8)
#undef VAR_FN
+unsigned int aom_highbd_10_mse16x16_avx2(const uint8_t *src8, int src_stride,
+ const uint8_t *ref8, int ref_stride,
+ unsigned int *sse) {
+ int sum;
+ uint16_t *src = CONVERT_TO_SHORTPTR(src8);
+ uint16_t *ref = CONVERT_TO_SHORTPTR(ref8);
+ highbd_10_variance_avx2(src, src_stride, ref, ref_stride, 16, 16, sse, &sum,
+ highbd_calc16x16var_avx2, 16);
+ return *sse;
+}
+
#define SSE2_HEIGHT(H) \
uint32_t aom_highbd_10_sub_pixel_variance8x##H##_sse2( \
const uint8_t *src8, int src_stride, int x_offset, int y_offset, \
@@ -749,7 +760,7 @@ VAR_FN(8, 32, 8, 8)
SSE2_HEIGHT(8)
SSE2_HEIGHT(16)
-#undef SSE2_Height
+#undef SSE2_HEIGHT
#define HIGHBD_SUBPIX_VAR(W, H) \
uint32_t aom_highbd_10_sub_pixel_variance##W##x##H##_avx2( \
@@ -782,8 +793,8 @@ HIGHBD_SUBPIX_VAR(8, 8)
#undef HIGHBD_SUBPIX_VAR
-uint64_t aom_mse_4xh_16bit_highbd_avx2(uint16_t *dst, int dstride,
- uint16_t *src, int sstride, int h) {
+static uint64_t mse_4xh_16bit_highbd_avx2(uint16_t *dst, int dstride,
+ uint16_t *src, int sstride, int h) {
uint64_t sum = 0;
__m128i reg0_4x16, reg1_4x16, reg2_4x16, reg3_4x16;
__m256i src0_8x16, src1_8x16, src_16x16;
@@ -840,8 +851,8 @@ uint64_t aom_mse_4xh_16bit_highbd_avx2(uint16_t *dst, int dstride,
return sum;
}
-uint64_t aom_mse_8xh_16bit_highbd_avx2(uint16_t *dst, int dstride,
- uint16_t *src, int sstride, int h) {
+static uint64_t mse_8xh_16bit_highbd_avx2(uint16_t *dst, int dstride,
+ uint16_t *src, int sstride, int h) {
uint64_t sum = 0;
__m256i src0_8x16, src1_8x16, src_16x16;
__m256i dst0_8x16, dst1_8x16, dst_16x16;
@@ -897,8 +908,8 @@ uint64_t aom_mse_wxh_16bit_highbd_avx2(uint16_t *dst, int dstride,
assert((w == 8 || w == 4) && (h == 8 || h == 4) &&
"w=8/4 and h=8/4 must satisfy");
switch (w) {
- case 4: return aom_mse_4xh_16bit_highbd_avx2(dst, dstride, src, sstride, h);
- case 8: return aom_mse_8xh_16bit_highbd_avx2(dst, dstride, src, sstride, h);
+ case 4: return mse_4xh_16bit_highbd_avx2(dst, dstride, src, sstride, h);
+ case 8: return mse_8xh_16bit_highbd_avx2(dst, dstride, src, sstride, h);
default: assert(0 && "unsupported width"); return -1;
}
}
diff --git a/third_party/aom/aom_dsp/x86/highbd_variance_sse2.c b/third_party/aom/aom_dsp/x86/highbd_variance_sse2.c
index e897aab645..2fc2e1c0dd 100644
--- a/third_party/aom/aom_dsp/x86/highbd_variance_sse2.c
+++ b/third_party/aom/aom_dsp/x86/highbd_variance_sse2.c
@@ -637,8 +637,8 @@ void aom_highbd_dist_wtd_comp_avg_pred_sse2(
}
}
-uint64_t aom_mse_4xh_16bit_highbd_sse2(uint16_t *dst, int dstride,
- uint16_t *src, int sstride, int h) {
+static uint64_t mse_4xh_16bit_highbd_sse2(uint16_t *dst, int dstride,
+ uint16_t *src, int sstride, int h) {
uint64_t sum = 0;
__m128i reg0_4x16, reg1_4x16;
__m128i src_8x16;
@@ -682,8 +682,8 @@ uint64_t aom_mse_4xh_16bit_highbd_sse2(uint16_t *dst, int dstride,
return sum;
}
-uint64_t aom_mse_8xh_16bit_highbd_sse2(uint16_t *dst, int dstride,
- uint16_t *src, int sstride, int h) {
+static uint64_t mse_8xh_16bit_highbd_sse2(uint16_t *dst, int dstride,
+ uint16_t *src, int sstride, int h) {
uint64_t sum = 0;
__m128i src_8x16;
__m128i dst_8x16;
@@ -728,8 +728,8 @@ uint64_t aom_mse_wxh_16bit_highbd_sse2(uint16_t *dst, int dstride,
assert((w == 8 || w == 4) && (h == 8 || h == 4) &&
"w=8/4 and h=8/4 must satisfy");
switch (w) {
- case 4: return aom_mse_4xh_16bit_highbd_sse2(dst, dstride, src, sstride, h);
- case 8: return aom_mse_8xh_16bit_highbd_sse2(dst, dstride, src, sstride, h);
+ case 4: return mse_4xh_16bit_highbd_sse2(dst, dstride, src, sstride, h);
+ case 8: return mse_8xh_16bit_highbd_sse2(dst, dstride, src, sstride, h);
default: assert(0 && "unsupported width"); return -1;
}
}
diff --git a/third_party/aom/aom_dsp/x86/intrapred_ssse3.c b/third_party/aom/aom_dsp/x86/intrapred_ssse3.c
index fd48260c6f..869f880bda 100644
--- a/third_party/aom/aom_dsp/x86/intrapred_ssse3.c
+++ b/third_party/aom/aom_dsp/x86/intrapred_ssse3.c
@@ -940,10 +940,10 @@ static AOM_FORCE_INLINE __m128i cvtepu16_epi32(__m128i x) {
return _mm_unpacklo_epi16((x), _mm_setzero_si128());
}
-void smooth_predictor_wxh(uint8_t *LIBAOM_RESTRICT dst, ptrdiff_t stride,
- const uint8_t *LIBAOM_RESTRICT top_row,
- const uint8_t *LIBAOM_RESTRICT left_column, int width,
- int height) {
+static void smooth_predictor_wxh(uint8_t *LIBAOM_RESTRICT dst, ptrdiff_t stride,
+ const uint8_t *LIBAOM_RESTRICT top_row,
+ const uint8_t *LIBAOM_RESTRICT left_column,
+ int width, int height) {
const uint8_t *const sm_weights_h = smooth_weights + height - 4;
const uint8_t *const sm_weights_w = smooth_weights + width - 4;
const __m128i zero = _mm_setzero_si128();
diff --git a/third_party/aom/aom_dsp/x86/masked_sad4d_ssse3.c b/third_party/aom/aom_dsp/x86/masked_sad4d_ssse3.c
index 799ce9ef44..d96a9dd23d 100644
--- a/third_party/aom/aom_dsp/x86/masked_sad4d_ssse3.c
+++ b/third_party/aom/aom_dsp/x86/masked_sad4d_ssse3.c
@@ -103,11 +103,12 @@ static INLINE void masked_sadx4d_ssse3(const uint8_t *src_ptr, int src_stride,
pred = _mm_packus_epi16(pred_l, pred_r); \
res##idx = _mm_add_epi32(res##idx, _mm_sad_epu8(pred, src));
-void aom_masked_sad8xhx4d_ssse3(const uint8_t *src_ptr, int src_stride,
- const uint8_t *ref_array[4], int a_stride,
- const uint8_t *b_ptr, int b_stride,
- const uint8_t *m_ptr, int m_stride, int height,
- int inv_mask, unsigned sad_array[4]) {
+static void masked_sad8xhx4d_ssse3(const uint8_t *src_ptr, int src_stride,
+ const uint8_t *ref_array[4], int a_stride,
+ const uint8_t *b_ptr, int b_stride,
+ const uint8_t *m_ptr, int m_stride,
+ int height, int inv_mask,
+ unsigned sad_array[4]) {
const uint8_t *ref0 = ref_array[0];
const uint8_t *ref1 = ref_array[1];
const uint8_t *ref2 = ref_array[2];
@@ -164,11 +165,12 @@ void aom_masked_sad8xhx4d_ssse3(const uint8_t *src_ptr, int src_stride,
pred = _mm_packus_epi16(pred, _mm_setzero_si128()); \
res##idx = _mm_add_epi32(res##idx, _mm_sad_epu8(pred, src));
-void aom_masked_sad4xhx4d_ssse3(const uint8_t *src_ptr, int src_stride,
- const uint8_t *ref_array[4], int a_stride,
- const uint8_t *b_ptr, int b_stride,
- const uint8_t *m_ptr, int m_stride, int height,
- int inv_mask, unsigned sad_array[4]) {
+static void masked_sad4xhx4d_ssse3(const uint8_t *src_ptr, int src_stride,
+ const uint8_t *ref_array[4], int a_stride,
+ const uint8_t *b_ptr, int b_stride,
+ const uint8_t *m_ptr, int m_stride,
+ int height, int inv_mask,
+ unsigned sad_array[4]) {
const uint8_t *ref0 = ref_array[0];
const uint8_t *ref1 = ref_array[1];
const uint8_t *ref2 = ref_array[2];
@@ -224,22 +226,22 @@ void aom_masked_sad4xhx4d_ssse3(const uint8_t *src_ptr, int src_stride,
msk_stride, m, n, inv_mask, sad_array); \
}
-#define MASKSAD8XN_SSSE3(n) \
- void aom_masked_sad8x##n##x4d_ssse3( \
- const uint8_t *src, int src_stride, const uint8_t *ref[4], \
- int ref_stride, const uint8_t *second_pred, const uint8_t *msk, \
- int msk_stride, int inv_mask, unsigned sad_array[4]) { \
- aom_masked_sad8xhx4d_ssse3(src, src_stride, ref, ref_stride, second_pred, \
- 8, msk, msk_stride, n, inv_mask, sad_array); \
+#define MASKSAD8XN_SSSE3(n) \
+ void aom_masked_sad8x##n##x4d_ssse3( \
+ const uint8_t *src, int src_stride, const uint8_t *ref[4], \
+ int ref_stride, const uint8_t *second_pred, const uint8_t *msk, \
+ int msk_stride, int inv_mask, unsigned sad_array[4]) { \
+ masked_sad8xhx4d_ssse3(src, src_stride, ref, ref_stride, second_pred, 8, \
+ msk, msk_stride, n, inv_mask, sad_array); \
}
-#define MASKSAD4XN_SSSE3(n) \
- void aom_masked_sad4x##n##x4d_ssse3( \
- const uint8_t *src, int src_stride, const uint8_t *ref[4], \
- int ref_stride, const uint8_t *second_pred, const uint8_t *msk, \
- int msk_stride, int inv_mask, unsigned sad_array[4]) { \
- aom_masked_sad4xhx4d_ssse3(src, src_stride, ref, ref_stride, second_pred, \
- 4, msk, msk_stride, n, inv_mask, sad_array); \
+#define MASKSAD4XN_SSSE3(n) \
+ void aom_masked_sad4x##n##x4d_ssse3( \
+ const uint8_t *src, int src_stride, const uint8_t *ref[4], \
+ int ref_stride, const uint8_t *second_pred, const uint8_t *msk, \
+ int msk_stride, int inv_mask, unsigned sad_array[4]) { \
+ masked_sad4xhx4d_ssse3(src, src_stride, ref, ref_stride, second_pred, 4, \
+ msk, msk_stride, n, inv_mask, sad_array); \
}
MASKSADMXN_SSSE3(128, 128)
diff --git a/third_party/aom/aom_dsp/x86/subpel_variance_sse2.asm b/third_party/aom/aom_dsp/x86/subpel_variance_ssse3.asm
index d1d8373456..f424ce01dd 100644
--- a/third_party/aom/aom_dsp/x86/subpel_variance_sse2.asm
+++ b/third_party/aom/aom_dsp/x86/subpel_variance_ssse3.asm
@@ -15,21 +15,6 @@
SECTION_RODATA
pw_8: times 8 dw 8
-bilin_filter_m_sse2: times 8 dw 16
- times 8 dw 0
- times 8 dw 14
- times 8 dw 2
- times 8 dw 12
- times 8 dw 4
- times 8 dw 10
- times 8 dw 6
- times 16 dw 8
- times 8 dw 6
- times 8 dw 10
- times 8 dw 4
- times 8 dw 12
- times 8 dw 2
- times 8 dw 14
bilin_filter_m_ssse3: times 8 db 16, 0
times 8 db 14, 2
@@ -109,9 +94,6 @@ SECTION .text
%if cpuflag(ssse3)
%define bilin_filter_m bilin_filter_m_ssse3
%define filter_idx_shift 4
-%else
-%define bilin_filter_m bilin_filter_m_sse2
-%define filter_idx_shift 5
%endif
; FIXME(rbultje) only bilinear filters use >8 registers, and ssse3 only uses
; 11, not 13, if the registers are ordered correctly. May make a minor speed
@@ -1449,21 +1431,11 @@ SECTION .text
; location in the sse/2 version, rather than duplicating that code in the
; binary.
-INIT_XMM sse2
-SUBPEL_VARIANCE 4
-SUBPEL_VARIANCE 8
-SUBPEL_VARIANCE 16
-
INIT_XMM ssse3
SUBPEL_VARIANCE 4
SUBPEL_VARIANCE 8
SUBPEL_VARIANCE 16
-INIT_XMM sse2
-SUBPEL_VARIANCE 4, 1
-SUBPEL_VARIANCE 8, 1
-SUBPEL_VARIANCE 16, 1
-
INIT_XMM ssse3
SUBPEL_VARIANCE 4, 1
SUBPEL_VARIANCE 8, 1
diff --git a/third_party/aom/aom_dsp/x86/synonyms.h b/third_party/aom/aom_dsp/x86/synonyms.h
index 6744ec51d0..74318de2e5 100644
--- a/third_party/aom/aom_dsp/x86/synonyms.h
+++ b/third_party/aom/aom_dsp/x86/synonyms.h
@@ -46,6 +46,25 @@ static INLINE __m128i xx_loadu_128(const void *a) {
return _mm_loadu_si128((const __m128i *)a);
}
+
+// _mm_loadu_si64 has been introduced in GCC 9, reimplement the function
+// manually on older compilers.
+#if !defined(__clang__) && __GNUC_MAJOR__ < 9
+static INLINE __m128i xx_loadu_2x64(const void *hi, const void *lo) {
+ __m64 hi_, lo_;
+ memcpy(&hi_, hi, sizeof(hi_));
+ memcpy(&lo_, lo, sizeof(lo_));
+ return _mm_set_epi64(hi_, lo_);
+}
+#else
+// Load 64 bits from each of hi and low, and pack into an SSE register
+// Since directly loading as `int64_t`s and using _mm_set_epi64 may violate
+// the strict aliasing rule, this takes a different approach
+static INLINE __m128i xx_loadu_2x64(const void *hi, const void *lo) {
+ return _mm_unpacklo_epi64(_mm_loadu_si64(lo), _mm_loadu_si64(hi));
+}
+#endif
+
static INLINE void xx_storel_32(void *const a, const __m128i v) {
const int val = _mm_cvtsi128_si32(v);
memcpy(a, &val, sizeof(val));
diff --git a/third_party/aom/aom_dsp/x86/synonyms_avx2.h b/third_party/aom/aom_dsp/x86/synonyms_avx2.h
index b729e5f410..7548d4d4f4 100644
--- a/third_party/aom/aom_dsp/x86/synonyms_avx2.h
+++ b/third_party/aom/aom_dsp/x86/synonyms_avx2.h
@@ -43,6 +43,16 @@ static INLINE void yy_storeu_256(void *const a, const __m256i v) {
_mm256_storeu_si256((__m256i *)a, v);
}
+// Fill an AVX register using an interleaved pair of values, ie. set the
+// 16 channels to {a, b} repeated 8 times, using the same channel ordering
+// as when a register is stored to / loaded from memory.
+//
+// This is useful for rearranging filter kernels for use with the _mm_madd_epi16
+// instruction
+static INLINE __m256i yy_set2_epi16(int16_t a, int16_t b) {
+ return _mm256_setr_epi16(a, b, a, b, a, b, a, b, a, b, a, b, a, b, a, b);
+}
+
// The _mm256_set1_epi64x() intrinsic is undefined for some Visual Studio
// compilers. The following function is equivalent to _mm256_set1_epi64x()
// acting on a 32-bit integer.
@@ -61,11 +71,26 @@ static INLINE __m256i yy_set_m128i(__m128i hi, __m128i lo) {
return _mm256_insertf128_si256(_mm256_castsi128_si256(lo), hi, 1);
}
+#define GCC_VERSION (__GNUC__ * 10000 \
+ + __GNUC_MINOR__ * 100 \
+ + __GNUC_PATCHLEVEL__)
+
+// _mm256_loadu2_m128i has been introduced in GCC 10.1
+#if !defined(__clang__) && GCC_VERSION < 101000
+static INLINE __m256i yy_loadu2_128(const void *hi, const void *lo) {
+ __m128i mhi = _mm_loadu_si128((const __m128i *)(hi));
+ __m128i mlo = _mm_loadu_si128((const __m128i *)(lo));
+ return _mm256_set_m128i(mhi, mlo);
+}
+#else
static INLINE __m256i yy_loadu2_128(const void *hi, const void *lo) {
__m128i mhi = _mm_loadu_si128((const __m128i *)(hi));
__m128i mlo = _mm_loadu_si128((const __m128i *)(lo));
return yy_set_m128i(mhi, mlo);
}
+#endif
+
+#undef GCC_VERSION
static INLINE void yy_storeu2_128(void *hi, void *lo, const __m256i a) {
_mm_storeu_si128((__m128i *)hi, _mm256_extracti128_si256(a, 1));
diff --git a/third_party/aom/aom_dsp/x86/variance_avx2.c b/third_party/aom/aom_dsp/x86/variance_avx2.c
index 046d6f10f8..0f872fc392 100644
--- a/third_party/aom/aom_dsp/x86/variance_avx2.c
+++ b/third_party/aom/aom_dsp/x86/variance_avx2.c
@@ -518,8 +518,8 @@ void aom_highbd_comp_mask_pred_avx2(uint8_t *comp_pred8, const uint8_t *pred8,
}
}
-uint64_t aom_mse_4xh_16bit_avx2(uint8_t *dst, int dstride, uint16_t *src,
- int sstride, int h) {
+static uint64_t mse_4xh_16bit_avx2(uint8_t *dst, int dstride, uint16_t *src,
+ int sstride, int h) {
uint64_t sum = 0;
__m128i dst0_4x8, dst1_4x8, dst2_4x8, dst3_4x8, dst_16x8;
__m128i src0_4x16, src1_4x16, src2_4x16, src3_4x16;
@@ -575,8 +575,9 @@ uint64_t aom_mse_4xh_16bit_avx2(uint8_t *dst, int dstride, uint16_t *src,
// In src buffer, each 4x4 block in a 32x32 filter block is stored sequentially.
// Hence src_blk_stride is same as block width. Whereas dst buffer is a frame
// buffer, thus dstride is a frame level stride.
-uint64_t aom_mse_4xh_quad_16bit_avx2(uint8_t *dst, int dstride, uint16_t *src,
- int src_blk_stride, int h) {
+static uint64_t mse_4xh_quad_16bit_avx2(uint8_t *dst, int dstride,
+ uint16_t *src, int src_blk_stride,
+ int h) {
uint64_t sum = 0;
__m128i dst0_16x8, dst1_16x8, dst2_16x8, dst3_16x8;
__m256i dst0_16x16, dst1_16x16, dst2_16x16, dst3_16x16;
@@ -665,8 +666,8 @@ uint64_t aom_mse_4xh_quad_16bit_avx2(uint8_t *dst, int dstride, uint16_t *src,
return sum;
}
-uint64_t aom_mse_8xh_16bit_avx2(uint8_t *dst, int dstride, uint16_t *src,
- int sstride, int h) {
+static uint64_t mse_8xh_16bit_avx2(uint8_t *dst, int dstride, uint16_t *src,
+ int sstride, int h) {
uint64_t sum = 0;
__m128i dst0_8x8, dst1_8x8, dst3_16x8;
__m256i src0_8x16, src1_8x16, src_16x16, dst_16x16;
@@ -715,8 +716,9 @@ uint64_t aom_mse_8xh_16bit_avx2(uint8_t *dst, int dstride, uint16_t *src,
// In src buffer, each 8x8 block in a 64x64 filter block is stored sequentially.
// Hence src_blk_stride is same as block width. Whereas dst buffer is a frame
// buffer, thus dstride is a frame level stride.
-uint64_t aom_mse_8xh_dual_16bit_avx2(uint8_t *dst, int dstride, uint16_t *src,
- int src_blk_stride, int h) {
+static uint64_t mse_8xh_dual_16bit_avx2(uint8_t *dst, int dstride,
+ uint16_t *src, int src_blk_stride,
+ int h) {
uint64_t sum = 0;
__m128i dst0_16x8, dst1_16x8;
__m256i dst0_16x16, dst1_16x16;
@@ -780,8 +782,8 @@ uint64_t aom_mse_wxh_16bit_avx2(uint8_t *dst, int dstride, uint16_t *src,
assert((w == 8 || w == 4) && (h == 8 || h == 4) &&
"w=8/4 and h=8/4 must be satisfied");
switch (w) {
- case 4: return aom_mse_4xh_16bit_avx2(dst, dstride, src, sstride, h);
- case 8: return aom_mse_8xh_16bit_avx2(dst, dstride, src, sstride, h);
+ case 4: return mse_4xh_16bit_avx2(dst, dstride, src, sstride, h);
+ case 8: return mse_8xh_16bit_avx2(dst, dstride, src, sstride, h);
default: assert(0 && "unsupported width"); return -1;
}
}
@@ -795,8 +797,8 @@ uint64_t aom_mse_16xh_16bit_avx2(uint8_t *dst, int dstride, uint16_t *src,
assert((w == 8 || w == 4) && (h == 8 || h == 4) &&
"w=8/4 and h=8/4 must be satisfied");
switch (w) {
- case 4: return aom_mse_4xh_quad_16bit_avx2(dst, dstride, src, w * h, h);
- case 8: return aom_mse_8xh_dual_16bit_avx2(dst, dstride, src, w * h, h);
+ case 4: return mse_4xh_quad_16bit_avx2(dst, dstride, src, w * h, h);
+ case 8: return mse_8xh_dual_16bit_avx2(dst, dstride, src, w * h, h);
default: assert(0 && "unsupported width"); return -1;
}
}
diff --git a/third_party/aom/aom_dsp/x86/variance_impl_avx2.c b/third_party/aom/aom_dsp/x86/variance_impl_avx2.c
index 9e9e70ea01..57a1cee781 100644
--- a/third_party/aom/aom_dsp/x86/variance_impl_avx2.c
+++ b/third_party/aom/aom_dsp/x86/variance_impl_avx2.c
@@ -648,7 +648,7 @@ MAKE_SUB_PIXEL_VAR_16XH(4, 2)
#endif
#define MAKE_SUB_PIXEL_AVG_VAR_32XH(height, log2height) \
- int aom_sub_pixel_avg_variance32x##height##_imp_avx2( \
+ static int sub_pixel_avg_variance32x##height##_imp_avx2( \
const uint8_t *src, int src_stride, int x_offset, int y_offset, \
const uint8_t *dst, int dst_stride, const uint8_t *sec, int sec_stride, \
unsigned int *sse) { \
@@ -876,7 +876,7 @@ MAKE_SUB_PIXEL_VAR_16XH(4, 2)
const uint8_t *src, int src_stride, int x_offset, int y_offset, \
const uint8_t *dst, int dst_stride, unsigned int *sse, \
const uint8_t *sec_ptr) { \
- const int sum = aom_sub_pixel_avg_variance32x##height##_imp_avx2( \
+ const int sum = sub_pixel_avg_variance32x##height##_imp_avx2( \
src, src_stride, x_offset, y_offset, dst, dst_stride, sec_ptr, 32, \
sse); \
return *sse - (unsigned int)(((int64_t)sum * sum) >> (5 + log2height)); \
@@ -899,7 +899,7 @@ MAKE_SUB_PIXEL_AVG_VAR_32XH(16, 4)
const uint8_t *sec_ptr = sec; \
for (int j = 0; j < (h / hf); ++j) { \
unsigned int sse2; \
- const int se2 = aom_sub_pixel_avg_variance##wf##x##hf##_imp_avx2( \
+ const int se2 = sub_pixel_avg_variance##wf##x##hf##_imp_avx2( \
src_ptr, src_stride, x_offset, y_offset, dst_ptr, dst_stride, \
sec_ptr, w, &sse2); \
dst_ptr += hf * dst_stride; \
diff --git a/third_party/aom/aom_dsp/x86/variance_sse2.c b/third_party/aom/aom_dsp/x86/variance_sse2.c
index faec9cf73d..81b30072a5 100644
--- a/third_party/aom/aom_dsp/x86/variance_sse2.c
+++ b/third_party/aom/aom_dsp/x86/variance_sse2.c
@@ -415,7 +415,6 @@ unsigned int aom_mse16x16_sse2(const uint8_t *src, int src_stride,
DECL(8, opt); \
DECL(16, opt)
-DECLS(sse2);
DECLS(ssse3);
#undef DECLS
#undef DECL
@@ -492,7 +491,6 @@ DECLS(ssse3);
FN(4, 4, 4, 2, 2, opt, (int32_t), (int32_t))
#endif
-FNS(sse2)
FNS(ssse3)
#undef FNS
@@ -510,7 +508,6 @@ FNS(ssse3)
DECL(8, opt); \
DECL(16, opt)
-DECLS(sse2);
DECLS(ssse3);
#undef DECL
#undef DECLS
@@ -591,7 +588,6 @@ DECLS(ssse3);
FN(4, 4, 4, 2, 2, opt, (uint32_t), (int32_t))
#endif
-FNS(sse2)
FNS(ssse3)
#undef FNS
@@ -710,8 +706,8 @@ void aom_highbd_comp_mask_pred_sse2(uint8_t *comp_pred8, const uint8_t *pred8,
}
}
-uint64_t aom_mse_4xh_16bit_sse2(uint8_t *dst, int dstride, uint16_t *src,
- int sstride, int h) {
+static uint64_t mse_4xh_16bit_sse2(uint8_t *dst, int dstride, uint16_t *src,
+ int sstride, int h) {
uint64_t sum = 0;
__m128i dst0_8x8, dst1_8x8, dst_16x8;
__m128i src0_16x4, src1_16x4, src_16x8;
@@ -744,8 +740,8 @@ uint64_t aom_mse_4xh_16bit_sse2(uint8_t *dst, int dstride, uint16_t *src,
return sum;
}
-uint64_t aom_mse_8xh_16bit_sse2(uint8_t *dst, int dstride, uint16_t *src,
- int sstride, int h) {
+static uint64_t mse_8xh_16bit_sse2(uint8_t *dst, int dstride, uint16_t *src,
+ int sstride, int h) {
uint64_t sum = 0;
__m128i dst_8x8, dst_16x8;
__m128i src_16x8;
@@ -781,8 +777,8 @@ uint64_t aom_mse_wxh_16bit_sse2(uint8_t *dst, int dstride, uint16_t *src,
assert((w == 8 || w == 4) && (h == 8 || h == 4) &&
"w=8/4 and h=8/4 must satisfy");
switch (w) {
- case 4: return aom_mse_4xh_16bit_sse2(dst, dstride, src, sstride, h);
- case 8: return aom_mse_8xh_16bit_sse2(dst, dstride, src, sstride, h);
+ case 4: return mse_4xh_16bit_sse2(dst, dstride, src, sstride, h);
+ case 8: return mse_8xh_16bit_sse2(dst, dstride, src, sstride, h);
default: assert(0 && "unsupported width"); return -1;
}
}
diff --git a/third_party/aom/aom_ports/aarch64_cpudetect.c b/third_party/aom/aom_ports/aarch64_cpudetect.c
index 43d5a149c8..159e5b1008 100644
--- a/third_party/aom/aom_ports/aarch64_cpudetect.c
+++ b/third_party/aom/aom_ports/aarch64_cpudetect.c
@@ -9,8 +9,12 @@
* PATENTS file, you can obtain it at www.aomedia.org/license/patent.
*/
+#include "config/aom_config.h"
+
#include "arm_cpudetect.h"
+#include "aom_ports/arm.h"
+
#if defined(__APPLE__)
#include <sys/sysctl.h>
#endif
@@ -104,12 +108,18 @@ static int arm_get_cpu_caps(void) {
#define AOM_AARCH64_HWCAP_CRC32 (1 << 7)
#define AOM_AARCH64_HWCAP_ASIMDDP (1 << 20)
#define AOM_AARCH64_HWCAP_SVE (1 << 22)
+#define AOM_AARCH64_HWCAP2_SVE2 (1 << 1)
#define AOM_AARCH64_HWCAP2_I8MM (1 << 13)
static int arm_get_cpu_caps(void) {
int flags = 0;
+#if HAVE_ARM_CRC32 || HAVE_NEON_DOTPROD || HAVE_SVE
unsigned long hwcap = getauxval(AT_HWCAP);
+#endif
+#if HAVE_NEON_I8MM || HAVE_SVE2
unsigned long hwcap2 = getauxval(AT_HWCAP2);
+#endif
+
#if HAVE_NEON
flags |= HAS_NEON; // Neon is mandatory in Armv8.0-A.
#endif // HAVE_NEON
@@ -125,6 +135,9 @@ static int arm_get_cpu_caps(void) {
#if HAVE_SVE
if (hwcap & AOM_AARCH64_HWCAP_SVE) flags |= HAS_SVE;
#endif // HAVE_SVE
+#if HAVE_SVE2
+ if (hwcap2 & AOM_AARCH64_HWCAP2_SVE2) flags |= HAS_SVE2;
+#endif // HAVE_SVE2
return flags;
}
@@ -184,5 +197,8 @@ int aom_arm_cpu_caps(void) {
if (!(flags & HAS_NEON_DOTPROD)) flags &= ~HAS_SVE;
if (!(flags & HAS_NEON_I8MM)) flags &= ~HAS_SVE;
+ // Restrict flags: SVE2 assumes that FEAT_SVE is available.
+ if (!(flags & HAS_SVE)) flags &= ~HAS_SVE2;
+
return flags;
}
diff --git a/third_party/aom/aom_ports/arm.h b/third_party/aom/aom_ports/arm.h
index 853741d19a..a57510895b 100644
--- a/third_party/aom/aom_ports/arm.h
+++ b/third_party/aom/aom_ports/arm.h
@@ -29,6 +29,8 @@ extern "C" {
#define HAS_NEON_I8MM (1 << 3)
// Armv8.2-A optional SVE instructions, mandatory from Armv9.0-A.
#define HAS_SVE (1 << 4)
+// Armv9.0-A SVE2 instructions.
+#define HAS_SVE2 (1 << 5)
int aom_arm_cpu_caps(void);
diff --git a/third_party/aom/aom_ports/mem.h b/third_party/aom/aom_ports/mem.h
index a70ce825b1..77180068ae 100644
--- a/third_party/aom/aom_ports/mem.h
+++ b/third_party/aom/aom_ports/mem.h
@@ -24,7 +24,13 @@
#define DECLARE_ALIGNED(n, typ, val) typ val
#endif
-#if HAVE_NEON && defined(_MSC_VER)
+#if defined(__has_builtin)
+#define AOM_HAS_BUILTIN(x) __has_builtin(x)
+#else
+#define AOM_HAS_BUILTIN(x) 0
+#endif
+
+#if !AOM_HAS_BUILTIN(__builtin_prefetch) && !defined(__GNUC__)
#define __builtin_prefetch(x)
#endif
diff --git a/third_party/aom/aom_scale/aom_scale_rtcd.pl b/third_party/aom/aom_scale/aom_scale_rtcd.pl
index ae0a85687f..0d545c2f3c 100644
--- a/third_party/aom/aom_scale/aom_scale_rtcd.pl
+++ b/third_party/aom/aom_scale/aom_scale_rtcd.pl
@@ -10,6 +10,8 @@
##
sub aom_scale_forward_decls() {
print <<EOF
+#include <stdbool.h>
+
struct yv12_buffer_config;
EOF
}
@@ -26,17 +28,17 @@ if (aom_config("CONFIG_SPATIAL_RESAMPLING") eq "yes") {
add_proto qw/void aom_vertical_band_2_1_scale_i/, "unsigned char *source, int src_pitch, unsigned char *dest, int dest_pitch, unsigned int dest_width";
}
-add_proto qw/int aom_yv12_realloc_with_new_border/, "struct yv12_buffer_config *ybf, int new_border, int byte_alignment, int num_pyramid_levels, int num_planes";
+add_proto qw/int aom_yv12_realloc_with_new_border/, "struct yv12_buffer_config *ybf, int new_border, int byte_alignment, bool alloc_pyramid, int num_planes";
add_proto qw/void aom_yv12_extend_frame_borders/, "struct yv12_buffer_config *ybf, const int num_planes";
add_proto qw/void aom_yv12_copy_frame/, "const struct yv12_buffer_config *src_bc, struct yv12_buffer_config *dst_bc, const int num_planes";
-add_proto qw/void aom_yv12_copy_y/, "const struct yv12_buffer_config *src_ybc, struct yv12_buffer_config *dst_ybc";
+add_proto qw/void aom_yv12_copy_y/, "const struct yv12_buffer_config *src_ybc, struct yv12_buffer_config *dst_ybc, int use_crop";
-add_proto qw/void aom_yv12_copy_u/, "const struct yv12_buffer_config *src_bc, struct yv12_buffer_config *dst_bc";
+add_proto qw/void aom_yv12_copy_u/, "const struct yv12_buffer_config *src_bc, struct yv12_buffer_config *dst_bc, int use_crop";
-add_proto qw/void aom_yv12_copy_v/, "const struct yv12_buffer_config *src_bc, struct yv12_buffer_config *dst_bc";
+add_proto qw/void aom_yv12_copy_v/, "const struct yv12_buffer_config *src_bc, struct yv12_buffer_config *dst_bc, int use_crop";
add_proto qw/void aom_yv12_partial_copy_y/, "const struct yv12_buffer_config *src_ybc, int hstart1, int hend1, int vstart1, int vend1, struct yv12_buffer_config *dst_ybc, int hstart2, int vstart2";
add_proto qw/void aom_yv12_partial_coloc_copy_y/, "const struct yv12_buffer_config *src_ybc, struct yv12_buffer_config *dst_ybc, int hstart, int hend, int vstart, int vend";
@@ -47,7 +49,7 @@ add_proto qw/void aom_yv12_partial_coloc_copy_v/, "const struct yv12_buffer_conf
add_proto qw/void aom_extend_frame_borders_plane_row/, "const struct yv12_buffer_config *ybf, int plane, int v_start, int v_end";
-add_proto qw/void aom_extend_frame_borders/, "struct yv12_buffer_config *ybf, const int num_planes";
+add_proto qw/void aom_extend_frame_borders/, "struct yv12_buffer_config *ybf, int num_planes";
add_proto qw/void aom_extend_frame_inner_borders/, "struct yv12_buffer_config *ybf, const int num_planes";
diff --git a/third_party/aom/aom_scale/generic/yv12config.c b/third_party/aom/aom_scale/generic/yv12config.c
index 94b400b9e0..ed35bb1acb 100644
--- a/third_party/aom/aom_scale/generic/yv12config.c
+++ b/third_party/aom/aom_scale/generic/yv12config.c
@@ -11,9 +11,12 @@
#include <assert.h>
+#include "config/aom_config.h"
+
+#include "aom/aom_image.h"
#include "aom/internal/aom_image_internal.h"
-#include "aom_dsp/pyramid.h"
#include "aom_dsp/flow_estimation/corner_detect.h"
+#include "aom_dsp/pyramid.h"
#include "aom_mem/aom_mem.h"
#include "aom_ports/mem.h"
#include "aom_scale/yv12config.h"
@@ -60,7 +63,7 @@ static int realloc_frame_buffer_aligned(
const uint64_t uvplane_size, const int aligned_width,
const int aligned_height, const int uv_width, const int uv_height,
const int uv_stride, const int uv_border_w, const int uv_border_h,
- int num_pyramid_levels, int alloc_y_plane_only) {
+ bool alloc_pyramid, int alloc_y_plane_only) {
if (ybf) {
const int aom_byte_align = (byte_alignment == 0) ? 1 : byte_alignment;
const uint64_t frame_size =
@@ -71,8 +74,8 @@ static int realloc_frame_buffer_aligned(
#if CONFIG_REALTIME_ONLY || !CONFIG_AV1_ENCODER
// We should only need an 8-bit version of the source frame if we are
// encoding in non-realtime mode
- (void)num_pyramid_levels;
- assert(num_pyramid_levels == 0);
+ (void)alloc_pyramid;
+ assert(!alloc_pyramid);
#endif // CONFIG_REALTIME_ONLY || !CONFIG_AV1_ENCODER
#if defined AOM_MAX_ALLOCABLE_MEMORY
@@ -80,9 +83,8 @@ static int realloc_frame_buffer_aligned(
uint64_t alloc_size = frame_size;
#if CONFIG_AV1_ENCODER && !CONFIG_REALTIME_ONLY
// The size of ybf->y_pyramid
- if (num_pyramid_levels > 0) {
- alloc_size += aom_get_pyramid_alloc_size(
- width, height, num_pyramid_levels, use_highbitdepth);
+ if (alloc_pyramid) {
+ alloc_size += aom_get_pyramid_alloc_size(width, height, use_highbitdepth);
alloc_size += av1_get_corner_list_size();
}
#endif // CONFIG_AV1_ENCODER && !CONFIG_REALTIME_ONLY
@@ -190,9 +192,8 @@ static int realloc_frame_buffer_aligned(
av1_free_corner_list(ybf->corners);
ybf->corners = NULL;
}
- if (num_pyramid_levels > 0) {
- ybf->y_pyramid = aom_alloc_pyramid(width, height, num_pyramid_levels,
- use_highbitdepth);
+ if (alloc_pyramid) {
+ ybf->y_pyramid = aom_alloc_pyramid(width, height, use_highbitdepth);
if (!ybf->y_pyramid) return AOM_CODEC_MEM_ERROR;
ybf->corners = av1_alloc_corner_list();
if (!ybf->corners) return AOM_CODEC_MEM_ERROR;
@@ -237,7 +238,7 @@ int aom_realloc_frame_buffer(YV12_BUFFER_CONFIG *ybf, int width, int height,
int border, int byte_alignment,
aom_codec_frame_buffer_t *fb,
aom_get_frame_buffer_cb_fn_t cb, void *cb_priv,
- int num_pyramid_levels, int alloc_y_plane_only) {
+ bool alloc_pyramid, int alloc_y_plane_only) {
#if CONFIG_SIZE_LIMIT
if (width > DECODE_WIDTH_LIMIT || height > DECODE_HEIGHT_LIMIT)
return AOM_CODEC_MEM_ERROR;
@@ -264,21 +265,20 @@ int aom_realloc_frame_buffer(YV12_BUFFER_CONFIG *ybf, int width, int height,
ybf, width, height, ss_x, ss_y, use_highbitdepth, border,
byte_alignment, fb, cb, cb_priv, y_stride, yplane_size, uvplane_size,
aligned_width, aligned_height, uv_width, uv_height, uv_stride,
- uv_border_w, uv_border_h, num_pyramid_levels, alloc_y_plane_only);
+ uv_border_w, uv_border_h, alloc_pyramid, alloc_y_plane_only);
}
return AOM_CODEC_MEM_ERROR;
}
int aom_alloc_frame_buffer(YV12_BUFFER_CONFIG *ybf, int width, int height,
int ss_x, int ss_y, int use_highbitdepth, int border,
- int byte_alignment, int num_pyramid_levels,
+ int byte_alignment, bool alloc_pyramid,
int alloc_y_plane_only) {
if (ybf) {
aom_free_frame_buffer(ybf);
- return aom_realloc_frame_buffer(ybf, width, height, ss_x, ss_y,
- use_highbitdepth, border, byte_alignment,
- NULL, NULL, NULL, num_pyramid_levels,
- alloc_y_plane_only);
+ return aom_realloc_frame_buffer(
+ ybf, width, height, ss_x, ss_y, use_highbitdepth, border,
+ byte_alignment, NULL, NULL, NULL, alloc_pyramid, alloc_y_plane_only);
}
return AOM_CODEC_MEM_ERROR;
}
diff --git a/third_party/aom/aom_scale/generic/yv12extend.c b/third_party/aom/aom_scale/generic/yv12extend.c
index 5546112d40..384b72c21e 100644
--- a/third_party/aom/aom_scale/generic/yv12extend.c
+++ b/third_party/aom/aom_scale/generic/yv12extend.c
@@ -302,8 +302,10 @@ void aom_yv12_copy_frame_c(const YV12_BUFFER_CONFIG *src_bc,
}
void aom_yv12_copy_y_c(const YV12_BUFFER_CONFIG *src_ybc,
- YV12_BUFFER_CONFIG *dst_ybc) {
+ YV12_BUFFER_CONFIG *dst_ybc, int use_crop) {
int row;
+ int width = use_crop ? src_ybc->y_crop_width : src_ybc->y_width;
+ int height = use_crop ? src_ybc->y_crop_height : src_ybc->y_height;
const uint8_t *src = src_ybc->y_buffer;
uint8_t *dst = dst_ybc->y_buffer;
@@ -311,8 +313,8 @@ void aom_yv12_copy_y_c(const YV12_BUFFER_CONFIG *src_ybc,
if (src_ybc->flags & YV12_FLAG_HIGHBITDEPTH) {
const uint16_t *src16 = CONVERT_TO_SHORTPTR(src);
uint16_t *dst16 = CONVERT_TO_SHORTPTR(dst);
- for (row = 0; row < src_ybc->y_height; ++row) {
- memcpy(dst16, src16, src_ybc->y_width * sizeof(uint16_t));
+ for (row = 0; row < height; ++row) {
+ memcpy(dst16, src16, width * sizeof(uint16_t));
src16 += src_ybc->y_stride;
dst16 += dst_ybc->y_stride;
}
@@ -320,56 +322,60 @@ void aom_yv12_copy_y_c(const YV12_BUFFER_CONFIG *src_ybc,
}
#endif
- for (row = 0; row < src_ybc->y_height; ++row) {
- memcpy(dst, src, src_ybc->y_width);
+ for (row = 0; row < height; ++row) {
+ memcpy(dst, src, width);
src += src_ybc->y_stride;
dst += dst_ybc->y_stride;
}
}
void aom_yv12_copy_u_c(const YV12_BUFFER_CONFIG *src_bc,
- YV12_BUFFER_CONFIG *dst_bc) {
+ YV12_BUFFER_CONFIG *dst_bc, int use_crop) {
int row;
+ int width = use_crop ? src_bc->uv_crop_width : src_bc->uv_width;
+ int height = use_crop ? src_bc->uv_crop_height : src_bc->uv_height;
const uint8_t *src = src_bc->u_buffer;
uint8_t *dst = dst_bc->u_buffer;
#if CONFIG_AV1_HIGHBITDEPTH
if (src_bc->flags & YV12_FLAG_HIGHBITDEPTH) {
const uint16_t *src16 = CONVERT_TO_SHORTPTR(src);
uint16_t *dst16 = CONVERT_TO_SHORTPTR(dst);
- for (row = 0; row < src_bc->uv_height; ++row) {
- memcpy(dst16, src16, src_bc->uv_width * sizeof(uint16_t));
+ for (row = 0; row < height; ++row) {
+ memcpy(dst16, src16, width * sizeof(uint16_t));
src16 += src_bc->uv_stride;
dst16 += dst_bc->uv_stride;
}
return;
}
#endif
- for (row = 0; row < src_bc->uv_height; ++row) {
- memcpy(dst, src, src_bc->uv_width);
+ for (row = 0; row < height; ++row) {
+ memcpy(dst, src, width);
src += src_bc->uv_stride;
dst += dst_bc->uv_stride;
}
}
void aom_yv12_copy_v_c(const YV12_BUFFER_CONFIG *src_bc,
- YV12_BUFFER_CONFIG *dst_bc) {
+ YV12_BUFFER_CONFIG *dst_bc, int use_crop) {
int row;
+ int width = use_crop ? src_bc->uv_crop_width : src_bc->uv_width;
+ int height = use_crop ? src_bc->uv_crop_height : src_bc->uv_height;
const uint8_t *src = src_bc->v_buffer;
uint8_t *dst = dst_bc->v_buffer;
#if CONFIG_AV1_HIGHBITDEPTH
if (src_bc->flags & YV12_FLAG_HIGHBITDEPTH) {
const uint16_t *src16 = CONVERT_TO_SHORTPTR(src);
uint16_t *dst16 = CONVERT_TO_SHORTPTR(dst);
- for (row = 0; row < src_bc->uv_height; ++row) {
- memcpy(dst16, src16, src_bc->uv_width * sizeof(uint16_t));
+ for (row = 0; row < height; ++row) {
+ memcpy(dst16, src16, width * sizeof(uint16_t));
src16 += src_bc->uv_stride;
dst16 += dst_bc->uv_stride;
}
return;
}
#endif
- for (row = 0; row < src_bc->uv_height; ++row) {
- memcpy(dst, src, src_bc->uv_width);
+ for (row = 0; row < height; ++row) {
+ memcpy(dst, src, width);
src += src_bc->uv_stride;
dst += dst_bc->uv_stride;
}
@@ -491,8 +497,8 @@ void aom_yv12_partial_coloc_copy_v_c(const YV12_BUFFER_CONFIG *src_bc,
}
int aom_yv12_realloc_with_new_border_c(YV12_BUFFER_CONFIG *ybf, int new_border,
- int byte_alignment,
- int num_pyramid_levels, int num_planes) {
+ int byte_alignment, bool alloc_pyramid,
+ int num_planes) {
if (ybf) {
if (new_border == ybf->border) return 0;
YV12_BUFFER_CONFIG new_buf;
@@ -500,7 +506,7 @@ int aom_yv12_realloc_with_new_border_c(YV12_BUFFER_CONFIG *ybf, int new_border,
const int error = aom_alloc_frame_buffer(
&new_buf, ybf->y_crop_width, ybf->y_crop_height, ybf->subsampling_x,
ybf->subsampling_y, ybf->flags & YV12_FLAG_HIGHBITDEPTH, new_border,
- byte_alignment, num_pyramid_levels, 0);
+ byte_alignment, alloc_pyramid, 0);
if (error) return error;
// Copy image buffer
aom_yv12_copy_frame(ybf, &new_buf, num_planes);
diff --git a/third_party/aom/aom_scale/yv12config.h b/third_party/aom/aom_scale/yv12config.h
index f192a3032e..bc05de2102 100644
--- a/third_party/aom/aom_scale/yv12config.h
+++ b/third_party/aom/aom_scale/yv12config.h
@@ -16,6 +16,8 @@
extern "C" {
#endif
+#include <stdbool.h>
+
#include "config/aom_config.h"
#include "aom/aom_codec.h"
@@ -45,18 +47,29 @@ typedef struct yv12_buffer_config {
/*!\cond */
union {
struct {
+ // The aligned frame width of luma.
+ // It is aligned to a multiple of 8:
+ // y_width = (y_crop_width + 7) & ~7
int y_width;
+ // The aligned frame width of chroma.
+ // uv_width = y_width >> subsampling_x
int uv_width;
};
int widths[2];
};
union {
struct {
+ // The aligned frame height of luma.
+ // It is aligned to a multiple of 8:
+ // y_height = (y_crop_height + 7) & ~7
int y_height;
+ // The aligned frame height of chroma.
+ // uv_height = y_height >> subsampling_y
int uv_height;
};
int heights[2];
};
+ // The frame size en/decoded by AV1
union {
struct {
int y_crop_width;
@@ -139,7 +152,7 @@ typedef struct yv12_buffer_config {
// available return values.
int aom_alloc_frame_buffer(YV12_BUFFER_CONFIG *ybf, int width, int height,
int ss_x, int ss_y, int use_highbitdepth, int border,
- int byte_alignment, int num_pyramid_levels,
+ int byte_alignment, bool alloc_pyramid,
int alloc_y_plane_only);
// Updates the yv12 buffer config with the frame buffer. |byte_alignment| must
@@ -149,15 +162,11 @@ int aom_alloc_frame_buffer(YV12_BUFFER_CONFIG *ybf, int width, int height,
// to decode the current frame. If cb is NULL, libaom will allocate memory
// internally to decode the current frame.
//
-// If num_pyramid_levels > 0, then an image pyramid will be allocated with
-// the specified number of levels.
-//
-// Any buffer which may become a source or ref frame buffer in the encoder
-// must have num_pyramid_levels = cpi->image_pyramid_levels. This will cause
-// an image pyramid to be allocated if one is needed.
-//
-// Any other buffers (in particular, any buffers inside the decoder)
-// must have cpi->image_pyramid_levels = 0, as a pyramid is unneeded there.
+// If alloc_pyramid is true, then an image pyramid will be allocated
+// for use in global motion estimation. This is only needed if this frame
+// buffer will be used to store a source frame or a reference frame in
+// the encoder. Any other framebuffers (eg, intermediates for filtering,
+// or any buffer in the decoder) can set alloc_pyramid = false.
//
// Returns 0 on success. Returns < 0 on failure.
int aom_realloc_frame_buffer(YV12_BUFFER_CONFIG *ybf, int width, int height,
@@ -165,7 +174,7 @@ int aom_realloc_frame_buffer(YV12_BUFFER_CONFIG *ybf, int width, int height,
int border, int byte_alignment,
aom_codec_frame_buffer_t *fb,
aom_get_frame_buffer_cb_fn_t cb, void *cb_priv,
- int num_pyramid_levels, int alloc_y_plane_only);
+ bool alloc_pyramid, int alloc_y_plane_only);
int aom_free_frame_buffer(YV12_BUFFER_CONFIG *ybf);
diff --git a/third_party/aom/aom_util/aom_pthread.h b/third_party/aom/aom_util/aom_pthread.h
new file mode 100644
index 0000000000..99deeb292a
--- /dev/null
+++ b/third_party/aom/aom_util/aom_pthread.h
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2024, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+//
+// pthread.h wrapper
+
+#ifndef AOM_AOM_UTIL_AOM_PTHREAD_H_
+#define AOM_AOM_UTIL_AOM_PTHREAD_H_
+
+#include "config/aom_config.h"
+
+#if CONFIG_MULTITHREAD
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(_WIN32) && !HAVE_PTHREAD_H
+// Prevent leaking max/min macros.
+#undef NOMINMAX
+#define NOMINMAX
+#undef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#include <process.h> // NOLINT
+#include <stddef.h> // NOLINT
+#include <windows.h> // NOLINT
+typedef HANDLE pthread_t;
+typedef int pthread_attr_t;
+typedef CRITICAL_SECTION pthread_mutex_t;
+
+#include <errno.h>
+
+#if _WIN32_WINNT < 0x0600
+#error _WIN32_WINNT must target Windows Vista / Server 2008 or newer.
+#endif
+typedef CONDITION_VARIABLE pthread_cond_t;
+
+#ifndef WINAPI_FAMILY_PARTITION
+#define WINAPI_PARTITION_DESKTOP 1
+#define WINAPI_FAMILY_PARTITION(x) x
+#endif
+
+#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
+#define USE_CREATE_THREAD
+#endif
+
+//------------------------------------------------------------------------------
+// simplistic pthread emulation layer
+
+// _beginthreadex requires __stdcall
+#if defined(__GNUC__) && \
+ (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2))
+#define THREADFN __attribute__((force_align_arg_pointer)) unsigned int __stdcall
+#else
+#define THREADFN unsigned int __stdcall
+#endif
+#define THREAD_EXIT_SUCCESS 0
+
+static INLINE int pthread_attr_init(pthread_attr_t *attr) {
+ (void)attr;
+ return 0;
+}
+
+static INLINE int pthread_attr_destroy(pthread_attr_t *attr) {
+ (void)attr;
+ return 0;
+}
+
+static INLINE int pthread_create(pthread_t *const thread,
+ const pthread_attr_t *attr,
+ unsigned int(__stdcall *start)(void *),
+ void *arg) {
+ (void)attr;
+#ifdef USE_CREATE_THREAD
+ *thread = CreateThread(NULL, /* lpThreadAttributes */
+ 0, /* dwStackSize */
+ start, arg, 0, /* dwStackSize */
+ NULL); /* lpThreadId */
+#else
+ *thread = (pthread_t)_beginthreadex(NULL, /* void *security */
+ 0, /* unsigned stack_size */
+ start, arg, 0, /* unsigned initflag */
+ NULL); /* unsigned *thrdaddr */
+#endif
+ if (*thread == NULL) return 1;
+ SetThreadPriority(*thread, THREAD_PRIORITY_ABOVE_NORMAL);
+ return 0;
+}
+
+static INLINE int pthread_join(pthread_t thread, void **value_ptr) {
+ (void)value_ptr;
+ return (WaitForSingleObjectEx(thread, INFINITE, FALSE /*bAlertable*/) !=
+ WAIT_OBJECT_0 ||
+ CloseHandle(thread) == 0);
+}
+
+// Mutex
+static INLINE int pthread_mutex_init(pthread_mutex_t *const mutex,
+ void *mutexattr) {
+ (void)mutexattr;
+ InitializeCriticalSectionEx(mutex, 0 /*dwSpinCount*/, 0 /*Flags*/);
+ return 0;
+}
+
+static INLINE int pthread_mutex_trylock(pthread_mutex_t *const mutex) {
+ return TryEnterCriticalSection(mutex) ? 0 : EBUSY;
+}
+
+static INLINE int pthread_mutex_lock(pthread_mutex_t *const mutex) {
+ EnterCriticalSection(mutex);
+ return 0;
+}
+
+static INLINE int pthread_mutex_unlock(pthread_mutex_t *const mutex) {
+ LeaveCriticalSection(mutex);
+ return 0;
+}
+
+static INLINE int pthread_mutex_destroy(pthread_mutex_t *const mutex) {
+ DeleteCriticalSection(mutex);
+ return 0;
+}
+
+// Condition
+static INLINE int pthread_cond_destroy(pthread_cond_t *const condition) {
+ (void)condition;
+ return 0;
+}
+
+static INLINE int pthread_cond_init(pthread_cond_t *const condition,
+ void *cond_attr) {
+ (void)cond_attr;
+ InitializeConditionVariable(condition);
+ return 0;
+}
+
+static INLINE int pthread_cond_signal(pthread_cond_t *const condition) {
+ WakeConditionVariable(condition);
+ return 0;
+}
+
+static INLINE int pthread_cond_broadcast(pthread_cond_t *const condition) {
+ WakeAllConditionVariable(condition);
+ return 0;
+}
+
+static INLINE int pthread_cond_wait(pthread_cond_t *const condition,
+ pthread_mutex_t *const mutex) {
+ int ok;
+ ok = SleepConditionVariableCS(condition, mutex, INFINITE);
+ return !ok;
+}
+#else // _WIN32
+#include <pthread.h> // NOLINT
+#define THREADFN void *
+#define THREAD_EXIT_SUCCESS NULL
+#endif
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // CONFIG_MULTITHREAD
+
+#endif // AOM_AOM_UTIL_AOM_PTHREAD_H_
diff --git a/third_party/aom/aom_util/aom_thread.c b/third_party/aom/aom_util/aom_thread.c
index fa3b0a25e4..bdf2b7dfa6 100644
--- a/third_party/aom/aom_util/aom_thread.c
+++ b/third_party/aom/aom_util/aom_thread.c
@@ -23,8 +23,11 @@
#include <assert.h>
#include <string.h> // for memset()
+#include "config/aom_config.h"
+
#include "aom_mem/aom_mem.h"
#include "aom_ports/sanitizer.h"
+#include "aom_util/aom_pthread.h"
#include "aom_util/aom_thread.h"
#if CONFIG_MULTITHREAD
@@ -65,29 +68,30 @@ static THREADFN thread_loop(void *ptr) {
#endif
pthread_mutex_lock(&worker->impl_->mutex_);
for (;;) {
- while (worker->status_ == OK) { // wait in idling mode
+ while (worker->status_ == AVX_WORKER_STATUS_OK) { // wait in idling mode
pthread_cond_wait(&worker->impl_->condition_, &worker->impl_->mutex_);
}
- if (worker->status_ == WORK) {
- // When worker->status_ is WORK, the main thread doesn't change
- // worker->status_ and will wait until the worker changes worker->status_
- // to OK. See change_state(). So the worker can safely call execute()
- // without holding worker->impl_->mutex_. When the worker reacquires
- // worker->impl_->mutex_, worker->status_ must still be WORK.
+ if (worker->status_ == AVX_WORKER_STATUS_WORKING) {
+ // When worker->status_ is AVX_WORKER_STATUS_WORKING, the main thread
+ // doesn't change worker->status_ and will wait until the worker changes
+ // worker->status_ to AVX_WORKER_STATUS_OK. See change_state(). So the
+ // worker can safely call execute() without holding worker->impl_->mutex_.
+ // When the worker reacquires worker->impl_->mutex_, worker->status_ must
+ // still be AVX_WORKER_STATUS_WORKING.
pthread_mutex_unlock(&worker->impl_->mutex_);
execute(worker);
pthread_mutex_lock(&worker->impl_->mutex_);
- assert(worker->status_ == WORK);
- worker->status_ = OK;
+ assert(worker->status_ == AVX_WORKER_STATUS_WORKING);
+ worker->status_ = AVX_WORKER_STATUS_OK;
// signal to the main thread that we're done (for sync())
pthread_cond_signal(&worker->impl_->condition_);
} else {
- assert(worker->status_ == NOT_OK); // finish the worker
+ assert(worker->status_ == AVX_WORKER_STATUS_NOT_OK); // finish the worker
break;
}
}
pthread_mutex_unlock(&worker->impl_->mutex_);
- return THREAD_RETURN(NULL); // Thread is finished
+ return THREAD_EXIT_SUCCESS; // Thread is finished
}
// main thread state control
@@ -98,13 +102,13 @@ static void change_state(AVxWorker *const worker, AVxWorkerStatus new_status) {
if (worker->impl_ == NULL) return;
pthread_mutex_lock(&worker->impl_->mutex_);
- if (worker->status_ >= OK) {
+ if (worker->status_ >= AVX_WORKER_STATUS_OK) {
// wait for the worker to finish
- while (worker->status_ != OK) {
+ while (worker->status_ != AVX_WORKER_STATUS_OK) {
pthread_cond_wait(&worker->impl_->condition_, &worker->impl_->mutex_);
}
// assign new status and release the working thread if needed
- if (new_status != OK) {
+ if (new_status != AVX_WORKER_STATUS_OK) {
worker->status_ = new_status;
pthread_cond_signal(&worker->impl_->condition_);
}
@@ -118,21 +122,21 @@ static void change_state(AVxWorker *const worker, AVxWorkerStatus new_status) {
static void init(AVxWorker *const worker) {
memset(worker, 0, sizeof(*worker));
- worker->status_ = NOT_OK;
+ worker->status_ = AVX_WORKER_STATUS_NOT_OK;
}
static int sync(AVxWorker *const worker) {
#if CONFIG_MULTITHREAD
- change_state(worker, OK);
+ change_state(worker, AVX_WORKER_STATUS_OK);
#endif
- assert(worker->status_ <= OK);
+ assert(worker->status_ <= AVX_WORKER_STATUS_OK);
return !worker->had_error;
}
static int reset(AVxWorker *const worker) {
int ok = 1;
worker->had_error = 0;
- if (worker->status_ < OK) {
+ if (worker->status_ < AVX_WORKER_STATUS_OK) {
#if CONFIG_MULTITHREAD
worker->impl_ = (AVxWorkerImpl *)aom_calloc(1, sizeof(*worker->impl_));
if (worker->impl_ == NULL) {
@@ -164,7 +168,7 @@ static int reset(AVxWorker *const worker) {
#endif
pthread_mutex_lock(&worker->impl_->mutex_);
ok = !pthread_create(&worker->impl_->thread_, &attr, thread_loop, worker);
- if (ok) worker->status_ = OK;
+ if (ok) worker->status_ = AVX_WORKER_STATUS_OK;
pthread_mutex_unlock(&worker->impl_->mutex_);
pthread_attr_destroy(&attr);
if (!ok) {
@@ -177,12 +181,12 @@ static int reset(AVxWorker *const worker) {
return 0;
}
#else
- worker->status_ = OK;
+ worker->status_ = AVX_WORKER_STATUS_OK;
#endif
- } else if (worker->status_ > OK) {
+ } else if (worker->status_ > AVX_WORKER_STATUS_OK) {
ok = sync(worker);
}
- assert(!ok || (worker->status_ == OK));
+ assert(!ok || (worker->status_ == AVX_WORKER_STATUS_OK));
return ok;
}
@@ -194,7 +198,7 @@ static void execute(AVxWorker *const worker) {
static void launch(AVxWorker *const worker) {
#if CONFIG_MULTITHREAD
- change_state(worker, WORK);
+ change_state(worker, AVX_WORKER_STATUS_WORKING);
#else
execute(worker);
#endif
@@ -203,7 +207,7 @@ static void launch(AVxWorker *const worker) {
static void end(AVxWorker *const worker) {
#if CONFIG_MULTITHREAD
if (worker->impl_ != NULL) {
- change_state(worker, NOT_OK);
+ change_state(worker, AVX_WORKER_STATUS_NOT_OK);
pthread_join(worker->impl_->thread_, NULL);
pthread_mutex_destroy(&worker->impl_->mutex_);
pthread_cond_destroy(&worker->impl_->condition_);
@@ -211,10 +215,10 @@ static void end(AVxWorker *const worker) {
worker->impl_ = NULL;
}
#else
- worker->status_ = NOT_OK;
+ worker->status_ = AVX_WORKER_STATUS_NOT_OK;
assert(worker->impl_ == NULL);
#endif
- assert(worker->status_ == NOT_OK);
+ assert(worker->status_ == AVX_WORKER_STATUS_NOT_OK);
}
//------------------------------------------------------------------------------
diff --git a/third_party/aom/aom_util/aom_thread.h b/third_party/aom/aom_util/aom_thread.h
index ec2ea43491..92e162f121 100644
--- a/third_party/aom/aom_util/aom_thread.h
+++ b/third_party/aom/aom_util/aom_thread.h
@@ -17,157 +17,17 @@
#ifndef AOM_AOM_UTIL_AOM_THREAD_H_
#define AOM_AOM_UTIL_AOM_THREAD_H_
-#include "config/aom_config.h"
-
#ifdef __cplusplus
extern "C" {
#endif
#define MAX_NUM_THREADS 64
-#if CONFIG_MULTITHREAD
-
-#if defined(_WIN32) && !HAVE_PTHREAD_H
-// Prevent leaking max/min macros.
-#undef NOMINMAX
-#define NOMINMAX
-#undef WIN32_LEAN_AND_MEAN
-#define WIN32_LEAN_AND_MEAN
-#include <errno.h> // NOLINT
-#include <process.h> // NOLINT
-#include <windows.h> // NOLINT
-typedef HANDLE pthread_t;
-typedef int pthread_attr_t;
-typedef CRITICAL_SECTION pthread_mutex_t;
-
-#if _WIN32_WINNT < 0x0600
-#error _WIN32_WINNT must target Windows Vista / Server 2008 or newer.
-#endif
-typedef CONDITION_VARIABLE pthread_cond_t;
-
-#ifndef WINAPI_FAMILY_PARTITION
-#define WINAPI_PARTITION_DESKTOP 1
-#define WINAPI_FAMILY_PARTITION(x) x
-#endif
-
-#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
-#define USE_CREATE_THREAD
-#endif
-
-//------------------------------------------------------------------------------
-// simplistic pthread emulation layer
-
-// _beginthreadex requires __stdcall
-#define THREADFN unsigned int __stdcall
-#define THREAD_RETURN(val) (unsigned int)((DWORD_PTR)val)
-
-static INLINE int pthread_attr_init(pthread_attr_t *attr) {
- (void)attr;
- return 0;
-}
-
-static INLINE int pthread_attr_destroy(pthread_attr_t *attr) {
- (void)attr;
- return 0;
-}
-
-static INLINE int pthread_create(pthread_t *const thread,
- const pthread_attr_t *attr,
- unsigned int(__stdcall *start)(void *),
- void *arg) {
- (void)attr;
-#ifdef USE_CREATE_THREAD
- *thread = CreateThread(NULL, /* lpThreadAttributes */
- 0, /* dwStackSize */
- start, arg, 0, /* dwStackSize */
- NULL); /* lpThreadId */
-#else
- *thread = (pthread_t)_beginthreadex(NULL, /* void *security */
- 0, /* unsigned stack_size */
- start, arg, 0, /* unsigned initflag */
- NULL); /* unsigned *thrdaddr */
-#endif
- if (*thread == NULL) return 1;
- SetThreadPriority(*thread, THREAD_PRIORITY_ABOVE_NORMAL);
- return 0;
-}
-
-static INLINE int pthread_join(pthread_t thread, void **value_ptr) {
- (void)value_ptr;
- return (WaitForSingleObjectEx(thread, INFINITE, FALSE /*bAlertable*/) !=
- WAIT_OBJECT_0 ||
- CloseHandle(thread) == 0);
-}
-
-// Mutex
-static INLINE int pthread_mutex_init(pthread_mutex_t *const mutex,
- void *mutexattr) {
- (void)mutexattr;
- InitializeCriticalSectionEx(mutex, 0 /*dwSpinCount*/, 0 /*Flags*/);
- return 0;
-}
-
-static INLINE int pthread_mutex_trylock(pthread_mutex_t *const mutex) {
- return TryEnterCriticalSection(mutex) ? 0 : EBUSY;
-}
-
-static INLINE int pthread_mutex_lock(pthread_mutex_t *const mutex) {
- EnterCriticalSection(mutex);
- return 0;
-}
-
-static INLINE int pthread_mutex_unlock(pthread_mutex_t *const mutex) {
- LeaveCriticalSection(mutex);
- return 0;
-}
-
-static INLINE int pthread_mutex_destroy(pthread_mutex_t *const mutex) {
- DeleteCriticalSection(mutex);
- return 0;
-}
-
-// Condition
-static INLINE int pthread_cond_destroy(pthread_cond_t *const condition) {
- (void)condition;
- return 0;
-}
-
-static INLINE int pthread_cond_init(pthread_cond_t *const condition,
- void *cond_attr) {
- (void)cond_attr;
- InitializeConditionVariable(condition);
- return 0;
-}
-
-static INLINE int pthread_cond_signal(pthread_cond_t *const condition) {
- WakeConditionVariable(condition);
- return 0;
-}
-
-static INLINE int pthread_cond_broadcast(pthread_cond_t *const condition) {
- WakeAllConditionVariable(condition);
- return 0;
-}
-
-static INLINE int pthread_cond_wait(pthread_cond_t *const condition,
- pthread_mutex_t *const mutex) {
- int ok;
- ok = SleepConditionVariableCS(condition, mutex, INFINITE);
- return !ok;
-}
-#else // _WIN32
-#include <pthread.h> // NOLINT
-#define THREADFN void *
-#define THREAD_RETURN(val) val
-#endif
-
-#endif // CONFIG_MULTITHREAD
-
// State of the worker thread object
typedef enum {
- NOT_OK = 0, // object is unusable
- OK, // ready to work
- WORK // busy finishing the current task
+ AVX_WORKER_STATUS_NOT_OK = 0, // object is unusable
+ AVX_WORKER_STATUS_OK, // ready to work
+ AVX_WORKER_STATUS_WORKING // busy finishing the current task
} AVxWorkerStatus;
// Function to be called by the worker thread. Takes two opaque pointers as
diff --git a/third_party/aom/aom_util/aom_util.cmake b/third_party/aom/aom_util/aom_util.cmake
index 6bf4fafc4c..d3da550485 100644
--- a/third_party/aom/aom_util/aom_util.cmake
+++ b/third_party/aom/aom_util/aom_util.cmake
@@ -13,7 +13,8 @@ if(AOM_AOM_UTIL_AOM_UTIL_CMAKE_)
endif() # AOM_AOM_UTIL_AOM_UTIL_CMAKE_
set(AOM_AOM_UTIL_AOM_UTIL_CMAKE_ 1)
-list(APPEND AOM_UTIL_SOURCES "${AOM_ROOT}/aom_util/aom_thread.c"
+list(APPEND AOM_UTIL_SOURCES "${AOM_ROOT}/aom_util/aom_pthread.h"
+ "${AOM_ROOT}/aom_util/aom_thread.c"
"${AOM_ROOT}/aom_util/aom_thread.h"
"${AOM_ROOT}/aom_util/endian_inl.h")
diff --git a/third_party/aom/apps/aomenc.c b/third_party/aom/apps/aomenc.c
index 3c9c136eed..799fb3a4f8 100644
--- a/third_party/aom/apps/aomenc.c
+++ b/third_party/aom/apps/aomenc.c
@@ -442,12 +442,12 @@ const arg_def_t *av1_ctrl_args[] = {
#endif
&g_av1_codec_arg_defs.dv_cost_upd_freq,
&g_av1_codec_arg_defs.partition_info_path,
- &g_av1_codec_arg_defs.enable_rate_guide_deltaq,
- &g_av1_codec_arg_defs.rate_distribution_info,
&g_av1_codec_arg_defs.enable_directional_intra,
&g_av1_codec_arg_defs.enable_tx_size_search,
&g_av1_codec_arg_defs.loopfilter_control,
&g_av1_codec_arg_defs.auto_intra_tools_off,
+ &g_av1_codec_arg_defs.enable_rate_guide_deltaq,
+ &g_av1_codec_arg_defs.rate_distribution_info,
NULL,
};
diff --git a/third_party/aom/av1/av1.cmake b/third_party/aom/av1/av1.cmake
index c66a748d40..32645f6065 100644
--- a/third_party/aom/av1/av1.cmake
+++ b/third_party/aom/av1/av1.cmake
@@ -262,7 +262,6 @@ list(APPEND AOM_AV1_ENCODER_SOURCES
list(APPEND AOM_AV1_COMMON_INTRIN_SSE2
"${AOM_ROOT}/av1/common/x86/av1_txfm_sse2.h"
- "${AOM_ROOT}/av1/common/x86/cdef_block_sse2.c"
"${AOM_ROOT}/av1/common/x86/cfl_sse2.c"
"${AOM_ROOT}/av1/common/x86/convolve_2d_sse2.c"
"${AOM_ROOT}/av1/common/x86/convolve_sse2.c"
@@ -272,11 +271,14 @@ list(APPEND AOM_AV1_COMMON_INTRIN_SSE2
list(APPEND AOM_AV1_COMMON_INTRIN_SSSE3
"${AOM_ROOT}/av1/common/x86/av1_inv_txfm_ssse3.c"
"${AOM_ROOT}/av1/common/x86/av1_inv_txfm_ssse3.h"
- "${AOM_ROOT}/av1/common/x86/cdef_block_ssse3.c"
"${AOM_ROOT}/av1/common/x86/cfl_ssse3.c"
"${AOM_ROOT}/av1/common/x86/jnt_convolve_ssse3.c"
"${AOM_ROOT}/av1/common/x86/resize_ssse3.c")
+# Fallbacks to support Valgrind on 32-bit x86
+list(APPEND AOM_AV1_COMMON_INTRIN_SSSE3_X86
+ "${AOM_ROOT}/av1/common/x86/cdef_block_ssse3.c")
+
list(APPEND AOM_AV1_COMMON_INTRIN_SSE4_1
"${AOM_ROOT}/av1/common/x86/av1_convolve_horiz_rs_sse4.c"
"${AOM_ROOT}/av1/common/x86/av1_convolve_scale_sse4.c"
@@ -372,7 +374,8 @@ list(APPEND AOM_AV1_ENCODER_INTRIN_NEON_DOTPROD
"${AOM_ROOT}/av1/encoder/arm/neon/temporal_filter_neon_dotprod.c")
list(APPEND AOM_AV1_ENCODER_INTRIN_SVE
- "${AOM_ROOT}/av1/encoder/arm/neon/av1_error_sve.c")
+ "${AOM_ROOT}/av1/encoder/arm/neon/av1_error_sve.c"
+ "${AOM_ROOT}/av1/encoder/arm/neon/wedge_utils_sve.c")
list(APPEND AOM_AV1_ENCODER_INTRIN_ARM_CRC32
"${AOM_ROOT}/av1/encoder/arm/crc32/hash_arm_crc32.c")
@@ -477,6 +480,10 @@ if(CONFIG_AV1_HIGHBITDEPTH)
"${AOM_ROOT}/av1/common/arm/highbd_warp_plane_neon.c"
"${AOM_ROOT}/av1/common/arm/highbd_wiener_convolve_neon.c")
+ list(APPEND AOM_AV1_COMMON_INTRIN_SVE2
+ "${AOM_ROOT}/av1/common/arm/highbd_compound_convolve_sve2.c"
+ "${AOM_ROOT}/av1/common/arm/highbd_convolve_sve2.c")
+
list(APPEND AOM_AV1_ENCODER_INTRIN_SSE2
"${AOM_ROOT}/av1/encoder/x86/highbd_block_error_intrin_sse2.c"
"${AOM_ROOT}/av1/encoder/x86/highbd_temporal_filter_sse2.c")
@@ -605,6 +612,10 @@ function(setup_av1_targets)
require_compiler_flag_nomsvc("-mssse3" NO)
add_intrinsics_object_library("-mssse3" "ssse3" "aom_av1_common"
"AOM_AV1_COMMON_INTRIN_SSSE3")
+ if(AOM_ARCH_X86)
+ add_intrinsics_object_library("-mssse3" "ssse3_x86" "aom_av1_common"
+ "AOM_AV1_COMMON_INTRIN_SSSE3_X86")
+ endif()
if(CONFIG_AV1_DECODER)
if(AOM_AV1_DECODER_INTRIN_SSSE3)
@@ -703,6 +714,11 @@ function(setup_av1_targets)
endif()
endif()
+ if(HAVE_SVE2)
+ add_intrinsics_object_library("${AOM_SVE2_FLAG}" "sve2" "aom_av1_common"
+ "AOM_AV1_COMMON_INTRIN_SVE2")
+ endif()
+
if(HAVE_VSX)
if(AOM_AV1_COMMON_INTRIN_VSX)
add_intrinsics_object_library("-mvsx -maltivec" "vsx" "aom_av1_common"
diff --git a/third_party/aom/av1/av1_cx_iface.c b/third_party/aom/av1/av1_cx_iface.c
index 9214feb4e6..2b6b1504e6 100644
--- a/third_party/aom/av1/av1_cx_iface.c
+++ b/third_party/aom/av1/av1_cx_iface.c
@@ -9,22 +9,28 @@
* PATENTS file, you can obtain it at www.aomedia.org/license/patent.
*/
#include <limits.h>
+#include <stdint.h>
#include <stdlib.h>
#include <string.h>
-#include "aom_mem/aom_mem.h"
#include "config/aom_config.h"
#include "config/aom_version.h"
-#include "aom_ports/mem_ops.h"
-
+#include "aom/aomcx.h"
#include "aom/aom_encoder.h"
+#include "aom/aom_external_partition.h"
+#include "aom/aom_image.h"
#include "aom/internal/aom_codec_internal.h"
-
#include "aom_dsp/flow_estimation/flow_estimation.h"
+#include "aom_mem/aom_mem.h"
+#include "aom_scale/yv12config.h"
+#include "aom_util/aom_pthread.h"
#include "av1/av1_cx_iface.h"
#include "av1/av1_iface_common.h"
+#include "av1/common/av1_common_int.h"
+#include "av1/common/enums.h"
+#include "av1/common/scale.h"
#include "av1/encoder/bitstream.h"
#include "av1/encoder/encoder.h"
#include "av1/encoder/encoder_alloc.h"
@@ -32,6 +38,7 @@
#include "av1/encoder/ethread.h"
#include "av1/encoder/external_partition.h"
#include "av1/encoder/firstpass.h"
+#include "av1/encoder/lookahead.h"
#include "av1/encoder/rc_utils.h"
#include "av1/arg_defs.h"
@@ -1836,6 +1843,11 @@ static aom_codec_err_t ctrl_set_enable_qm(aom_codec_alg_priv_t *ctx,
va_list args) {
struct av1_extracfg extra_cfg = ctx->extra_cfg;
extra_cfg.enable_qm = CAST(AV1E_SET_ENABLE_QM, args);
+#if !CONFIG_QUANT_MATRIX
+ if (extra_cfg.enable_qm) {
+ ERROR("QM can't be enabled with CONFIG_QUANT_MATRIX=0.");
+ }
+#endif
return update_extra_cfg(ctx, &extra_cfg);
}
static aom_codec_err_t ctrl_set_qm_y(aom_codec_alg_priv_t *ctx, va_list args) {
@@ -3072,11 +3084,36 @@ static aom_codec_err_t encoder_encode(aom_codec_alg_priv_t *ctx,
ctx->pts_offset = ptsvol;
ctx->pts_offset_initialized = 1;
}
+ if (ptsvol < ctx->pts_offset) {
+ aom_internal_error(&ppi->error, AOM_CODEC_INVALID_PARAM,
+ "pts is smaller than initial pts");
+ }
ptsvol -= ctx->pts_offset;
+ if (ptsvol > INT64_MAX / cpi_data.timestamp_ratio->num) {
+ aom_internal_error(
+ &ppi->error, AOM_CODEC_INVALID_PARAM,
+ "conversion of relative pts to ticks would overflow");
+ }
int64_t src_time_stamp =
timebase_units_to_ticks(cpi_data.timestamp_ratio, ptsvol);
+#if ULONG_MAX > INT64_MAX
+ if (duration > INT64_MAX) {
+ aom_internal_error(&ppi->error, AOM_CODEC_INVALID_PARAM,
+ "duration is too big");
+ }
+#endif
+ if (ptsvol > INT64_MAX - (int64_t)duration) {
+ aom_internal_error(&ppi->error, AOM_CODEC_INVALID_PARAM,
+ "relative pts + duration is too big");
+ }
+ aom_codec_pts_t pts_end = ptsvol + (int64_t)duration;
+ if (pts_end > INT64_MAX / cpi_data.timestamp_ratio->num) {
+ aom_internal_error(
+ &ppi->error, AOM_CODEC_INVALID_PARAM,
+ "conversion of relative pts + duration to ticks would overflow");
+ }
int64_t src_end_time_stamp =
- timebase_units_to_ticks(cpi_data.timestamp_ratio, ptsvol + duration);
+ timebase_units_to_ticks(cpi_data.timestamp_ratio, pts_end);
YV12_BUFFER_CONFIG sd;
res = image2yuvconfig(img, &sd);
@@ -3110,7 +3147,7 @@ static aom_codec_err_t encoder_encode(aom_codec_alg_priv_t *ctx,
subsampling_x, subsampling_y, use_highbitdepth, lag_in_frames,
src_border_in_pixels, cpi->common.features.byte_alignment,
ctx->num_lap_buffers, (cpi->oxcf.kf_cfg.key_freq_max == 0),
- cpi->image_pyramid_levels);
+ cpi->alloc_pyramid);
}
if (!ppi->lookahead)
aom_internal_error(&ppi->error, AOM_CODEC_MEM_ERROR,
diff --git a/third_party/aom/av1/av1_dx_iface.c b/third_party/aom/av1/av1_dx_iface.c
index 3d7e132ab8..1a2dea37b6 100644
--- a/third_party/aom/av1/av1_dx_iface.c
+++ b/third_party/aom/av1/av1_dx_iface.c
@@ -19,18 +19,23 @@
#include "aom/internal/aom_image_internal.h"
#include "aom/aomdx.h"
#include "aom/aom_decoder.h"
+#include "aom/aom_image.h"
#include "aom_dsp/bitreader_buffer.h"
#include "aom_dsp/aom_dsp_common.h"
+#include "aom_ports/mem.h"
#include "aom_ports/mem_ops.h"
+#include "aom_util/aom_pthread.h"
#include "aom_util/aom_thread.h"
#include "av1/common/alloccommon.h"
+#include "av1/common/av1_common_int.h"
#include "av1/common/frame_buffers.h"
#include "av1/common/enums.h"
#include "av1/common/obu_util.h"
#include "av1/decoder/decoder.h"
#include "av1/decoder/decodeframe.h"
+#include "av1/decoder/dthread.h"
#include "av1/decoder/grain_synthesis.h"
#include "av1/decoder/obu.h"
@@ -865,7 +870,9 @@ static aom_image_t *decoder_get_frame(aom_codec_alg_priv_t *ctx,
if (pbi->ext_tile_debug && tiles->single_tile_decoding &&
pbi->dec_tile_row >= 0) {
int tile_width, tile_height;
- av1_get_uniform_tile_size(cm, &tile_width, &tile_height);
+ if (!av1_get_uniform_tile_size(cm, &tile_width, &tile_height)) {
+ return NULL;
+ }
const int tile_row = AOMMIN(pbi->dec_tile_row, tiles->rows - 1);
const int mi_row = tile_row * tile_height;
const int ssy = ctx->img.y_chroma_shift;
@@ -884,7 +891,9 @@ static aom_image_t *decoder_get_frame(aom_codec_alg_priv_t *ctx,
if (pbi->ext_tile_debug && tiles->single_tile_decoding &&
pbi->dec_tile_col >= 0) {
int tile_width, tile_height;
- av1_get_uniform_tile_size(cm, &tile_width, &tile_height);
+ if (!av1_get_uniform_tile_size(cm, &tile_width, &tile_height)) {
+ return NULL;
+ }
const int tile_col = AOMMIN(pbi->dec_tile_col, tiles->cols - 1);
const int mi_col = tile_col * tile_width;
const int ssx = ctx->img.x_chroma_shift;
@@ -1428,7 +1437,9 @@ static aom_codec_err_t ctrl_get_tile_size(aom_codec_alg_priv_t *ctx,
(FrameWorkerData *)worker->data1;
const AV1_COMMON *const cm = &frame_worker_data->pbi->common;
int tile_width, tile_height;
- av1_get_uniform_tile_size(cm, &tile_width, &tile_height);
+ if (!av1_get_uniform_tile_size(cm, &tile_width, &tile_height)) {
+ return AOM_CODEC_CORRUPT_FRAME;
+ }
*tile_size = ((tile_width * MI_SIZE) << 16) + tile_height * MI_SIZE;
return AOM_CODEC_OK;
} else {
diff --git a/third_party/aom/av1/common/alloccommon.c b/third_party/aom/av1/common/alloccommon.c
index 2a9a8beb40..e9a38c4a60 100644
--- a/third_party/aom/av1/common/alloccommon.c
+++ b/third_party/aom/av1/common/alloccommon.c
@@ -13,6 +13,8 @@
#include "config/aom_config.h"
#include "aom_mem/aom_mem.h"
+#include "aom_scale/yv12config.h"
+#include "aom_util/aom_pthread.h"
#include "av1/common/alloccommon.h"
#include "av1/common/av1_common_int.h"
@@ -20,6 +22,8 @@
#include "av1/common/cdef_block.h"
#include "av1/common/entropymode.h"
#include "av1/common/entropymv.h"
+#include "av1/common/enums.h"
+#include "av1/common/restoration.h"
#include "av1/common/thread_common.h"
int av1_get_MBs(int width, int height) {
@@ -200,7 +204,7 @@ void av1_alloc_cdef_buffers(AV1_COMMON *const cm,
const int is_num_workers_changed =
cdef_info->allocated_num_workers != num_workers;
const int is_cdef_enabled =
- cm->seq_params->enable_cdef && !cm->tiles.large_scale;
+ cm->seq_params->enable_cdef && !cm->tiles.single_tile_decoding;
// num-bufs=3 represents ping-pong buffers for top linebuf,
// followed by bottom linebuf.
diff --git a/third_party/aom/av1/common/arm/highbd_compound_convolve_neon.c b/third_party/aom/av1/common/arm/highbd_compound_convolve_neon.c
index fc03a2ee04..9247ded6bf 100644
--- a/third_party/aom/av1/common/arm/highbd_compound_convolve_neon.c
+++ b/third_party/aom/av1/common/arm/highbd_compound_convolve_neon.c
@@ -20,266 +20,9 @@
#include "aom_ports/mem.h"
#include "av1/common/convolve.h"
#include "av1/common/filter.h"
+#include "av1/common/arm/highbd_compound_convolve_neon.h"
#include "av1/common/arm/highbd_convolve_neon.h"
-#define ROUND_SHIFT 2 * FILTER_BITS - ROUND0_BITS - COMPOUND_ROUND1_BITS
-
-static INLINE void highbd_12_comp_avg_neon(const uint16_t *src_ptr,
- int src_stride, uint16_t *dst_ptr,
- int dst_stride, int w, int h,
- ConvolveParams *conv_params,
- const int offset, const int bd) {
- CONV_BUF_TYPE *ref_ptr = conv_params->dst;
- const int ref_stride = conv_params->dst_stride;
- const uint16x4_t offset_vec = vdup_n_u16(offset);
- const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
-
- if (w == 4) {
- do {
- const uint16x4_t src = vld1_u16(src_ptr);
- const uint16x4_t ref = vld1_u16(ref_ptr);
-
- uint16x4_t avg = vhadd_u16(src, ref);
- int32x4_t d0 = vreinterpretq_s32_u32(vsubl_u16(avg, offset_vec));
-
- uint16x4_t d0_u16 = vqrshrun_n_s32(d0, ROUND_SHIFT - 2);
- d0_u16 = vmin_u16(d0_u16, vget_low_u16(max));
-
- vst1_u16(dst_ptr, d0_u16);
-
- src_ptr += src_stride;
- ref_ptr += ref_stride;
- dst_ptr += dst_stride;
- } while (--h != 0);
- } else {
- do {
- int width = w;
- const uint16_t *src = src_ptr;
- const uint16_t *ref = ref_ptr;
- uint16_t *dst = dst_ptr;
- do {
- const uint16x8_t s = vld1q_u16(src);
- const uint16x8_t r = vld1q_u16(ref);
-
- uint16x8_t avg = vhaddq_u16(s, r);
- int32x4_t d0_lo =
- vreinterpretq_s32_u32(vsubl_u16(vget_low_u16(avg), offset_vec));
- int32x4_t d0_hi =
- vreinterpretq_s32_u32(vsubl_u16(vget_high_u16(avg), offset_vec));
-
- uint16x8_t d0 = vcombine_u16(vqrshrun_n_s32(d0_lo, ROUND_SHIFT - 2),
- vqrshrun_n_s32(d0_hi, ROUND_SHIFT - 2));
- d0 = vminq_u16(d0, max);
- vst1q_u16(dst, d0);
-
- src += 8;
- ref += 8;
- dst += 8;
- width -= 8;
- } while (width != 0);
-
- src_ptr += src_stride;
- ref_ptr += ref_stride;
- dst_ptr += dst_stride;
- } while (--h != 0);
- }
-}
-
-static INLINE void highbd_comp_avg_neon(const uint16_t *src_ptr, int src_stride,
- uint16_t *dst_ptr, int dst_stride,
- int w, int h,
- ConvolveParams *conv_params,
- const int offset, const int bd) {
- CONV_BUF_TYPE *ref_ptr = conv_params->dst;
- const int ref_stride = conv_params->dst_stride;
- const uint16x4_t offset_vec = vdup_n_u16(offset);
- const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
-
- if (w == 4) {
- do {
- const uint16x4_t src = vld1_u16(src_ptr);
- const uint16x4_t ref = vld1_u16(ref_ptr);
-
- uint16x4_t avg = vhadd_u16(src, ref);
- int32x4_t d0 = vreinterpretq_s32_u32(vsubl_u16(avg, offset_vec));
-
- uint16x4_t d0_u16 = vqrshrun_n_s32(d0, ROUND_SHIFT);
- d0_u16 = vmin_u16(d0_u16, vget_low_u16(max));
-
- vst1_u16(dst_ptr, d0_u16);
-
- src_ptr += src_stride;
- ref_ptr += ref_stride;
- dst_ptr += dst_stride;
- } while (--h != 0);
- } else {
- do {
- int width = w;
- const uint16_t *src = src_ptr;
- const uint16_t *ref = ref_ptr;
- uint16_t *dst = dst_ptr;
- do {
- const uint16x8_t s = vld1q_u16(src);
- const uint16x8_t r = vld1q_u16(ref);
-
- uint16x8_t avg = vhaddq_u16(s, r);
- int32x4_t d0_lo =
- vreinterpretq_s32_u32(vsubl_u16(vget_low_u16(avg), offset_vec));
- int32x4_t d0_hi =
- vreinterpretq_s32_u32(vsubl_u16(vget_high_u16(avg), offset_vec));
-
- uint16x8_t d0 = vcombine_u16(vqrshrun_n_s32(d0_lo, ROUND_SHIFT),
- vqrshrun_n_s32(d0_hi, ROUND_SHIFT));
- d0 = vminq_u16(d0, max);
- vst1q_u16(dst, d0);
-
- src += 8;
- ref += 8;
- dst += 8;
- width -= 8;
- } while (width != 0);
-
- src_ptr += src_stride;
- ref_ptr += ref_stride;
- dst_ptr += dst_stride;
- } while (--h != 0);
- }
-}
-
-static INLINE void highbd_12_dist_wtd_comp_avg_neon(
- const uint16_t *src_ptr, int src_stride, uint16_t *dst_ptr, int dst_stride,
- int w, int h, ConvolveParams *conv_params, const int offset, const int bd) {
- CONV_BUF_TYPE *ref_ptr = conv_params->dst;
- const int ref_stride = conv_params->dst_stride;
- const uint32x4_t offset_vec = vdupq_n_u32(offset);
- const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
- uint16x4_t fwd_offset = vdup_n_u16(conv_params->fwd_offset);
- uint16x4_t bck_offset = vdup_n_u16(conv_params->bck_offset);
-
- // Weighted averaging
- if (w == 4) {
- do {
- const uint16x4_t src = vld1_u16(src_ptr);
- const uint16x4_t ref = vld1_u16(ref_ptr);
-
- uint32x4_t wtd_avg = vmull_u16(ref, fwd_offset);
- wtd_avg = vmlal_u16(wtd_avg, src, bck_offset);
- wtd_avg = vshrq_n_u32(wtd_avg, DIST_PRECISION_BITS);
- int32x4_t d0 = vreinterpretq_s32_u32(vsubq_u32(wtd_avg, offset_vec));
-
- uint16x4_t d0_u16 = vqrshrun_n_s32(d0, ROUND_SHIFT - 2);
- d0_u16 = vmin_u16(d0_u16, vget_low_u16(max));
-
- vst1_u16(dst_ptr, d0_u16);
-
- src_ptr += src_stride;
- dst_ptr += dst_stride;
- ref_ptr += ref_stride;
- } while (--h != 0);
- } else {
- do {
- int width = w;
- const uint16_t *src = src_ptr;
- const uint16_t *ref = ref_ptr;
- uint16_t *dst = dst_ptr;
- do {
- const uint16x8_t s = vld1q_u16(src);
- const uint16x8_t r = vld1q_u16(ref);
-
- uint32x4_t wtd_avg0 = vmull_u16(vget_low_u16(r), fwd_offset);
- wtd_avg0 = vmlal_u16(wtd_avg0, vget_low_u16(s), bck_offset);
- wtd_avg0 = vshrq_n_u32(wtd_avg0, DIST_PRECISION_BITS);
- int32x4_t d0 = vreinterpretq_s32_u32(vsubq_u32(wtd_avg0, offset_vec));
-
- uint32x4_t wtd_avg1 = vmull_u16(vget_high_u16(r), fwd_offset);
- wtd_avg1 = vmlal_u16(wtd_avg1, vget_high_u16(s), bck_offset);
- wtd_avg1 = vshrq_n_u32(wtd_avg1, DIST_PRECISION_BITS);
- int32x4_t d1 = vreinterpretq_s32_u32(vsubq_u32(wtd_avg1, offset_vec));
-
- uint16x8_t d01 = vcombine_u16(vqrshrun_n_s32(d0, ROUND_SHIFT - 2),
- vqrshrun_n_s32(d1, ROUND_SHIFT - 2));
- d01 = vminq_u16(d01, max);
- vst1q_u16(dst, d01);
-
- src += 8;
- ref += 8;
- dst += 8;
- width -= 8;
- } while (width != 0);
- src_ptr += src_stride;
- dst_ptr += dst_stride;
- ref_ptr += ref_stride;
- } while (--h != 0);
- }
-}
-
-static INLINE void highbd_dist_wtd_comp_avg_neon(
- const uint16_t *src_ptr, int src_stride, uint16_t *dst_ptr, int dst_stride,
- int w, int h, ConvolveParams *conv_params, const int offset, const int bd) {
- CONV_BUF_TYPE *ref_ptr = conv_params->dst;
- const int ref_stride = conv_params->dst_stride;
- const uint32x4_t offset_vec = vdupq_n_u32(offset);
- const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
- uint16x4_t fwd_offset = vdup_n_u16(conv_params->fwd_offset);
- uint16x4_t bck_offset = vdup_n_u16(conv_params->bck_offset);
-
- // Weighted averaging
- if (w == 4) {
- do {
- const uint16x4_t src = vld1_u16(src_ptr);
- const uint16x4_t ref = vld1_u16(ref_ptr);
-
- uint32x4_t wtd_avg = vmull_u16(ref, fwd_offset);
- wtd_avg = vmlal_u16(wtd_avg, src, bck_offset);
- wtd_avg = vshrq_n_u32(wtd_avg, DIST_PRECISION_BITS);
- int32x4_t d0 = vreinterpretq_s32_u32(vsubq_u32(wtd_avg, offset_vec));
-
- uint16x4_t d0_u16 = vqrshrun_n_s32(d0, ROUND_SHIFT);
- d0_u16 = vmin_u16(d0_u16, vget_low_u16(max));
-
- vst1_u16(dst_ptr, d0_u16);
-
- src_ptr += src_stride;
- dst_ptr += dst_stride;
- ref_ptr += ref_stride;
- } while (--h != 0);
- } else {
- do {
- int width = w;
- const uint16_t *src = src_ptr;
- const uint16_t *ref = ref_ptr;
- uint16_t *dst = dst_ptr;
- do {
- const uint16x8_t s = vld1q_u16(src);
- const uint16x8_t r = vld1q_u16(ref);
-
- uint32x4_t wtd_avg0 = vmull_u16(vget_low_u16(r), fwd_offset);
- wtd_avg0 = vmlal_u16(wtd_avg0, vget_low_u16(s), bck_offset);
- wtd_avg0 = vshrq_n_u32(wtd_avg0, DIST_PRECISION_BITS);
- int32x4_t d0 = vreinterpretq_s32_u32(vsubq_u32(wtd_avg0, offset_vec));
-
- uint32x4_t wtd_avg1 = vmull_u16(vget_high_u16(r), fwd_offset);
- wtd_avg1 = vmlal_u16(wtd_avg1, vget_high_u16(s), bck_offset);
- wtd_avg1 = vshrq_n_u32(wtd_avg1, DIST_PRECISION_BITS);
- int32x4_t d1 = vreinterpretq_s32_u32(vsubq_u32(wtd_avg1, offset_vec));
-
- uint16x8_t d01 = vcombine_u16(vqrshrun_n_s32(d0, ROUND_SHIFT),
- vqrshrun_n_s32(d1, ROUND_SHIFT));
- d01 = vminq_u16(d01, max);
- vst1q_u16(dst, d01);
-
- src += 8;
- ref += 8;
- dst += 8;
- width -= 8;
- } while (width != 0);
- src_ptr += src_stride;
- dst_ptr += dst_stride;
- ref_ptr += ref_stride;
- } while (--h != 0);
- }
-}
-
static INLINE uint16x4_t highbd_12_convolve6_4(
const int16x4_t s0, const int16x4_t s1, const int16x4_t s2,
const int16x4_t s3, const int16x4_t s4, const int16x4_t s5,
@@ -743,9 +486,6 @@ void av1_highbd_dist_wtd_convolve_x_neon(
const int im_stride = MAX_SB_SIZE;
const int horiz_offset = filter_params_x->taps / 2 - 1;
assert(FILTER_BITS == COMPOUND_ROUND1_BITS);
- const int offset_bits = bd + 2 * FILTER_BITS - conv_params->round_0;
- const int offset_avg = (1 << (offset_bits - conv_params->round_1)) +
- (1 << (offset_bits - conv_params->round_1 - 1));
const int offset_convolve = (1 << (conv_params->round_0 - 1)) +
(1 << (bd + FILTER_BITS)) +
(1 << (bd + FILTER_BITS - 1));
@@ -768,10 +508,10 @@ void av1_highbd_dist_wtd_convolve_x_neon(
}
if (conv_params->use_dist_wtd_comp_avg) {
highbd_12_dist_wtd_comp_avg_neon(im_block, im_stride, dst, dst_stride,
- w, h, conv_params, offset_avg, bd);
+ w, h, conv_params);
} else {
highbd_12_comp_avg_neon(im_block, im_stride, dst, dst_stride, w, h,
- conv_params, offset_avg, bd);
+ conv_params);
}
} else {
if (x_filter_taps <= 6 && w != 4) {
@@ -795,10 +535,10 @@ void av1_highbd_dist_wtd_convolve_x_neon(
}
if (conv_params->use_dist_wtd_comp_avg) {
highbd_dist_wtd_comp_avg_neon(im_block, im_stride, dst, dst_stride, w,
- h, conv_params, offset_avg, bd);
+ h, conv_params, bd);
} else {
highbd_comp_avg_neon(im_block, im_stride, dst, dst_stride, w, h,
- conv_params, offset_avg, bd);
+ conv_params, bd);
}
} else {
if (x_filter_taps <= 6 && w != 4) {
@@ -971,6 +711,212 @@ static INLINE void highbd_dist_wtd_convolve_y_6tap_neon(
}
}
+static INLINE uint16x4_t highbd_12_convolve4_4(
+ const int16x4_t s0, const int16x4_t s1, const int16x4_t s2,
+ const int16x4_t s3, const int16x4_t filter, const int32x4_t offset) {
+ int32x4_t sum = vmlal_lane_s16(offset, s0, filter, 0);
+ sum = vmlal_lane_s16(sum, s1, filter, 1);
+ sum = vmlal_lane_s16(sum, s2, filter, 2);
+ sum = vmlal_lane_s16(sum, s3, filter, 3);
+
+ return vqshrun_n_s32(sum, ROUND0_BITS + 2);
+}
+
+static INLINE uint16x8_t highbd_12_convolve4_8(
+ const int16x8_t s0, const int16x8_t s1, const int16x8_t s2,
+ const int16x8_t s3, const int16x4_t filter, const int32x4_t offset) {
+ int32x4_t sum0 = vmlal_lane_s16(offset, vget_low_s16(s0), filter, 0);
+ sum0 = vmlal_lane_s16(sum0, vget_low_s16(s1), filter, 1);
+ sum0 = vmlal_lane_s16(sum0, vget_low_s16(s2), filter, 2);
+ sum0 = vmlal_lane_s16(sum0, vget_low_s16(s3), filter, 3);
+
+ int32x4_t sum1 = vmlal_lane_s16(offset, vget_high_s16(s0), filter, 0);
+ sum1 = vmlal_lane_s16(sum1, vget_high_s16(s1), filter, 1);
+ sum1 = vmlal_lane_s16(sum1, vget_high_s16(s2), filter, 2);
+ sum1 = vmlal_lane_s16(sum1, vget_high_s16(s3), filter, 3);
+
+ return vcombine_u16(vqshrun_n_s32(sum0, ROUND0_BITS + 2),
+ vqshrun_n_s32(sum1, ROUND0_BITS + 2));
+}
+
+static INLINE void highbd_12_dist_wtd_convolve_y_4tap_neon(
+ const uint16_t *src_ptr, int src_stride, uint16_t *dst_ptr, int dst_stride,
+ int w, int h, const int16_t *y_filter_ptr, const int offset) {
+ const int16x4_t y_filter = vld1_s16(y_filter_ptr + 2);
+ const int32x4_t offset_vec = vdupq_n_s32(offset);
+
+ if (w == 4) {
+ const int16_t *s = (const int16_t *)src_ptr;
+ uint16_t *d = dst_ptr;
+
+ int16x4_t s0, s1, s2;
+ load_s16_4x3(s, src_stride, &s0, &s1, &s2);
+ s += 3 * src_stride;
+
+ do {
+ int16x4_t s3, s4, s5, s6;
+ load_s16_4x4(s, src_stride, &s3, &s4, &s5, &s6);
+
+ uint16x4_t d0 =
+ highbd_12_convolve4_4(s0, s1, s2, s3, y_filter, offset_vec);
+ uint16x4_t d1 =
+ highbd_12_convolve4_4(s1, s2, s3, s4, y_filter, offset_vec);
+ uint16x4_t d2 =
+ highbd_12_convolve4_4(s2, s3, s4, s5, y_filter, offset_vec);
+ uint16x4_t d3 =
+ highbd_12_convolve4_4(s3, s4, s5, s6, y_filter, offset_vec);
+
+ store_u16_4x4(d, dst_stride, d0, d1, d2, d3);
+
+ s0 = s4;
+ s1 = s5;
+ s2 = s6;
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ } else {
+ do {
+ int height = h;
+ const int16_t *s = (const int16_t *)src_ptr;
+ uint16_t *d = dst_ptr;
+
+ int16x8_t s0, s1, s2;
+ load_s16_8x3(s, src_stride, &s0, &s1, &s2);
+ s += 3 * src_stride;
+
+ do {
+ int16x8_t s3, s4, s5, s6;
+ load_s16_8x4(s, src_stride, &s3, &s4, &s5, &s6);
+
+ uint16x8_t d0 =
+ highbd_12_convolve4_8(s0, s1, s2, s3, y_filter, offset_vec);
+ uint16x8_t d1 =
+ highbd_12_convolve4_8(s1, s2, s3, s4, y_filter, offset_vec);
+ uint16x8_t d2 =
+ highbd_12_convolve4_8(s2, s3, s4, s5, y_filter, offset_vec);
+ uint16x8_t d3 =
+ highbd_12_convolve4_8(s3, s4, s5, s6, y_filter, offset_vec);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s0 = s4;
+ s1 = s5;
+ s2 = s6;
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ src_ptr += 8;
+ dst_ptr += 8;
+ w -= 8;
+ } while (w != 0);
+ }
+}
+
+static INLINE uint16x4_t highbd_convolve4_4(
+ const int16x4_t s0, const int16x4_t s1, const int16x4_t s2,
+ const int16x4_t s3, const int16x4_t filter, const int32x4_t offset) {
+ int32x4_t sum = vmlal_lane_s16(offset, s0, filter, 0);
+ sum = vmlal_lane_s16(sum, s1, filter, 1);
+ sum = vmlal_lane_s16(sum, s2, filter, 2);
+ sum = vmlal_lane_s16(sum, s3, filter, 3);
+
+ return vqshrun_n_s32(sum, ROUND0_BITS);
+}
+
+static INLINE uint16x8_t highbd_convolve4_8(
+ const int16x8_t s0, const int16x8_t s1, const int16x8_t s2,
+ const int16x8_t s3, const int16x4_t filter, const int32x4_t offset) {
+ int32x4_t sum0 = vmlal_lane_s16(offset, vget_low_s16(s0), filter, 0);
+ sum0 = vmlal_lane_s16(sum0, vget_low_s16(s1), filter, 1);
+ sum0 = vmlal_lane_s16(sum0, vget_low_s16(s2), filter, 2);
+ sum0 = vmlal_lane_s16(sum0, vget_low_s16(s3), filter, 3);
+
+ int32x4_t sum1 = vmlal_lane_s16(offset, vget_high_s16(s0), filter, 0);
+ sum1 = vmlal_lane_s16(sum1, vget_high_s16(s1), filter, 1);
+ sum1 = vmlal_lane_s16(sum1, vget_high_s16(s2), filter, 2);
+ sum1 = vmlal_lane_s16(sum1, vget_high_s16(s3), filter, 3);
+
+ return vcombine_u16(vqshrun_n_s32(sum0, ROUND0_BITS),
+ vqshrun_n_s32(sum1, ROUND0_BITS));
+}
+
+static INLINE void highbd_dist_wtd_convolve_y_4tap_neon(
+ const uint16_t *src_ptr, int src_stride, uint16_t *dst_ptr, int dst_stride,
+ int w, int h, const int16_t *y_filter_ptr, const int offset) {
+ const int16x4_t y_filter = vld1_s16(y_filter_ptr + 2);
+ const int32x4_t offset_vec = vdupq_n_s32(offset);
+
+ if (w == 4) {
+ const int16_t *s = (const int16_t *)src_ptr;
+ uint16_t *d = dst_ptr;
+
+ int16x4_t s0, s1, s2;
+ load_s16_4x3(s, src_stride, &s0, &s1, &s2);
+ s += 3 * src_stride;
+
+ do {
+ int16x4_t s3, s4, s5, s6;
+ load_s16_4x4(s, src_stride, &s3, &s4, &s5, &s6);
+
+ uint16x4_t d0 = highbd_convolve4_4(s0, s1, s2, s3, y_filter, offset_vec);
+ uint16x4_t d1 = highbd_convolve4_4(s1, s2, s3, s4, y_filter, offset_vec);
+ uint16x4_t d2 = highbd_convolve4_4(s2, s3, s4, s5, y_filter, offset_vec);
+ uint16x4_t d3 = highbd_convolve4_4(s3, s4, s5, s6, y_filter, offset_vec);
+
+ store_u16_4x4(d, dst_stride, d0, d1, d2, d3);
+
+ s0 = s4;
+ s1 = s5;
+ s2 = s6;
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ } else {
+ do {
+ int height = h;
+ const int16_t *s = (const int16_t *)src_ptr;
+ uint16_t *d = dst_ptr;
+
+ int16x8_t s0, s1, s2;
+ load_s16_8x3(s, src_stride, &s0, &s1, &s2);
+ s += 3 * src_stride;
+
+ do {
+ int16x8_t s3, s4, s5, s6;
+ load_s16_8x4(s, src_stride, &s3, &s4, &s5, &s6);
+
+ uint16x8_t d0 =
+ highbd_convolve4_8(s0, s1, s2, s3, y_filter, offset_vec);
+ uint16x8_t d1 =
+ highbd_convolve4_8(s1, s2, s3, s4, y_filter, offset_vec);
+ uint16x8_t d2 =
+ highbd_convolve4_8(s2, s3, s4, s5, y_filter, offset_vec);
+ uint16x8_t d3 =
+ highbd_convolve4_8(s3, s4, s5, s6, y_filter, offset_vec);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s0 = s4;
+ s1 = s5;
+ s2 = s6;
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ src_ptr += 8;
+ dst_ptr += 8;
+ w -= 8;
+ } while (w != 0);
+ }
+}
+
static INLINE void highbd_12_dist_wtd_convolve_y_8tap_neon(
const uint16_t *src_ptr, int src_stride, uint16_t *dst_ptr, int dst_stride,
int w, int h, const int16_t *y_filter_ptr, const int offset) {
@@ -1148,9 +1094,6 @@ void av1_highbd_dist_wtd_convolve_y_neon(
const int im_stride = MAX_SB_SIZE;
const int vert_offset = filter_params_y->taps / 2 - 1;
assert(FILTER_BITS == COMPOUND_ROUND1_BITS);
- const int offset_bits = bd + 2 * FILTER_BITS - conv_params->round_0;
- const int round_offset_avg = (1 << (offset_bits - conv_params->round_1)) +
- (1 << (offset_bits - conv_params->round_1 - 1));
const int round_offset_conv = (1 << (conv_params->round_0 - 1)) +
(1 << (bd + FILTER_BITS)) +
(1 << (bd + FILTER_BITS - 1));
@@ -1162,7 +1105,11 @@ void av1_highbd_dist_wtd_convolve_y_neon(
if (bd == 12) {
if (conv_params->do_average) {
- if (y_filter_taps <= 6) {
+ if (y_filter_taps <= 4) {
+ highbd_12_dist_wtd_convolve_y_4tap_neon(
+ src + 2 * src_stride, src_stride, im_block, im_stride, w, h,
+ y_filter_ptr, round_offset_conv);
+ } else if (y_filter_taps == 6) {
highbd_12_dist_wtd_convolve_y_6tap_neon(
src + src_stride, src_stride, im_block, im_stride, w, h,
y_filter_ptr, round_offset_conv);
@@ -1173,14 +1120,17 @@ void av1_highbd_dist_wtd_convolve_y_neon(
}
if (conv_params->use_dist_wtd_comp_avg) {
highbd_12_dist_wtd_comp_avg_neon(im_block, im_stride, dst, dst_stride,
- w, h, conv_params, round_offset_avg,
- bd);
+ w, h, conv_params);
} else {
highbd_12_comp_avg_neon(im_block, im_stride, dst, dst_stride, w, h,
- conv_params, round_offset_avg, bd);
+ conv_params);
}
} else {
- if (y_filter_taps <= 6) {
+ if (y_filter_taps <= 4) {
+ highbd_12_dist_wtd_convolve_y_4tap_neon(
+ src + 2 * src_stride, src_stride, dst16, dst16_stride, w, h,
+ y_filter_ptr, round_offset_conv);
+ } else if (y_filter_taps == 6) {
highbd_12_dist_wtd_convolve_y_6tap_neon(
src + src_stride, src_stride, dst16, dst16_stride, w, h,
y_filter_ptr, round_offset_conv);
@@ -1192,7 +1142,11 @@ void av1_highbd_dist_wtd_convolve_y_neon(
}
} else {
if (conv_params->do_average) {
- if (y_filter_taps <= 6) {
+ if (y_filter_taps <= 4) {
+ highbd_dist_wtd_convolve_y_4tap_neon(src + 2 * src_stride, src_stride,
+ im_block, im_stride, w, h,
+ y_filter_ptr, round_offset_conv);
+ } else if (y_filter_taps == 6) {
highbd_dist_wtd_convolve_y_6tap_neon(src + src_stride, src_stride,
im_block, im_stride, w, h,
y_filter_ptr, round_offset_conv);
@@ -1203,13 +1157,17 @@ void av1_highbd_dist_wtd_convolve_y_neon(
}
if (conv_params->use_dist_wtd_comp_avg) {
highbd_dist_wtd_comp_avg_neon(im_block, im_stride, dst, dst_stride, w,
- h, conv_params, round_offset_avg, bd);
+ h, conv_params, bd);
} else {
highbd_comp_avg_neon(im_block, im_stride, dst, dst_stride, w, h,
- conv_params, round_offset_avg, bd);
+ conv_params, bd);
}
} else {
- if (y_filter_taps <= 6) {
+ if (y_filter_taps <= 4) {
+ highbd_dist_wtd_convolve_y_4tap_neon(src + 2 * src_stride, src_stride,
+ dst16, dst16_stride, w, h,
+ y_filter_ptr, round_offset_conv);
+ } else if (y_filter_taps == 6) {
highbd_dist_wtd_convolve_y_6tap_neon(src + src_stride, src_stride,
dst16, dst16_stride, w, h,
y_filter_ptr, round_offset_conv);
@@ -1285,18 +1243,18 @@ void av1_highbd_dist_wtd_convolve_2d_copy_neon(const uint16_t *src,
if (conv_params->use_dist_wtd_comp_avg) {
if (bd == 12) {
highbd_12_dist_wtd_comp_avg_neon(im_block, im_stride, dst, dst_stride,
- w, h, conv_params, round_offset, bd);
+ w, h, conv_params);
} else {
highbd_dist_wtd_comp_avg_neon(im_block, im_stride, dst, dst_stride, w,
- h, conv_params, round_offset, bd);
+ h, conv_params, bd);
}
} else {
if (bd == 12) {
highbd_12_comp_avg_neon(im_block, im_stride, dst, dst_stride, w, h,
- conv_params, round_offset, bd);
+ conv_params);
} else {
highbd_comp_avg_neon(im_block, im_stride, dst, dst_stride, w, h,
- conv_params, round_offset, bd);
+ conv_params, bd);
}
}
}
@@ -1949,9 +1907,6 @@ void av1_highbd_dist_wtd_convolve_2d_neon(
(1 << (bd + FILTER_BITS - 1)) + (1 << (conv_params->round_0 - 1));
const int y_offset_bits = bd + 2 * FILTER_BITS - conv_params->round_0;
const int round_offset_conv_y = (1 << y_offset_bits);
- const int round_offset_avg =
- ((1 << (y_offset_bits - conv_params->round_1)) +
- (1 << (y_offset_bits - conv_params->round_1 - 1)));
const uint16_t *src_ptr = src - vert_offset * src_stride - horiz_offset;
@@ -2012,19 +1967,18 @@ void av1_highbd_dist_wtd_convolve_2d_neon(
if (conv_params->use_dist_wtd_comp_avg) {
if (bd == 12) {
highbd_12_dist_wtd_comp_avg_neon(im_block2, im_stride, dst, dst_stride,
- w, h, conv_params, round_offset_avg,
- bd);
+ w, h, conv_params);
} else {
highbd_dist_wtd_comp_avg_neon(im_block2, im_stride, dst, dst_stride, w,
- h, conv_params, round_offset_avg, bd);
+ h, conv_params, bd);
}
} else {
if (bd == 12) {
highbd_12_comp_avg_neon(im_block2, im_stride, dst, dst_stride, w, h,
- conv_params, round_offset_avg, bd);
+ conv_params);
} else {
highbd_comp_avg_neon(im_block2, im_stride, dst, dst_stride, w, h,
- conv_params, round_offset_avg, bd);
+ conv_params, bd);
}
}
}
diff --git a/third_party/aom/av1/common/arm/highbd_compound_convolve_neon.h b/third_party/aom/av1/common/arm/highbd_compound_convolve_neon.h
new file mode 100644
index 0000000000..c9344f3adf
--- /dev/null
+++ b/third_party/aom/av1/common/arm/highbd_compound_convolve_neon.h
@@ -0,0 +1,293 @@
+/*
+ * Copyright (c) 2024, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#include <assert.h>
+#include <arm_neon.h>
+
+#include "config/aom_config.h"
+#include "config/av1_rtcd.h"
+
+#include "aom_dsp/aom_dsp_common.h"
+#include "aom_dsp/arm/mem_neon.h"
+#include "aom_ports/mem.h"
+
+#define ROUND_SHIFT 2 * FILTER_BITS - ROUND0_BITS - COMPOUND_ROUND1_BITS
+
+static INLINE void highbd_12_comp_avg_neon(const uint16_t *src_ptr,
+ int src_stride, uint16_t *dst_ptr,
+ int dst_stride, int w, int h,
+ ConvolveParams *conv_params) {
+ const int offset_bits = 12 + 2 * FILTER_BITS - ROUND0_BITS - 2;
+ const int offset = (1 << (offset_bits - COMPOUND_ROUND1_BITS)) +
+ (1 << (offset_bits - COMPOUND_ROUND1_BITS - 1));
+
+ CONV_BUF_TYPE *ref_ptr = conv_params->dst;
+ const int ref_stride = conv_params->dst_stride;
+ const uint16x4_t offset_vec = vdup_n_u16((uint16_t)offset);
+ const uint16x8_t max = vdupq_n_u16((1 << 12) - 1);
+
+ if (w == 4) {
+ do {
+ const uint16x4_t src = vld1_u16(src_ptr);
+ const uint16x4_t ref = vld1_u16(ref_ptr);
+
+ uint16x4_t avg = vhadd_u16(src, ref);
+ int32x4_t d0 = vreinterpretq_s32_u32(vsubl_u16(avg, offset_vec));
+
+ uint16x4_t d0_u16 = vqrshrun_n_s32(d0, ROUND_SHIFT - 2);
+ d0_u16 = vmin_u16(d0_u16, vget_low_u16(max));
+
+ vst1_u16(dst_ptr, d0_u16);
+
+ src_ptr += src_stride;
+ ref_ptr += ref_stride;
+ dst_ptr += dst_stride;
+ } while (--h != 0);
+ } else {
+ do {
+ int width = w;
+ const uint16_t *src = src_ptr;
+ const uint16_t *ref = ref_ptr;
+ uint16_t *dst = dst_ptr;
+ do {
+ const uint16x8_t s = vld1q_u16(src);
+ const uint16x8_t r = vld1q_u16(ref);
+
+ uint16x8_t avg = vhaddq_u16(s, r);
+ int32x4_t d0_lo =
+ vreinterpretq_s32_u32(vsubl_u16(vget_low_u16(avg), offset_vec));
+ int32x4_t d0_hi =
+ vreinterpretq_s32_u32(vsubl_u16(vget_high_u16(avg), offset_vec));
+
+ uint16x8_t d0 = vcombine_u16(vqrshrun_n_s32(d0_lo, ROUND_SHIFT - 2),
+ vqrshrun_n_s32(d0_hi, ROUND_SHIFT - 2));
+ d0 = vminq_u16(d0, max);
+ vst1q_u16(dst, d0);
+
+ src += 8;
+ ref += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+
+ src_ptr += src_stride;
+ ref_ptr += ref_stride;
+ dst_ptr += dst_stride;
+ } while (--h != 0);
+ }
+}
+
+static INLINE void highbd_comp_avg_neon(const uint16_t *src_ptr, int src_stride,
+ uint16_t *dst_ptr, int dst_stride,
+ int w, int h,
+ ConvolveParams *conv_params,
+ const int bd) {
+ const int offset_bits = bd + 2 * FILTER_BITS - ROUND0_BITS;
+ const int offset = (1 << (offset_bits - COMPOUND_ROUND1_BITS)) +
+ (1 << (offset_bits - COMPOUND_ROUND1_BITS - 1));
+
+ CONV_BUF_TYPE *ref_ptr = conv_params->dst;
+ const int ref_stride = conv_params->dst_stride;
+ const uint16x4_t offset_vec = vdup_n_u16((uint16_t)offset);
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+
+ if (w == 4) {
+ do {
+ const uint16x4_t src = vld1_u16(src_ptr);
+ const uint16x4_t ref = vld1_u16(ref_ptr);
+
+ uint16x4_t avg = vhadd_u16(src, ref);
+ int32x4_t d0 = vreinterpretq_s32_u32(vsubl_u16(avg, offset_vec));
+
+ uint16x4_t d0_u16 = vqrshrun_n_s32(d0, ROUND_SHIFT);
+ d0_u16 = vmin_u16(d0_u16, vget_low_u16(max));
+
+ vst1_u16(dst_ptr, d0_u16);
+
+ src_ptr += src_stride;
+ ref_ptr += ref_stride;
+ dst_ptr += dst_stride;
+ } while (--h != 0);
+ } else {
+ do {
+ int width = w;
+ const uint16_t *src = src_ptr;
+ const uint16_t *ref = ref_ptr;
+ uint16_t *dst = dst_ptr;
+ do {
+ const uint16x8_t s = vld1q_u16(src);
+ const uint16x8_t r = vld1q_u16(ref);
+
+ uint16x8_t avg = vhaddq_u16(s, r);
+ int32x4_t d0_lo =
+ vreinterpretq_s32_u32(vsubl_u16(vget_low_u16(avg), offset_vec));
+ int32x4_t d0_hi =
+ vreinterpretq_s32_u32(vsubl_u16(vget_high_u16(avg), offset_vec));
+
+ uint16x8_t d0 = vcombine_u16(vqrshrun_n_s32(d0_lo, ROUND_SHIFT),
+ vqrshrun_n_s32(d0_hi, ROUND_SHIFT));
+ d0 = vminq_u16(d0, max);
+ vst1q_u16(dst, d0);
+
+ src += 8;
+ ref += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+
+ src_ptr += src_stride;
+ ref_ptr += ref_stride;
+ dst_ptr += dst_stride;
+ } while (--h != 0);
+ }
+}
+
+static INLINE void highbd_12_dist_wtd_comp_avg_neon(
+ const uint16_t *src_ptr, int src_stride, uint16_t *dst_ptr, int dst_stride,
+ int w, int h, ConvolveParams *conv_params) {
+ const int offset_bits = 12 + 2 * FILTER_BITS - ROUND0_BITS - 2;
+ const int offset = (1 << (offset_bits - COMPOUND_ROUND1_BITS)) +
+ (1 << (offset_bits - COMPOUND_ROUND1_BITS - 1));
+
+ CONV_BUF_TYPE *ref_ptr = conv_params->dst;
+ const int ref_stride = conv_params->dst_stride;
+ const uint32x4_t offset_vec = vdupq_n_u32(offset);
+ const uint16x8_t max = vdupq_n_u16((1 << 12) - 1);
+ uint16x4_t fwd_offset = vdup_n_u16(conv_params->fwd_offset);
+ uint16x4_t bck_offset = vdup_n_u16(conv_params->bck_offset);
+
+ // Weighted averaging
+ if (w == 4) {
+ do {
+ const uint16x4_t src = vld1_u16(src_ptr);
+ const uint16x4_t ref = vld1_u16(ref_ptr);
+
+ uint32x4_t wtd_avg = vmull_u16(ref, fwd_offset);
+ wtd_avg = vmlal_u16(wtd_avg, src, bck_offset);
+ wtd_avg = vshrq_n_u32(wtd_avg, DIST_PRECISION_BITS);
+ int32x4_t d0 = vreinterpretq_s32_u32(vsubq_u32(wtd_avg, offset_vec));
+
+ uint16x4_t d0_u16 = vqrshrun_n_s32(d0, ROUND_SHIFT - 2);
+ d0_u16 = vmin_u16(d0_u16, vget_low_u16(max));
+
+ vst1_u16(dst_ptr, d0_u16);
+
+ src_ptr += src_stride;
+ dst_ptr += dst_stride;
+ ref_ptr += ref_stride;
+ } while (--h != 0);
+ } else {
+ do {
+ int width = w;
+ const uint16_t *src = src_ptr;
+ const uint16_t *ref = ref_ptr;
+ uint16_t *dst = dst_ptr;
+ do {
+ const uint16x8_t s = vld1q_u16(src);
+ const uint16x8_t r = vld1q_u16(ref);
+
+ uint32x4_t wtd_avg0 = vmull_u16(vget_low_u16(r), fwd_offset);
+ wtd_avg0 = vmlal_u16(wtd_avg0, vget_low_u16(s), bck_offset);
+ wtd_avg0 = vshrq_n_u32(wtd_avg0, DIST_PRECISION_BITS);
+ int32x4_t d0 = vreinterpretq_s32_u32(vsubq_u32(wtd_avg0, offset_vec));
+
+ uint32x4_t wtd_avg1 = vmull_u16(vget_high_u16(r), fwd_offset);
+ wtd_avg1 = vmlal_u16(wtd_avg1, vget_high_u16(s), bck_offset);
+ wtd_avg1 = vshrq_n_u32(wtd_avg1, DIST_PRECISION_BITS);
+ int32x4_t d1 = vreinterpretq_s32_u32(vsubq_u32(wtd_avg1, offset_vec));
+
+ uint16x8_t d01 = vcombine_u16(vqrshrun_n_s32(d0, ROUND_SHIFT - 2),
+ vqrshrun_n_s32(d1, ROUND_SHIFT - 2));
+ d01 = vminq_u16(d01, max);
+ vst1q_u16(dst, d01);
+
+ src += 8;
+ ref += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ src_ptr += src_stride;
+ dst_ptr += dst_stride;
+ ref_ptr += ref_stride;
+ } while (--h != 0);
+ }
+}
+
+static INLINE void highbd_dist_wtd_comp_avg_neon(
+ const uint16_t *src_ptr, int src_stride, uint16_t *dst_ptr, int dst_stride,
+ int w, int h, ConvolveParams *conv_params, const int bd) {
+ const int offset_bits = bd + 2 * FILTER_BITS - ROUND0_BITS;
+ const int offset = (1 << (offset_bits - COMPOUND_ROUND1_BITS)) +
+ (1 << (offset_bits - COMPOUND_ROUND1_BITS - 1));
+
+ CONV_BUF_TYPE *ref_ptr = conv_params->dst;
+ const int ref_stride = conv_params->dst_stride;
+ const uint32x4_t offset_vec = vdupq_n_u32(offset);
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+ uint16x4_t fwd_offset = vdup_n_u16(conv_params->fwd_offset);
+ uint16x4_t bck_offset = vdup_n_u16(conv_params->bck_offset);
+
+ // Weighted averaging
+ if (w == 4) {
+ do {
+ const uint16x4_t src = vld1_u16(src_ptr);
+ const uint16x4_t ref = vld1_u16(ref_ptr);
+
+ uint32x4_t wtd_avg = vmull_u16(ref, fwd_offset);
+ wtd_avg = vmlal_u16(wtd_avg, src, bck_offset);
+ wtd_avg = vshrq_n_u32(wtd_avg, DIST_PRECISION_BITS);
+ int32x4_t d0 = vreinterpretq_s32_u32(vsubq_u32(wtd_avg, offset_vec));
+
+ uint16x4_t d0_u16 = vqrshrun_n_s32(d0, ROUND_SHIFT);
+ d0_u16 = vmin_u16(d0_u16, vget_low_u16(max));
+
+ vst1_u16(dst_ptr, d0_u16);
+
+ src_ptr += src_stride;
+ dst_ptr += dst_stride;
+ ref_ptr += ref_stride;
+ } while (--h != 0);
+ } else {
+ do {
+ int width = w;
+ const uint16_t *src = src_ptr;
+ const uint16_t *ref = ref_ptr;
+ uint16_t *dst = dst_ptr;
+ do {
+ const uint16x8_t s = vld1q_u16(src);
+ const uint16x8_t r = vld1q_u16(ref);
+
+ uint32x4_t wtd_avg0 = vmull_u16(vget_low_u16(r), fwd_offset);
+ wtd_avg0 = vmlal_u16(wtd_avg0, vget_low_u16(s), bck_offset);
+ wtd_avg0 = vshrq_n_u32(wtd_avg0, DIST_PRECISION_BITS);
+ int32x4_t d0 = vreinterpretq_s32_u32(vsubq_u32(wtd_avg0, offset_vec));
+
+ uint32x4_t wtd_avg1 = vmull_u16(vget_high_u16(r), fwd_offset);
+ wtd_avg1 = vmlal_u16(wtd_avg1, vget_high_u16(s), bck_offset);
+ wtd_avg1 = vshrq_n_u32(wtd_avg1, DIST_PRECISION_BITS);
+ int32x4_t d1 = vreinterpretq_s32_u32(vsubq_u32(wtd_avg1, offset_vec));
+
+ uint16x8_t d01 = vcombine_u16(vqrshrun_n_s32(d0, ROUND_SHIFT),
+ vqrshrun_n_s32(d1, ROUND_SHIFT));
+ d01 = vminq_u16(d01, max);
+ vst1q_u16(dst, d01);
+
+ src += 8;
+ ref += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ src_ptr += src_stride;
+ dst_ptr += dst_stride;
+ ref_ptr += ref_stride;
+ } while (--h != 0);
+ }
+}
diff --git a/third_party/aom/av1/common/arm/highbd_compound_convolve_sve2.c b/third_party/aom/av1/common/arm/highbd_compound_convolve_sve2.c
new file mode 100644
index 0000000000..1d6c9b4faf
--- /dev/null
+++ b/third_party/aom/av1/common/arm/highbd_compound_convolve_sve2.c
@@ -0,0 +1,1555 @@
+/*
+ * Copyright (c) 2024, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#include <assert.h>
+#include <arm_neon.h>
+
+#include "config/aom_config.h"
+#include "config/av1_rtcd.h"
+
+#include "aom_dsp/aom_dsp_common.h"
+#include "aom_dsp/arm/aom_neon_sve_bridge.h"
+#include "aom_dsp/arm/aom_neon_sve2_bridge.h"
+#include "aom_dsp/arm/mem_neon.h"
+#include "aom_ports/mem.h"
+#include "av1/common/convolve.h"
+#include "av1/common/filter.h"
+#include "av1/common/filter.h"
+#include "av1/common/arm/highbd_compound_convolve_neon.h"
+#include "av1/common/arm/highbd_convolve_neon.h"
+#include "av1/common/arm/highbd_convolve_sve2.h"
+
+DECLARE_ALIGNED(16, static const uint16_t, kDotProdTbl[32]) = {
+ 0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6,
+ 4, 5, 6, 7, 5, 6, 7, 0, 6, 7, 0, 1, 7, 0, 1, 2,
+};
+
+static INLINE uint16x8_t highbd_12_convolve8_8_x(int16x8_t s0[8],
+ int16x8_t filter,
+ int64x2_t offset) {
+ int64x2_t sum[8];
+ sum[0] = aom_sdotq_s16(offset, s0[0], filter);
+ sum[1] = aom_sdotq_s16(offset, s0[1], filter);
+ sum[2] = aom_sdotq_s16(offset, s0[2], filter);
+ sum[3] = aom_sdotq_s16(offset, s0[3], filter);
+ sum[4] = aom_sdotq_s16(offset, s0[4], filter);
+ sum[5] = aom_sdotq_s16(offset, s0[5], filter);
+ sum[6] = aom_sdotq_s16(offset, s0[6], filter);
+ sum[7] = aom_sdotq_s16(offset, s0[7], filter);
+
+ sum[0] = vpaddq_s64(sum[0], sum[1]);
+ sum[2] = vpaddq_s64(sum[2], sum[3]);
+ sum[4] = vpaddq_s64(sum[4], sum[5]);
+ sum[6] = vpaddq_s64(sum[6], sum[7]);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum[0]), vmovn_s64(sum[2]));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum[4]), vmovn_s64(sum[6]));
+
+ return vcombine_u16(vqrshrun_n_s32(sum0123, ROUND0_BITS + 2),
+ vqrshrun_n_s32(sum4567, ROUND0_BITS + 2));
+}
+
+static INLINE void highbd_12_dist_wtd_convolve_x_8tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *x_filter_ptr) {
+ const int64x1_t offset_vec =
+ vcreate_s64((1 << (12 + FILTER_BITS)) + (1 << (12 + FILTER_BITS - 1)));
+ const int64x2_t offset_lo = vcombine_s64(offset_vec, vdup_n_s64(0));
+
+ const int16x8_t filter = vld1q_s16(x_filter_ptr);
+
+ do {
+ const int16_t *s = (const int16_t *)src;
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[8], s1[8], s2[8], s3[8];
+ load_s16_8x8(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3],
+ &s0[4], &s0[5], &s0[6], &s0[7]);
+ load_s16_8x8(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3],
+ &s1[4], &s1[5], &s1[6], &s1[7]);
+ load_s16_8x8(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3],
+ &s2[4], &s2[5], &s2[6], &s2[7]);
+ load_s16_8x8(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3],
+ &s3[4], &s3[5], &s3[6], &s3[7]);
+
+ uint16x8_t d0 = highbd_12_convolve8_8_x(s0, filter, offset_lo);
+ uint16x8_t d1 = highbd_12_convolve8_8_x(s1, filter, offset_lo);
+ uint16x8_t d2 = highbd_12_convolve8_8_x(s2, filter, offset_lo);
+ uint16x8_t d3 = highbd_12_convolve8_8_x(s3, filter, offset_lo);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+}
+
+static INLINE uint16x8_t highbd_convolve8_8_x(int16x8_t s0[8], int16x8_t filter,
+ int64x2_t offset) {
+ int64x2_t sum[8];
+ sum[0] = aom_sdotq_s16(offset, s0[0], filter);
+ sum[1] = aom_sdotq_s16(offset, s0[1], filter);
+ sum[2] = aom_sdotq_s16(offset, s0[2], filter);
+ sum[3] = aom_sdotq_s16(offset, s0[3], filter);
+ sum[4] = aom_sdotq_s16(offset, s0[4], filter);
+ sum[5] = aom_sdotq_s16(offset, s0[5], filter);
+ sum[6] = aom_sdotq_s16(offset, s0[6], filter);
+ sum[7] = aom_sdotq_s16(offset, s0[7], filter);
+
+ sum[0] = vpaddq_s64(sum[0], sum[1]);
+ sum[2] = vpaddq_s64(sum[2], sum[3]);
+ sum[4] = vpaddq_s64(sum[4], sum[5]);
+ sum[6] = vpaddq_s64(sum[6], sum[7]);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum[0]), vmovn_s64(sum[2]));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum[4]), vmovn_s64(sum[6]));
+
+ return vcombine_u16(vqrshrun_n_s32(sum0123, ROUND0_BITS),
+ vqrshrun_n_s32(sum4567, ROUND0_BITS));
+}
+
+static INLINE void highbd_dist_wtd_convolve_x_8tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *x_filter_ptr, const int bd) {
+ const int64x1_t offset_vec =
+ vcreate_s64((1 << (bd + FILTER_BITS)) + (1 << (bd + FILTER_BITS - 1)));
+ const int64x2_t offset_lo = vcombine_s64(offset_vec, vdup_n_s64(0));
+
+ const int16x8_t filter = vld1q_s16(x_filter_ptr);
+
+ do {
+ const int16_t *s = (const int16_t *)src;
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[8], s1[8], s2[8], s3[8];
+ load_s16_8x8(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3],
+ &s0[4], &s0[5], &s0[6], &s0[7]);
+ load_s16_8x8(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3],
+ &s1[4], &s1[5], &s1[6], &s1[7]);
+ load_s16_8x8(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3],
+ &s2[4], &s2[5], &s2[6], &s2[7]);
+ load_s16_8x8(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3],
+ &s3[4], &s3[5], &s3[6], &s3[7]);
+
+ uint16x8_t d0 = highbd_convolve8_8_x(s0, filter, offset_lo);
+ uint16x8_t d1 = highbd_convolve8_8_x(s1, filter, offset_lo);
+ uint16x8_t d2 = highbd_convolve8_8_x(s2, filter, offset_lo);
+ uint16x8_t d3 = highbd_convolve8_8_x(s3, filter, offset_lo);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+}
+
+// clang-format off
+DECLARE_ALIGNED(16, static const uint16_t, kDeinterleaveTbl[8]) = {
+ 0, 2, 4, 6, 1, 3, 5, 7,
+};
+// clang-format on
+
+static INLINE uint16x4_t highbd_12_convolve4_4_x(int16x8_t s0, int16x8_t filter,
+ int64x2_t offset,
+ uint16x8x2_t permute_tbl) {
+ int16x8_t permuted_samples0 = aom_tbl_s16(s0, permute_tbl.val[0]);
+ int16x8_t permuted_samples1 = aom_tbl_s16(s0, permute_tbl.val[1]);
+
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, permuted_samples0, filter, 0);
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, permuted_samples1, filter, 0);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+
+ return vqrshrun_n_s32(sum0123, ROUND0_BITS + 2);
+}
+
+static INLINE uint16x8_t highbd_12_convolve4_8_x(int16x8_t s0[4],
+ int16x8_t filter,
+ int64x2_t offset,
+ uint16x8_t tbl) {
+ int64x2_t sum04 = aom_svdot_lane_s16(offset, s0[0], filter, 0);
+ int64x2_t sum15 = aom_svdot_lane_s16(offset, s0[1], filter, 0);
+ int64x2_t sum26 = aom_svdot_lane_s16(offset, s0[2], filter, 0);
+ int64x2_t sum37 = aom_svdot_lane_s16(offset, s0[3], filter, 0);
+
+ int32x4_t sum0415 = vcombine_s32(vmovn_s64(sum04), vmovn_s64(sum15));
+ int32x4_t sum2637 = vcombine_s32(vmovn_s64(sum26), vmovn_s64(sum37));
+
+ uint16x8_t res = vcombine_u16(vqrshrun_n_s32(sum0415, ROUND0_BITS + 2),
+ vqrshrun_n_s32(sum2637, ROUND0_BITS + 2));
+ return aom_tbl_u16(res, tbl);
+}
+
+static INLINE void highbd_12_dist_wtd_convolve_x_4tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *x_filter_ptr) {
+ const int64x2_t offset =
+ vdupq_n_s64((1 << (12 + FILTER_BITS)) + (1 << (12 + FILTER_BITS - 1)));
+
+ const int16x4_t x_filter = vld1_s16(x_filter_ptr + 2);
+ const int16x8_t filter = vcombine_s16(x_filter, vdup_n_s16(0));
+
+ if (width == 4) {
+ uint16x8x2_t permute_tbl = vld1q_u16_x2(kDotProdTbl);
+
+ const int16_t *s = (const int16_t *)(src);
+
+ do {
+ int16x8_t s0, s1, s2, s3;
+ load_s16_8x4(s, src_stride, &s0, &s1, &s2, &s3);
+
+ uint16x4_t d0 = highbd_12_convolve4_4_x(s0, filter, offset, permute_tbl);
+ uint16x4_t d1 = highbd_12_convolve4_4_x(s1, filter, offset, permute_tbl);
+ uint16x4_t d2 = highbd_12_convolve4_4_x(s2, filter, offset, permute_tbl);
+ uint16x4_t d3 = highbd_12_convolve4_4_x(s3, filter, offset, permute_tbl);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ uint16x8_t idx = vld1q_u16(kDeinterleaveTbl);
+
+ do {
+ const int16_t *s = (const int16_t *)(src);
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[4], s1[4], s2[4], s3[4];
+ load_s16_8x4(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3]);
+ load_s16_8x4(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3]);
+ load_s16_8x4(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3]);
+ load_s16_8x4(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3]);
+
+ uint16x8_t d0 = highbd_12_convolve4_8_x(s0, filter, offset, idx);
+ uint16x8_t d1 = highbd_12_convolve4_8_x(s1, filter, offset, idx);
+ uint16x8_t d2 = highbd_12_convolve4_8_x(s2, filter, offset, idx);
+ uint16x8_t d3 = highbd_12_convolve4_8_x(s3, filter, offset, idx);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ }
+}
+
+static INLINE uint16x4_t highbd_convolve4_4_x(int16x8_t s0, int16x8_t filter,
+ int64x2_t offset,
+ uint16x8x2_t permute_tbl) {
+ int16x8_t permuted_samples0 = aom_tbl_s16(s0, permute_tbl.val[0]);
+ int16x8_t permuted_samples1 = aom_tbl_s16(s0, permute_tbl.val[1]);
+
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, permuted_samples0, filter, 0);
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, permuted_samples1, filter, 0);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+
+ return vqrshrun_n_s32(sum0123, ROUND0_BITS);
+}
+
+static INLINE uint16x8_t highbd_convolve4_8_x(int16x8_t s0[4], int16x8_t filter,
+ int64x2_t offset,
+ uint16x8_t tbl) {
+ int64x2_t sum04 = aom_svdot_lane_s16(offset, s0[0], filter, 0);
+ int64x2_t sum15 = aom_svdot_lane_s16(offset, s0[1], filter, 0);
+ int64x2_t sum26 = aom_svdot_lane_s16(offset, s0[2], filter, 0);
+ int64x2_t sum37 = aom_svdot_lane_s16(offset, s0[3], filter, 0);
+
+ int32x4_t sum0415 = vcombine_s32(vmovn_s64(sum04), vmovn_s64(sum15));
+ int32x4_t sum2637 = vcombine_s32(vmovn_s64(sum26), vmovn_s64(sum37));
+
+ uint16x8_t res = vcombine_u16(vqrshrun_n_s32(sum0415, ROUND0_BITS),
+ vqrshrun_n_s32(sum2637, ROUND0_BITS));
+ return aom_tbl_u16(res, tbl);
+}
+
+static INLINE void highbd_dist_wtd_convolve_x_4tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *x_filter_ptr, const int bd) {
+ const int64x2_t offset =
+ vdupq_n_s64((1 << (bd + FILTER_BITS)) + (1 << (bd + FILTER_BITS - 1)));
+
+ const int16x4_t x_filter = vld1_s16(x_filter_ptr + 2);
+ const int16x8_t filter = vcombine_s16(x_filter, vdup_n_s16(0));
+
+ if (width == 4) {
+ uint16x8x2_t permute_tbl = vld1q_u16_x2(kDotProdTbl);
+
+ const int16_t *s = (const int16_t *)(src);
+
+ do {
+ int16x8_t s0, s1, s2, s3;
+ load_s16_8x4(s, src_stride, &s0, &s1, &s2, &s3);
+
+ uint16x4_t d0 = highbd_convolve4_4_x(s0, filter, offset, permute_tbl);
+ uint16x4_t d1 = highbd_convolve4_4_x(s1, filter, offset, permute_tbl);
+ uint16x4_t d2 = highbd_convolve4_4_x(s2, filter, offset, permute_tbl);
+ uint16x4_t d3 = highbd_convolve4_4_x(s3, filter, offset, permute_tbl);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ uint16x8_t idx = vld1q_u16(kDeinterleaveTbl);
+
+ do {
+ const int16_t *s = (const int16_t *)(src);
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[4], s1[4], s2[4], s3[4];
+ load_s16_8x4(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3]);
+ load_s16_8x4(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3]);
+ load_s16_8x4(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3]);
+ load_s16_8x4(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3]);
+
+ uint16x8_t d0 = highbd_convolve4_8_x(s0, filter, offset, idx);
+ uint16x8_t d1 = highbd_convolve4_8_x(s1, filter, offset, idx);
+ uint16x8_t d2 = highbd_convolve4_8_x(s2, filter, offset, idx);
+ uint16x8_t d3 = highbd_convolve4_8_x(s3, filter, offset, idx);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ }
+}
+
+void av1_highbd_dist_wtd_convolve_x_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride, int w,
+ int h, const InterpFilterParams *filter_params_x, const int subpel_x_qn,
+ ConvolveParams *conv_params, int bd) {
+ DECLARE_ALIGNED(16, uint16_t,
+ im_block[(MAX_SB_SIZE + MAX_FILTER_TAP) * MAX_SB_SIZE]);
+ CONV_BUF_TYPE *dst16 = conv_params->dst;
+ const int x_filter_taps = get_filter_tap(filter_params_x, subpel_x_qn);
+
+ if (x_filter_taps == 6) {
+ av1_highbd_dist_wtd_convolve_x_neon(src, src_stride, dst, dst_stride, w, h,
+ filter_params_x, subpel_x_qn,
+ conv_params, bd);
+ return;
+ }
+
+ int dst16_stride = conv_params->dst_stride;
+ const int im_stride = MAX_SB_SIZE;
+ const int horiz_offset = filter_params_x->taps / 2 - 1;
+ assert(FILTER_BITS == COMPOUND_ROUND1_BITS);
+
+ const int16_t *x_filter_ptr = av1_get_interp_filter_subpel_kernel(
+ filter_params_x, subpel_x_qn & SUBPEL_MASK);
+
+ src -= horiz_offset;
+
+ if (bd == 12) {
+ if (conv_params->do_average) {
+ if (x_filter_taps <= 4) {
+ highbd_12_dist_wtd_convolve_x_4tap_sve2(src + 2, src_stride, im_block,
+ im_stride, w, h, x_filter_ptr);
+ } else {
+ highbd_12_dist_wtd_convolve_x_8tap_sve2(src, src_stride, im_block,
+ im_stride, w, h, x_filter_ptr);
+ }
+
+ if (conv_params->use_dist_wtd_comp_avg) {
+ highbd_12_dist_wtd_comp_avg_neon(im_block, im_stride, dst, dst_stride,
+ w, h, conv_params);
+
+ } else {
+ highbd_12_comp_avg_neon(im_block, im_stride, dst, dst_stride, w, h,
+ conv_params);
+ }
+ } else {
+ if (x_filter_taps <= 4) {
+ highbd_12_dist_wtd_convolve_x_4tap_sve2(
+ src + 2, src_stride, dst16, dst16_stride, w, h, x_filter_ptr);
+ } else {
+ highbd_12_dist_wtd_convolve_x_8tap_sve2(
+ src, src_stride, dst16, dst16_stride, w, h, x_filter_ptr);
+ }
+ }
+ } else {
+ if (conv_params->do_average) {
+ if (x_filter_taps <= 4) {
+ highbd_dist_wtd_convolve_x_4tap_sve2(src + 2, src_stride, im_block,
+ im_stride, w, h, x_filter_ptr, bd);
+ } else {
+ highbd_dist_wtd_convolve_x_8tap_sve2(src, src_stride, im_block,
+ im_stride, w, h, x_filter_ptr, bd);
+ }
+
+ if (conv_params->use_dist_wtd_comp_avg) {
+ highbd_dist_wtd_comp_avg_neon(im_block, im_stride, dst, dst_stride, w,
+ h, conv_params, bd);
+ } else {
+ highbd_comp_avg_neon(im_block, im_stride, dst, dst_stride, w, h,
+ conv_params, bd);
+ }
+ } else {
+ if (x_filter_taps <= 4) {
+ highbd_dist_wtd_convolve_x_4tap_sve2(
+ src + 2, src_stride, dst16, dst16_stride, w, h, x_filter_ptr, bd);
+ } else {
+ highbd_dist_wtd_convolve_x_8tap_sve2(
+ src, src_stride, dst16, dst16_stride, w, h, x_filter_ptr, bd);
+ }
+ }
+ }
+}
+
+static INLINE uint16x4_t highbd_12_convolve8_4_y(int16x8_t samples_lo[2],
+ int16x8_t samples_hi[2],
+ int16x8_t filter,
+ int64x2_t offset) {
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, samples_lo[0], filter, 0);
+ sum01 = aom_svdot_lane_s16(sum01, samples_hi[0], filter, 1);
+
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, samples_lo[1], filter, 0);
+ sum23 = aom_svdot_lane_s16(sum23, samples_hi[1], filter, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+
+ return vqrshrun_n_s32(sum0123, ROUND0_BITS + 2);
+}
+
+static INLINE uint16x8_t highbd_12_convolve8_8_y(int16x8_t samples_lo[4],
+ int16x8_t samples_hi[4],
+ int16x8_t filter,
+ int64x2_t offset) {
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, samples_lo[0], filter, 0);
+ sum01 = aom_svdot_lane_s16(sum01, samples_hi[0], filter, 1);
+
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, samples_lo[1], filter, 0);
+ sum23 = aom_svdot_lane_s16(sum23, samples_hi[1], filter, 1);
+
+ int64x2_t sum45 = aom_svdot_lane_s16(offset, samples_lo[2], filter, 0);
+ sum45 = aom_svdot_lane_s16(sum45, samples_hi[2], filter, 1);
+
+ int64x2_t sum67 = aom_svdot_lane_s16(offset, samples_lo[3], filter, 0);
+ sum67 = aom_svdot_lane_s16(sum67, samples_hi[3], filter, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum45), vmovn_s64(sum67));
+
+ return vcombine_u16(vqrshrun_n_s32(sum0123, ROUND0_BITS + 2),
+ vqrshrun_n_s32(sum4567, ROUND0_BITS + 2));
+}
+
+static INLINE void highbd_12_dist_wtd_convolve_y_8tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *y_filter_ptr) {
+ const int64x2_t offset =
+ vdupq_n_s64((1 << (12 + FILTER_BITS)) + (1 << (12 + FILTER_BITS - 1)));
+ const int16x8_t y_filter = vld1q_s16(y_filter_ptr);
+
+ uint16x8x3_t merge_block_tbl = vld1q_u16_x3(kDotProdMergeBlockTbl);
+ // Scale indices by size of the true vector length to avoid reading from an
+ // 'undefined' portion of a vector on a system with SVE vectors > 128-bit.
+ uint16x8_t correction0 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000000000000ULL));
+ merge_block_tbl.val[0] = vaddq_u16(merge_block_tbl.val[0], correction0);
+ uint16x8_t correction1 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100000000ULL));
+ merge_block_tbl.val[1] = vaddq_u16(merge_block_tbl.val[1], correction1);
+
+ uint16x8_t correction2 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100010000ULL));
+ merge_block_tbl.val[2] = vaddq_u16(merge_block_tbl.val[2], correction2);
+
+ if (width == 4) {
+ int16_t *s = (int16_t *)src;
+ int16x4_t s0, s1, s2, s3, s4, s5, s6;
+ load_s16_4x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
+ s += 7 * src_stride;
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[2], s1234[2], s2345[2], s3456[2];
+ transpose_concat_4x4(s0, s1, s2, s3, s0123);
+ transpose_concat_4x4(s1, s2, s3, s4, s1234);
+ transpose_concat_4x4(s2, s3, s4, s5, s2345);
+ transpose_concat_4x4(s3, s4, s5, s6, s3456);
+
+ do {
+ int16x4_t s7, s8, s9, s10;
+ load_s16_4x4(s, src_stride, &s7, &s8, &s9, &s10);
+
+ int16x8_t s4567[2], s5678[2], s6789[2], s789A[2];
+ // Transpose and shuffle the 4 lines that were loaded.
+ transpose_concat_4x4(s7, s8, s9, s10, s789A);
+
+ // Merge new data into block from previous iteration.
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[0], s4567);
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[1], s5678);
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[2], s6789);
+
+ uint16x4_t d0 = highbd_12_convolve8_4_y(s0123, s4567, y_filter, offset);
+ uint16x4_t d1 = highbd_12_convolve8_4_y(s1234, s5678, y_filter, offset);
+ uint16x4_t d2 = highbd_12_convolve8_4_y(s2345, s6789, y_filter, offset);
+ uint16x4_t d3 = highbd_12_convolve8_4_y(s3456, s789A, y_filter, offset);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s3456[0] = s789A[0];
+ s3456[1] = s789A[1];
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ do {
+ int h = height;
+ int16_t *s = (int16_t *)src;
+ uint16_t *d = dst;
+
+ int16x8_t s0, s1, s2, s3, s4, s5, s6;
+ load_s16_8x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
+ s += 7 * src_stride;
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[4], s1234[4], s2345[4], s3456[4];
+ transpose_concat_8x4(s0, s1, s2, s3, s0123);
+ transpose_concat_8x4(s1, s2, s3, s4, s1234);
+ transpose_concat_8x4(s2, s3, s4, s5, s2345);
+ transpose_concat_8x4(s3, s4, s5, s6, s3456);
+
+ do {
+ int16x8_t s7, s8, s9, s10;
+ load_s16_8x4(s, src_stride, &s7, &s8, &s9, &s10);
+ int16x8_t s4567[4], s5678[4], s6789[4], s789A[4];
+
+ // Transpose and shuffle the 4 lines that were loaded.
+ transpose_concat_8x4(s7, s8, s9, s10, s789A);
+
+ // Merge new data into block from previous iteration.
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[0], s4567);
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[1], s5678);
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[2], s6789);
+
+ uint16x8_t d0 = highbd_12_convolve8_8_y(s0123, s4567, y_filter, offset);
+ uint16x8_t d1 = highbd_12_convolve8_8_y(s1234, s5678, y_filter, offset);
+ uint16x8_t d2 = highbd_12_convolve8_8_y(s2345, s6789, y_filter, offset);
+ uint16x8_t d3 = highbd_12_convolve8_8_y(s3456, s789A, y_filter, offset);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s0123[2] = s4567[2];
+ s0123[3] = s4567[3];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s1234[2] = s5678[2];
+ s1234[3] = s5678[3];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s2345[2] = s6789[2];
+ s2345[3] = s6789[3];
+ s3456[0] = s789A[0];
+ s3456[1] = s789A[1];
+ s3456[2] = s789A[2];
+ s3456[3] = s789A[3];
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ src += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ }
+}
+
+static INLINE uint16x4_t highbd_convolve8_4_y(int16x8_t samples_lo[2],
+ int16x8_t samples_hi[2],
+ int16x8_t filter,
+ int64x2_t offset) {
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, samples_lo[0], filter, 0);
+ sum01 = aom_svdot_lane_s16(sum01, samples_hi[0], filter, 1);
+
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, samples_lo[1], filter, 0);
+ sum23 = aom_svdot_lane_s16(sum23, samples_hi[1], filter, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+
+ return vqrshrun_n_s32(sum0123, ROUND0_BITS);
+}
+
+static INLINE uint16x8_t highbd_convolve8_8_y(int16x8_t samples_lo[4],
+ int16x8_t samples_hi[4],
+ int16x8_t filter,
+ int64x2_t offset) {
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, samples_lo[0], filter, 0);
+ sum01 = aom_svdot_lane_s16(sum01, samples_hi[0], filter, 1);
+
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, samples_lo[1], filter, 0);
+ sum23 = aom_svdot_lane_s16(sum23, samples_hi[1], filter, 1);
+
+ int64x2_t sum45 = aom_svdot_lane_s16(offset, samples_lo[2], filter, 0);
+ sum45 = aom_svdot_lane_s16(sum45, samples_hi[2], filter, 1);
+
+ int64x2_t sum67 = aom_svdot_lane_s16(offset, samples_lo[3], filter, 0);
+ sum67 = aom_svdot_lane_s16(sum67, samples_hi[3], filter, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum45), vmovn_s64(sum67));
+
+ return vcombine_u16(vqrshrun_n_s32(sum0123, ROUND0_BITS),
+ vqrshrun_n_s32(sum4567, ROUND0_BITS));
+}
+
+static INLINE void highbd_dist_wtd_convolve_y_8tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *y_filter_ptr, const int bd) {
+ const int64x2_t offset =
+ vdupq_n_s64((1 << (bd + FILTER_BITS)) + (1 << (bd + FILTER_BITS - 1)));
+ const int16x8_t y_filter = vld1q_s16(y_filter_ptr);
+
+ uint16x8x3_t merge_block_tbl = vld1q_u16_x3(kDotProdMergeBlockTbl);
+ // Scale indices by size of the true vector length to avoid reading from an
+ // 'undefined' portion of a vector on a system with SVE vectors > 128-bit.
+ uint16x8_t correction0 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000000000000ULL));
+ merge_block_tbl.val[0] = vaddq_u16(merge_block_tbl.val[0], correction0);
+ uint16x8_t correction1 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100000000ULL));
+ merge_block_tbl.val[1] = vaddq_u16(merge_block_tbl.val[1], correction1);
+
+ uint16x8_t correction2 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100010000ULL));
+ merge_block_tbl.val[2] = vaddq_u16(merge_block_tbl.val[2], correction2);
+
+ if (width == 4) {
+ int16_t *s = (int16_t *)src;
+ int16x4_t s0, s1, s2, s3, s4, s5, s6;
+ load_s16_4x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
+ s += 7 * src_stride;
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[2], s1234[2], s2345[2], s3456[2];
+ transpose_concat_4x4(s0, s1, s2, s3, s0123);
+ transpose_concat_4x4(s1, s2, s3, s4, s1234);
+ transpose_concat_4x4(s2, s3, s4, s5, s2345);
+ transpose_concat_4x4(s3, s4, s5, s6, s3456);
+
+ do {
+ int16x4_t s7, s8, s9, s10;
+ load_s16_4x4(s, src_stride, &s7, &s8, &s9, &s10);
+
+ int16x8_t s4567[2], s5678[2], s6789[2], s789A[2];
+ // Transpose and shuffle the 4 lines that were loaded.
+ transpose_concat_4x4(s7, s8, s9, s10, s789A);
+
+ // Merge new data into block from previous iteration.
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[0], s4567);
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[1], s5678);
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[2], s6789);
+
+ uint16x4_t d0 = highbd_convolve8_4_y(s0123, s4567, y_filter, offset);
+ uint16x4_t d1 = highbd_convolve8_4_y(s1234, s5678, y_filter, offset);
+ uint16x4_t d2 = highbd_convolve8_4_y(s2345, s6789, y_filter, offset);
+ uint16x4_t d3 = highbd_convolve8_4_y(s3456, s789A, y_filter, offset);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s3456[0] = s789A[0];
+ s3456[1] = s789A[1];
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ do {
+ int h = height;
+ int16_t *s = (int16_t *)src;
+ uint16_t *d = dst;
+
+ int16x8_t s0, s1, s2, s3, s4, s5, s6;
+ load_s16_8x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
+ s += 7 * src_stride;
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[4], s1234[4], s2345[4], s3456[4];
+ transpose_concat_8x4(s0, s1, s2, s3, s0123);
+ transpose_concat_8x4(s1, s2, s3, s4, s1234);
+ transpose_concat_8x4(s2, s3, s4, s5, s2345);
+ transpose_concat_8x4(s3, s4, s5, s6, s3456);
+
+ do {
+ int16x8_t s7, s8, s9, s10;
+ load_s16_8x4(s, src_stride, &s7, &s8, &s9, &s10);
+ int16x8_t s4567[4], s5678[4], s6789[4], s789A[4];
+
+ // Transpose and shuffle the 4 lines that were loaded.
+ transpose_concat_8x4(s7, s8, s9, s10, s789A);
+
+ // Merge new data into block from previous iteration.
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[0], s4567);
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[1], s5678);
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[2], s6789);
+
+ uint16x8_t d0 = highbd_convolve8_8_y(s0123, s4567, y_filter, offset);
+ uint16x8_t d1 = highbd_convolve8_8_y(s1234, s5678, y_filter, offset);
+ uint16x8_t d2 = highbd_convolve8_8_y(s2345, s6789, y_filter, offset);
+ uint16x8_t d3 = highbd_convolve8_8_y(s3456, s789A, y_filter, offset);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s0123[2] = s4567[2];
+ s0123[3] = s4567[3];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s1234[2] = s5678[2];
+ s1234[3] = s5678[3];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s2345[2] = s6789[2];
+ s2345[3] = s6789[3];
+ s3456[0] = s789A[0];
+ s3456[1] = s789A[1];
+ s3456[2] = s789A[2];
+ s3456[3] = s789A[3];
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ src += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ }
+}
+
+void av1_highbd_dist_wtd_convolve_y_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride, int w,
+ int h, const InterpFilterParams *filter_params_y, const int subpel_y_qn,
+ ConvolveParams *conv_params, int bd) {
+ DECLARE_ALIGNED(16, uint16_t,
+ im_block[(MAX_SB_SIZE + MAX_FILTER_TAP) * MAX_SB_SIZE]);
+ CONV_BUF_TYPE *dst16 = conv_params->dst;
+ const int y_filter_taps = get_filter_tap(filter_params_y, subpel_y_qn);
+
+ if (y_filter_taps != 8) {
+ av1_highbd_dist_wtd_convolve_y_neon(src, src_stride, dst, dst_stride, w, h,
+ filter_params_y, subpel_y_qn,
+ conv_params, bd);
+ return;
+ }
+
+ int dst16_stride = conv_params->dst_stride;
+ const int im_stride = MAX_SB_SIZE;
+ const int vert_offset = filter_params_y->taps / 2 - 1;
+ assert(FILTER_BITS == COMPOUND_ROUND1_BITS);
+
+ const int16_t *y_filter_ptr = av1_get_interp_filter_subpel_kernel(
+ filter_params_y, subpel_y_qn & SUBPEL_MASK);
+
+ src -= vert_offset * src_stride;
+
+ if (bd == 12) {
+ if (conv_params->do_average) {
+ highbd_12_dist_wtd_convolve_y_8tap_sve2(src, src_stride, im_block,
+ im_stride, w, h, y_filter_ptr);
+ if (conv_params->use_dist_wtd_comp_avg) {
+ highbd_12_dist_wtd_comp_avg_neon(im_block, im_stride, dst, dst_stride,
+ w, h, conv_params);
+ } else {
+ highbd_12_comp_avg_neon(im_block, im_stride, dst, dst_stride, w, h,
+ conv_params);
+ }
+ } else {
+ highbd_12_dist_wtd_convolve_y_8tap_sve2(src, src_stride, dst16,
+ dst16_stride, w, h, y_filter_ptr);
+ }
+ } else {
+ if (conv_params->do_average) {
+ highbd_dist_wtd_convolve_y_8tap_sve2(src, src_stride, im_block, im_stride,
+ w, h, y_filter_ptr, bd);
+ if (conv_params->use_dist_wtd_comp_avg) {
+ highbd_dist_wtd_comp_avg_neon(im_block, im_stride, dst, dst_stride, w,
+ h, conv_params, bd);
+ } else {
+ highbd_comp_avg_neon(im_block, im_stride, dst, dst_stride, w, h,
+ conv_params, bd);
+ }
+ } else {
+ highbd_dist_wtd_convolve_y_8tap_sve2(src, src_stride, dst16, dst16_stride,
+ w, h, y_filter_ptr, bd);
+ }
+ }
+}
+
+static INLINE void highbd_12_dist_wtd_convolve_2d_horiz_8tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *x_filter_ptr) {
+ const int64x2_t offset = vdupq_n_s64(1 << (12 + FILTER_BITS - 2));
+ const int16x8_t filter = vld1q_s16(x_filter_ptr);
+
+ // We are only doing 8-tap and 4-tap vertical convolutions, therefore we know
+ // that im_h % 4 = 3, so we can do the loop across the whole block 4 rows at
+ // a time and then process the last 3 rows separately.
+
+ do {
+ const int16_t *s = (const int16_t *)src;
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[8], s1[8], s2[8], s3[8];
+ load_s16_8x8(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3],
+ &s0[4], &s0[5], &s0[6], &s0[7]);
+ load_s16_8x8(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3],
+ &s1[4], &s1[5], &s1[6], &s1[7]);
+ load_s16_8x8(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3],
+ &s2[4], &s2[5], &s2[6], &s2[7]);
+ load_s16_8x8(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3],
+ &s3[4], &s3[5], &s3[6], &s3[7]);
+
+ uint16x8_t d0 = highbd_12_convolve8_8_x(s0, filter, offset);
+ uint16x8_t d1 = highbd_12_convolve8_8_x(s1, filter, offset);
+ uint16x8_t d2 = highbd_12_convolve8_8_x(s2, filter, offset);
+ uint16x8_t d3 = highbd_12_convolve8_8_x(s3, filter, offset);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height > 4);
+
+ // Process final 3 rows.
+ const int16_t *s = (const int16_t *)src;
+ do {
+ int16x8_t s0[8], s1[8], s2[8];
+ load_s16_8x8(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3], &s0[4],
+ &s0[5], &s0[6], &s0[7]);
+ load_s16_8x8(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3], &s1[4],
+ &s1[5], &s1[6], &s1[7]);
+ load_s16_8x8(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3], &s2[4],
+ &s2[5], &s2[6], &s2[7]);
+
+ uint16x8_t d0 = highbd_12_convolve8_8_x(s0, filter, offset);
+ uint16x8_t d1 = highbd_12_convolve8_8_x(s1, filter, offset);
+ uint16x8_t d2 = highbd_12_convolve8_8_x(s2, filter, offset);
+
+ store_u16_8x3(dst, dst_stride, d0, d1, d2);
+ s += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+}
+
+static INLINE void highbd_dist_wtd_convolve_2d_horiz_8tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *x_filter_ptr, const int bd) {
+ const int64x2_t offset = vdupq_n_s64(1 << (bd + FILTER_BITS - 2));
+ const int16x8_t filter = vld1q_s16(x_filter_ptr);
+
+ // We are only doing 8-tap and 4-tap vertical convolutions, therefore we know
+ // that im_h % 4 = 3, so we can do the loop across the whole block 4 rows at
+ // a time and then process the last 3 rows separately.
+
+ do {
+ const int16_t *s = (const int16_t *)src;
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[8], s1[8], s2[8], s3[8];
+ load_s16_8x8(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3],
+ &s0[4], &s0[5], &s0[6], &s0[7]);
+ load_s16_8x8(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3],
+ &s1[4], &s1[5], &s1[6], &s1[7]);
+ load_s16_8x8(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3],
+ &s2[4], &s2[5], &s2[6], &s2[7]);
+ load_s16_8x8(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3],
+ &s3[4], &s3[5], &s3[6], &s3[7]);
+
+ uint16x8_t d0 = highbd_convolve8_8_x(s0, filter, offset);
+ uint16x8_t d1 = highbd_convolve8_8_x(s1, filter, offset);
+ uint16x8_t d2 = highbd_convolve8_8_x(s2, filter, offset);
+ uint16x8_t d3 = highbd_convolve8_8_x(s3, filter, offset);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height > 4);
+
+ // Process final 3 rows.
+ const int16_t *s = (const int16_t *)src;
+ do {
+ int16x8_t s0[8], s1[8], s2[8];
+ load_s16_8x8(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3], &s0[4],
+ &s0[5], &s0[6], &s0[7]);
+ load_s16_8x8(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3], &s1[4],
+ &s1[5], &s1[6], &s1[7]);
+ load_s16_8x8(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3], &s2[4],
+ &s2[5], &s2[6], &s2[7]);
+
+ uint16x8_t d0 = highbd_convolve8_8_x(s0, filter, offset);
+ uint16x8_t d1 = highbd_convolve8_8_x(s1, filter, offset);
+ uint16x8_t d2 = highbd_convolve8_8_x(s2, filter, offset);
+
+ store_u16_8x3(dst, dst_stride, d0, d1, d2);
+ s += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+}
+
+static INLINE void highbd_12_dist_wtd_convolve_2d_horiz_4tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *x_filter_ptr) {
+ const int64x2_t offset = vdupq_n_s64(1 << (12 + FILTER_BITS - 1));
+ const int16x4_t x_filter = vld1_s16(x_filter_ptr + 2);
+ const int16x8_t filter = vcombine_s16(x_filter, vdup_n_s16(0));
+
+ // We are only doing 8-tap and 4-tap vertical convolutions, therefore we know
+ // that im_h % 4 = 3, so we can do the loop across the whole block 4 rows at
+ // a time and then process the last 3 rows separately.
+
+ if (width == 4) {
+ uint16x8x2_t permute_tbl = vld1q_u16_x2(kDotProdTbl);
+
+ const int16_t *s = (const int16_t *)(src);
+
+ do {
+ int16x8_t s0, s1, s2, s3;
+ load_s16_8x4(s, src_stride, &s0, &s1, &s2, &s3);
+
+ uint16x4_t d0 = highbd_12_convolve4_4_x(s0, filter, offset, permute_tbl);
+ uint16x4_t d1 = highbd_12_convolve4_4_x(s1, filter, offset, permute_tbl);
+ uint16x4_t d2 = highbd_12_convolve4_4_x(s2, filter, offset, permute_tbl);
+ uint16x4_t d3 = highbd_12_convolve4_4_x(s3, filter, offset, permute_tbl);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height > 4);
+
+ // Process final 3 rows.
+ int16x8_t s0, s1, s2;
+ load_s16_8x3(s, src_stride, &s0, &s1, &s2);
+
+ uint16x4_t d0 = highbd_12_convolve4_4_x(s0, filter, offset, permute_tbl);
+ uint16x4_t d1 = highbd_12_convolve4_4_x(s1, filter, offset, permute_tbl);
+ uint16x4_t d2 = highbd_12_convolve4_4_x(s2, filter, offset, permute_tbl);
+
+ store_u16_4x3(dst, dst_stride, d0, d1, d2);
+
+ } else {
+ uint16x8_t idx = vld1q_u16(kDeinterleaveTbl);
+
+ do {
+ const int16_t *s = (const int16_t *)(src);
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[4], s1[4], s2[4], s3[4];
+ load_s16_8x4(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3]);
+ load_s16_8x4(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3]);
+ load_s16_8x4(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3]);
+ load_s16_8x4(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3]);
+
+ uint16x8_t d0 = highbd_12_convolve4_8_x(s0, filter, offset, idx);
+ uint16x8_t d1 = highbd_12_convolve4_8_x(s1, filter, offset, idx);
+ uint16x8_t d2 = highbd_12_convolve4_8_x(s2, filter, offset, idx);
+ uint16x8_t d3 = highbd_12_convolve4_8_x(s3, filter, offset, idx);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height > 4);
+
+ // Process final 3 rows.
+ const int16_t *s = (const int16_t *)(src);
+
+ do {
+ int16x8_t s0[4], s1[4], s2[4];
+ load_s16_8x4(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3]);
+ load_s16_8x4(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3]);
+ load_s16_8x4(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3]);
+
+ uint16x8_t d0 = highbd_12_convolve4_8_x(s0, filter, offset, idx);
+ uint16x8_t d1 = highbd_12_convolve4_8_x(s1, filter, offset, idx);
+ uint16x8_t d2 = highbd_12_convolve4_8_x(s2, filter, offset, idx);
+
+ store_u16_8x3(dst, dst_stride, d0, d1, d2);
+
+ s += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ }
+}
+
+static INLINE void highbd_dist_wtd_convolve_2d_horiz_4tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *x_filter_ptr, const int bd) {
+ const int64x2_t offset = vdupq_n_s64(1 << (bd + FILTER_BITS - 1));
+ const int16x4_t x_filter = vld1_s16(x_filter_ptr + 2);
+ const int16x8_t filter = vcombine_s16(x_filter, vdup_n_s16(0));
+
+ // We are only doing 8-tap and 4-tap vertical convolutions, therefore we know
+ // that im_h % 4 = 3, so we can do the loop across the whole block 4 rows at
+ // a time and then process the last 3 rows separately.
+
+ if (width == 4) {
+ uint16x8x2_t permute_tbl = vld1q_u16_x2(kDotProdTbl);
+
+ const int16_t *s = (const int16_t *)(src);
+
+ do {
+ int16x8_t s0, s1, s2, s3;
+ load_s16_8x4(s, src_stride, &s0, &s1, &s2, &s3);
+
+ uint16x4_t d0 = highbd_convolve4_4_x(s0, filter, offset, permute_tbl);
+ uint16x4_t d1 = highbd_convolve4_4_x(s1, filter, offset, permute_tbl);
+ uint16x4_t d2 = highbd_convolve4_4_x(s2, filter, offset, permute_tbl);
+ uint16x4_t d3 = highbd_convolve4_4_x(s3, filter, offset, permute_tbl);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height > 4);
+
+ // Process final 3 rows.
+ int16x8_t s0, s1, s2;
+ load_s16_8x3(s, src_stride, &s0, &s1, &s2);
+
+ uint16x4_t d0 = highbd_convolve4_4_x(s0, filter, offset, permute_tbl);
+ uint16x4_t d1 = highbd_convolve4_4_x(s1, filter, offset, permute_tbl);
+ uint16x4_t d2 = highbd_convolve4_4_x(s2, filter, offset, permute_tbl);
+
+ store_u16_4x3(dst, dst_stride, d0, d1, d2);
+ } else {
+ uint16x8_t idx = vld1q_u16(kDeinterleaveTbl);
+
+ do {
+ const int16_t *s = (const int16_t *)(src);
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[4], s1[4], s2[4], s3[4];
+ load_s16_8x4(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3]);
+ load_s16_8x4(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3]);
+ load_s16_8x4(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3]);
+ load_s16_8x4(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3]);
+
+ uint16x8_t d0 = highbd_convolve4_8_x(s0, filter, offset, idx);
+ uint16x8_t d1 = highbd_convolve4_8_x(s1, filter, offset, idx);
+ uint16x8_t d2 = highbd_convolve4_8_x(s2, filter, offset, idx);
+ uint16x8_t d3 = highbd_convolve4_8_x(s3, filter, offset, idx);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height > 4);
+
+ // Process final 3 rows.
+ const int16_t *s = (const int16_t *)(src);
+
+ do {
+ int16x8_t s0[4], s1[4], s2[4];
+ load_s16_8x4(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3]);
+ load_s16_8x4(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3]);
+ load_s16_8x4(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3]);
+
+ uint16x8_t d0 = highbd_convolve4_8_x(s0, filter, offset, idx);
+ uint16x8_t d1 = highbd_convolve4_8_x(s1, filter, offset, idx);
+ uint16x8_t d2 = highbd_convolve4_8_x(s2, filter, offset, idx);
+
+ store_u16_8x3(dst, dst_stride, d0, d1, d2);
+
+ s += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ }
+}
+
+static INLINE uint16x4_t highbd_convolve8_4_2d_v(int16x8_t samples_lo[2],
+ int16x8_t samples_hi[2],
+ int16x8_t filter,
+ int64x2_t offset) {
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, samples_lo[0], filter, 0);
+ sum01 = aom_svdot_lane_s16(sum01, samples_hi[0], filter, 1);
+
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, samples_lo[1], filter, 0);
+ sum23 = aom_svdot_lane_s16(sum23, samples_hi[1], filter, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+
+ return vqrshrun_n_s32(sum0123, COMPOUND_ROUND1_BITS);
+}
+
+static INLINE uint16x8_t highbd_convolve8_8_2d_v(int16x8_t samples_lo[4],
+ int16x8_t samples_hi[4],
+ int16x8_t filter,
+ int64x2_t offset) {
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, samples_lo[0], filter, 0);
+ sum01 = aom_svdot_lane_s16(sum01, samples_hi[0], filter, 1);
+
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, samples_lo[1], filter, 0);
+ sum23 = aom_svdot_lane_s16(sum23, samples_hi[1], filter, 1);
+
+ int64x2_t sum45 = aom_svdot_lane_s16(offset, samples_lo[2], filter, 0);
+ sum45 = aom_svdot_lane_s16(sum45, samples_hi[2], filter, 1);
+
+ int64x2_t sum67 = aom_svdot_lane_s16(offset, samples_lo[3], filter, 0);
+ sum67 = aom_svdot_lane_s16(sum67, samples_hi[3], filter, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum45), vmovn_s64(sum67));
+
+ return vcombine_u16(vqrshrun_n_s32(sum0123, COMPOUND_ROUND1_BITS),
+ vqrshrun_n_s32(sum4567, COMPOUND_ROUND1_BITS));
+}
+
+static INLINE void highbd_dist_wtd_convolve_2d_vert_8tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *y_filter_ptr, int offset) {
+ const int16x8_t y_filter = vld1q_s16(y_filter_ptr);
+ const int64x2_t offset_s64 = vdupq_n_s64(offset);
+
+ uint16x8x3_t merge_block_tbl = vld1q_u16_x3(kDotProdMergeBlockTbl);
+ // Scale indices by size of the true vector length to avoid reading from an
+ // 'undefined' portion of a vector on a system with SVE vectors > 128-bit.
+ uint16x8_t correction0 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000000000000ULL));
+ merge_block_tbl.val[0] = vaddq_u16(merge_block_tbl.val[0], correction0);
+
+ uint16x8_t correction1 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100000000ULL));
+ merge_block_tbl.val[1] = vaddq_u16(merge_block_tbl.val[1], correction1);
+
+ uint16x8_t correction2 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100010000ULL));
+ merge_block_tbl.val[2] = vaddq_u16(merge_block_tbl.val[2], correction2);
+
+ if (width == 4) {
+ int16_t *s = (int16_t *)src;
+ int16x4_t s0, s1, s2, s3, s4, s5, s6;
+ load_s16_4x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
+ s += 7 * src_stride;
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[2], s1234[2], s2345[2], s3456[2];
+ transpose_concat_4x4(s0, s1, s2, s3, s0123);
+ transpose_concat_4x4(s1, s2, s3, s4, s1234);
+ transpose_concat_4x4(s2, s3, s4, s5, s2345);
+ transpose_concat_4x4(s3, s4, s5, s6, s3456);
+
+ do {
+ int16x4_t s7, s8, s9, s10;
+ load_s16_4x4(s, src_stride, &s7, &s8, &s9, &s10);
+
+ int16x8_t s4567[2], s5678[2], s6789[2], s789A[2];
+ // Transpose and shuffle the 4 lines that were loaded.
+ transpose_concat_4x4(s7, s8, s9, s10, s789A);
+
+ // Merge new data into block from previous iteration.
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[0], s4567);
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[1], s5678);
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[2], s6789);
+
+ uint16x4_t d0 =
+ highbd_convolve8_4_2d_v(s0123, s4567, y_filter, offset_s64);
+ uint16x4_t d1 =
+ highbd_convolve8_4_2d_v(s1234, s5678, y_filter, offset_s64);
+ uint16x4_t d2 =
+ highbd_convolve8_4_2d_v(s2345, s6789, y_filter, offset_s64);
+ uint16x4_t d3 =
+ highbd_convolve8_4_2d_v(s3456, s789A, y_filter, offset_s64);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s3456[0] = s789A[0];
+ s3456[1] = s789A[1];
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ do {
+ int h = height;
+ int16_t *s = (int16_t *)src;
+ uint16_t *d = dst;
+
+ int16x8_t s0, s1, s2, s3, s4, s5, s6;
+ load_s16_8x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
+ s += 7 * src_stride;
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[4], s1234[4], s2345[4], s3456[4];
+ transpose_concat_8x4(s0, s1, s2, s3, s0123);
+ transpose_concat_8x4(s1, s2, s3, s4, s1234);
+ transpose_concat_8x4(s2, s3, s4, s5, s2345);
+ transpose_concat_8x4(s3, s4, s5, s6, s3456);
+
+ do {
+ int16x8_t s7, s8, s9, s10;
+ load_s16_8x4(s, src_stride, &s7, &s8, &s9, &s10);
+ int16x8_t s4567[4], s5678[4], s6789[4], s789A[4];
+
+ // Transpose and shuffle the 4 lines that were loaded.
+ transpose_concat_8x4(s7, s8, s9, s10, s789A);
+
+ // Merge new data into block from previous iteration.
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[0], s4567);
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[1], s5678);
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[2], s6789);
+
+ uint16x8_t d0 =
+ highbd_convolve8_8_2d_v(s0123, s4567, y_filter, offset_s64);
+ uint16x8_t d1 =
+ highbd_convolve8_8_2d_v(s1234, s5678, y_filter, offset_s64);
+ uint16x8_t d2 =
+ highbd_convolve8_8_2d_v(s2345, s6789, y_filter, offset_s64);
+ uint16x8_t d3 =
+ highbd_convolve8_8_2d_v(s3456, s789A, y_filter, offset_s64);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s0123[2] = s4567[2];
+ s0123[3] = s4567[3];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s1234[2] = s5678[2];
+ s1234[3] = s5678[3];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s2345[2] = s6789[2];
+ s2345[3] = s6789[3];
+ s3456[0] = s789A[0];
+ s3456[1] = s789A[1];
+ s3456[2] = s789A[2];
+ s3456[3] = s789A[3];
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ src += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ }
+}
+
+static INLINE uint16x4_t highbd_convolve4_4_2d_v(
+ const int16x4_t s0, const int16x4_t s1, const int16x4_t s2,
+ const int16x4_t s3, const int16x4_t filter, const int32x4_t offset) {
+ int32x4_t sum = vmlal_lane_s16(offset, s0, filter, 0);
+ sum = vmlal_lane_s16(sum, s1, filter, 1);
+ sum = vmlal_lane_s16(sum, s2, filter, 2);
+ sum = vmlal_lane_s16(sum, s3, filter, 3);
+
+ return vqrshrun_n_s32(sum, COMPOUND_ROUND1_BITS);
+}
+
+static INLINE uint16x8_t highbd_convolve4_8_2d_v(
+ const int16x8_t s0, const int16x8_t s1, const int16x8_t s2,
+ const int16x8_t s3, const int16x4_t filter, const int32x4_t offset) {
+ int32x4_t sum0 = vmlal_lane_s16(offset, vget_low_s16(s0), filter, 0);
+ sum0 = vmlal_lane_s16(sum0, vget_low_s16(s1), filter, 1);
+ sum0 = vmlal_lane_s16(sum0, vget_low_s16(s2), filter, 2);
+ sum0 = vmlal_lane_s16(sum0, vget_low_s16(s3), filter, 3);
+
+ int32x4_t sum1 = vmlal_lane_s16(offset, vget_high_s16(s0), filter, 0);
+ sum1 = vmlal_lane_s16(sum1, vget_high_s16(s1), filter, 1);
+ sum1 = vmlal_lane_s16(sum1, vget_high_s16(s2), filter, 2);
+ sum1 = vmlal_lane_s16(sum1, vget_high_s16(s3), filter, 3);
+
+ return vcombine_u16(vqrshrun_n_s32(sum0, COMPOUND_ROUND1_BITS),
+ vqrshrun_n_s32(sum1, COMPOUND_ROUND1_BITS));
+}
+
+static INLINE void highbd_dist_wtd_convolve_2d_vert_4tap_neon(
+ const uint16_t *src_ptr, int src_stride, uint16_t *dst_ptr, int dst_stride,
+ int w, int h, const int16_t *y_filter_ptr, const int offset) {
+ const int16x4_t y_filter = vld1_s16(y_filter_ptr + 2);
+ const int32x4_t offset_vec = vdupq_n_s32(offset);
+
+ if (w == 4) {
+ const int16_t *s = (const int16_t *)src_ptr;
+ uint16_t *d = dst_ptr;
+
+ int16x4_t s0, s1, s2;
+ load_s16_4x3(s, src_stride, &s0, &s1, &s2);
+ s += 3 * src_stride;
+
+ do {
+ int16x4_t s3, s4, s5, s6;
+ load_s16_4x4(s, src_stride, &s3, &s4, &s5, &s6);
+
+ uint16x4_t d0 =
+ highbd_convolve4_4_2d_v(s0, s1, s2, s3, y_filter, offset_vec);
+ uint16x4_t d1 =
+ highbd_convolve4_4_2d_v(s1, s2, s3, s4, y_filter, offset_vec);
+ uint16x4_t d2 =
+ highbd_convolve4_4_2d_v(s2, s3, s4, s5, y_filter, offset_vec);
+ uint16x4_t d3 =
+ highbd_convolve4_4_2d_v(s3, s4, s5, s6, y_filter, offset_vec);
+
+ store_u16_4x4(d, dst_stride, d0, d1, d2, d3);
+
+ s0 = s4;
+ s1 = s5;
+ s2 = s6;
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ } else {
+ do {
+ int height = h;
+ const int16_t *s = (const int16_t *)src_ptr;
+ uint16_t *d = dst_ptr;
+
+ int16x8_t s0, s1, s2;
+ load_s16_8x3(s, src_stride, &s0, &s1, &s2);
+ s += 3 * src_stride;
+
+ do {
+ int16x8_t s3, s4, s5, s6;
+ load_s16_8x4(s, src_stride, &s3, &s4, &s5, &s6);
+
+ uint16x8_t d0 =
+ highbd_convolve4_8_2d_v(s0, s1, s2, s3, y_filter, offset_vec);
+ uint16x8_t d1 =
+ highbd_convolve4_8_2d_v(s1, s2, s3, s4, y_filter, offset_vec);
+ uint16x8_t d2 =
+ highbd_convolve4_8_2d_v(s2, s3, s4, s5, y_filter, offset_vec);
+ uint16x8_t d3 =
+ highbd_convolve4_8_2d_v(s3, s4, s5, s6, y_filter, offset_vec);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s0 = s4;
+ s1 = s5;
+ s2 = s6;
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ src_ptr += 8;
+ dst_ptr += 8;
+ w -= 8;
+ } while (w != 0);
+ }
+}
+
+void av1_highbd_dist_wtd_convolve_2d_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride, int w,
+ int h, const InterpFilterParams *filter_params_x,
+ const InterpFilterParams *filter_params_y, const int subpel_x_qn,
+ const int subpel_y_qn, ConvolveParams *conv_params, int bd) {
+ DECLARE_ALIGNED(16, uint16_t,
+ im_block[(MAX_SB_SIZE + MAX_FILTER_TAP) * MAX_SB_SIZE]);
+ DECLARE_ALIGNED(16, uint16_t,
+ im_block2[(MAX_SB_SIZE + MAX_FILTER_TAP) * MAX_SB_SIZE]);
+
+ CONV_BUF_TYPE *dst16 = conv_params->dst;
+ int dst16_stride = conv_params->dst_stride;
+ const int x_filter_taps = get_filter_tap(filter_params_x, subpel_x_qn);
+ const int clamped_x_taps = x_filter_taps < 4 ? 4 : x_filter_taps;
+
+ const int y_filter_taps = get_filter_tap(filter_params_y, subpel_y_qn);
+ const int clamped_y_taps = y_filter_taps < 4 ? 4 : y_filter_taps;
+
+ if (x_filter_taps == 6 || y_filter_taps == 6) {
+ av1_highbd_dist_wtd_convolve_2d_neon(
+ src, src_stride, dst, dst_stride, w, h, filter_params_x,
+ filter_params_y, subpel_x_qn, subpel_y_qn, conv_params, bd);
+ return;
+ }
+
+ const int im_h = h + clamped_y_taps - 1;
+ const int im_stride = MAX_SB_SIZE;
+ const int vert_offset = clamped_y_taps / 2 - 1;
+ const int horiz_offset = clamped_x_taps / 2 - 1;
+ const int y_offset_bits = bd + 2 * FILTER_BITS - conv_params->round_0;
+ const int round_offset_conv_y = (1 << y_offset_bits);
+
+ const uint16_t *src_ptr = src - vert_offset * src_stride - horiz_offset;
+
+ const int16_t *x_filter_ptr = av1_get_interp_filter_subpel_kernel(
+ filter_params_x, subpel_x_qn & SUBPEL_MASK);
+ const int16_t *y_filter_ptr = av1_get_interp_filter_subpel_kernel(
+ filter_params_y, subpel_y_qn & SUBPEL_MASK);
+
+ if (bd == 12) {
+ if (x_filter_taps <= 4) {
+ highbd_12_dist_wtd_convolve_2d_horiz_4tap_sve2(
+ src_ptr, src_stride, im_block, im_stride, w, im_h, x_filter_ptr);
+ } else {
+ highbd_12_dist_wtd_convolve_2d_horiz_8tap_sve2(
+ src_ptr, src_stride, im_block, im_stride, w, im_h, x_filter_ptr);
+ }
+ } else {
+ if (x_filter_taps <= 4) {
+ highbd_dist_wtd_convolve_2d_horiz_4tap_sve2(
+ src_ptr, src_stride, im_block, im_stride, w, im_h, x_filter_ptr, bd);
+ } else {
+ highbd_dist_wtd_convolve_2d_horiz_8tap_sve2(
+ src_ptr, src_stride, im_block, im_stride, w, im_h, x_filter_ptr, bd);
+ }
+ }
+
+ if (conv_params->do_average) {
+ if (y_filter_taps <= 4) {
+ highbd_dist_wtd_convolve_2d_vert_4tap_neon(im_block, im_stride, im_block2,
+ im_stride, w, h, y_filter_ptr,
+ round_offset_conv_y);
+ } else {
+ highbd_dist_wtd_convolve_2d_vert_8tap_sve2(im_block, im_stride, im_block2,
+ im_stride, w, h, y_filter_ptr,
+ round_offset_conv_y);
+ }
+ if (conv_params->use_dist_wtd_comp_avg) {
+ if (bd == 12) {
+ highbd_12_dist_wtd_comp_avg_neon(im_block2, im_stride, dst, dst_stride,
+ w, h, conv_params);
+
+ } else {
+ highbd_dist_wtd_comp_avg_neon(im_block2, im_stride, dst, dst_stride, w,
+ h, conv_params, bd);
+ }
+ } else {
+ if (bd == 12) {
+ highbd_12_comp_avg_neon(im_block2, im_stride, dst, dst_stride, w, h,
+ conv_params);
+
+ } else {
+ highbd_comp_avg_neon(im_block2, im_stride, dst, dst_stride, w, h,
+ conv_params, bd);
+ }
+ }
+ } else {
+ if (y_filter_taps <= 4) {
+ highbd_dist_wtd_convolve_2d_vert_4tap_neon(
+ im_block, im_stride, dst16, dst16_stride, w, h, y_filter_ptr,
+ round_offset_conv_y);
+ } else {
+ highbd_dist_wtd_convolve_2d_vert_8tap_sve2(
+ im_block, im_stride, dst16, dst16_stride, w, h, y_filter_ptr,
+ round_offset_conv_y);
+ }
+ }
+}
diff --git a/third_party/aom/av1/common/arm/highbd_convolve_sve2.c b/third_party/aom/av1/common/arm/highbd_convolve_sve2.c
new file mode 100644
index 0000000000..82eb12fcea
--- /dev/null
+++ b/third_party/aom/av1/common/arm/highbd_convolve_sve2.c
@@ -0,0 +1,1720 @@
+/*
+ * Copyright (c) 2024, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#include <assert.h>
+#include <arm_neon.h>
+
+#include "config/aom_config.h"
+#include "config/av1_rtcd.h"
+
+#include "aom_dsp/aom_dsp_common.h"
+#include "aom_dsp/arm/aom_neon_sve_bridge.h"
+#include "aom_dsp/arm/aom_neon_sve2_bridge.h"
+#include "aom_dsp/arm/mem_neon.h"
+#include "aom_ports/mem.h"
+#include "av1/common/convolve.h"
+#include "av1/common/filter.h"
+#include "av1/common/arm/highbd_convolve_sve2.h"
+
+DECLARE_ALIGNED(16, static const uint16_t, kDotProdTbl[32]) = {
+ 0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6,
+ 4, 5, 6, 7, 5, 6, 7, 0, 6, 7, 0, 1, 7, 0, 1, 2,
+};
+
+static INLINE uint16x4_t convolve12_4_x(
+ int16x8_t s0, int16x8_t s1, int16x8_t filter_0_7, int16x8_t filter_4_11,
+ const int64x2_t offset, uint16x8x4_t permute_tbl, uint16x4_t max) {
+ int16x8_t permuted_samples[6];
+ permuted_samples[0] = aom_tbl_s16(s0, permute_tbl.val[0]);
+ permuted_samples[1] = aom_tbl_s16(s0, permute_tbl.val[1]);
+ permuted_samples[2] = aom_tbl2_s16(s0, s1, permute_tbl.val[2]);
+ permuted_samples[3] = aom_tbl2_s16(s0, s1, permute_tbl.val[3]);
+ permuted_samples[4] = aom_tbl_s16(s1, permute_tbl.val[0]);
+ permuted_samples[5] = aom_tbl_s16(s1, permute_tbl.val[1]);
+
+ int64x2_t sum01 =
+ aom_svdot_lane_s16(offset, permuted_samples[0], filter_0_7, 0);
+ sum01 = aom_svdot_lane_s16(sum01, permuted_samples[2], filter_0_7, 1);
+ sum01 = aom_svdot_lane_s16(sum01, permuted_samples[4], filter_4_11, 1);
+
+ int64x2_t sum23 =
+ aom_svdot_lane_s16(offset, permuted_samples[1], filter_0_7, 0);
+ sum23 = aom_svdot_lane_s16(sum23, permuted_samples[3], filter_0_7, 1);
+ sum23 = aom_svdot_lane_s16(sum23, permuted_samples[5], filter_4_11, 1);
+
+ int32x4_t res0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ uint16x4_t res = vqrshrun_n_s32(res0123, FILTER_BITS);
+
+ return vmin_u16(res, max);
+}
+
+static INLINE uint16x8_t convolve12_8_x(int16x8_t s0, int16x8_t s1,
+ int16x8_t s2, int16x8_t filter_0_7,
+ int16x8_t filter_4_11, int64x2_t offset,
+ uint16x8x4_t permute_tbl,
+ uint16x8_t max) {
+ int16x8_t permuted_samples[8];
+ permuted_samples[0] = aom_tbl_s16(s0, permute_tbl.val[0]);
+ permuted_samples[1] = aom_tbl_s16(s0, permute_tbl.val[1]);
+ permuted_samples[2] = aom_tbl2_s16(s0, s1, permute_tbl.val[2]);
+ permuted_samples[3] = aom_tbl2_s16(s0, s1, permute_tbl.val[3]);
+ permuted_samples[4] = aom_tbl_s16(s1, permute_tbl.val[0]);
+ permuted_samples[5] = aom_tbl_s16(s1, permute_tbl.val[1]);
+ permuted_samples[6] = aom_tbl2_s16(s1, s2, permute_tbl.val[2]);
+ permuted_samples[7] = aom_tbl2_s16(s1, s2, permute_tbl.val[3]);
+
+ int64x2_t sum01 =
+ aom_svdot_lane_s16(offset, permuted_samples[0], filter_0_7, 0);
+ sum01 = aom_svdot_lane_s16(sum01, permuted_samples[2], filter_0_7, 1);
+ sum01 = aom_svdot_lane_s16(sum01, permuted_samples[4], filter_4_11, 1);
+
+ int64x2_t sum23 =
+ aom_svdot_lane_s16(offset, permuted_samples[1], filter_0_7, 0);
+ sum23 = aom_svdot_lane_s16(sum23, permuted_samples[3], filter_0_7, 1);
+ sum23 = aom_svdot_lane_s16(sum23, permuted_samples[5], filter_4_11, 1);
+
+ int64x2_t sum45 =
+ aom_svdot_lane_s16(offset, permuted_samples[2], filter_0_7, 0);
+ sum45 = aom_svdot_lane_s16(sum45, permuted_samples[4], filter_0_7, 1);
+ sum45 = aom_svdot_lane_s16(sum45, permuted_samples[6], filter_4_11, 1);
+
+ int64x2_t sum67 =
+ aom_svdot_lane_s16(offset, permuted_samples[3], filter_0_7, 0);
+ sum67 = aom_svdot_lane_s16(sum67, permuted_samples[5], filter_0_7, 1);
+ sum67 = aom_svdot_lane_s16(sum67, permuted_samples[7], filter_4_11, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum45), vmovn_s64(sum67));
+
+ uint16x8_t res = vcombine_u16(vqrshrun_n_s32(sum0123, FILTER_BITS),
+ vqrshrun_n_s32(sum4567, FILTER_BITS));
+
+ return vminq_u16(res, max);
+}
+
+static INLINE void highbd_convolve_x_sr_12tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *y_filter_ptr,
+ ConvolveParams *conv_params, int bd) {
+ // This shim allows to do only one rounding shift instead of two.
+ const int64x2_t offset = vdupq_n_s64(1 << (conv_params->round_0 - 1));
+
+ const int16x8_t y_filter_0_7 = vld1q_s16(y_filter_ptr);
+ const int16x8_t y_filter_4_11 = vld1q_s16(y_filter_ptr + 4);
+
+ uint16x8x4_t permute_tbl = vld1q_u16_x4(kDotProdTbl);
+ // Scale indices by size of the true vector length to avoid reading from an
+ // 'undefined' portion of a vector on a system with SVE vectors > 128-bit.
+ uint16x8_t correction0 = vreinterpretq_u16_u64(vcombine_u64(
+ vdup_n_u64(0), vdup_n_u64(svcnth() * 0x0001000000000000ULL)));
+ permute_tbl.val[2] = vaddq_u16(permute_tbl.val[2], correction0);
+
+ uint16x8_t correction1 = vreinterpretq_u16_u64(
+ vcombine_u64(vdup_n_u64(svcnth() * 0x0001000100000000ULL),
+ vdup_n_u64(svcnth() * 0x0001000100010000ULL)));
+ permute_tbl.val[3] = vaddq_u16(permute_tbl.val[3], correction1);
+
+ if (width == 4) {
+ const uint16x4_t max = vdup_n_u16((1 << bd) - 1);
+ const int16_t *s = (const int16_t *)src;
+
+ do {
+ int16x8_t s0, s1, s2, s3, s4, s5, s6, s7;
+ load_s16_8x4(s, src_stride, &s0, &s2, &s4, &s6);
+ load_s16_8x4(s + 8, src_stride, &s1, &s3, &s5, &s7);
+
+ uint16x4_t d0 = convolve12_4_x(s0, s1, y_filter_0_7, y_filter_4_11,
+ offset, permute_tbl, max);
+ uint16x4_t d1 = convolve12_4_x(s2, s3, y_filter_0_7, y_filter_4_11,
+ offset, permute_tbl, max);
+ uint16x4_t d2 = convolve12_4_x(s4, s5, y_filter_0_7, y_filter_4_11,
+ offset, permute_tbl, max);
+ uint16x4_t d3 = convolve12_4_x(s6, s7, y_filter_0_7, y_filter_4_11,
+ offset, permute_tbl, max);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+
+ do {
+ const int16_t *s = (const int16_t *)src;
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11;
+ load_s16_8x4(s, src_stride, &s0, &s3, &s6, &s9);
+ load_s16_8x4(s + 8, src_stride, &s1, &s4, &s7, &s10);
+ load_s16_8x4(s + 16, src_stride, &s2, &s5, &s8, &s11);
+
+ uint16x8_t d0 = convolve12_8_x(s0, s1, s2, y_filter_0_7, y_filter_4_11,
+ offset, permute_tbl, max);
+ uint16x8_t d1 = convolve12_8_x(s3, s4, s5, y_filter_0_7, y_filter_4_11,
+ offset, permute_tbl, max);
+ uint16x8_t d2 = convolve12_8_x(s6, s7, s8, y_filter_0_7, y_filter_4_11,
+ offset, permute_tbl, max);
+ uint16x8_t d3 = convolve12_8_x(s9, s10, s11, y_filter_0_7,
+ y_filter_4_11, offset, permute_tbl, max);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ }
+}
+
+static INLINE uint16x8_t convolve8_8_x(int16x8_t s0[8], int16x8_t filter,
+ int64x2_t offset, uint16x8_t max) {
+ int64x2_t sum[8];
+ sum[0] = aom_sdotq_s16(offset, s0[0], filter);
+ sum[1] = aom_sdotq_s16(offset, s0[1], filter);
+ sum[2] = aom_sdotq_s16(offset, s0[2], filter);
+ sum[3] = aom_sdotq_s16(offset, s0[3], filter);
+ sum[4] = aom_sdotq_s16(offset, s0[4], filter);
+ sum[5] = aom_sdotq_s16(offset, s0[5], filter);
+ sum[6] = aom_sdotq_s16(offset, s0[6], filter);
+ sum[7] = aom_sdotq_s16(offset, s0[7], filter);
+
+ sum[0] = vpaddq_s64(sum[0], sum[1]);
+ sum[2] = vpaddq_s64(sum[2], sum[3]);
+ sum[4] = vpaddq_s64(sum[4], sum[5]);
+ sum[6] = vpaddq_s64(sum[6], sum[7]);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum[0]), vmovn_s64(sum[2]));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum[4]), vmovn_s64(sum[6]));
+
+ uint16x8_t res = vcombine_u16(vqrshrun_n_s32(sum0123, FILTER_BITS),
+ vqrshrun_n_s32(sum4567, FILTER_BITS));
+
+ return vminq_u16(res, max);
+}
+
+static INLINE void highbd_convolve_x_sr_8tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *y_filter_ptr,
+ ConvolveParams *conv_params, int bd) {
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+ // This shim allows to do only one rounding shift instead of two.
+ const int64_t offset = 1 << (conv_params->round_0 - 1);
+ const int64x2_t offset_lo = vcombine_s64((int64x1_t)(offset), vdup_n_s64(0));
+
+ const int16x8_t filter = vld1q_s16(y_filter_ptr);
+
+ do {
+ const int16_t *s = (const int16_t *)src;
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[8], s1[8], s2[8], s3[8];
+ load_s16_8x8(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3],
+ &s0[4], &s0[5], &s0[6], &s0[7]);
+ load_s16_8x8(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3],
+ &s1[4], &s1[5], &s1[6], &s1[7]);
+ load_s16_8x8(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3],
+ &s2[4], &s2[5], &s2[6], &s2[7]);
+ load_s16_8x8(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3],
+ &s3[4], &s3[5], &s3[6], &s3[7]);
+
+ uint16x8_t d0 = convolve8_8_x(s0, filter, offset_lo, max);
+ uint16x8_t d1 = convolve8_8_x(s1, filter, offset_lo, max);
+ uint16x8_t d2 = convolve8_8_x(s2, filter, offset_lo, max);
+ uint16x8_t d3 = convolve8_8_x(s3, filter, offset_lo, max);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+}
+
+// clang-format off
+DECLARE_ALIGNED(16, static const uint16_t, kDeinterleaveTbl[8]) = {
+ 0, 2, 4, 6, 1, 3, 5, 7,
+};
+// clang-format on
+
+static INLINE uint16x4_t convolve4_4_x(int16x8_t s0, int16x8_t filter,
+ int64x2_t offset,
+ uint16x8x2_t permute_tbl,
+ uint16x4_t max) {
+ int16x8_t permuted_samples0 = aom_tbl_s16(s0, permute_tbl.val[0]);
+ int16x8_t permuted_samples1 = aom_tbl_s16(s0, permute_tbl.val[1]);
+
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, permuted_samples0, filter, 0);
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, permuted_samples1, filter, 0);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ uint16x4_t res = vqrshrun_n_s32(sum0123, FILTER_BITS);
+
+ return vmin_u16(res, max);
+}
+
+static INLINE uint16x8_t convolve4_8_x(int16x8_t s0[4], int16x8_t filter,
+ int64x2_t offset, uint16x8_t tbl,
+ uint16x8_t max) {
+ int64x2_t sum04 = aom_svdot_lane_s16(offset, s0[0], filter, 0);
+ int64x2_t sum15 = aom_svdot_lane_s16(offset, s0[1], filter, 0);
+ int64x2_t sum26 = aom_svdot_lane_s16(offset, s0[2], filter, 0);
+ int64x2_t sum37 = aom_svdot_lane_s16(offset, s0[3], filter, 0);
+
+ int32x4_t sum0415 = vcombine_s32(vmovn_s64(sum04), vmovn_s64(sum15));
+ int32x4_t sum2637 = vcombine_s32(vmovn_s64(sum26), vmovn_s64(sum37));
+
+ uint16x8_t res = vcombine_u16(vqrshrun_n_s32(sum0415, FILTER_BITS),
+ vqrshrun_n_s32(sum2637, FILTER_BITS));
+ res = aom_tbl_u16(res, tbl);
+
+ return vminq_u16(res, max);
+}
+
+static INLINE void highbd_convolve_x_sr_4tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *x_filter_ptr,
+ ConvolveParams *conv_params, int bd) {
+ // This shim allows to do only one rounding shift instead of two.
+ const int64x2_t offset = vdupq_n_s64(1 << (conv_params->round_0 - 1));
+
+ const int16x4_t x_filter = vld1_s16(x_filter_ptr + 2);
+ const int16x8_t filter = vcombine_s16(x_filter, vdup_n_s16(0));
+
+ if (width == 4) {
+ const uint16x4_t max = vdup_n_u16((1 << bd) - 1);
+ uint16x8x2_t permute_tbl = vld1q_u16_x2(kDotProdTbl);
+
+ const int16_t *s = (const int16_t *)(src);
+
+ do {
+ int16x8_t s0, s1, s2, s3;
+ load_s16_8x4(s, src_stride, &s0, &s1, &s2, &s3);
+
+ uint16x4_t d0 = convolve4_4_x(s0, filter, offset, permute_tbl, max);
+ uint16x4_t d1 = convolve4_4_x(s1, filter, offset, permute_tbl, max);
+ uint16x4_t d2 = convolve4_4_x(s2, filter, offset, permute_tbl, max);
+ uint16x4_t d3 = convolve4_4_x(s3, filter, offset, permute_tbl, max);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+ uint16x8_t idx = vld1q_u16(kDeinterleaveTbl);
+
+ do {
+ const int16_t *s = (const int16_t *)(src);
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[4], s1[4], s2[4], s3[4];
+ load_s16_8x4(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3]);
+ load_s16_8x4(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3]);
+ load_s16_8x4(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3]);
+ load_s16_8x4(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3]);
+
+ uint16x8_t d0 = convolve4_8_x(s0, filter, offset, idx, max);
+ uint16x8_t d1 = convolve4_8_x(s1, filter, offset, idx, max);
+ uint16x8_t d2 = convolve4_8_x(s2, filter, offset, idx, max);
+ uint16x8_t d3 = convolve4_8_x(s3, filter, offset, idx, max);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ }
+}
+
+void av1_highbd_convolve_x_sr_sve2(const uint16_t *src, int src_stride,
+ uint16_t *dst, int dst_stride, int w, int h,
+ const InterpFilterParams *filter_params_x,
+ const int subpel_x_qn,
+ ConvolveParams *conv_params, int bd) {
+ if (w == 2 || h == 2) {
+ av1_highbd_convolve_x_sr_c(src, src_stride, dst, dst_stride, w, h,
+ filter_params_x, subpel_x_qn, conv_params, bd);
+ return;
+ }
+
+ const int x_filter_taps = get_filter_tap(filter_params_x, subpel_x_qn);
+
+ if (x_filter_taps == 6) {
+ av1_highbd_convolve_x_sr_neon(src, src_stride, dst, dst_stride, w, h,
+ filter_params_x, subpel_x_qn, conv_params,
+ bd);
+ return;
+ }
+
+ const int horiz_offset = filter_params_x->taps / 2 - 1;
+ const int16_t *x_filter_ptr = av1_get_interp_filter_subpel_kernel(
+ filter_params_x, subpel_x_qn & SUBPEL_MASK);
+
+ src -= horiz_offset;
+
+ if (x_filter_taps == 12) {
+ highbd_convolve_x_sr_12tap_sve2(src, src_stride, dst, dst_stride, w, h,
+ x_filter_ptr, conv_params, bd);
+ return;
+ }
+
+ if (x_filter_taps == 8) {
+ highbd_convolve_x_sr_8tap_sve2(src, src_stride, dst, dst_stride, w, h,
+ x_filter_ptr, conv_params, bd);
+ return;
+ }
+
+ highbd_convolve_x_sr_4tap_sve2(src + 2, src_stride, dst, dst_stride, w, h,
+ x_filter_ptr, conv_params, bd);
+}
+
+static INLINE uint16x4_t highbd_convolve12_4_y(int16x8_t s0[2], int16x8_t s1[2],
+ int16x8_t s2[2],
+ int16x8_t filter_0_7,
+ int16x8_t filter_4_11,
+ uint16x4_t max) {
+ int64x2_t sum[2];
+
+ sum[0] = aom_svdot_lane_s16(vdupq_n_s64(0), s0[0], filter_0_7, 0);
+ sum[0] = aom_svdot_lane_s16(sum[0], s1[0], filter_0_7, 1);
+ sum[0] = aom_svdot_lane_s16(sum[0], s2[0], filter_4_11, 1);
+
+ sum[1] = aom_svdot_lane_s16(vdupq_n_s64(0), s0[1], filter_0_7, 0);
+ sum[1] = aom_svdot_lane_s16(sum[1], s1[1], filter_0_7, 1);
+ sum[1] = aom_svdot_lane_s16(sum[1], s2[1], filter_4_11, 1);
+
+ int32x4_t res_s32 = vcombine_s32(vmovn_s64(sum[0]), vmovn_s64(sum[1]));
+
+ uint16x4_t res = vqrshrun_n_s32(res_s32, FILTER_BITS);
+
+ return vmin_u16(res, max);
+}
+
+static INLINE void highbd_convolve_y_sr_12tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *y_filter_ptr, int bd) {
+ const int16x8_t y_filter_0_7 = vld1q_s16(y_filter_ptr);
+ const int16x8_t y_filter_4_11 = vld1q_s16(y_filter_ptr + 4);
+
+ uint16x8x3_t merge_block_tbl = vld1q_u16_x3(kDotProdMergeBlockTbl);
+ // Scale indices by size of the true vector length to avoid reading from an
+ // 'undefined' portion of a vector on a system with SVE vectors > 128-bit.
+ uint16x8_t correction0 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000000000000ULL));
+ merge_block_tbl.val[0] = vaddq_u16(merge_block_tbl.val[0], correction0);
+
+ uint16x8_t correction1 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100000000ULL));
+ merge_block_tbl.val[1] = vaddq_u16(merge_block_tbl.val[1], correction1);
+
+ uint16x8_t correction2 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100010000ULL));
+ merge_block_tbl.val[2] = vaddq_u16(merge_block_tbl.val[2], correction2);
+
+ const uint16x4_t max = vdup_n_u16((1 << bd) - 1);
+
+ do {
+ int16_t *s = (int16_t *)src;
+ uint16_t *d = dst;
+ int h = height;
+
+ int16x4_t s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA;
+ load_s16_4x11(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6, &s7, &s8,
+ &s9, &sA);
+ s += 11 * src_stride;
+
+ int16x8_t s0123[2], s1234[2], s2345[2], s3456[2], s4567[2], s5678[2],
+ s6789[2], s789A[2];
+ transpose_concat_4x4(s0, s1, s2, s3, s0123);
+ transpose_concat_4x4(s1, s2, s3, s4, s1234);
+ transpose_concat_4x4(s2, s3, s4, s5, s2345);
+ transpose_concat_4x4(s3, s4, s5, s6, s3456);
+ transpose_concat_4x4(s4, s5, s6, s7, s4567);
+ transpose_concat_4x4(s5, s6, s7, s8, s5678);
+ transpose_concat_4x4(s6, s7, s8, s9, s6789);
+ transpose_concat_4x4(s7, s8, s9, sA, s789A);
+
+ do {
+ int16x4_t sB, sC, sD, sE;
+ load_s16_4x4(s, src_stride, &sB, &sC, &sD, &sE);
+
+ int16x8_t s89AB[2], s9ABC[2], sABCD[2], sBCDE[2];
+ transpose_concat_4x4(sB, sC, sD, sE, sBCDE);
+
+ // Use the above transpose and reuse data from the previous loop to get
+ // the rest.
+ aom_tbl2x2_s16(s789A, sBCDE, merge_block_tbl.val[0], s89AB);
+ aom_tbl2x2_s16(s789A, sBCDE, merge_block_tbl.val[1], s9ABC);
+ aom_tbl2x2_s16(s789A, sBCDE, merge_block_tbl.val[2], sABCD);
+
+ uint16x4_t d0 = highbd_convolve12_4_y(s0123, s4567, s89AB, y_filter_0_7,
+ y_filter_4_11, max);
+ uint16x4_t d1 = highbd_convolve12_4_y(s1234, s5678, s9ABC, y_filter_0_7,
+ y_filter_4_11, max);
+ uint16x4_t d2 = highbd_convolve12_4_y(s2345, s6789, sABCD, y_filter_0_7,
+ y_filter_4_11, max);
+ uint16x4_t d3 = highbd_convolve12_4_y(s3456, s789A, sBCDE, y_filter_0_7,
+ y_filter_4_11, max);
+
+ store_u16_4x4(d, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s3456[0] = s789A[0];
+ s3456[1] = s789A[1];
+ s4567[0] = s89AB[0];
+ s4567[1] = s89AB[1];
+ s5678[0] = s9ABC[0];
+ s5678[1] = s9ABC[1];
+ s6789[0] = sABCD[0];
+ s6789[1] = sABCD[1];
+ s789A[0] = sBCDE[0];
+ s789A[1] = sBCDE[1];
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ src += 4;
+ dst += 4;
+ width -= 4;
+ } while (width != 0);
+}
+
+static INLINE uint16x4_t highbd_convolve8_4_y(int16x8_t samples_lo[2],
+ int16x8_t samples_hi[2],
+ int16x8_t filter,
+ uint16x4_t max) {
+ int64x2_t sum01 =
+ aom_svdot_lane_s16(vdupq_n_s64(0), samples_lo[0], filter, 0);
+ sum01 = aom_svdot_lane_s16(sum01, samples_hi[0], filter, 1);
+
+ int64x2_t sum23 =
+ aom_svdot_lane_s16(vdupq_n_s64(0), samples_lo[1], filter, 0);
+ sum23 = aom_svdot_lane_s16(sum23, samples_hi[1], filter, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ uint16x4_t res = vqrshrun_n_s32(sum0123, FILTER_BITS);
+ return vmin_u16(res, max);
+}
+
+static INLINE uint16x8_t highbd_convolve8_8_y(int16x8_t samples_lo[4],
+ int16x8_t samples_hi[4],
+ int16x8_t filter,
+ uint16x8_t max) {
+ int64x2_t sum01 =
+ aom_svdot_lane_s16(vdupq_n_s64(0), samples_lo[0], filter, 0);
+ sum01 = aom_svdot_lane_s16(sum01, samples_hi[0], filter, 1);
+
+ int64x2_t sum23 =
+ aom_svdot_lane_s16(vdupq_n_s64(0), samples_lo[1], filter, 0);
+ sum23 = aom_svdot_lane_s16(sum23, samples_hi[1], filter, 1);
+
+ int64x2_t sum45 =
+ aom_svdot_lane_s16(vdupq_n_s64(0), samples_lo[2], filter, 0);
+ sum45 = aom_svdot_lane_s16(sum45, samples_hi[2], filter, 1);
+
+ int64x2_t sum67 =
+ aom_svdot_lane_s16(vdupq_n_s64(0), samples_lo[3], filter, 0);
+ sum67 = aom_svdot_lane_s16(sum67, samples_hi[3], filter, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum45), vmovn_s64(sum67));
+ uint16x8_t res = vcombine_u16(vqrshrun_n_s32(sum0123, FILTER_BITS),
+ vqrshrun_n_s32(sum4567, FILTER_BITS));
+ return vminq_u16(res, max);
+}
+
+void highbd_convolve_y_sr_8tap_sve2(const uint16_t *src, ptrdiff_t src_stride,
+ uint16_t *dst, ptrdiff_t dst_stride,
+ int width, int height,
+ const int16_t *filter_y, int bd) {
+ assert(w >= 4 && h >= 4);
+
+ const int16x8_t y_filter = vld1q_s16(filter_y);
+
+ uint16x8x3_t merge_block_tbl = vld1q_u16_x3(kDotProdMergeBlockTbl);
+ // Scale indices by size of the true vector length to avoid reading from an
+ // 'undefined' portion of a vector on a system with SVE vectors > 128-bit.
+ uint16x8_t correction0 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000000000000ULL));
+ merge_block_tbl.val[0] = vaddq_u16(merge_block_tbl.val[0], correction0);
+
+ uint16x8_t correction1 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100000000ULL));
+ merge_block_tbl.val[1] = vaddq_u16(merge_block_tbl.val[1], correction1);
+
+ uint16x8_t correction2 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100010000ULL));
+ merge_block_tbl.val[2] = vaddq_u16(merge_block_tbl.val[2], correction2);
+
+ if (width == 4) {
+ const uint16x4_t max = vdup_n_u16((1 << bd) - 1);
+ int16_t *s = (int16_t *)src;
+
+ int16x4_t s0, s1, s2, s3, s4, s5, s6;
+ load_s16_4x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
+ s += 7 * src_stride;
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[2], s1234[2], s2345[2], s3456[2];
+ transpose_concat_4x4(s0, s1, s2, s3, s0123);
+ transpose_concat_4x4(s1, s2, s3, s4, s1234);
+ transpose_concat_4x4(s2, s3, s4, s5, s2345);
+ transpose_concat_4x4(s3, s4, s5, s6, s3456);
+
+ do {
+ int16x4_t s7, s8, s9, s10;
+ load_s16_4x4(s, src_stride, &s7, &s8, &s9, &s10);
+
+ int16x8_t s4567[2], s5678[2], s6789[2], s789A[2];
+ // Transpose and shuffle the 4 lines that were loaded.
+ transpose_concat_4x4(s7, s8, s9, s10, s789A);
+
+ // Merge new data into block from previous iteration.
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[0], s4567);
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[1], s5678);
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[2], s6789);
+
+ uint16x4_t d0 = highbd_convolve8_4_y(s0123, s4567, y_filter, max);
+ uint16x4_t d1 = highbd_convolve8_4_y(s1234, s5678, y_filter, max);
+ uint16x4_t d2 = highbd_convolve8_4_y(s2345, s6789, y_filter, max);
+ uint16x4_t d3 = highbd_convolve8_4_y(s3456, s789A, y_filter, max);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s3456[0] = s789A[0];
+ s3456[1] = s789A[1];
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+
+ do {
+ int h = height;
+ int16_t *s = (int16_t *)src;
+ uint16_t *d = dst;
+
+ int16x8_t s0, s1, s2, s3, s4, s5, s6;
+ load_s16_8x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
+ s += 7 * src_stride;
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[4], s1234[4], s2345[4], s3456[4];
+ transpose_concat_8x4(s0, s1, s2, s3, s0123);
+ transpose_concat_8x4(s1, s2, s3, s4, s1234);
+ transpose_concat_8x4(s2, s3, s4, s5, s2345);
+ transpose_concat_8x4(s3, s4, s5, s6, s3456);
+
+ do {
+ int16x8_t s7, s8, s9, s10;
+ load_s16_8x4(s, src_stride, &s7, &s8, &s9, &s10);
+
+ int16x8_t s4567[4], s5678[4], s6789[4], s789A[4];
+ // Transpose and shuffle the 4 lines that were loaded.
+ transpose_concat_8x4(s7, s8, s9, s10, s789A);
+
+ // Merge new data into block from previous iteration.
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[0], s4567);
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[1], s5678);
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[2], s6789);
+
+ uint16x8_t d0 = highbd_convolve8_8_y(s0123, s4567, y_filter, max);
+ uint16x8_t d1 = highbd_convolve8_8_y(s1234, s5678, y_filter, max);
+ uint16x8_t d2 = highbd_convolve8_8_y(s2345, s6789, y_filter, max);
+ uint16x8_t d3 = highbd_convolve8_8_y(s3456, s789A, y_filter, max);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s0123[2] = s4567[2];
+ s0123[3] = s4567[3];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s1234[2] = s5678[2];
+ s1234[3] = s5678[3];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s2345[2] = s6789[2];
+ s2345[3] = s6789[3];
+ s3456[0] = s789A[0];
+ s3456[1] = s789A[1];
+ s3456[2] = s789A[2];
+ s3456[3] = s789A[3];
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ src += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ }
+}
+
+static INLINE uint16x4_t highbd_convolve4_4_y(int16x8_t samples[2],
+ int16x8_t filter,
+ uint16x4_t max) {
+ int64x2_t sum01 = aom_svdot_lane_s16(vdupq_n_s64(0), samples[0], filter, 0);
+ int64x2_t sum23 = aom_svdot_lane_s16(vdupq_n_s64(0), samples[1], filter, 0);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ uint16x4_t res = vqrshrun_n_s32(sum0123, FILTER_BITS);
+ return vmin_u16(res, max);
+}
+
+static INLINE uint16x8_t highbd_convolve4_8_y(int16x8_t samples[4],
+ int16x8_t filter,
+ uint16x8_t max) {
+ int64x2_t sum01 = aom_svdot_lane_s16(vdupq_n_s64(0), samples[0], filter, 0);
+ int64x2_t sum23 = aom_svdot_lane_s16(vdupq_n_s64(0), samples[1], filter, 0);
+ int64x2_t sum45 = aom_svdot_lane_s16(vdupq_n_s64(0), samples[2], filter, 0);
+ int64x2_t sum67 = aom_svdot_lane_s16(vdupq_n_s64(0), samples[3], filter, 0);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum45), vmovn_s64(sum67));
+ uint16x8_t res = vcombine_u16(vqrshrun_n_s32(sum0123, FILTER_BITS),
+ vqrshrun_n_s32(sum4567, FILTER_BITS));
+ return vminq_u16(res, max);
+}
+
+void highbd_convolve_y_sr_4tap_sve2(const uint16_t *src, ptrdiff_t src_stride,
+ uint16_t *dst, ptrdiff_t dst_stride,
+ int width, int height,
+ const int16_t *filter_y, int bd) {
+ assert(w >= 4 && h >= 4);
+
+ const int16x8_t y_filter =
+ vcombine_s16(vld1_s16(filter_y + 2), vdup_n_s16(0));
+
+ if (width == 4) {
+ const uint16x4_t max = vdup_n_u16((1 << bd) - 1);
+ int16_t *s = (int16_t *)src;
+
+ int16x4_t s0, s1, s2;
+ load_s16_4x3(s, src_stride, &s0, &s1, &s2);
+ s += 3 * src_stride;
+
+ do {
+ int16x4_t s3, s4, s5, s6;
+ load_s16_4x4(s, src_stride, &s3, &s4, &s5, &s6);
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[2], s1234[2], s2345[2], s3456[2];
+ transpose_concat_4x4(s0, s1, s2, s3, s0123);
+ transpose_concat_4x4(s1, s2, s3, s4, s1234);
+ transpose_concat_4x4(s2, s3, s4, s5, s2345);
+ transpose_concat_4x4(s3, s4, s5, s6, s3456);
+
+ uint16x4_t d0 = highbd_convolve4_4_y(s0123, y_filter, max);
+ uint16x4_t d1 = highbd_convolve4_4_y(s1234, y_filter, max);
+ uint16x4_t d2 = highbd_convolve4_4_y(s2345, y_filter, max);
+ uint16x4_t d3 = highbd_convolve4_4_y(s3456, y_filter, max);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ // Shuffle everything up four rows.
+ s0 = s4;
+ s1 = s5;
+ s2 = s6;
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+
+ do {
+ int h = height;
+ int16_t *s = (int16_t *)src;
+ uint16_t *d = dst;
+
+ int16x8_t s0, s1, s2;
+ load_s16_8x3(s, src_stride, &s0, &s1, &s2);
+ s += 3 * src_stride;
+
+ do {
+ int16x8_t s3, s4, s5, s6;
+ load_s16_8x4(s, src_stride, &s3, &s4, &s5, &s6);
+
+ // This operation combines a conventional transpose and the sample
+ // permute required before computing the dot product.
+ int16x8_t s0123[4], s1234[4], s2345[4], s3456[4];
+ transpose_concat_8x4(s0, s1, s2, s3, s0123);
+ transpose_concat_8x4(s1, s2, s3, s4, s1234);
+ transpose_concat_8x4(s2, s3, s4, s5, s2345);
+ transpose_concat_8x4(s3, s4, s5, s6, s3456);
+
+ uint16x8_t d0 = highbd_convolve4_8_y(s0123, y_filter, max);
+ uint16x8_t d1 = highbd_convolve4_8_y(s1234, y_filter, max);
+ uint16x8_t d2 = highbd_convolve4_8_y(s2345, y_filter, max);
+ uint16x8_t d3 = highbd_convolve4_8_y(s3456, y_filter, max);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ // Shuffle everything up four rows.
+ s0 = s4;
+ s1 = s5;
+ s2 = s6;
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ src += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ }
+}
+
+void av1_highbd_convolve_y_sr_sve2(const uint16_t *src, int src_stride,
+ uint16_t *dst, int dst_stride, int w, int h,
+ const InterpFilterParams *filter_params_y,
+ const int subpel_y_qn, int bd) {
+ if (w == 2 || h == 2) {
+ av1_highbd_convolve_y_sr_c(src, src_stride, dst, dst_stride, w, h,
+ filter_params_y, subpel_y_qn, bd);
+ return;
+ }
+ const int y_filter_taps = get_filter_tap(filter_params_y, subpel_y_qn);
+
+ if (y_filter_taps == 6) {
+ av1_highbd_convolve_y_sr_neon(src, src_stride, dst, dst_stride, w, h,
+ filter_params_y, subpel_y_qn, bd);
+ return;
+ }
+
+ const int vert_offset = filter_params_y->taps / 2 - 1;
+ const int16_t *y_filter_ptr = av1_get_interp_filter_subpel_kernel(
+ filter_params_y, subpel_y_qn & SUBPEL_MASK);
+
+ src -= vert_offset * src_stride;
+
+ if (y_filter_taps > 8) {
+ highbd_convolve_y_sr_12tap_sve2(src, src_stride, dst, dst_stride, w, h,
+ y_filter_ptr, bd);
+ return;
+ }
+
+ if (y_filter_taps == 4) {
+ highbd_convolve_y_sr_4tap_sve2(src + 2 * src_stride, src_stride, dst,
+ dst_stride, w, h, y_filter_ptr, bd);
+ return;
+ }
+
+ highbd_convolve_y_sr_8tap_sve2(src, src_stride, dst, dst_stride, w, h,
+ y_filter_ptr, bd);
+}
+
+static INLINE uint16x4_t convolve12_4_2d_h(
+ int16x8_t s0, int16x8_t s1, int16x8_t filter_0_7, int16x8_t filter_4_11,
+ const int64x2_t offset, int32x4_t shift, uint16x8x4_t permute_tbl) {
+ int16x8_t permuted_samples[6];
+ permuted_samples[0] = aom_tbl_s16(s0, permute_tbl.val[0]);
+ permuted_samples[1] = aom_tbl_s16(s0, permute_tbl.val[1]);
+ permuted_samples[2] = aom_tbl2_s16(s0, s1, permute_tbl.val[2]);
+ permuted_samples[3] = aom_tbl2_s16(s0, s1, permute_tbl.val[3]);
+ permuted_samples[4] = aom_tbl_s16(s1, permute_tbl.val[0]);
+ permuted_samples[5] = aom_tbl_s16(s1, permute_tbl.val[1]);
+
+ int64x2_t sum01 =
+ aom_svdot_lane_s16(offset, permuted_samples[0], filter_0_7, 0);
+ sum01 = aom_svdot_lane_s16(sum01, permuted_samples[2], filter_0_7, 1);
+ sum01 = aom_svdot_lane_s16(sum01, permuted_samples[4], filter_4_11, 1);
+
+ int64x2_t sum23 =
+ aom_svdot_lane_s16(offset, permuted_samples[1], filter_0_7, 0);
+ sum23 = aom_svdot_lane_s16(sum23, permuted_samples[3], filter_0_7, 1);
+ sum23 = aom_svdot_lane_s16(sum23, permuted_samples[5], filter_4_11, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ sum0123 = vqrshlq_s32(sum0123, shift);
+ return vqmovun_s32(sum0123);
+}
+
+static INLINE uint16x8_t convolve12_8_2d_h(int16x8_t s0, int16x8_t s1,
+ int16x8_t s2, int16x8_t filter_0_7,
+ int16x8_t filter_4_11,
+ int64x2_t offset, int32x4_t shift,
+ uint16x8x4_t permute_tbl) {
+ int16x8_t permuted_samples[8];
+ permuted_samples[0] = aom_tbl_s16(s0, permute_tbl.val[0]);
+ permuted_samples[1] = aom_tbl_s16(s0, permute_tbl.val[1]);
+ permuted_samples[2] = aom_tbl2_s16(s0, s1, permute_tbl.val[2]);
+ permuted_samples[3] = aom_tbl2_s16(s0, s1, permute_tbl.val[3]);
+ permuted_samples[4] = aom_tbl_s16(s1, permute_tbl.val[0]);
+ permuted_samples[5] = aom_tbl_s16(s1, permute_tbl.val[1]);
+ permuted_samples[6] = aom_tbl2_s16(s1, s2, permute_tbl.val[2]);
+ permuted_samples[7] = aom_tbl2_s16(s1, s2, permute_tbl.val[3]);
+
+ int64x2_t sum01 =
+ aom_svdot_lane_s16(offset, permuted_samples[0], filter_0_7, 0);
+ sum01 = aom_svdot_lane_s16(sum01, permuted_samples[2], filter_0_7, 1);
+ sum01 = aom_svdot_lane_s16(sum01, permuted_samples[4], filter_4_11, 1);
+
+ int64x2_t sum23 =
+ aom_svdot_lane_s16(offset, permuted_samples[1], filter_0_7, 0);
+ sum23 = aom_svdot_lane_s16(sum23, permuted_samples[3], filter_0_7, 1);
+ sum23 = aom_svdot_lane_s16(sum23, permuted_samples[5], filter_4_11, 1);
+
+ int64x2_t sum45 =
+ aom_svdot_lane_s16(offset, permuted_samples[2], filter_0_7, 0);
+ sum45 = aom_svdot_lane_s16(sum45, permuted_samples[4], filter_0_7, 1);
+ sum45 = aom_svdot_lane_s16(sum45, permuted_samples[6], filter_4_11, 1);
+
+ int64x2_t sum67 =
+ aom_svdot_lane_s16(offset, permuted_samples[3], filter_0_7, 0);
+ sum67 = aom_svdot_lane_s16(sum67, permuted_samples[5], filter_0_7, 1);
+ sum67 = aom_svdot_lane_s16(sum67, permuted_samples[7], filter_4_11, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum45), vmovn_s64(sum67));
+
+ sum0123 = vqrshlq_s32(sum0123, shift);
+ sum4567 = vqrshlq_s32(sum4567, shift);
+
+ return vcombine_u16(vqmovun_s32(sum0123), vqmovun_s32(sum4567));
+}
+
+static INLINE void highbd_convolve_2d_sr_horiz_12tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *y_filter_ptr,
+ ConvolveParams *conv_params, const int x_offset) {
+ const int64x2_t offset = vdupq_n_s64(x_offset);
+ const int32x4_t shift = vdupq_n_s32(-conv_params->round_0);
+
+ const int16x8_t y_filter_0_7 = vld1q_s16(y_filter_ptr);
+ const int16x8_t y_filter_4_11 = vld1q_s16(y_filter_ptr + 4);
+
+ uint16x8x4_t permute_tbl = vld1q_u16_x4(kDotProdTbl);
+ // Scale indices by size of the true vector length to avoid reading from an
+ // 'undefined' portion of a vector on a system with SVE vectors > 128-bit.
+ uint16x8_t correction0 = vreinterpretq_u16_u64(vcombine_u64(
+ vdup_n_u64(0), vdup_n_u64(svcnth() * 0x0001000000000000ULL)));
+ permute_tbl.val[2] = vaddq_u16(permute_tbl.val[2], correction0);
+
+ uint16x8_t correction1 = vreinterpretq_u16_u64(
+ vcombine_u64(vdup_n_u64(svcnth() * 0x0001000100000000ULL),
+ vdup_n_u64(svcnth() * 0x0001000100010000ULL)));
+ permute_tbl.val[3] = vaddq_u16(permute_tbl.val[3], correction1);
+
+ if (width == 4) {
+ const int16_t *s = (const int16_t *)src;
+
+ do {
+ int16x8_t s0, s1, s2, s3, s4, s5, s6, s7;
+ load_s16_8x4(s, src_stride, &s0, &s2, &s4, &s6);
+ load_s16_8x4(s + 8, src_stride, &s1, &s3, &s5, &s7);
+
+ uint16x4_t d0 = convolve12_4_2d_h(s0, s1, y_filter_0_7, y_filter_4_11,
+ offset, shift, permute_tbl);
+ uint16x4_t d1 = convolve12_4_2d_h(s2, s3, y_filter_0_7, y_filter_4_11,
+ offset, shift, permute_tbl);
+ uint16x4_t d2 = convolve12_4_2d_h(s4, s5, y_filter_0_7, y_filter_4_11,
+ offset, shift, permute_tbl);
+ uint16x4_t d3 = convolve12_4_2d_h(s6, s7, y_filter_0_7, y_filter_4_11,
+ offset, shift, permute_tbl);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ dst += 4 * dst_stride;
+ s += 4 * src_stride;
+ height -= 4;
+ } while (height > 0);
+ } else {
+ do {
+ const int16_t *s = (const int16_t *)src;
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11;
+ load_s16_8x4(s, src_stride, &s0, &s3, &s6, &s9);
+ load_s16_8x4(s + 8, src_stride, &s1, &s4, &s7, &s10);
+ load_s16_8x4(s + 16, src_stride, &s2, &s5, &s8, &s11);
+
+ uint16x8_t d0 =
+ convolve12_8_2d_h(s0, s1, s2, y_filter_0_7, y_filter_4_11, offset,
+ shift, permute_tbl);
+ uint16x8_t d1 =
+ convolve12_8_2d_h(s3, s4, s5, y_filter_0_7, y_filter_4_11, offset,
+ shift, permute_tbl);
+ uint16x8_t d2 =
+ convolve12_8_2d_h(s6, s7, s8, y_filter_0_7, y_filter_4_11, offset,
+ shift, permute_tbl);
+ uint16x8_t d3 =
+ convolve12_8_2d_h(s9, s10, s11, y_filter_0_7, y_filter_4_11, offset,
+ shift, permute_tbl);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height > 0);
+ }
+}
+
+static INLINE uint16x8_t convolve8_8_2d_h(int16x8_t s0[8], int16x8_t filter,
+ int64x2_t offset, int32x4_t shift) {
+ int64x2_t sum[8];
+ sum[0] = aom_sdotq_s16(offset, s0[0], filter);
+ sum[1] = aom_sdotq_s16(offset, s0[1], filter);
+ sum[2] = aom_sdotq_s16(offset, s0[2], filter);
+ sum[3] = aom_sdotq_s16(offset, s0[3], filter);
+ sum[4] = aom_sdotq_s16(offset, s0[4], filter);
+ sum[5] = aom_sdotq_s16(offset, s0[5], filter);
+ sum[6] = aom_sdotq_s16(offset, s0[6], filter);
+ sum[7] = aom_sdotq_s16(offset, s0[7], filter);
+
+ sum[0] = vpaddq_s64(sum[0], sum[1]);
+ sum[2] = vpaddq_s64(sum[2], sum[3]);
+ sum[4] = vpaddq_s64(sum[4], sum[5]);
+ sum[6] = vpaddq_s64(sum[6], sum[7]);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum[0]), vmovn_s64(sum[2]));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum[4]), vmovn_s64(sum[6]));
+
+ sum0123 = vqrshlq_s32(sum0123, shift);
+ sum4567 = vqrshlq_s32(sum4567, shift);
+
+ return vcombine_u16(vqmovun_s32(sum0123), vqmovun_s32(sum4567));
+}
+
+static INLINE void highbd_convolve_2d_sr_horiz_8tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *y_filter_ptr,
+ ConvolveParams *conv_params, const int x_offset) {
+ const int64x2_t offset = vdupq_n_s64(x_offset);
+ const int64x2_t offset_lo = vcombine_s64(vget_low_s64(offset), vdup_n_s64(0));
+ const int32x4_t shift = vdupq_n_s32(-conv_params->round_0);
+
+ const int16x8_t filter = vld1q_s16(y_filter_ptr);
+
+ do {
+ const int16_t *s = (const int16_t *)src;
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[8], s1[8], s2[8], s3[8];
+ load_s16_8x8(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3],
+ &s0[4], &s0[5], &s0[6], &s0[7]);
+ load_s16_8x8(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3],
+ &s1[4], &s1[5], &s1[6], &s1[7]);
+ load_s16_8x8(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3],
+ &s2[4], &s2[5], &s2[6], &s2[7]);
+ load_s16_8x8(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3],
+ &s3[4], &s3[5], &s3[6], &s3[7]);
+
+ uint16x8_t d0 = convolve8_8_2d_h(s0, filter, offset_lo, shift);
+ uint16x8_t d1 = convolve8_8_2d_h(s1, filter, offset_lo, shift);
+ uint16x8_t d2 = convolve8_8_2d_h(s2, filter, offset_lo, shift);
+ uint16x8_t d3 = convolve8_8_2d_h(s3, filter, offset_lo, shift);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height > 0);
+}
+
+static INLINE uint16x4_t convolve4_4_2d_h(int16x8_t s0, int16x8_t filter,
+ int64x2_t offset, int32x4_t shift,
+ uint16x8x2_t permute_tbl) {
+ int16x8_t permuted_samples0 = aom_tbl_s16(s0, permute_tbl.val[0]);
+ int16x8_t permuted_samples1 = aom_tbl_s16(s0, permute_tbl.val[1]);
+
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, permuted_samples0, filter, 0);
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, permuted_samples1, filter, 0);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ sum0123 = vqrshlq_s32(sum0123, shift);
+ return vqmovun_s32(sum0123);
+}
+
+static INLINE uint16x8_t convolve4_8_2d_h(int16x8_t s0[8], int16x8_t filter,
+ int64x2_t offset, int32x4_t shift,
+ uint16x8_t tbl) {
+ int64x2_t sum04 = aom_svdot_lane_s16(offset, s0[0], filter, 0);
+ int64x2_t sum15 = aom_svdot_lane_s16(offset, s0[1], filter, 0);
+ int64x2_t sum26 = aom_svdot_lane_s16(offset, s0[2], filter, 0);
+ int64x2_t sum37 = aom_svdot_lane_s16(offset, s0[3], filter, 0);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum04), vmovn_s64(sum15));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum26), vmovn_s64(sum37));
+
+ sum0123 = vqrshlq_s32(sum0123, shift);
+ sum4567 = vqrshlq_s32(sum4567, shift);
+
+ uint16x8_t res = vcombine_u16(vqmovun_s32(sum0123), vqmovun_s32(sum4567));
+ return aom_tbl_u16(res, tbl);
+}
+
+static INLINE void highbd_convolve_2d_sr_horiz_4tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *x_filter_ptr,
+ ConvolveParams *conv_params, const int x_offset) {
+ const int64x2_t offset = vdupq_n_s64(x_offset);
+ const int32x4_t shift = vdupq_n_s32(-conv_params->round_0);
+
+ const int16x4_t x_filter = vld1_s16(x_filter_ptr + 2);
+ const int16x8_t filter = vcombine_s16(x_filter, vdup_n_s16(0));
+
+ if (width == 4) {
+ const int16_t *s = (const int16_t *)(src);
+
+ uint16x8x2_t permute_tbl = vld1q_u16_x2(kDotProdTbl);
+
+ do {
+ int16x8_t s0, s1, s2, s3;
+ load_s16_8x4(s, src_stride, &s0, &s1, &s2, &s3);
+
+ uint16x4_t d0 = convolve4_4_2d_h(s0, filter, offset, shift, permute_tbl);
+ uint16x4_t d1 = convolve4_4_2d_h(s1, filter, offset, shift, permute_tbl);
+ uint16x4_t d2 = convolve4_4_2d_h(s2, filter, offset, shift, permute_tbl);
+ uint16x4_t d3 = convolve4_4_2d_h(s3, filter, offset, shift, permute_tbl);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height > 0);
+ } else {
+ uint16x8_t idx = vld1q_u16(kDeinterleaveTbl);
+
+ do {
+ const int16_t *s = (const int16_t *)(src);
+ uint16_t *d = dst;
+ int w = width;
+
+ do {
+ int16x8_t s0[8], s1[8], s2[8], s3[8];
+ load_s16_8x8(s + 0 * src_stride, 1, &s0[0], &s0[1], &s0[2], &s0[3],
+ &s0[4], &s0[5], &s0[6], &s0[7]);
+ load_s16_8x8(s + 1 * src_stride, 1, &s1[0], &s1[1], &s1[2], &s1[3],
+ &s1[4], &s1[5], &s1[6], &s1[7]);
+ load_s16_8x8(s + 2 * src_stride, 1, &s2[0], &s2[1], &s2[2], &s2[3],
+ &s2[4], &s2[5], &s2[6], &s2[7]);
+ load_s16_8x8(s + 3 * src_stride, 1, &s3[0], &s3[1], &s3[2], &s3[3],
+ &s3[4], &s3[5], &s3[6], &s3[7]);
+
+ uint16x8_t d0 = convolve4_8_2d_h(s0, filter, offset, shift, idx);
+ uint16x8_t d1 = convolve4_8_2d_h(s1, filter, offset, shift, idx);
+ uint16x8_t d2 = convolve4_8_2d_h(s2, filter, offset, shift, idx);
+ uint16x8_t d3 = convolve4_8_2d_h(s3, filter, offset, shift, idx);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ s += 8;
+ d += 8;
+ w -= 8;
+ } while (w != 0);
+ src += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height > 0);
+ }
+}
+
+static INLINE uint16x4_t highbd_convolve12_4_2d_v(
+ int16x8_t s0[2], int16x8_t s1[2], int16x8_t s2[2], int16x8_t filter_0_7,
+ int16x8_t filter_4_11, int32x4_t shift, int64x2_t offset, uint16x4_t max) {
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, s0[0], filter_0_7, 0);
+ sum01 = aom_svdot_lane_s16(sum01, s1[0], filter_0_7, 1);
+ sum01 = aom_svdot_lane_s16(sum01, s2[0], filter_4_11, 1);
+
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, s0[1], filter_0_7, 0);
+ sum23 = aom_svdot_lane_s16(sum23, s1[1], filter_0_7, 1);
+ sum23 = aom_svdot_lane_s16(sum23, s2[1], filter_4_11, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ sum0123 = vshlq_s32(sum0123, shift);
+
+ uint16x4_t res = vqmovun_s32(sum0123);
+
+ return vmin_u16(res, max);
+}
+
+static INLINE void highbd_convolve_2d_sr_vert_12tap_sve2(
+ const uint16_t *src, int src_stride, uint16_t *dst, int dst_stride,
+ int width, int height, const int16_t *y_filter_ptr,
+ ConvolveParams *conv_params, int bd, const int y_offset) {
+ const int64x2_t offset = vdupq_n_s64(y_offset);
+ const int32x4_t shift = vdupq_n_s32(-conv_params->round_1);
+
+ const int16x8_t y_filter_0_7 = vld1q_s16(y_filter_ptr);
+ const int16x8_t y_filter_4_11 = vld1q_s16(y_filter_ptr + 4);
+
+ uint16x8x3_t merge_block_tbl = vld1q_u16_x3(kDotProdMergeBlockTbl);
+ // Scale indices by size of the true vector length to avoid reading from an
+ // 'undefined' portion of a vector on a system with SVE vectors > 128-bit.
+ uint16x8_t correction0 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000000000000ULL));
+ merge_block_tbl.val[0] = vaddq_u16(merge_block_tbl.val[0], correction0);
+
+ uint16x8_t correction1 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100000000ULL));
+ merge_block_tbl.val[1] = vaddq_u16(merge_block_tbl.val[1], correction1);
+
+ uint16x8_t correction2 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100010000ULL));
+ merge_block_tbl.val[2] = vaddq_u16(merge_block_tbl.val[2], correction2);
+
+ const uint16x4_t max = vdup_n_u16((1 << bd) - 1);
+
+ do {
+ int16_t *s = (int16_t *)src;
+ uint16_t *d = (uint16_t *)dst;
+ int h = height;
+
+ int16x4_t s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA;
+ load_s16_4x11(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6, &s7, &s8,
+ &s9, &sA);
+ s += 11 * src_stride;
+
+ int16x8_t s0123[2], s1234[2], s2345[2], s3456[2], s4567[2], s5678[2],
+ s6789[2], s789A[2];
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ transpose_concat_4x4(s0, s1, s2, s3, s0123);
+ transpose_concat_4x4(s1, s2, s3, s4, s1234);
+ transpose_concat_4x4(s2, s3, s4, s5, s2345);
+ transpose_concat_4x4(s3, s4, s5, s6, s3456);
+ transpose_concat_4x4(s4, s5, s6, s7, s4567);
+ transpose_concat_4x4(s5, s6, s7, s8, s5678);
+ transpose_concat_4x4(s6, s7, s8, s9, s6789);
+ transpose_concat_4x4(s7, s8, s9, sA, s789A);
+
+ do {
+ int16x4_t sB, sC, sD, sE;
+ load_s16_4x4(s, src_stride, &sB, &sC, &sD, &sE);
+
+ int16x8_t s89AB[2], s9ABC[2], sABCD[2], sBCDE[2];
+ transpose_concat_4x4(sB, sC, sD, sE, sBCDE);
+
+ // Use the above transpose and reuse data from the previous loop to get
+ // the rest.
+ aom_tbl2x2_s16(s789A, sBCDE, merge_block_tbl.val[0], s89AB);
+ aom_tbl2x2_s16(s789A, sBCDE, merge_block_tbl.val[1], s9ABC);
+ aom_tbl2x2_s16(s789A, sBCDE, merge_block_tbl.val[2], sABCD);
+
+ uint16x4_t d0 = highbd_convolve12_4_2d_v(
+ s0123, s4567, s89AB, y_filter_0_7, y_filter_4_11, shift, offset, max);
+ uint16x4_t d1 = highbd_convolve12_4_2d_v(
+ s1234, s5678, s9ABC, y_filter_0_7, y_filter_4_11, shift, offset, max);
+ uint16x4_t d2 = highbd_convolve12_4_2d_v(
+ s2345, s6789, sABCD, y_filter_0_7, y_filter_4_11, shift, offset, max);
+ uint16x4_t d3 = highbd_convolve12_4_2d_v(
+ s3456, s789A, sBCDE, y_filter_0_7, y_filter_4_11, shift, offset, max);
+
+ store_u16_4x4(d, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s3456[0] = s789A[0];
+ s3456[1] = s789A[1];
+ s4567[0] = s89AB[0];
+ s4567[1] = s89AB[1];
+ s5678[0] = s9ABC[0];
+ s5678[1] = s9ABC[1];
+ s6789[0] = sABCD[0];
+ s6789[1] = sABCD[1];
+ s789A[0] = sBCDE[0];
+ s789A[1] = sBCDE[1];
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ src += 4;
+ dst += 4;
+ width -= 4;
+ } while (width != 0);
+}
+
+static INLINE uint16x4_t highbd_convolve8_4_2d_v(
+ int16x8_t samples_lo[2], int16x8_t samples_hi[2], int16x8_t filter,
+ int32x4_t shift, int64x2_t offset, uint16x4_t max) {
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, samples_lo[0], filter, 0);
+ sum01 = aom_svdot_lane_s16(sum01, samples_hi[0], filter, 1);
+
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, samples_lo[1], filter, 0);
+ sum23 = aom_svdot_lane_s16(sum23, samples_hi[1], filter, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ sum0123 = vshlq_s32(sum0123, shift);
+
+ uint16x4_t res = vqmovun_s32(sum0123);
+ return vmin_u16(res, max);
+}
+
+static INLINE uint16x8_t highbd_convolve8_8_2d_v(
+ int16x8_t samples_lo[4], int16x8_t samples_hi[4], int16x8_t filter,
+ int32x4_t shift, int64x2_t offset, uint16x8_t max) {
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, samples_lo[0], filter, 0);
+ sum01 = aom_svdot_lane_s16(sum01, samples_hi[0], filter, 1);
+
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, samples_lo[1], filter, 0);
+ sum23 = aom_svdot_lane_s16(sum23, samples_hi[1], filter, 1);
+
+ int64x2_t sum45 = aom_svdot_lane_s16(offset, samples_lo[2], filter, 0);
+ sum45 = aom_svdot_lane_s16(sum45, samples_hi[2], filter, 1);
+
+ int64x2_t sum67 = aom_svdot_lane_s16(offset, samples_lo[3], filter, 0);
+ sum67 = aom_svdot_lane_s16(sum67, samples_hi[3], filter, 1);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum45), vmovn_s64(sum67));
+
+ sum0123 = vshlq_s32(sum0123, shift);
+ sum4567 = vshlq_s32(sum4567, shift);
+
+ uint16x8_t res = vcombine_u16(vqmovun_s32(sum0123), vqmovun_s32(sum4567));
+ return vminq_u16(res, max);
+}
+
+void highbd_convolve_2d_sr_vert_8tap_sve2(const uint16_t *src,
+ ptrdiff_t src_stride, uint16_t *dst,
+ ptrdiff_t dst_stride, int width,
+ int height, const int16_t *filter_y,
+ ConvolveParams *conv_params, int bd,
+ const int y_offset) {
+ assert(w >= 4 && h >= 4);
+ const int64x2_t offset = vdupq_n_s64(y_offset);
+ const int32x4_t shift = vdupq_n_s32(-conv_params->round_1);
+ const int16x8_t y_filter = vld1q_s16(filter_y);
+
+ uint16x8x3_t merge_block_tbl = vld1q_u16_x3(kDotProdMergeBlockTbl);
+ // Scale indices by size of the true vector length to avoid reading from an
+ // 'undefined' portion of a vector on a system with SVE vectors > 128-bit.
+ uint16x8_t correction0 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000000000000ULL));
+ merge_block_tbl.val[0] = vaddq_u16(merge_block_tbl.val[0], correction0);
+
+ uint16x8_t correction1 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100000000ULL));
+ merge_block_tbl.val[1] = vaddq_u16(merge_block_tbl.val[1], correction1);
+
+ uint16x8_t correction2 =
+ vreinterpretq_u16_u64(vdupq_n_u64(svcnth() * 0x0001000100010000ULL));
+ merge_block_tbl.val[2] = vaddq_u16(merge_block_tbl.val[2], correction2);
+
+ if (width == 4) {
+ const uint16x4_t max = vdup_n_u16((1 << bd) - 1);
+ int16_t *s = (int16_t *)src;
+
+ int16x4_t s0, s1, s2, s3, s4, s5, s6;
+ load_s16_4x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
+ s += 7 * src_stride;
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[2], s1234[2], s2345[2], s3456[2];
+ transpose_concat_4x4(s0, s1, s2, s3, s0123);
+ transpose_concat_4x4(s1, s2, s3, s4, s1234);
+ transpose_concat_4x4(s2, s3, s4, s5, s2345);
+ transpose_concat_4x4(s3, s4, s5, s6, s3456);
+
+ do {
+ int16x4_t s7, s8, s9, s10;
+ load_s16_4x4(s, src_stride, &s7, &s8, &s9, &s10);
+
+ int16x8_t s4567[2], s5678[2], s6789[2], s789A[2];
+ // Transpose and shuffle the 4 lines that were loaded.
+ transpose_concat_4x4(s7, s8, s9, s10, s789A);
+
+ // Merge new data into block from previous iteration.
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[0], s4567);
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[1], s5678);
+ aom_tbl2x2_s16(s3456, s789A, merge_block_tbl.val[2], s6789);
+
+ uint16x4_t d0 =
+ highbd_convolve8_4_2d_v(s0123, s4567, y_filter, shift, offset, max);
+ uint16x4_t d1 =
+ highbd_convolve8_4_2d_v(s1234, s5678, y_filter, shift, offset, max);
+ uint16x4_t d2 =
+ highbd_convolve8_4_2d_v(s2345, s6789, y_filter, shift, offset, max);
+ uint16x4_t d3 =
+ highbd_convolve8_4_2d_v(s3456, s789A, y_filter, shift, offset, max);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s3456[0] = s789A[0];
+ s3456[1] = s789A[1];
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+
+ do {
+ int h = height;
+ int16_t *s = (int16_t *)src;
+ uint16_t *d = dst;
+
+ int16x8_t s0, s1, s2, s3, s4, s5, s6;
+ load_s16_8x7(s, src_stride, &s0, &s1, &s2, &s3, &s4, &s5, &s6);
+ s += 7 * src_stride;
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[4], s1234[4], s2345[4], s3456[4];
+ transpose_concat_8x4(s0, s1, s2, s3, s0123);
+ transpose_concat_8x4(s1, s2, s3, s4, s1234);
+ transpose_concat_8x4(s2, s3, s4, s5, s2345);
+ transpose_concat_8x4(s3, s4, s5, s6, s3456);
+
+ do {
+ int16x8_t s7, s8, s9, s10;
+ load_s16_8x4(s, src_stride, &s7, &s8, &s9, &s10);
+
+ int16x8_t s4567[4], s5678[4], s6789[4], s789A[4];
+ // Transpose and shuffle the 4 lines that were loaded.
+ transpose_concat_8x4(s7, s8, s9, s10, s789A);
+
+ // Merge new data into block from previous iteration.
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[0], s4567);
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[1], s5678);
+ aom_tbl2x4_s16(s3456, s789A, merge_block_tbl.val[2], s6789);
+
+ uint16x8_t d0 =
+ highbd_convolve8_8_2d_v(s0123, s4567, y_filter, shift, offset, max);
+ uint16x8_t d1 =
+ highbd_convolve8_8_2d_v(s1234, s5678, y_filter, shift, offset, max);
+ uint16x8_t d2 =
+ highbd_convolve8_8_2d_v(s2345, s6789, y_filter, shift, offset, max);
+ uint16x8_t d3 =
+ highbd_convolve8_8_2d_v(s3456, s789A, y_filter, shift, offset, max);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ // Prepare block for next iteration - re-using as much as possible.
+ // Shuffle everything up four rows.
+ s0123[0] = s4567[0];
+ s0123[1] = s4567[1];
+ s0123[2] = s4567[2];
+ s0123[3] = s4567[3];
+ s1234[0] = s5678[0];
+ s1234[1] = s5678[1];
+ s1234[2] = s5678[2];
+ s1234[3] = s5678[3];
+ s2345[0] = s6789[0];
+ s2345[1] = s6789[1];
+ s2345[2] = s6789[2];
+ s2345[3] = s6789[3];
+ s3456[0] = s789A[0];
+ s3456[1] = s789A[1];
+ s3456[2] = s789A[2];
+ s3456[3] = s789A[3];
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ src += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ }
+}
+
+static INLINE uint16x4_t highbd_convolve4_4_2d_v(int16x8_t samples[2],
+ int16x8_t filter,
+ int32x4_t shift,
+ int64x2_t offset,
+ uint16x4_t max) {
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, samples[0], filter, 0);
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, samples[1], filter, 0);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ sum0123 = vshlq_s32(sum0123, shift);
+
+ uint16x4_t res = vqmovun_s32(sum0123);
+ return vmin_u16(res, max);
+}
+
+static INLINE uint16x8_t highbd_convolve4_8_2d_v(int16x8_t samples[4],
+ int16x8_t filter,
+ int32x4_t shift,
+ int64x2_t offset,
+ uint16x8_t max) {
+ int64x2_t sum01 = aom_svdot_lane_s16(offset, samples[0], filter, 0);
+ int64x2_t sum23 = aom_svdot_lane_s16(offset, samples[1], filter, 0);
+ int64x2_t sum45 = aom_svdot_lane_s16(offset, samples[2], filter, 0);
+ int64x2_t sum67 = aom_svdot_lane_s16(offset, samples[3], filter, 0);
+
+ int32x4_t sum0123 = vcombine_s32(vmovn_s64(sum01), vmovn_s64(sum23));
+ int32x4_t sum4567 = vcombine_s32(vmovn_s64(sum45), vmovn_s64(sum67));
+
+ sum0123 = vshlq_s32(sum0123, shift);
+ sum4567 = vshlq_s32(sum4567, shift);
+
+ uint16x8_t res = vcombine_u16(vqmovun_s32(sum0123), vqmovun_s32(sum4567));
+ return vminq_u16(res, max);
+}
+
+void highbd_convolve_2d_sr_vert_4tap_sve2(const uint16_t *src,
+ ptrdiff_t src_stride, uint16_t *dst,
+ ptrdiff_t dst_stride, int width,
+ int height, const int16_t *filter_y,
+ ConvolveParams *conv_params, int bd,
+ const int y_offset) {
+ assert(w >= 4 && h >= 4);
+ const int64x2_t offset = vdupq_n_s64(y_offset);
+ const int32x4_t shift = vdupq_n_s32(-conv_params->round_1);
+
+ const int16x8_t y_filter =
+ vcombine_s16(vld1_s16(filter_y + 2), vdup_n_s16(0));
+
+ if (width == 4) {
+ const uint16x4_t max = vdup_n_u16((1 << bd) - 1);
+ int16_t *s = (int16_t *)(src);
+
+ int16x4_t s0, s1, s2;
+ load_s16_4x3(s, src_stride, &s0, &s1, &s2);
+ s += 3 * src_stride;
+
+ do {
+ int16x4_t s3, s4, s5, s6;
+ load_s16_4x4(s, src_stride, &s3, &s4, &s5, &s6);
+
+ // This operation combines a conventional transpose and the sample permute
+ // required before computing the dot product.
+ int16x8_t s0123[2], s1234[2], s2345[2], s3456[2];
+ transpose_concat_4x4(s0, s1, s2, s3, s0123);
+ transpose_concat_4x4(s1, s2, s3, s4, s1234);
+ transpose_concat_4x4(s2, s3, s4, s5, s2345);
+ transpose_concat_4x4(s3, s4, s5, s6, s3456);
+
+ uint16x4_t d0 =
+ highbd_convolve4_4_2d_v(s0123, y_filter, shift, offset, max);
+ uint16x4_t d1 =
+ highbd_convolve4_4_2d_v(s1234, y_filter, shift, offset, max);
+ uint16x4_t d2 =
+ highbd_convolve4_4_2d_v(s2345, y_filter, shift, offset, max);
+ uint16x4_t d3 =
+ highbd_convolve4_4_2d_v(s3456, y_filter, shift, offset, max);
+
+ store_u16_4x4(dst, dst_stride, d0, d1, d2, d3);
+
+ // Shuffle everything up four rows.
+ s0 = s4;
+ s1 = s5;
+ s2 = s6;
+
+ s += 4 * src_stride;
+ dst += 4 * dst_stride;
+ height -= 4;
+ } while (height != 0);
+ } else {
+ const uint16x8_t max = vdupq_n_u16((1 << bd) - 1);
+
+ do {
+ int h = height;
+ int16_t *s = (int16_t *)(src);
+ uint16_t *d = dst;
+
+ int16x8_t s0, s1, s2;
+ load_s16_8x3(s, src_stride, &s0, &s1, &s2);
+ s += 3 * src_stride;
+
+ do {
+ int16x8_t s3, s4, s5, s6;
+ load_s16_8x4(s, src_stride, &s3, &s4, &s5, &s6);
+
+ // This operation combines a conventional transpose and the sample
+ // permute required before computing the dot product.
+ int16x8_t s0123[4], s1234[4], s2345[4], s3456[4];
+ transpose_concat_8x4(s0, s1, s2, s3, s0123);
+ transpose_concat_8x4(s1, s2, s3, s4, s1234);
+ transpose_concat_8x4(s2, s3, s4, s5, s2345);
+ transpose_concat_8x4(s3, s4, s5, s6, s3456);
+
+ uint16x8_t d0 =
+ highbd_convolve4_8_2d_v(s0123, y_filter, shift, offset, max);
+ uint16x8_t d1 =
+ highbd_convolve4_8_2d_v(s1234, y_filter, shift, offset, max);
+ uint16x8_t d2 =
+ highbd_convolve4_8_2d_v(s2345, y_filter, shift, offset, max);
+ uint16x8_t d3 =
+ highbd_convolve4_8_2d_v(s3456, y_filter, shift, offset, max);
+
+ store_u16_8x4(d, dst_stride, d0, d1, d2, d3);
+
+ // Shuffle everything up four rows.
+ s0 = s4;
+ s1 = s5;
+ s2 = s6;
+
+ s += 4 * src_stride;
+ d += 4 * dst_stride;
+ h -= 4;
+ } while (h != 0);
+ src += 8;
+ dst += 8;
+ width -= 8;
+ } while (width != 0);
+ }
+}
+
+void av1_highbd_convolve_2d_sr_sve2(const uint16_t *src, int src_stride,
+ uint16_t *dst, int dst_stride, int w, int h,
+ const InterpFilterParams *filter_params_x,
+ const InterpFilterParams *filter_params_y,
+ const int subpel_x_qn,
+ const int subpel_y_qn,
+ ConvolveParams *conv_params, int bd) {
+ if (w == 2 || h == 2) {
+ av1_highbd_convolve_2d_sr_c(src, src_stride, dst, dst_stride, w, h,
+ filter_params_x, filter_params_y, subpel_x_qn,
+ subpel_y_qn, conv_params, bd);
+ return;
+ }
+
+ DECLARE_ALIGNED(16, uint16_t,
+ im_block[(MAX_SB_SIZE + MAX_FILTER_TAP) * MAX_SB_SIZE]);
+ const int x_filter_taps = get_filter_tap(filter_params_x, subpel_x_qn);
+ const int y_filter_taps = get_filter_tap(filter_params_y, subpel_y_qn);
+
+ if (x_filter_taps == 6 || y_filter_taps == 6) {
+ av1_highbd_convolve_2d_sr_neon(src, src_stride, dst, dst_stride, w, h,
+ filter_params_x, filter_params_y,
+ subpel_x_qn, subpel_y_qn, conv_params, bd);
+ return;
+ }
+
+ const int clamped_x_taps = x_filter_taps < 4 ? 4 : x_filter_taps;
+ const int clamped_y_taps = y_filter_taps < 4 ? 4 : y_filter_taps;
+
+ const int im_stride = MAX_SB_SIZE;
+ const int vert_offset = clamped_y_taps / 2 - 1;
+ const int horiz_offset = clamped_x_taps / 2 - 1;
+ const int x_offset = (1 << (bd + FILTER_BITS - 1));
+ const int y_offset_bits = bd + 2 * FILTER_BITS - conv_params->round_0;
+ // The extra shim of (1 << (conv_params->round_1 - 1)) allows us to do a
+ // simple shift left instead of a rounding saturating shift left.
+ const int y_offset =
+ (1 << (conv_params->round_1 - 1)) - (1 << (y_offset_bits - 1));
+
+ const uint16_t *src_ptr = src - vert_offset * src_stride - horiz_offset;
+
+ const int16_t *x_filter_ptr = av1_get_interp_filter_subpel_kernel(
+ filter_params_x, subpel_x_qn & SUBPEL_MASK);
+ const int16_t *y_filter_ptr = av1_get_interp_filter_subpel_kernel(
+ filter_params_y, subpel_y_qn & SUBPEL_MASK);
+ const int im_h = h + clamped_y_taps - 1;
+
+ if (x_filter_taps > 8) {
+ highbd_convolve_2d_sr_horiz_12tap_sve2(src_ptr, src_stride, im_block,
+ im_stride, w, im_h, x_filter_ptr,
+ conv_params, x_offset);
+
+ highbd_convolve_2d_sr_vert_12tap_sve2(im_block, im_stride, dst, dst_stride,
+ w, h, y_filter_ptr, conv_params, bd,
+ y_offset);
+ return;
+ }
+
+ if (x_filter_taps <= 4) {
+ highbd_convolve_2d_sr_horiz_4tap_sve2(src_ptr, src_stride, im_block,
+ im_stride, w, im_h, x_filter_ptr,
+ conv_params, x_offset);
+ } else {
+ highbd_convolve_2d_sr_horiz_8tap_sve2(src_ptr, src_stride, im_block,
+ im_stride, w, im_h, x_filter_ptr,
+ conv_params, x_offset);
+ }
+
+ if (y_filter_taps <= 4) {
+ highbd_convolve_2d_sr_vert_4tap_sve2(im_block, im_stride, dst, dst_stride,
+ w, h, y_filter_ptr, conv_params, bd,
+ y_offset);
+ } else {
+ highbd_convolve_2d_sr_vert_8tap_sve2(im_block, im_stride, dst, dst_stride,
+ w, h, y_filter_ptr, conv_params, bd,
+ y_offset);
+ }
+}
diff --git a/third_party/aom/av1/common/arm/highbd_convolve_sve2.h b/third_party/aom/av1/common/arm/highbd_convolve_sve2.h
new file mode 100644
index 0000000000..05e23deef4
--- /dev/null
+++ b/third_party/aom/av1/common/arm/highbd_convolve_sve2.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2023, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#ifndef AOM_AV1_COMMON_ARM_HIGHBD_CONVOLVE_SVE2_H_
+#define AOM_AV1_COMMON_ARM_HIGHBD_CONVOLVE_SVE2_H_
+
+#include <arm_neon.h>
+
+#include "aom_dsp/arm/aom_neon_sve2_bridge.h"
+
+// clang-format off
+DECLARE_ALIGNED(16, static const uint16_t, kDotProdMergeBlockTbl[24]) = {
+ // Shift left and insert new last column in transposed 4x4 block.
+ 1, 2, 3, 0, 5, 6, 7, 4,
+ // Shift left and insert two new columns in transposed 4x4 block.
+ 2, 3, 0, 1, 6, 7, 4, 5,
+ // Shift left and insert three new columns in transposed 4x4 block.
+ 3, 0, 1, 2, 7, 4, 5, 6,
+};
+// clang-format on
+
+static INLINE void transpose_concat_4x4(int16x4_t s0, int16x4_t s1,
+ int16x4_t s2, int16x4_t s3,
+ int16x8_t res[2]) {
+ // Transpose 16-bit elements and concatenate result rows as follows:
+ // s0: 00, 01, 02, 03
+ // s1: 10, 11, 12, 13
+ // s2: 20, 21, 22, 23
+ // s3: 30, 31, 32, 33
+ //
+ // res[0]: 00 10 20 30 01 11 21 31
+ // res[1]: 02 12 22 32 03 13 23 33
+
+ int16x8_t s0q = vcombine_s16(s0, vdup_n_s16(0));
+ int16x8_t s1q = vcombine_s16(s1, vdup_n_s16(0));
+ int16x8_t s2q = vcombine_s16(s2, vdup_n_s16(0));
+ int16x8_t s3q = vcombine_s16(s3, vdup_n_s16(0));
+
+ int32x4_t s01 = vreinterpretq_s32_s16(vzip1q_s16(s0q, s1q));
+ int32x4_t s23 = vreinterpretq_s32_s16(vzip1q_s16(s2q, s3q));
+
+ int32x4x2_t s0123 = vzipq_s32(s01, s23);
+
+ res[0] = vreinterpretq_s16_s32(s0123.val[0]);
+ res[1] = vreinterpretq_s16_s32(s0123.val[1]);
+}
+
+static INLINE void transpose_concat_8x4(int16x8_t s0, int16x8_t s1,
+ int16x8_t s2, int16x8_t s3,
+ int16x8_t res[4]) {
+ // Transpose 16-bit elements and concatenate result rows as follows:
+ // s0: 00, 01, 02, 03, 04, 05, 06, 07
+ // s1: 10, 11, 12, 13, 14, 15, 16, 17
+ // s2: 20, 21, 22, 23, 24, 25, 26, 27
+ // s3: 30, 31, 32, 33, 34, 35, 36, 37
+ //
+ // res[0]: 00 10 20 30 01 11 21 31
+ // res[1]: 02 12 22 32 03 13 23 33
+ // res[2]: 04 14 24 34 05 15 25 35
+ // res[3]: 06 16 26 36 07 17 27 37
+
+ int16x8x2_t tr01_16 = vzipq_s16(s0, s1);
+ int16x8x2_t tr23_16 = vzipq_s16(s2, s3);
+ int32x4x2_t tr01_32 = vzipq_s32(vreinterpretq_s32_s16(tr01_16.val[0]),
+ vreinterpretq_s32_s16(tr23_16.val[0]));
+ int32x4x2_t tr23_32 = vzipq_s32(vreinterpretq_s32_s16(tr01_16.val[1]),
+ vreinterpretq_s32_s16(tr23_16.val[1]));
+
+ res[0] = vreinterpretq_s16_s32(tr01_32.val[0]);
+ res[1] = vreinterpretq_s16_s32(tr01_32.val[1]);
+ res[2] = vreinterpretq_s16_s32(tr23_32.val[0]);
+ res[3] = vreinterpretq_s16_s32(tr23_32.val[1]);
+}
+
+static INLINE void aom_tbl2x4_s16(int16x8_t t0[4], int16x8_t t1[4],
+ uint16x8_t tbl, int16x8_t res[4]) {
+ res[0] = aom_tbl2_s16(t0[0], t1[0], tbl);
+ res[1] = aom_tbl2_s16(t0[1], t1[1], tbl);
+ res[2] = aom_tbl2_s16(t0[2], t1[2], tbl);
+ res[3] = aom_tbl2_s16(t0[3], t1[3], tbl);
+}
+
+static INLINE void aom_tbl2x2_s16(int16x8_t t0[2], int16x8_t t1[2],
+ uint16x8_t tbl, int16x8_t res[2]) {
+ res[0] = aom_tbl2_s16(t0[0], t1[0], tbl);
+ res[1] = aom_tbl2_s16(t0[1], t1[1], tbl);
+}
+
+#endif // AOM_AV1_COMMON_ARM_HIGHBD_CONVOLVE_SVE2_H_
diff --git a/third_party/aom/av1/common/arm/highbd_warp_plane_neon.c b/third_party/aom/av1/common/arm/highbd_warp_plane_neon.c
index c6f1e3ad92..89647bc921 100644
--- a/third_party/aom/av1/common/arm/highbd_warp_plane_neon.c
+++ b/third_party/aom/av1/common/arm/highbd_warp_plane_neon.c
@@ -23,8 +23,8 @@
#include "config/av1_rtcd.h"
#include "highbd_warp_plane_neon.h"
-static INLINE int16x8_t highbd_horizontal_filter_4x1_f4(uint16x8x2_t in, int bd,
- int sx, int alpha) {
+static AOM_FORCE_INLINE int16x8_t
+highbd_horizontal_filter_4x1_f4(uint16x8x2_t in, int bd, int sx, int alpha) {
int16x8_t f[4];
load_filters_4(f, sx, alpha);
@@ -57,8 +57,8 @@ static INLINE int16x8_t highbd_horizontal_filter_4x1_f4(uint16x8x2_t in, int bd,
return vcombine_s16(vmovn_s32(res), vdup_n_s16(0));
}
-static INLINE int16x8_t highbd_horizontal_filter_8x1_f8(uint16x8x2_t in, int bd,
- int sx, int alpha) {
+static AOM_FORCE_INLINE int16x8_t
+highbd_horizontal_filter_8x1_f8(uint16x8x2_t in, int bd, int sx, int alpha) {
int16x8_t f[8];
load_filters_8(f, sx, alpha);
@@ -111,8 +111,8 @@ static INLINE int16x8_t highbd_horizontal_filter_8x1_f8(uint16x8x2_t in, int bd,
return vcombine_s16(vmovn_s32(res0), vmovn_s32(res1));
}
-static INLINE int16x8_t highbd_horizontal_filter_4x1_f1(uint16x8x2_t in, int bd,
- int sx) {
+static AOM_FORCE_INLINE int16x8_t
+highbd_horizontal_filter_4x1_f1(uint16x8x2_t in, int bd, int sx) {
int16x8_t f = load_filters_1(sx);
int16x8_t rv0 = vextq_s16(vreinterpretq_s16_u16(in.val[0]),
@@ -144,8 +144,8 @@ static INLINE int16x8_t highbd_horizontal_filter_4x1_f1(uint16x8x2_t in, int bd,
return vcombine_s16(vmovn_s32(res), vdup_n_s16(0));
}
-static INLINE int16x8_t highbd_horizontal_filter_8x1_f1(uint16x8x2_t in, int bd,
- int sx) {
+static AOM_FORCE_INLINE int16x8_t
+highbd_horizontal_filter_8x1_f1(uint16x8x2_t in, int bd, int sx) {
int16x8_t f = load_filters_1(sx);
int16x8_t rv0 = vextq_s16(vreinterpretq_s16_u16(in.val[0]),
@@ -197,7 +197,8 @@ static INLINE int16x8_t highbd_horizontal_filter_8x1_f1(uint16x8x2_t in, int bd,
return vcombine_s16(vmovn_s32(res0), vmovn_s32(res1));
}
-static INLINE int32x4_t vertical_filter_4x1_f1(const int16x8_t *tmp, int sy) {
+static AOM_FORCE_INLINE int32x4_t vertical_filter_4x1_f1(const int16x8_t *tmp,
+ int sy) {
const int16x8_t f = load_filters_1(sy);
const int16x4_t f0123 = vget_low_s16(f);
const int16x4_t f4567 = vget_high_s16(f);
@@ -213,7 +214,8 @@ static INLINE int32x4_t vertical_filter_4x1_f1(const int16x8_t *tmp, int sy) {
return m0123;
}
-static INLINE int32x4x2_t vertical_filter_8x1_f1(const int16x8_t *tmp, int sy) {
+static AOM_FORCE_INLINE int32x4x2_t vertical_filter_8x1_f1(const int16x8_t *tmp,
+ int sy) {
const int16x8_t f = load_filters_1(sy);
const int16x4_t f0123 = vget_low_s16(f);
const int16x4_t f4567 = vget_high_s16(f);
@@ -238,8 +240,8 @@ static INLINE int32x4x2_t vertical_filter_8x1_f1(const int16x8_t *tmp, int sy) {
return (int32x4x2_t){ { m0123, m4567 } };
}
-static INLINE int32x4_t vertical_filter_4x1_f4(const int16x8_t *tmp, int sy,
- int gamma) {
+static AOM_FORCE_INLINE int32x4_t vertical_filter_4x1_f4(const int16x8_t *tmp,
+ int sy, int gamma) {
int16x8_t s0, s1, s2, s3;
transpose_elems_s16_4x8(
vget_low_s16(tmp[0]), vget_low_s16(tmp[1]), vget_low_s16(tmp[2]),
@@ -262,8 +264,8 @@ static INLINE int32x4_t vertical_filter_4x1_f4(const int16x8_t *tmp, int sy,
return horizontal_add_4d_s32x4(m0123);
}
-static INLINE int32x4x2_t vertical_filter_8x1_f8(const int16x8_t *tmp, int sy,
- int gamma) {
+static AOM_FORCE_INLINE int32x4x2_t vertical_filter_8x1_f8(const int16x8_t *tmp,
+ int sy, int gamma) {
int16x8_t s0 = tmp[0];
int16x8_t s1 = tmp[1];
int16x8_t s2 = tmp[2];
diff --git a/third_party/aom/av1/common/arm/highbd_warp_plane_neon.h b/third_party/aom/av1/common/arm/highbd_warp_plane_neon.h
index 3b8982898e..48af4a707b 100644
--- a/third_party/aom/av1/common/arm/highbd_warp_plane_neon.h
+++ b/third_party/aom/av1/common/arm/highbd_warp_plane_neon.h
@@ -23,29 +23,31 @@
#include "av1/common/warped_motion.h"
#include "config/av1_rtcd.h"
-static INLINE int16x8_t highbd_horizontal_filter_4x1_f4(uint16x8x2_t in, int bd,
- int sx, int alpha);
+static AOM_FORCE_INLINE int16x8_t
+highbd_horizontal_filter_4x1_f4(uint16x8x2_t in, int bd, int sx, int alpha);
-static INLINE int16x8_t highbd_horizontal_filter_8x1_f8(uint16x8x2_t in, int bd,
- int sx, int alpha);
+static AOM_FORCE_INLINE int16x8_t
+highbd_horizontal_filter_8x1_f8(uint16x8x2_t in, int bd, int sx, int alpha);
-static INLINE int16x8_t highbd_horizontal_filter_4x1_f1(uint16x8x2_t in, int bd,
- int sx);
+static AOM_FORCE_INLINE int16x8_t
+highbd_horizontal_filter_4x1_f1(uint16x8x2_t in, int bd, int sx);
-static INLINE int16x8_t highbd_horizontal_filter_8x1_f1(uint16x8x2_t in, int bd,
- int sx);
+static AOM_FORCE_INLINE int16x8_t
+highbd_horizontal_filter_8x1_f1(uint16x8x2_t in, int bd, int sx);
-static INLINE int32x4_t vertical_filter_4x1_f1(const int16x8_t *tmp, int sy);
+static AOM_FORCE_INLINE int32x4_t vertical_filter_4x1_f1(const int16x8_t *tmp,
+ int sy);
-static INLINE int32x4x2_t vertical_filter_8x1_f1(const int16x8_t *tmp, int sy);
+static AOM_FORCE_INLINE int32x4x2_t vertical_filter_8x1_f1(const int16x8_t *tmp,
+ int sy);
-static INLINE int32x4_t vertical_filter_4x1_f4(const int16x8_t *tmp, int sy,
- int gamma);
+static AOM_FORCE_INLINE int32x4_t vertical_filter_4x1_f4(const int16x8_t *tmp,
+ int sy, int gamma);
-static INLINE int32x4x2_t vertical_filter_8x1_f8(const int16x8_t *tmp, int sy,
- int gamma);
+static AOM_FORCE_INLINE int32x4x2_t vertical_filter_8x1_f8(const int16x8_t *tmp,
+ int sy, int gamma);
-static INLINE int16x8_t load_filters_1(int ofs) {
+static AOM_FORCE_INLINE int16x8_t load_filters_1(int ofs) {
const int ofs0 = ROUND_POWER_OF_TWO(ofs, WARPEDDIFF_PREC_BITS);
const int16_t *base =
@@ -53,7 +55,8 @@ static INLINE int16x8_t load_filters_1(int ofs) {
return vld1q_s16(base + ofs0 * 8);
}
-static INLINE void load_filters_4(int16x8_t out[], int ofs, int stride) {
+static AOM_FORCE_INLINE void load_filters_4(int16x8_t out[], int ofs,
+ int stride) {
const int ofs0 = ROUND_POWER_OF_TWO(ofs + stride * 0, WARPEDDIFF_PREC_BITS);
const int ofs1 = ROUND_POWER_OF_TWO(ofs + stride * 1, WARPEDDIFF_PREC_BITS);
const int ofs2 = ROUND_POWER_OF_TWO(ofs + stride * 2, WARPEDDIFF_PREC_BITS);
@@ -67,7 +70,8 @@ static INLINE void load_filters_4(int16x8_t out[], int ofs, int stride) {
out[3] = vld1q_s16(base + ofs3 * 8);
}
-static INLINE void load_filters_8(int16x8_t out[], int ofs, int stride) {
+static AOM_FORCE_INLINE void load_filters_8(int16x8_t out[], int ofs,
+ int stride) {
const int ofs0 = ROUND_POWER_OF_TWO(ofs + stride * 0, WARPEDDIFF_PREC_BITS);
const int ofs1 = ROUND_POWER_OF_TWO(ofs + stride * 1, WARPEDDIFF_PREC_BITS);
const int ofs2 = ROUND_POWER_OF_TWO(ofs + stride * 2, WARPEDDIFF_PREC_BITS);
@@ -89,16 +93,18 @@ static INLINE void load_filters_8(int16x8_t out[], int ofs, int stride) {
out[7] = vld1q_s16(base + ofs7 * 8);
}
-static INLINE uint16x4_t clip_pixel_highbd_vec(int32x4_t val, int bd) {
+static AOM_FORCE_INLINE uint16x4_t clip_pixel_highbd_vec(int32x4_t val,
+ int bd) {
const int limit = (1 << bd) - 1;
return vqmovun_s32(vminq_s32(val, vdupq_n_s32(limit)));
}
-static INLINE void warp_affine_horizontal(const uint16_t *ref, int width,
- int height, int stride, int p_width,
- int16_t alpha, int16_t beta, int iy4,
- int sx4, int ix4, int16x8_t tmp[],
- int bd) {
+static AOM_FORCE_INLINE void warp_affine_horizontal(const uint16_t *ref,
+ int width, int height,
+ int stride, int p_width,
+ int16_t alpha, int16_t beta,
+ int iy4, int sx4, int ix4,
+ int16x8_t tmp[], int bd) {
const int round0 = (bd == 12) ? ROUND0_BITS + 2 : ROUND0_BITS;
if (ix4 <= -7) {
@@ -197,7 +203,7 @@ static INLINE void warp_affine_horizontal(const uint16_t *ref, int width,
}
}
-static INLINE void highbd_vertical_filter_4x1_f4(
+static AOM_FORCE_INLINE void highbd_vertical_filter_4x1_f4(
uint16_t *pred, int p_stride, int bd, uint16_t *dst, int dst_stride,
bool is_compound, bool do_average, bool use_dist_wtd_comp_avg, int fwd,
int bwd, int16_t gamma, const int16x8_t *tmp, int i, int sy, int j) {
@@ -253,7 +259,7 @@ static INLINE void highbd_vertical_filter_4x1_f4(
vst1_u16(dst16, res0);
}
-static INLINE void highbd_vertical_filter_8x1_f8(
+static AOM_FORCE_INLINE void highbd_vertical_filter_8x1_f8(
uint16_t *pred, int p_stride, int bd, uint16_t *dst, int dst_stride,
bool is_compound, bool do_average, bool use_dist_wtd_comp_avg, int fwd,
int bwd, int16_t gamma, const int16x8_t *tmp, int i, int sy, int j) {
@@ -328,7 +334,7 @@ static INLINE void highbd_vertical_filter_8x1_f8(
vst1_u16(dst16 + 4, res1);
}
-static INLINE void warp_affine_vertical(
+static AOM_FORCE_INLINE void warp_affine_vertical(
uint16_t *pred, int p_width, int p_height, int p_stride, int bd,
uint16_t *dst, int dst_stride, bool is_compound, bool do_average,
bool use_dist_wtd_comp_avg, int fwd, int bwd, int16_t gamma, int16_t delta,
@@ -354,7 +360,7 @@ static INLINE void warp_affine_vertical(
}
}
-static INLINE void highbd_warp_affine_common(
+static AOM_FORCE_INLINE void highbd_warp_affine_common(
const int32_t *mat, const uint16_t *ref, int width, int height, int stride,
uint16_t *pred, int p_col, int p_row, int p_width, int p_height,
int p_stride, int subsampling_x, int subsampling_y, int bd,
diff --git a/third_party/aom/av1/common/arm/highbd_warp_plane_sve.c b/third_party/aom/av1/common/arm/highbd_warp_plane_sve.c
index 7a14f21846..87e033fd00 100644
--- a/third_party/aom/av1/common/arm/highbd_warp_plane_sve.c
+++ b/third_party/aom/av1/common/arm/highbd_warp_plane_sve.c
@@ -15,7 +15,7 @@
#include <arm_neon_sve_bridge.h>
#include "aom_dsp/aom_dsp_common.h"
-#include "aom_dsp/arm/dot_sve.h"
+#include "aom_dsp/arm/aom_neon_sve_bridge.h"
#include "aom_dsp/arm/mem_neon.h"
#include "aom_dsp/arm/transpose_neon.h"
#include "aom_ports/mem.h"
@@ -24,8 +24,8 @@
#include "config/av1_rtcd.h"
#include "highbd_warp_plane_neon.h"
-static INLINE int16x8_t highbd_horizontal_filter_4x1_f4(uint16x8x2_t in, int bd,
- int sx, int alpha) {
+static AOM_FORCE_INLINE int16x8_t
+highbd_horizontal_filter_4x1_f4(uint16x8x2_t in, int bd, int sx, int alpha) {
int16x8_t f[4];
load_filters_4(f, sx, alpha);
@@ -55,8 +55,8 @@ static INLINE int16x8_t highbd_horizontal_filter_4x1_f4(uint16x8x2_t in, int bd,
return vcombine_s16(vmovn_s32(res), vdup_n_s16(0));
}
-static INLINE int16x8_t highbd_horizontal_filter_8x1_f8(uint16x8x2_t in, int bd,
- int sx, int alpha) {
+static AOM_FORCE_INLINE int16x8_t
+highbd_horizontal_filter_8x1_f8(uint16x8x2_t in, int bd, int sx, int alpha) {
int16x8_t f[8];
load_filters_8(f, sx, alpha);
@@ -103,8 +103,8 @@ static INLINE int16x8_t highbd_horizontal_filter_8x1_f8(uint16x8x2_t in, int bd,
return vcombine_s16(vmovn_s32(res0), vmovn_s32(res1));
}
-static INLINE int16x8_t highbd_horizontal_filter_4x1_f1(uint16x8x2_t in, int bd,
- int sx) {
+static AOM_FORCE_INLINE int16x8_t
+highbd_horizontal_filter_4x1_f1(uint16x8x2_t in, int bd, int sx) {
int16x8_t f = load_filters_1(sx);
int16x8_t rv0 = vextq_s16(vreinterpretq_s16_u16(in.val[0]),
@@ -133,8 +133,8 @@ static INLINE int16x8_t highbd_horizontal_filter_4x1_f1(uint16x8x2_t in, int bd,
return vcombine_s16(vmovn_s32(res), vdup_n_s16(0));
}
-static INLINE int16x8_t highbd_horizontal_filter_8x1_f1(uint16x8x2_t in, int bd,
- int sx) {
+static AOM_FORCE_INLINE int16x8_t
+highbd_horizontal_filter_8x1_f1(uint16x8x2_t in, int bd, int sx) {
int16x8_t f = load_filters_1(sx);
int16x8_t rv0 = vextq_s16(vreinterpretq_s16_u16(in.val[0]),
@@ -180,7 +180,8 @@ static INLINE int16x8_t highbd_horizontal_filter_8x1_f1(uint16x8x2_t in, int bd,
return vcombine_s16(vmovn_s32(res0), vmovn_s32(res1));
}
-static INLINE int32x4_t vertical_filter_4x1_f1(const int16x8_t *tmp, int sy) {
+static AOM_FORCE_INLINE int32x4_t vertical_filter_4x1_f1(const int16x8_t *tmp,
+ int sy) {
const int16x8_t f = load_filters_1(sy);
const int16x4_t f0123 = vget_low_s16(f);
const int16x4_t f4567 = vget_high_s16(f);
@@ -197,7 +198,8 @@ static INLINE int32x4_t vertical_filter_4x1_f1(const int16x8_t *tmp, int sy) {
return m0123;
}
-static INLINE int32x4x2_t vertical_filter_8x1_f1(const int16x8_t *tmp, int sy) {
+static AOM_FORCE_INLINE int32x4x2_t vertical_filter_8x1_f1(const int16x8_t *tmp,
+ int sy) {
const int16x8_t f = load_filters_1(sy);
const int16x4_t f0123 = vget_low_s16(f);
const int16x4_t f4567 = vget_high_s16(f);
@@ -223,8 +225,8 @@ static INLINE int32x4x2_t vertical_filter_8x1_f1(const int16x8_t *tmp, int sy) {
return (int32x4x2_t){ { m0123, m4567 } };
}
-static INLINE int32x4_t vertical_filter_4x1_f4(const int16x8_t *tmp, int sy,
- int gamma) {
+static AOM_FORCE_INLINE int32x4_t vertical_filter_4x1_f4(const int16x8_t *tmp,
+ int sy, int gamma) {
int16x8_t s0, s1, s2, s3;
transpose_elems_s16_4x8(
vget_low_s16(tmp[0]), vget_low_s16(tmp[1]), vget_low_s16(tmp[2]),
@@ -244,8 +246,8 @@ static INLINE int32x4_t vertical_filter_4x1_f4(const int16x8_t *tmp, int sy,
return vcombine_s32(vmovn_s64(m01), vmovn_s64(m23));
}
-static INLINE int32x4x2_t vertical_filter_8x1_f8(const int16x8_t *tmp, int sy,
- int gamma) {
+static AOM_FORCE_INLINE int32x4x2_t vertical_filter_8x1_f8(const int16x8_t *tmp,
+ int sy, int gamma) {
int16x8_t s0 = tmp[0];
int16x8_t s1 = tmp[1];
int16x8_t s2 = tmp[2];
diff --git a/third_party/aom/av1/common/arm/warp_plane_neon.c b/third_party/aom/av1/common/arm/warp_plane_neon.c
index 4723154398..546aa2965b 100644
--- a/third_party/aom/av1/common/arm/warp_plane_neon.c
+++ b/third_party/aom/av1/common/arm/warp_plane_neon.c
@@ -11,8 +11,8 @@
#include "warp_plane_neon.h"
-static INLINE int16x8_t horizontal_filter_4x1_f4(const uint8x16_t in, int sx,
- int alpha) {
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_4x1_f4(const uint8x16_t in,
+ int sx, int alpha) {
const int32x4_t add_const = vdupq_n_s32(1 << (8 + FILTER_BITS - 1));
// Loading the 8 filter taps
@@ -39,8 +39,8 @@ static INLINE int16x8_t horizontal_filter_4x1_f4(const uint8x16_t in, int sx,
return vreinterpretq_s16_u16(res);
}
-static INLINE int16x8_t horizontal_filter_8x1_f8(const uint8x16_t in, int sx,
- int alpha) {
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_8x1_f8(const uint8x16_t in,
+ int sx, int alpha) {
const int32x4_t add_const = vdupq_n_s32(1 << (8 + FILTER_BITS - 1));
// Loading the 8 filter taps
@@ -75,7 +75,8 @@ static INLINE int16x8_t horizontal_filter_8x1_f8(const uint8x16_t in, int sx,
return vreinterpretq_s16_u16(res);
}
-static INLINE int16x8_t horizontal_filter_4x1_f1(const uint8x16_t in, int sx) {
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_4x1_f1(const uint8x16_t in,
+ int sx) {
const int32x4_t add_const = vdupq_n_s32(1 << (8 + FILTER_BITS - 1));
int16x8_t f_s16 =
@@ -101,7 +102,8 @@ static INLINE int16x8_t horizontal_filter_4x1_f1(const uint8x16_t in, int sx) {
return vreinterpretq_s16_u16(res);
}
-static INLINE int16x8_t horizontal_filter_8x1_f1(const uint8x16_t in, int sx) {
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_8x1_f1(const uint8x16_t in,
+ int sx) {
const int32x4_t add_const = vdupq_n_s32(1 << (8 + FILTER_BITS - 1));
int16x8_t f_s16 =
@@ -135,8 +137,8 @@ static INLINE int16x8_t horizontal_filter_8x1_f1(const uint8x16_t in, int sx) {
return vreinterpretq_s16_u16(res);
}
-static INLINE void vertical_filter_4x1_f1(const int16x8_t *src, int32x4_t *res,
- int sy) {
+static AOM_FORCE_INLINE void vertical_filter_4x1_f1(const int16x8_t *src,
+ int32x4_t *res, int sy) {
int16x4_t s0 = vget_low_s16(src[0]);
int16x4_t s1 = vget_low_s16(src[1]);
int16x4_t s2 = vget_low_s16(src[2]);
@@ -161,8 +163,9 @@ static INLINE void vertical_filter_4x1_f1(const int16x8_t *src, int32x4_t *res,
*res = m0123;
}
-static INLINE void vertical_filter_4x1_f4(const int16x8_t *src, int32x4_t *res,
- int sy, int gamma) {
+static AOM_FORCE_INLINE void vertical_filter_4x1_f4(const int16x8_t *src,
+ int32x4_t *res, int sy,
+ int gamma) {
int16x8_t s0, s1, s2, s3;
transpose_elems_s16_4x8(
vget_low_s16(src[0]), vget_low_s16(src[1]), vget_low_s16(src[2]),
@@ -186,9 +189,10 @@ static INLINE void vertical_filter_4x1_f4(const int16x8_t *src, int32x4_t *res,
*res = horizontal_add_4d_s32x4(m0123_pairs);
}
-static INLINE void vertical_filter_8x1_f1(const int16x8_t *src,
- int32x4_t *res_low,
- int32x4_t *res_high, int sy) {
+static AOM_FORCE_INLINE void vertical_filter_8x1_f1(const int16x8_t *src,
+ int32x4_t *res_low,
+ int32x4_t *res_high,
+ int sy) {
int16x8_t s0 = src[0];
int16x8_t s1 = src[1];
int16x8_t s2 = src[2];
@@ -223,10 +227,10 @@ static INLINE void vertical_filter_8x1_f1(const int16x8_t *src,
*res_high = m4567;
}
-static INLINE void vertical_filter_8x1_f8(const int16x8_t *src,
- int32x4_t *res_low,
- int32x4_t *res_high, int sy,
- int gamma) {
+static AOM_FORCE_INLINE void vertical_filter_8x1_f8(const int16x8_t *src,
+ int32x4_t *res_low,
+ int32x4_t *res_high, int sy,
+ int gamma) {
int16x8_t s0 = src[0];
int16x8_t s1 = src[1];
int16x8_t s2 = src[2];
diff --git a/third_party/aom/av1/common/arm/warp_plane_neon.h b/third_party/aom/av1/common/arm/warp_plane_neon.h
index 5afd72f4ab..eece007ef3 100644
--- a/third_party/aom/av1/common/arm/warp_plane_neon.h
+++ b/third_party/aom/av1/common/arm/warp_plane_neon.h
@@ -24,32 +24,37 @@
#include "av1/common/warped_motion.h"
#include "av1/common/scale.h"
-static INLINE int16x8_t horizontal_filter_4x1_f4(const uint8x16_t in, int sx,
- int alpha);
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_4x1_f4(const uint8x16_t in,
+ int sx, int alpha);
-static INLINE int16x8_t horizontal_filter_8x1_f8(const uint8x16_t in, int sx,
- int alpha);
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_8x1_f8(const uint8x16_t in,
+ int sx, int alpha);
-static INLINE int16x8_t horizontal_filter_4x1_f1(const uint8x16_t in, int sx);
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_4x1_f1(const uint8x16_t in,
+ int sx);
-static INLINE int16x8_t horizontal_filter_8x1_f1(const uint8x16_t in, int sx);
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_8x1_f1(const uint8x16_t in,
+ int sx);
-static INLINE void vertical_filter_4x1_f1(const int16x8_t *src, int32x4_t *res,
- int sy);
+static AOM_FORCE_INLINE void vertical_filter_4x1_f1(const int16x8_t *src,
+ int32x4_t *res, int sy);
-static INLINE void vertical_filter_4x1_f4(const int16x8_t *src, int32x4_t *res,
- int sy, int gamma);
+static AOM_FORCE_INLINE void vertical_filter_4x1_f4(const int16x8_t *src,
+ int32x4_t *res, int sy,
+ int gamma);
-static INLINE void vertical_filter_8x1_f1(const int16x8_t *src,
- int32x4_t *res_low,
- int32x4_t *res_high, int sy);
+static AOM_FORCE_INLINE void vertical_filter_8x1_f1(const int16x8_t *src,
+ int32x4_t *res_low,
+ int32x4_t *res_high,
+ int sy);
-static INLINE void vertical_filter_8x1_f8(const int16x8_t *src,
- int32x4_t *res_low,
- int32x4_t *res_high, int sy,
- int gamma);
+static AOM_FORCE_INLINE void vertical_filter_8x1_f8(const int16x8_t *src,
+ int32x4_t *res_low,
+ int32x4_t *res_high, int sy,
+ int gamma);
-static INLINE void load_filters_4(int16x8_t out[], int offset, int stride) {
+static AOM_FORCE_INLINE void load_filters_4(int16x8_t out[], int offset,
+ int stride) {
out[0] = vld1q_s16((int16_t *)(av1_warped_filter + ((offset + 0 * stride) >>
WARPEDDIFF_PREC_BITS)));
out[1] = vld1q_s16((int16_t *)(av1_warped_filter + ((offset + 1 * stride) >>
@@ -60,7 +65,8 @@ static INLINE void load_filters_4(int16x8_t out[], int offset, int stride) {
WARPEDDIFF_PREC_BITS)));
}
-static INLINE void load_filters_8(int16x8_t out[], int offset, int stride) {
+static AOM_FORCE_INLINE void load_filters_8(int16x8_t out[], int offset,
+ int stride) {
out[0] = vld1q_s16((int16_t *)(av1_warped_filter + ((offset + 0 * stride) >>
WARPEDDIFF_PREC_BITS)));
out[1] = vld1q_s16((int16_t *)(av1_warped_filter + ((offset + 1 * stride) >>
@@ -79,16 +85,14 @@ static INLINE void load_filters_8(int16x8_t out[], int offset, int stride) {
WARPEDDIFF_PREC_BITS)));
}
-static INLINE int clamp_iy(int iy, int height) {
+static AOM_FORCE_INLINE int clamp_iy(int iy, int height) {
return clamp(iy, 0, height - 1);
}
-static INLINE void warp_affine_horizontal(const uint8_t *ref, int width,
- int height, int stride, int p_width,
- int p_height, int16_t alpha,
- int16_t beta, const int64_t x4,
- const int64_t y4, const int i,
- int16x8_t tmp[]) {
+static AOM_FORCE_INLINE void warp_affine_horizontal(
+ const uint8_t *ref, int width, int height, int stride, int p_width,
+ int p_height, int16_t alpha, int16_t beta, const int64_t x4,
+ const int64_t y4, const int i, int16x8_t tmp[]) {
const int bd = 8;
const int reduce_bits_horiz = ROUND0_BITS;
const int height_limit = AOMMIN(8, p_height - i) + 7;
@@ -197,7 +201,7 @@ static INLINE void warp_affine_horizontal(const uint8_t *ref, int width,
}
}
-static INLINE void warp_affine_vertical(
+static AOM_FORCE_INLINE void warp_affine_vertical(
uint8_t *pred, int p_width, int p_height, int p_stride, int is_compound,
uint16_t *dst, int dst_stride, int do_average, int use_dist_wtd_comp_avg,
int16_t gamma, int16_t delta, const int64_t y4, const int i, const int j,
@@ -325,7 +329,7 @@ static INLINE void warp_affine_vertical(
}
}
-static INLINE void av1_warp_affine_common(
+static AOM_FORCE_INLINE void av1_warp_affine_common(
const int32_t *mat, const uint8_t *ref, int width, int height, int stride,
uint8_t *pred, int p_col, int p_row, int p_width, int p_height,
int p_stride, int subsampling_x, int subsampling_y,
diff --git a/third_party/aom/av1/common/arm/warp_plane_neon_i8mm.c b/third_party/aom/av1/common/arm/warp_plane_neon_i8mm.c
index 39e3ad99f4..22a1be17b5 100644
--- a/third_party/aom/av1/common/arm/warp_plane_neon_i8mm.c
+++ b/third_party/aom/av1/common/arm/warp_plane_neon_i8mm.c
@@ -17,8 +17,8 @@ DECLARE_ALIGNED(16, static const uint8_t, usdot_permute_idx[48]) = {
8, 9, 10, 11, 9, 10, 11, 12, 10, 11, 12, 13, 11, 12, 13, 14
};
-static INLINE int16x8_t horizontal_filter_4x1_f4(const uint8x16_t in, int sx,
- int alpha) {
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_4x1_f4(const uint8x16_t in,
+ int sx, int alpha) {
const int32x4_t add_const = vdupq_n_s32(1 << (8 + FILTER_BITS - 1));
// Loading the 8 filter taps
@@ -45,8 +45,8 @@ static INLINE int16x8_t horizontal_filter_4x1_f4(const uint8x16_t in, int sx,
return vreinterpretq_s16_u16(res);
}
-static INLINE int16x8_t horizontal_filter_8x1_f8(const uint8x16_t in, int sx,
- int alpha) {
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_8x1_f8(const uint8x16_t in,
+ int sx, int alpha) {
const int32x4_t add_const = vdupq_n_s32(1 << (8 + FILTER_BITS - 1));
// Loading the 8 filter taps
@@ -83,7 +83,8 @@ static INLINE int16x8_t horizontal_filter_8x1_f8(const uint8x16_t in, int sx,
return vreinterpretq_s16_u16(res);
}
-static INLINE int16x8_t horizontal_filter_4x1_f1(const uint8x16_t in, int sx) {
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_4x1_f1(const uint8x16_t in,
+ int sx) {
const int32x4_t add_const = vdupq_n_s32(1 << (8 + FILTER_BITS - 1));
int16x8_t f_s16 =
@@ -112,7 +113,8 @@ static INLINE int16x8_t horizontal_filter_4x1_f1(const uint8x16_t in, int sx) {
return vreinterpretq_s16_u16(res);
}
-static INLINE int16x8_t horizontal_filter_8x1_f1(const uint8x16_t in, int sx) {
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_8x1_f1(const uint8x16_t in,
+ int sx) {
const int32x4_t add_const = vdupq_n_s32(1 << (8 + FILTER_BITS - 1));
int16x8_t f_s16 =
@@ -149,8 +151,8 @@ static INLINE int16x8_t horizontal_filter_8x1_f1(const uint8x16_t in, int sx) {
return vreinterpretq_s16_u16(res);
}
-static INLINE void vertical_filter_4x1_f1(const int16x8_t *src, int32x4_t *res,
- int sy) {
+static AOM_FORCE_INLINE void vertical_filter_4x1_f1(const int16x8_t *src,
+ int32x4_t *res, int sy) {
int16x4_t s0 = vget_low_s16(src[0]);
int16x4_t s1 = vget_low_s16(src[1]);
int16x4_t s2 = vget_low_s16(src[2]);
@@ -175,8 +177,9 @@ static INLINE void vertical_filter_4x1_f1(const int16x8_t *src, int32x4_t *res,
*res = m0123;
}
-static INLINE void vertical_filter_4x1_f4(const int16x8_t *src, int32x4_t *res,
- int sy, int gamma) {
+static AOM_FORCE_INLINE void vertical_filter_4x1_f4(const int16x8_t *src,
+ int32x4_t *res, int sy,
+ int gamma) {
int16x8_t s0, s1, s2, s3;
transpose_elems_s16_4x8(
vget_low_s16(src[0]), vget_low_s16(src[1]), vget_low_s16(src[2]),
@@ -200,9 +203,10 @@ static INLINE void vertical_filter_4x1_f4(const int16x8_t *src, int32x4_t *res,
*res = horizontal_add_4d_s32x4(m0123_pairs);
}
-static INLINE void vertical_filter_8x1_f1(const int16x8_t *src,
- int32x4_t *res_low,
- int32x4_t *res_high, int sy) {
+static AOM_FORCE_INLINE void vertical_filter_8x1_f1(const int16x8_t *src,
+ int32x4_t *res_low,
+ int32x4_t *res_high,
+ int sy) {
int16x8_t s0 = src[0];
int16x8_t s1 = src[1];
int16x8_t s2 = src[2];
@@ -237,10 +241,10 @@ static INLINE void vertical_filter_8x1_f1(const int16x8_t *src,
*res_high = m4567;
}
-static INLINE void vertical_filter_8x1_f8(const int16x8_t *src,
- int32x4_t *res_low,
- int32x4_t *res_high, int sy,
- int gamma) {
+static AOM_FORCE_INLINE void vertical_filter_8x1_f8(const int16x8_t *src,
+ int32x4_t *res_low,
+ int32x4_t *res_high, int sy,
+ int gamma) {
int16x8_t s0 = src[0];
int16x8_t s1 = src[1];
int16x8_t s2 = src[2];
diff --git a/third_party/aom/av1/common/arm/warp_plane_sve.c b/third_party/aom/av1/common/arm/warp_plane_sve.c
index 8a4bf5747b..c70b066174 100644
--- a/third_party/aom/av1/common/arm/warp_plane_sve.c
+++ b/third_party/aom/av1/common/arm/warp_plane_sve.c
@@ -11,7 +11,7 @@
#include <arm_neon.h>
-#include "aom_dsp/arm/dot_sve.h"
+#include "aom_dsp/arm/aom_neon_sve_bridge.h"
#include "warp_plane_neon.h"
DECLARE_ALIGNED(16, static const uint8_t, usdot_permute_idx[48]) = {
@@ -20,8 +20,8 @@ DECLARE_ALIGNED(16, static const uint8_t, usdot_permute_idx[48]) = {
8, 9, 10, 11, 9, 10, 11, 12, 10, 11, 12, 13, 11, 12, 13, 14
};
-static INLINE int16x8_t horizontal_filter_4x1_f4(const uint8x16_t in, int sx,
- int alpha) {
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_4x1_f4(const uint8x16_t in,
+ int sx, int alpha) {
const int32x4_t add_const = vdupq_n_s32(1 << (8 + FILTER_BITS - 1));
// Loading the 8 filter taps
@@ -48,8 +48,8 @@ static INLINE int16x8_t horizontal_filter_4x1_f4(const uint8x16_t in, int sx,
return vreinterpretq_s16_u16(res);
}
-static INLINE int16x8_t horizontal_filter_8x1_f8(const uint8x16_t in, int sx,
- int alpha) {
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_8x1_f8(const uint8x16_t in,
+ int sx, int alpha) {
const int32x4_t add_const = vdupq_n_s32(1 << (8 + FILTER_BITS - 1));
// Loading the 8 filter taps
@@ -86,7 +86,8 @@ static INLINE int16x8_t horizontal_filter_8x1_f8(const uint8x16_t in, int sx,
return vreinterpretq_s16_u16(res);
}
-static INLINE int16x8_t horizontal_filter_4x1_f1(const uint8x16_t in, int sx) {
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_4x1_f1(const uint8x16_t in,
+ int sx) {
const int32x4_t add_const = vdupq_n_s32(1 << (8 + FILTER_BITS - 1));
int16x8_t f_s16 =
@@ -115,7 +116,8 @@ static INLINE int16x8_t horizontal_filter_4x1_f1(const uint8x16_t in, int sx) {
return vreinterpretq_s16_u16(res);
}
-static INLINE int16x8_t horizontal_filter_8x1_f1(const uint8x16_t in, int sx) {
+static AOM_FORCE_INLINE int16x8_t horizontal_filter_8x1_f1(const uint8x16_t in,
+ int sx) {
const int32x4_t add_const = vdupq_n_s32(1 << (8 + FILTER_BITS - 1));
int16x8_t f_s16 =
@@ -152,8 +154,8 @@ static INLINE int16x8_t horizontal_filter_8x1_f1(const uint8x16_t in, int sx) {
return vreinterpretq_s16_u16(res);
}
-static INLINE void vertical_filter_4x1_f1(const int16x8_t *src, int32x4_t *res,
- int sy) {
+static AOM_FORCE_INLINE void vertical_filter_4x1_f1(const int16x8_t *src,
+ int32x4_t *res, int sy) {
int16x4_t s0 = vget_low_s16(src[0]);
int16x4_t s1 = vget_low_s16(src[1]);
int16x4_t s2 = vget_low_s16(src[2]);
@@ -178,8 +180,9 @@ static INLINE void vertical_filter_4x1_f1(const int16x8_t *src, int32x4_t *res,
*res = m0123;
}
-static INLINE void vertical_filter_4x1_f4(const int16x8_t *src, int32x4_t *res,
- int sy, int gamma) {
+static AOM_FORCE_INLINE void vertical_filter_4x1_f4(const int16x8_t *src,
+ int32x4_t *res, int sy,
+ int gamma) {
int16x8_t s0, s1, s2, s3;
transpose_elems_s16_4x8(
vget_low_s16(src[0]), vget_low_s16(src[1]), vget_low_s16(src[2]),
@@ -200,9 +203,10 @@ static INLINE void vertical_filter_4x1_f4(const int16x8_t *src, int32x4_t *res,
*res = vcombine_s32(vmovn_s64(m01), vmovn_s64(m23));
}
-static INLINE void vertical_filter_8x1_f1(const int16x8_t *src,
- int32x4_t *res_low,
- int32x4_t *res_high, int sy) {
+static AOM_FORCE_INLINE void vertical_filter_8x1_f1(const int16x8_t *src,
+ int32x4_t *res_low,
+ int32x4_t *res_high,
+ int sy) {
int16x8_t s0 = src[0];
int16x8_t s1 = src[1];
int16x8_t s2 = src[2];
@@ -237,10 +241,10 @@ static INLINE void vertical_filter_8x1_f1(const int16x8_t *src,
*res_high = m4567;
}
-static INLINE void vertical_filter_8x1_f8(const int16x8_t *src,
- int32x4_t *res_low,
- int32x4_t *res_high, int sy,
- int gamma) {
+static AOM_FORCE_INLINE void vertical_filter_8x1_f8(const int16x8_t *src,
+ int32x4_t *res_low,
+ int32x4_t *res_high, int sy,
+ int gamma) {
int16x8_t s0 = src[0];
int16x8_t s1 = src[1];
int16x8_t s2 = src[2];
diff --git a/third_party/aom/av1/common/av1_common_int.h b/third_party/aom/av1/common/av1_common_int.h
index 4c0cb99d2b..4e14c4a8be 100644
--- a/third_party/aom/av1/common/av1_common_int.h
+++ b/third_party/aom/av1/common/av1_common_int.h
@@ -17,7 +17,7 @@
#include "aom/internal/aom_codec_internal.h"
#include "aom_dsp/flow_estimation/corner_detect.h"
-#include "aom_util/aom_thread.h"
+#include "aom_util/aom_pthread.h"
#include "av1/common/alloccommon.h"
#include "av1/common/av1_loopfilter.h"
#include "av1/common/entropy.h"
diff --git a/third_party/aom/av1/common/av1_rtcd_defs.pl b/third_party/aom/av1/common/av1_rtcd_defs.pl
index ef999fbba2..c0831330d1 100644
--- a/third_party/aom/av1/common/av1_rtcd_defs.pl
+++ b/third_party/aom/av1/common/av1_rtcd_defs.pl
@@ -77,6 +77,16 @@ EOF
}
forward_decls qw/av1_common_forward_decls/;
+# Fallbacks for Valgrind support
+# For normal use, we require SSE4.1. However, 32-bit Valgrind does not support
+# SSE4.1, so we include fallbacks for some critical functions to improve
+# performance
+$sse2_x86 = $ssse3_x86 = '';
+if ($opts{arch} eq "x86") {
+ $sse2_x86 = 'sse2';
+ $ssse3_x86 = 'ssse3';
+}
+
# functions that are 64 bit only.
$mmx_x86_64 = $sse2_x86_64 = $ssse3_x86_64 = $avx_x86_64 = $avx2_x86_64 = '';
if ($opts{arch} eq "x86_64") {
@@ -345,7 +355,7 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
#fwd txfm
add_proto qw/void av1_lowbd_fwd_txfm/, "const int16_t *src_diff, tran_low_t *coeff, int diff_stride, TxfmParam *txfm_param";
- specialize qw/av1_lowbd_fwd_txfm sse2 sse4_1 avx2 neon/;
+ specialize qw/av1_lowbd_fwd_txfm sse4_1 avx2 neon/, $sse2_x86;
add_proto qw/void av1_fwd_txfm2d_4x8/, "const int16_t *input, int32_t *output, int stride, TX_TYPE tx_type, int bd";
specialize qw/av1_fwd_txfm2d_4x8 sse4_1 neon/;
@@ -436,9 +446,9 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
specialize qw/av1_txb_init_levels sse4_1 avx2 neon/;
add_proto qw/uint64_t av1_wedge_sse_from_residuals/, "const int16_t *r1, const int16_t *d, const uint8_t *m, int N";
- specialize qw/av1_wedge_sse_from_residuals sse2 avx2 neon/;
+ specialize qw/av1_wedge_sse_from_residuals sse2 avx2 neon sve/;
add_proto qw/int8_t av1_wedge_sign_from_residuals/, "const int16_t *ds, const uint8_t *m, int N, int64_t limit";
- specialize qw/av1_wedge_sign_from_residuals sse2 avx2 neon/;
+ specialize qw/av1_wedge_sign_from_residuals sse2 avx2 neon sve/;
add_proto qw/void av1_wedge_compute_delta_squares/, "int16_t *d, const int16_t *a, const int16_t *b, int N";
specialize qw/av1_wedge_compute_delta_squares sse2 avx2 neon/;
@@ -521,21 +531,21 @@ add_proto qw/void cdef_copy_rect8_16bit_to_16bit/, "uint16_t *dst, int dstride,
# structs as arguments, which makes the v256 type of the intrinsics
# hard to support, so optimizations for this target are disabled.
if ($opts{config} !~ /libs-x86-win32-vs.*/) {
- specialize qw/cdef_find_dir sse2 ssse3 sse4_1 avx2 neon/;
- specialize qw/cdef_find_dir_dual sse2 ssse3 sse4_1 avx2 neon/;
+ specialize qw/cdef_find_dir sse4_1 avx2 neon/, "$ssse3_x86";
+ specialize qw/cdef_find_dir_dual sse4_1 avx2 neon/, "$ssse3_x86";
- specialize qw/cdef_filter_8_0 sse2 ssse3 sse4_1 avx2 neon/;
- specialize qw/cdef_filter_8_1 sse2 ssse3 sse4_1 avx2 neon/;
- specialize qw/cdef_filter_8_2 sse2 ssse3 sse4_1 avx2 neon/;
- specialize qw/cdef_filter_8_3 sse2 ssse3 sse4_1 avx2 neon/;
+ specialize qw/cdef_filter_8_0 sse4_1 avx2 neon/, "$ssse3_x86";
+ specialize qw/cdef_filter_8_1 sse4_1 avx2 neon/, "$ssse3_x86";
+ specialize qw/cdef_filter_8_2 sse4_1 avx2 neon/, "$ssse3_x86";
+ specialize qw/cdef_filter_8_3 sse4_1 avx2 neon/, "$ssse3_x86";
- specialize qw/cdef_filter_16_0 sse2 ssse3 sse4_1 avx2 neon/;
- specialize qw/cdef_filter_16_1 sse2 ssse3 sse4_1 avx2 neon/;
- specialize qw/cdef_filter_16_2 sse2 ssse3 sse4_1 avx2 neon/;
- specialize qw/cdef_filter_16_3 sse2 ssse3 sse4_1 avx2 neon/;
+ specialize qw/cdef_filter_16_0 sse4_1 avx2 neon/, "$ssse3_x86";
+ specialize qw/cdef_filter_16_1 sse4_1 avx2 neon/, "$ssse3_x86";
+ specialize qw/cdef_filter_16_2 sse4_1 avx2 neon/, "$ssse3_x86";
+ specialize qw/cdef_filter_16_3 sse4_1 avx2 neon/, "$ssse3_x86";
- specialize qw/cdef_copy_rect8_8bit_to_16bit sse2 ssse3 sse4_1 avx2 neon/;
- specialize qw/cdef_copy_rect8_16bit_to_16bit sse2 ssse3 sse4_1 avx2 neon/;
+ specialize qw/cdef_copy_rect8_8bit_to_16bit sse4_1 avx2 neon/, "$ssse3_x86";
+ specialize qw/cdef_copy_rect8_16bit_to_16bit sse4_1 avx2 neon/, "$ssse3_x86";
}
# WARPED_MOTION / GLOBAL_MOTION functions
@@ -591,20 +601,20 @@ if(aom_config("CONFIG_AV1_HIGHBITDEPTH") eq "yes") {
specialize qw/av1_convolve_y_sr sse2 avx2 neon/;
specialize qw/av1_convolve_y_sr_intrabc neon/;
specialize qw/av1_convolve_2d_scale sse4_1/;
- specialize qw/av1_dist_wtd_convolve_2d sse2 ssse3 avx2 neon neon_dotprod neon_i8mm/;
+ specialize qw/av1_dist_wtd_convolve_2d ssse3 avx2 neon neon_dotprod neon_i8mm/;
specialize qw/av1_dist_wtd_convolve_2d_copy sse2 avx2 neon/;
specialize qw/av1_dist_wtd_convolve_x sse2 avx2 neon neon_dotprod neon_i8mm/;
specialize qw/av1_dist_wtd_convolve_y sse2 avx2 neon/;
if(aom_config("CONFIG_AV1_HIGHBITDEPTH") eq "yes") {
- specialize qw/av1_highbd_dist_wtd_convolve_2d sse4_1 avx2 neon/;
- specialize qw/av1_highbd_dist_wtd_convolve_x sse4_1 avx2 neon/;
- specialize qw/av1_highbd_dist_wtd_convolve_y sse4_1 avx2 neon/;
+ specialize qw/av1_highbd_dist_wtd_convolve_2d sse4_1 avx2 neon sve2/;
+ specialize qw/av1_highbd_dist_wtd_convolve_x sse4_1 avx2 neon sve2/;
+ specialize qw/av1_highbd_dist_wtd_convolve_y sse4_1 avx2 neon sve2/;
specialize qw/av1_highbd_dist_wtd_convolve_2d_copy sse4_1 avx2 neon/;
- specialize qw/av1_highbd_convolve_2d_sr ssse3 avx2 neon/;
+ specialize qw/av1_highbd_convolve_2d_sr ssse3 avx2 neon sve2/;
specialize qw/av1_highbd_convolve_2d_sr_intrabc neon/;
- specialize qw/av1_highbd_convolve_x_sr ssse3 avx2 neon/;
+ specialize qw/av1_highbd_convolve_x_sr ssse3 avx2 neon sve2/;
specialize qw/av1_highbd_convolve_x_sr_intrabc neon/;
- specialize qw/av1_highbd_convolve_y_sr ssse3 avx2 neon/;
+ specialize qw/av1_highbd_convolve_y_sr ssse3 avx2 neon sve2/;
specialize qw/av1_highbd_convolve_y_sr_intrabc neon/;
specialize qw/av1_highbd_convolve_2d_scale sse4_1 neon/;
}
diff --git a/third_party/aom/av1/common/cdef.c b/third_party/aom/av1/common/cdef.c
index 12e9545441..5cec940a8e 100644
--- a/third_party/aom/av1/common/cdef.c
+++ b/third_party/aom/av1/common/cdef.c
@@ -10,15 +10,19 @@
*/
#include <assert.h>
-#include <math.h>
+#include <stddef.h>
#include <string.h>
#include "config/aom_scale_rtcd.h"
#include "aom/aom_integer.h"
+#include "aom_util/aom_pthread.h"
#include "av1/common/av1_common_int.h"
#include "av1/common/cdef.h"
#include "av1/common/cdef_block.h"
+#include "av1/common/common.h"
+#include "av1/common/common_data.h"
+#include "av1/common/enums.h"
#include "av1/common/reconinter.h"
#include "av1/common/thread_common.h"
@@ -92,7 +96,7 @@ void av1_cdef_copy_sb8_16_lowbd(uint16_t *const dst, int dstride,
const uint8_t *src, int src_voffset,
int src_hoffset, int sstride, int vsize,
int hsize) {
- const uint8_t *base = &src[src_voffset * sstride + src_hoffset];
+ const uint8_t *base = &src[src_voffset * (ptrdiff_t)sstride + src_hoffset];
cdef_copy_rect8_8bit_to_16bit(dst, dstride, base, sstride, hsize, vsize);
}
@@ -101,7 +105,7 @@ void av1_cdef_copy_sb8_16_highbd(uint16_t *const dst, int dstride,
int src_hoffset, int sstride, int vsize,
int hsize) {
const uint16_t *base =
- &CONVERT_TO_SHORTPTR(src)[src_voffset * sstride + src_hoffset];
+ &CONVERT_TO_SHORTPTR(src)[src_voffset * (ptrdiff_t)sstride + src_hoffset];
cdef_copy_rect8_16bit_to_16bit(dst, dstride, base, sstride, hsize, vsize);
}
@@ -247,7 +251,8 @@ static void cdef_prepare_fb(const AV1_COMMON *const cm, CdefBlockInfo *fb_info,
static INLINE void cdef_filter_fb(CdefBlockInfo *const fb_info, int plane,
uint8_t use_highbitdepth) {
- int offset = fb_info->dst_stride * fb_info->roffset + fb_info->coffset;
+ ptrdiff_t offset =
+ (ptrdiff_t)fb_info->dst_stride * fb_info->roffset + fb_info->coffset;
if (use_highbitdepth) {
av1_cdef_filter_fb(
NULL, CONVERT_TO_SHORTPTR(fb_info->dst + offset), fb_info->dst_stride,
diff --git a/third_party/aom/av1/common/entropymode.h b/third_party/aom/av1/common/entropymode.h
index 09cd6bd1e9..028bd21ae3 100644
--- a/third_party/aom/av1/common/entropymode.h
+++ b/third_party/aom/av1/common/entropymode.h
@@ -12,6 +12,7 @@
#ifndef AOM_AV1_COMMON_ENTROPYMODE_H_
#define AOM_AV1_COMMON_ENTROPYMODE_H_
+#include "aom_ports/bitops.h"
#include "av1/common/entropy.h"
#include "av1/common/entropymv.h"
#include "av1/common/filter.h"
@@ -192,13 +193,7 @@ void av1_setup_past_independence(struct AV1Common *cm);
// Returns (int)ceil(log2(n)).
static INLINE int av1_ceil_log2(int n) {
if (n < 2) return 0;
- int i = 1;
- unsigned int p = 2;
- while (p < (unsigned int)n) {
- i++;
- p = p << 1;
- }
- return i;
+ return get_msb(n - 1) + 1;
}
// Returns the context for palette color index at row 'r' and column 'c',
diff --git a/third_party/aom/av1/common/quant_common.c b/third_party/aom/av1/common/quant_common.c
index b0976287ef..58eb113370 100644
--- a/third_party/aom/av1/common/quant_common.c
+++ b/third_party/aom/av1/common/quant_common.c
@@ -9,10 +9,15 @@
* PATENTS file, you can obtain it at www.aomedia.org/license/patent.
*/
+#include "config/aom_config.h"
+
+#include "aom/aom_frame_buffer.h"
+#include "aom_scale/yv12config.h"
#include "av1/common/av1_common_int.h"
#include "av1/common/blockd.h"
#include "av1/common/common.h"
#include "av1/common/entropy.h"
+#include "av1/common/filter.h"
#include "av1/common/quant_common.h"
#include "av1/common/seg_common.h"
@@ -274,13 +279,16 @@ const qm_val_t *av1_get_qmatrix(const CommonQuantParams *quant_params,
: quant_params->gqmatrix[NUM_QM_LEVELS - 1][0][qm_tx_size];
}
+#if CONFIG_QUANT_MATRIX || CONFIG_AV1_DECODER
#define QM_TOTAL_SIZE 3344
// We only use wt_matrix_ref[q] and iwt_matrix_ref[q]
// for q = 0, ..., NUM_QM_LEVELS - 2.
static const qm_val_t wt_matrix_ref[NUM_QM_LEVELS - 1][2][QM_TOTAL_SIZE];
static const qm_val_t iwt_matrix_ref[NUM_QM_LEVELS - 1][2][QM_TOTAL_SIZE];
+#endif
void av1_qm_init(CommonQuantParams *quant_params, int num_planes) {
+#if CONFIG_QUANT_MATRIX || CONFIG_AV1_DECODER
for (int q = 0; q < NUM_QM_LEVELS; ++q) {
for (int c = 0; c < num_planes; ++c) {
int current = 0;
@@ -306,6 +314,10 @@ void av1_qm_init(CommonQuantParams *quant_params, int num_planes) {
}
}
}
+#else
+ (void)quant_params;
+ (void)num_planes;
+#endif // CONFIG_QUANT_MATRIX || CONFIG_AV1_DECODER
}
/* Provide 15 sets of quantization matrices for chroma and luma
@@ -320,6 +332,8 @@ void av1_qm_init(CommonQuantParams *quant_params, int num_planes) {
distances. Matrices for QM level 15 are omitted because they are
not used.
*/
+
+#if CONFIG_QUANT_MATRIX || CONFIG_AV1_DECODER
static const qm_val_t iwt_matrix_ref[NUM_QM_LEVELS - 1][2][QM_TOTAL_SIZE] = {
{
{ /* Luma */
@@ -12873,4 +12887,6 @@ static const qm_val_t wt_matrix_ref[NUM_QM_LEVELS - 1][2][QM_TOTAL_SIZE] = {
33, 33, 32, 32, 32, 32, 34, 33, 33, 33, 32, 32, 32, 32, 34, 33, 33, 33,
32, 32, 32, 32 },
},
-}; \ No newline at end of file
+};
+
+#endif // CONFIG_QUANT_MATRIX || CONFIG_AV1_DECODER
diff --git a/third_party/aom/av1/common/reconintra.c b/third_party/aom/av1/common/reconintra.c
index f68af18cb1..497863e117 100644
--- a/third_party/aom/av1/common/reconintra.c
+++ b/third_party/aom/av1/common/reconintra.c
@@ -1196,7 +1196,8 @@ static void build_directional_and_filter_intra_predictors(
const int need_right = p_angle < 90;
const int need_bottom = p_angle > 180;
if (p_angle != 90 && p_angle != 180) {
- const int ab_le = need_above_left ? 1 : 0;
+ assert(need_above_left);
+ const int ab_le = 1;
if (need_above && need_left && (txwpx + txhpx >= 24)) {
filter_intra_edge_corner(above_row, left_col);
}
@@ -1500,7 +1501,8 @@ static void highbd_build_directional_and_filter_intra_predictors(
const int need_right = p_angle < 90;
const int need_bottom = p_angle > 180;
if (p_angle != 90 && p_angle != 180) {
- const int ab_le = need_above_left ? 1 : 0;
+ assert(need_above_left);
+ const int ab_le = 1;
if (need_above && need_left && (txwpx + txhpx >= 24)) {
highbd_filter_intra_edge_corner(above_row, left_col);
}
diff --git a/third_party/aom/av1/common/resize.c b/third_party/aom/av1/common/resize.c
index 1b348836a5..441323ab1f 100644
--- a/third_party/aom/av1/common/resize.c
+++ b/third_party/aom/av1/common/resize.c
@@ -524,7 +524,7 @@ static void fill_arr_to_col(uint8_t *img, int stride, int len, uint8_t *arr) {
}
}
-bool av1_resize_plane(const uint8_t *const input, int height, int width,
+bool av1_resize_plane(const uint8_t *input, int height, int width,
int in_stride, uint8_t *output, int height2, int width2,
int out_stride) {
int i;
@@ -881,7 +881,7 @@ static void highbd_fill_arr_to_col(uint16_t *img, int stride, int len,
}
}
-void av1_highbd_resize_plane(const uint8_t *const input, int height, int width,
+void av1_highbd_resize_plane(const uint8_t *input, int height, int width,
int in_stride, uint8_t *output, int height2,
int width2, int out_stride, int bd) {
int i;
@@ -980,10 +980,9 @@ static bool highbd_upscale_normative_rect(const uint8_t *const input,
}
#endif // CONFIG_AV1_HIGHBITDEPTH
-void av1_resize_frame420(const uint8_t *const y, int y_stride,
- const uint8_t *const u, const uint8_t *const v,
- int uv_stride, int height, int width, uint8_t *oy,
- int oy_stride, uint8_t *ou, uint8_t *ov,
+void av1_resize_frame420(const uint8_t *y, int y_stride, const uint8_t *u,
+ const uint8_t *v, int uv_stride, int height, int width,
+ uint8_t *oy, int oy_stride, uint8_t *ou, uint8_t *ov,
int ouv_stride, int oheight, int owidth) {
if (!av1_resize_plane(y, height, width, y_stride, oy, oheight, owidth,
oy_stride))
@@ -996,10 +995,9 @@ void av1_resize_frame420(const uint8_t *const y, int y_stride,
abort();
}
-bool av1_resize_frame422(const uint8_t *const y, int y_stride,
- const uint8_t *const u, const uint8_t *const v,
- int uv_stride, int height, int width, uint8_t *oy,
- int oy_stride, uint8_t *ou, uint8_t *ov,
+bool av1_resize_frame422(const uint8_t *y, int y_stride, const uint8_t *u,
+ const uint8_t *v, int uv_stride, int height, int width,
+ uint8_t *oy, int oy_stride, uint8_t *ou, uint8_t *ov,
int ouv_stride, int oheight, int owidth) {
if (!av1_resize_plane(y, height, width, y_stride, oy, oheight, owidth,
oy_stride))
@@ -1013,10 +1011,9 @@ bool av1_resize_frame422(const uint8_t *const y, int y_stride,
return true;
}
-bool av1_resize_frame444(const uint8_t *const y, int y_stride,
- const uint8_t *const u, const uint8_t *const v,
- int uv_stride, int height, int width, uint8_t *oy,
- int oy_stride, uint8_t *ou, uint8_t *ov,
+bool av1_resize_frame444(const uint8_t *y, int y_stride, const uint8_t *u,
+ const uint8_t *v, int uv_stride, int height, int width,
+ uint8_t *oy, int oy_stride, uint8_t *ou, uint8_t *ov,
int ouv_stride, int oheight, int owidth) {
if (!av1_resize_plane(y, height, width, y_stride, oy, oheight, owidth,
oy_stride))
@@ -1031,8 +1028,8 @@ bool av1_resize_frame444(const uint8_t *const y, int y_stride,
}
#if CONFIG_AV1_HIGHBITDEPTH
-void av1_highbd_resize_frame420(const uint8_t *const y, int y_stride,
- const uint8_t *const u, const uint8_t *const v,
+void av1_highbd_resize_frame420(const uint8_t *y, int y_stride,
+ const uint8_t *u, const uint8_t *v,
int uv_stride, int height, int width,
uint8_t *oy, int oy_stride, uint8_t *ou,
uint8_t *ov, int ouv_stride, int oheight,
@@ -1045,8 +1042,8 @@ void av1_highbd_resize_frame420(const uint8_t *const y, int y_stride,
owidth / 2, ouv_stride, bd);
}
-void av1_highbd_resize_frame422(const uint8_t *const y, int y_stride,
- const uint8_t *const u, const uint8_t *const v,
+void av1_highbd_resize_frame422(const uint8_t *y, int y_stride,
+ const uint8_t *u, const uint8_t *v,
int uv_stride, int height, int width,
uint8_t *oy, int oy_stride, uint8_t *ou,
uint8_t *ov, int ouv_stride, int oheight,
@@ -1059,8 +1056,8 @@ void av1_highbd_resize_frame422(const uint8_t *const y, int y_stride,
owidth / 2, ouv_stride, bd);
}
-void av1_highbd_resize_frame444(const uint8_t *const y, int y_stride,
- const uint8_t *const u, const uint8_t *const v,
+void av1_highbd_resize_frame444(const uint8_t *y, int y_stride,
+ const uint8_t *u, const uint8_t *v,
int uv_stride, int height, int width,
uint8_t *oy, int oy_stride, uint8_t *ou,
uint8_t *ov, int ouv_stride, int oheight,
@@ -1126,7 +1123,7 @@ void av1_resize_and_extend_frame_c(const YV12_BUFFER_CONFIG *src,
bool av1_resize_and_extend_frame_nonnormative(const YV12_BUFFER_CONFIG *src,
YV12_BUFFER_CONFIG *dst, int bd,
- const int num_planes) {
+ int num_planes) {
// TODO(dkovalev): replace YV12_BUFFER_CONFIG with aom_image_t
// We use AOMMIN(num_planes, MAX_MB_PLANE) instead of num_planes to quiet
@@ -1246,8 +1243,7 @@ void av1_upscale_normative_and_extend_frame(const AV1_COMMON *cm,
YV12_BUFFER_CONFIG *av1_realloc_and_scale_if_required(
AV1_COMMON *cm, YV12_BUFFER_CONFIG *unscaled, YV12_BUFFER_CONFIG *scaled,
const InterpFilter filter, const int phase, const bool use_optimized_scaler,
- const bool for_psnr, const int border_in_pixels,
- const int num_pyramid_levels) {
+ const bool for_psnr, const int border_in_pixels, const bool alloc_pyramid) {
// If scaling is performed for the sole purpose of calculating PSNR, then our
// target dimensions are superres upscaled width/height. Otherwise our target
// dimensions are coded width/height.
@@ -1267,7 +1263,7 @@ YV12_BUFFER_CONFIG *av1_realloc_and_scale_if_required(
scaled, scaled_width, scaled_height, seq_params->subsampling_x,
seq_params->subsampling_y, seq_params->use_highbitdepth,
border_in_pixels, cm->features.byte_alignment, NULL, NULL, NULL,
- num_pyramid_levels, 0))
+ alloc_pyramid, 0))
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate scaled buffer");
@@ -1363,7 +1359,7 @@ static void copy_buffer_config(const YV12_BUFFER_CONFIG *const src,
// TODO(afergs): aom_ vs av1_ functions? Which can I use?
// Upscale decoded image.
void av1_superres_upscale(AV1_COMMON *cm, BufferPool *const pool,
- int num_pyramid_levels) {
+ bool alloc_pyramid) {
const int num_planes = av1_num_planes(cm);
if (!av1_superres_scaled(cm)) return;
const SequenceHeader *const seq_params = cm->seq_params;
@@ -1378,7 +1374,7 @@ void av1_superres_upscale(AV1_COMMON *cm, BufferPool *const pool,
if (aom_alloc_frame_buffer(
&copy_buffer, aligned_width, cm->height, seq_params->subsampling_x,
seq_params->subsampling_y, seq_params->use_highbitdepth,
- AOM_BORDER_IN_PIXELS, byte_alignment, 0, 0))
+ AOM_BORDER_IN_PIXELS, byte_alignment, false, 0))
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate copy buffer for superres upscaling");
@@ -1411,7 +1407,7 @@ void av1_superres_upscale(AV1_COMMON *cm, BufferPool *const pool,
cm->superres_upscaled_height, seq_params->subsampling_x,
seq_params->subsampling_y, seq_params->use_highbitdepth,
AOM_BORDER_IN_PIXELS, byte_alignment, fb, cb, cb_priv,
- num_pyramid_levels, 0)) {
+ alloc_pyramid, 0)) {
unlock_buffer_pool(pool);
aom_internal_error(
cm->error, AOM_CODEC_MEM_ERROR,
@@ -1428,7 +1424,7 @@ void av1_superres_upscale(AV1_COMMON *cm, BufferPool *const pool,
frame_to_show, cm->superres_upscaled_width,
cm->superres_upscaled_height, seq_params->subsampling_x,
seq_params->subsampling_y, seq_params->use_highbitdepth,
- AOM_BORDER_IN_PIXELS, byte_alignment, num_pyramid_levels, 0))
+ AOM_BORDER_IN_PIXELS, byte_alignment, alloc_pyramid, 0))
aom_internal_error(
cm->error, AOM_CODEC_MEM_ERROR,
"Failed to reallocate current frame buffer for superres upscaling");
diff --git a/third_party/aom/av1/common/resize.h b/third_party/aom/av1/common/resize.h
index 0ba3108f72..d573a538bf 100644
--- a/third_party/aom/av1/common/resize.h
+++ b/third_party/aom/av1/common/resize.h
@@ -20,44 +20,41 @@
extern "C" {
#endif
-bool av1_resize_plane(const uint8_t *const input, int height, int width,
+bool av1_resize_plane(const uint8_t *input, int height, int width,
int in_stride, uint8_t *output, int height2, int width2,
int out_stride);
// TODO(aomedia:3228): In libaom 4.0.0, remove av1_resize_frame420 from
// av1/exports_com and delete this function.
-void av1_resize_frame420(const uint8_t *const y, int y_stride,
- const uint8_t *const u, const uint8_t *const v,
- int uv_stride, int height, int width, uint8_t *oy,
- int oy_stride, uint8_t *ou, uint8_t *ov,
+void av1_resize_frame420(const uint8_t *y, int y_stride, const uint8_t *u,
+ const uint8_t *v, int uv_stride, int height, int width,
+ uint8_t *oy, int oy_stride, uint8_t *ou, uint8_t *ov,
int ouv_stride, int oheight, int owidth);
-bool av1_resize_frame422(const uint8_t *const y, int y_stride,
- const uint8_t *const u, const uint8_t *const v,
- int uv_stride, int height, int width, uint8_t *oy,
- int oy_stride, uint8_t *ou, uint8_t *ov,
+bool av1_resize_frame422(const uint8_t *y, int y_stride, const uint8_t *u,
+ const uint8_t *v, int uv_stride, int height, int width,
+ uint8_t *oy, int oy_stride, uint8_t *ou, uint8_t *ov,
int ouv_stride, int oheight, int owidth);
-bool av1_resize_frame444(const uint8_t *const y, int y_stride,
- const uint8_t *const u, const uint8_t *const v,
- int uv_stride, int height, int width, uint8_t *oy,
- int oy_stride, uint8_t *ou, uint8_t *ov,
+bool av1_resize_frame444(const uint8_t *y, int y_stride, const uint8_t *u,
+ const uint8_t *v, int uv_stride, int height, int width,
+ uint8_t *oy, int oy_stride, uint8_t *ou, uint8_t *ov,
int ouv_stride, int oheight, int owidth);
-void av1_highbd_resize_plane(const uint8_t *const input, int height, int width,
+void av1_highbd_resize_plane(const uint8_t *input, int height, int width,
int in_stride, uint8_t *output, int height2,
int width2, int out_stride, int bd);
-void av1_highbd_resize_frame420(const uint8_t *const y, int y_stride,
- const uint8_t *const u, const uint8_t *const v,
+void av1_highbd_resize_frame420(const uint8_t *y, int y_stride,
+ const uint8_t *u, const uint8_t *v,
int uv_stride, int height, int width,
uint8_t *oy, int oy_stride, uint8_t *ou,
uint8_t *ov, int ouv_stride, int oheight,
int owidth, int bd);
-void av1_highbd_resize_frame422(const uint8_t *const y, int y_stride,
- const uint8_t *const u, const uint8_t *const v,
+void av1_highbd_resize_frame422(const uint8_t *y, int y_stride,
+ const uint8_t *u, const uint8_t *v,
int uv_stride, int height, int width,
uint8_t *oy, int oy_stride, uint8_t *ou,
uint8_t *ov, int ouv_stride, int oheight,
int owidth, int bd);
-void av1_highbd_resize_frame444(const uint8_t *const y, int y_stride,
- const uint8_t *const u, const uint8_t *const v,
+void av1_highbd_resize_frame444(const uint8_t *y, int y_stride,
+ const uint8_t *u, const uint8_t *v,
int uv_stride, int height, int width,
uint8_t *oy, int oy_stride, uint8_t *ou,
uint8_t *ov, int ouv_stride, int oheight,
@@ -73,12 +70,11 @@ void av1_upscale_normative_and_extend_frame(const AV1_COMMON *cm,
YV12_BUFFER_CONFIG *av1_realloc_and_scale_if_required(
AV1_COMMON *cm, YV12_BUFFER_CONFIG *unscaled, YV12_BUFFER_CONFIG *scaled,
const InterpFilter filter, const int phase, const bool use_optimized_scaler,
- const bool for_psnr, const int border_in_pixels,
- const int num_pyramid_levels);
+ const bool for_psnr, const int border_in_pixels, const bool alloc_pyramid);
bool av1_resize_and_extend_frame_nonnormative(const YV12_BUFFER_CONFIG *src,
YV12_BUFFER_CONFIG *dst, int bd,
- const int num_planes);
+ int num_planes);
// Calculates the scaled dimensions from the given original dimensions and the
// resize scale denominator.
@@ -95,7 +91,7 @@ void av1_calculate_scaled_superres_size(int *width, int *height,
void av1_calculate_unscaled_superres_size(int *width, int *height, int denom);
void av1_superres_upscale(AV1_COMMON *cm, BufferPool *const pool,
- int num_pyramid_levels);
+ bool alloc_pyramid);
// Returns 1 if a superres upscaled frame is scaled and 0 otherwise.
static INLINE int av1_superres_scaled(const AV1_COMMON *cm) {
diff --git a/third_party/aom/av1/common/restoration.c b/third_party/aom/av1/common/restoration.c
index 0be126fa65..335fdc8c2a 100644
--- a/third_party/aom/av1/common/restoration.c
+++ b/third_party/aom/av1/common/restoration.c
@@ -11,20 +11,24 @@
*/
#include <math.h>
+#include <stddef.h>
#include "config/aom_config.h"
-#include "config/aom_dsp_rtcd.h"
#include "config/aom_scale_rtcd.h"
+#include "aom/internal/aom_codec_internal.h"
#include "aom_mem/aom_mem.h"
+#include "aom_dsp/aom_dsp_common.h"
+#include "aom_mem/aom_mem.h"
+#include "aom_ports/mem.h"
+#include "aom_util/aom_pthread.h"
+
#include "av1/common/av1_common_int.h"
+#include "av1/common/convolve.h"
+#include "av1/common/enums.h"
#include "av1/common/resize.h"
#include "av1/common/restoration.h"
#include "av1/common/thread_common.h"
-#include "aom_dsp/aom_dsp_common.h"
-#include "aom_mem/aom_mem.h"
-
-#include "aom_ports/mem.h"
// The 's' values are calculated based on original 'r' and 'e' values in the
// spec using GenSgrprojVtable().
@@ -115,8 +119,9 @@ void av1_loop_restoration_precal(void) {
#endif
}
-static void extend_frame_lowbd(uint8_t *data, int width, int height, int stride,
- int border_horz, int border_vert) {
+static void extend_frame_lowbd(uint8_t *data, int width, int height,
+ ptrdiff_t stride, int border_horz,
+ int border_vert) {
uint8_t *data_p;
int i;
for (i = 0; i < height; ++i) {
@@ -136,7 +141,8 @@ static void extend_frame_lowbd(uint8_t *data, int width, int height, int stride,
#if CONFIG_AV1_HIGHBITDEPTH
static void extend_frame_highbd(uint16_t *data, int width, int height,
- int stride, int border_horz, int border_vert) {
+ ptrdiff_t stride, int border_horz,
+ int border_vert) {
uint16_t *data_p;
int i, j;
for (i = 0; i < height; ++i) {
@@ -988,8 +994,10 @@ void av1_loop_restoration_filter_unit(
int unit_h = limits->v_end - limits->v_start;
int unit_w = limits->h_end - limits->h_start;
- uint8_t *data8_tl = data8 + limits->v_start * stride + limits->h_start;
- uint8_t *dst8_tl = dst8 + limits->v_start * dst_stride + limits->h_start;
+ uint8_t *data8_tl =
+ data8 + limits->v_start * (ptrdiff_t)stride + limits->h_start;
+ uint8_t *dst8_tl =
+ dst8 + limits->v_start * (ptrdiff_t)dst_stride + limits->h_start;
if (unit_rtype == RESTORE_NONE) {
copy_rest_unit(unit_w, unit_h, data8_tl, stride, dst8_tl, dst_stride,
@@ -1074,7 +1082,8 @@ void av1_loop_restoration_filter_frame_init(AV1LrStruct *lr_ctxt,
if (aom_realloc_frame_buffer(
lr_ctxt->dst, frame_width, frame_height, seq_params->subsampling_x,
seq_params->subsampling_y, highbd, AOM_RESTORATION_FRAME_BORDER,
- cm->features.byte_alignment, NULL, NULL, NULL, 0, 0) != AOM_CODEC_OK)
+ cm->features.byte_alignment, NULL, NULL, NULL, false,
+ 0) != AOM_CODEC_OK)
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate restoration dst buffer");
@@ -1349,7 +1358,7 @@ static void save_deblock_boundary_lines(
const int is_uv = plane > 0;
const uint8_t *src_buf = REAL_PTR(use_highbd, frame->buffers[plane]);
const int src_stride = frame->strides[is_uv] << use_highbd;
- const uint8_t *src_rows = src_buf + row * src_stride;
+ const uint8_t *src_rows = src_buf + row * (ptrdiff_t)src_stride;
uint8_t *bdry_buf = is_above ? boundaries->stripe_boundary_above
: boundaries->stripe_boundary_below;
@@ -1404,7 +1413,7 @@ static void save_cdef_boundary_lines(const YV12_BUFFER_CONFIG *frame,
const int is_uv = plane > 0;
const uint8_t *src_buf = REAL_PTR(use_highbd, frame->buffers[plane]);
const int src_stride = frame->strides[is_uv] << use_highbd;
- const uint8_t *src_rows = src_buf + row * src_stride;
+ const uint8_t *src_rows = src_buf + row * (ptrdiff_t)src_stride;
uint8_t *bdry_buf = is_above ? boundaries->stripe_boundary_above
: boundaries->stripe_boundary_below;
diff --git a/third_party/aom/av1/common/thread_common.c b/third_party/aom/av1/common/thread_common.c
index 45695147ff..8a137cc9f7 100644
--- a/third_party/aom/av1/common/thread_common.c
+++ b/third_party/aom/av1/common/thread_common.c
@@ -14,12 +14,19 @@
#include "config/aom_scale_rtcd.h"
#include "aom_dsp/aom_dsp_common.h"
+#include "aom_dsp/txfm_common.h"
#include "aom_mem/aom_mem.h"
+#include "aom_util/aom_pthread.h"
+#include "aom_util/aom_thread.h"
#include "av1/common/av1_loopfilter.h"
+#include "av1/common/blockd.h"
+#include "av1/common/cdef.h"
#include "av1/common/entropymode.h"
+#include "av1/common/enums.h"
#include "av1/common/thread_common.h"
#include "av1/common/reconinter.h"
#include "av1/common/reconintra.h"
+#include "av1/common/restoration.h"
// Set up nsync by width.
static INLINE int get_sync_range(int width) {
diff --git a/third_party/aom/av1/common/thread_common.h b/third_party/aom/av1/common/thread_common.h
index 675687dc98..7e681f322b 100644
--- a/third_party/aom/av1/common/thread_common.h
+++ b/third_party/aom/av1/common/thread_common.h
@@ -16,6 +16,7 @@
#include "av1/common/av1_loopfilter.h"
#include "av1/common/cdef.h"
+#include "aom_util/aom_pthread.h"
#include "aom_util/aom_thread.h"
#ifdef __cplusplus
diff --git a/third_party/aom/av1/common/tile_common.c b/third_party/aom/av1/common/tile_common.c
index b964f259b8..45a189d69a 100644
--- a/third_party/aom/av1/common/tile_common.c
+++ b/third_party/aom/av1/common/tile_common.c
@@ -177,46 +177,16 @@ int av1_get_sb_cols_in_tile(const AV1_COMMON *cm, const TileInfo *tile) {
cm->seq_params->mib_size_log2);
}
-PixelRect av1_get_tile_rect(const TileInfo *tile_info, const AV1_COMMON *cm,
- int is_uv) {
- PixelRect r;
-
- // Calculate position in the Y plane
- r.left = tile_info->mi_col_start * MI_SIZE;
- r.right = tile_info->mi_col_end * MI_SIZE;
- r.top = tile_info->mi_row_start * MI_SIZE;
- r.bottom = tile_info->mi_row_end * MI_SIZE;
-
- // If upscaling is enabled, the tile limits need scaling to match the
- // upscaled frame where the restoration units live. To do this, scale up the
- // top-left and bottom-right of the tile.
- if (av1_superres_scaled(cm)) {
- av1_calculate_unscaled_superres_size(&r.left, &r.top,
- cm->superres_scale_denominator);
- av1_calculate_unscaled_superres_size(&r.right, &r.bottom,
- cm->superres_scale_denominator);
- }
-
- const int frame_w = cm->superres_upscaled_width;
- const int frame_h = cm->superres_upscaled_height;
-
- // Make sure we don't fall off the bottom-right of the frame.
- r.right = AOMMIN(r.right, frame_w);
- r.bottom = AOMMIN(r.bottom, frame_h);
-
- // Convert to coordinates in the appropriate plane
- const int ss_x = is_uv && cm->seq_params->subsampling_x;
- const int ss_y = is_uv && cm->seq_params->subsampling_y;
-
- r.left = ROUND_POWER_OF_TWO(r.left, ss_x);
- r.right = ROUND_POWER_OF_TWO(r.right, ss_x);
- r.top = ROUND_POWER_OF_TWO(r.top, ss_y);
- r.bottom = ROUND_POWER_OF_TWO(r.bottom, ss_y);
-
- return r;
-}
-
-void av1_get_uniform_tile_size(const AV1_COMMON *cm, int *w, int *h) {
+// Section 7.3.1 of the AV1 spec says, on pages 200-201:
+// It is a requirement of bitstream conformance that the following conditions
+// are met:
+// ...
+// * TileHeight is equal to (use_128x128_superblock ? 128 : 64) for all
+// tiles (i.e. the tile is exactly one superblock high)
+// * TileWidth is identical for all tiles and is an integer multiple of
+// TileHeight (i.e. the tile is an integer number of superblocks wide)
+// ...
+bool av1_get_uniform_tile_size(const AV1_COMMON *cm, int *w, int *h) {
const CommonTileParams *const tiles = &cm->tiles;
if (tiles->uniform_spacing) {
*w = tiles->width;
@@ -226,7 +196,10 @@ void av1_get_uniform_tile_size(const AV1_COMMON *cm, int *w, int *h) {
const int tile_width_sb =
tiles->col_start_sb[i + 1] - tiles->col_start_sb[i];
const int tile_w = tile_width_sb * cm->seq_params->mib_size;
- assert(i == 0 || tile_w == *w); // ensure all tiles have same dimension
+ // ensure all tiles have same dimension
+ if (i != 0 && tile_w != *w) {
+ return false;
+ }
*w = tile_w;
}
@@ -234,10 +207,14 @@ void av1_get_uniform_tile_size(const AV1_COMMON *cm, int *w, int *h) {
const int tile_height_sb =
tiles->row_start_sb[i + 1] - tiles->row_start_sb[i];
const int tile_h = tile_height_sb * cm->seq_params->mib_size;
- assert(i == 0 || tile_h == *h); // ensure all tiles have same dimension
+ // ensure all tiles have same dimension
+ if (i != 0 && tile_h != *h) {
+ return false;
+ }
*h = tile_h;
}
}
+ return true;
}
int av1_is_min_tile_width_satisfied(const AV1_COMMON *cm) {
diff --git a/third_party/aom/av1/common/tile_common.h b/third_party/aom/av1/common/tile_common.h
index 5383ae940b..12228c9e94 100644
--- a/third_party/aom/av1/common/tile_common.h
+++ b/third_party/aom/av1/common/tile_common.h
@@ -12,13 +12,14 @@
#ifndef AOM_AV1_COMMON_TILE_COMMON_H_
#define AOM_AV1_COMMON_TILE_COMMON_H_
+#include <stdbool.h>
+
+#include "config/aom_config.h"
+
#ifdef __cplusplus
extern "C" {
#endif
-#include "config/aom_config.h"
-#include "aom_dsp/rect.h"
-
struct AV1Common;
struct SequenceHeader;
struct CommonTileParams;
@@ -43,10 +44,6 @@ void av1_tile_set_col(TileInfo *tile, const struct AV1Common *cm, int col);
int av1_get_sb_rows_in_tile(const struct AV1Common *cm, const TileInfo *tile);
int av1_get_sb_cols_in_tile(const struct AV1Common *cm, const TileInfo *tile);
-// Return the pixel extents of the given tile
-PixelRect av1_get_tile_rect(const TileInfo *tile_info,
- const struct AV1Common *cm, int is_uv);
-
// Define tile maximum width and area
// There is no maximum height since height is limited by area and width limits
// The minimum tile width or height is fixed at one superblock
@@ -56,7 +53,9 @@ PixelRect av1_get_tile_rect(const TileInfo *tile_info,
#define MAX_TILE_AREA_LEVEL_7_AND_ABOVE (4096 * 4608)
#endif
-void av1_get_uniform_tile_size(const struct AV1Common *cm, int *w, int *h);
+// Gets the width and height (in units of MI_SIZE) of the tiles in a tile list.
+// Returns true on success, false on failure.
+bool av1_get_uniform_tile_size(const struct AV1Common *cm, int *w, int *h);
void av1_get_tile_limits(struct AV1Common *const cm);
void av1_calculate_tile_cols(const struct SequenceHeader *const seq_params,
int cm_mi_rows, int cm_mi_cols,
diff --git a/third_party/aom/av1/common/x86/cdef_block_sse2.c b/third_party/aom/av1/common/x86/cdef_block_sse2.c
deleted file mode 100644
index 5ab7ffa2ff..0000000000
--- a/third_party/aom/av1/common/x86/cdef_block_sse2.c
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (c) 2016, Alliance for Open Media. All rights reserved
- *
- * This source code is subject to the terms of the BSD 2 Clause License and
- * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
- * was not distributed with this source code in the LICENSE file, you can
- * obtain it at www.aomedia.org/license/software. If the Alliance for Open
- * Media Patent License 1.0 was not distributed with this source code in the
- * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
- */
-
-#include "aom_dsp/aom_simd.h"
-#define SIMD_FUNC(name) name##_sse2
-#include "av1/common/cdef_block_simd.h"
-
-void cdef_find_dir_dual_sse2(const uint16_t *img1, const uint16_t *img2,
- int stride, int32_t *var_out_1st,
- int32_t *var_out_2nd, int coeff_shift,
- int *out_dir_1st_8x8, int *out_dir_2nd_8x8) {
- // Process first 8x8.
- *out_dir_1st_8x8 = cdef_find_dir(img1, stride, var_out_1st, coeff_shift);
-
- // Process second 8x8.
- *out_dir_2nd_8x8 = cdef_find_dir(img2, stride, var_out_2nd, coeff_shift);
-}
-
-void cdef_copy_rect8_8bit_to_16bit_sse2(uint16_t *dst, int dstride,
- const uint8_t *src, int sstride,
- int width, int height) {
- int j = 0;
- for (int i = 0; i < height; i++) {
- for (j = 0; j < (width & ~0x7); j += 8) {
- v64 row = v64_load_unaligned(&src[i * sstride + j]);
- v128_store_unaligned(&dst[i * dstride + j], v128_unpack_u8_s16(row));
- }
- for (; j < width; j++) {
- dst[i * dstride + j] = src[i * sstride + j];
- }
- }
-}
diff --git a/third_party/aom/av1/common/x86/cdef_block_ssse3.c b/third_party/aom/av1/common/x86/cdef_block_ssse3.c
index 0fb36eb6e0..14eb6c9e31 100644
--- a/third_party/aom/av1/common/x86/cdef_block_ssse3.c
+++ b/third_party/aom/av1/common/x86/cdef_block_ssse3.c
@@ -9,6 +9,17 @@
* PATENTS file, you can obtain it at www.aomedia.org/license/patent.
*/
+// Include SSSE3 CDEF code only for 32-bit x86, to support Valgrind.
+// For normal use, we require SSE4.1, so cdef_*_sse4_1 will be used instead of
+// these functions. However, 32-bit Valgrind does not support SSE4.1, so we
+// include a fallback to SSSE3 to improve performance
+
+#include "config/aom_config.h"
+
+#if !AOM_ARCH_X86
+#error "cdef_block_ssse3.c is included for compatibility with 32-bit x86 only"
+#endif // !AOM_ARCH_X86
+
#include "aom_dsp/aom_simd.h"
#define SIMD_FUNC(name) name##_ssse3
#include "av1/common/cdef_block_simd.h"
diff --git a/third_party/aom/av1/common/x86/convolve_2d_avx2.c b/third_party/aom/av1/common/x86/convolve_2d_avx2.c
index 1b39a0a8d5..d4c1169cc3 100644
--- a/third_party/aom/av1/common/x86/convolve_2d_avx2.c
+++ b/third_party/aom/av1/common/x86/convolve_2d_avx2.c
@@ -21,13 +21,11 @@
#include "av1/common/convolve.h"
-void av1_convolve_2d_sr_general_avx2(const uint8_t *src, int src_stride,
- uint8_t *dst, int dst_stride, int w, int h,
- const InterpFilterParams *filter_params_x,
- const InterpFilterParams *filter_params_y,
- const int subpel_x_qn,
- const int subpel_y_qn,
- ConvolveParams *conv_params) {
+static void convolve_2d_sr_general_avx2(
+ const uint8_t *src, int src_stride, uint8_t *dst, int dst_stride, int w,
+ int h, const InterpFilterParams *filter_params_x,
+ const InterpFilterParams *filter_params_y, const int subpel_x_qn,
+ const int subpel_y_qn, ConvolveParams *conv_params) {
if (filter_params_x->taps > 8) {
const int bd = 8;
int im_stride = 8, i;
@@ -150,9 +148,9 @@ void av1_convolve_2d_sr_avx2(
const bool use_general = (tap_x == 12 || tap_y == 12);
if (use_general) {
- av1_convolve_2d_sr_general_avx2(src, src_stride, dst, dst_stride, w, h,
- filter_params_x, filter_params_y,
- subpel_x_q4, subpel_y_q4, conv_params);
+ convolve_2d_sr_general_avx2(src, src_stride, dst, dst_stride, w, h,
+ filter_params_x, filter_params_y, subpel_x_q4,
+ subpel_y_q4, conv_params);
} else {
av1_convolve_2d_sr_specialized_avx2(src, src_stride, dst, dst_stride, w, h,
filter_params_x, filter_params_y,
diff --git a/third_party/aom/av1/common/x86/convolve_2d_sse2.c b/third_party/aom/av1/common/x86/convolve_2d_sse2.c
index 1b85f37294..68971eacc1 100644
--- a/third_party/aom/av1/common/x86/convolve_2d_sse2.c
+++ b/third_party/aom/av1/common/x86/convolve_2d_sse2.c
@@ -19,12 +19,11 @@
#include "aom_dsp/x86/convolve_common_intrin.h"
#include "av1/common/convolve.h"
-void av1_convolve_2d_sr_12tap_sse2(const uint8_t *src, int src_stride,
- uint8_t *dst, int dst_stride, int w, int h,
- const InterpFilterParams *filter_params_x,
- const InterpFilterParams *filter_params_y,
- const int subpel_x_qn, const int subpel_y_qn,
- ConvolveParams *conv_params) {
+static void convolve_2d_sr_12tap_sse2(
+ const uint8_t *src, int src_stride, uint8_t *dst, int dst_stride, int w,
+ int h, const InterpFilterParams *filter_params_x,
+ const InterpFilterParams *filter_params_y, const int subpel_x_qn,
+ const int subpel_y_qn, ConvolveParams *conv_params) {
const int bd = 8;
DECLARE_ALIGNED(16, int16_t,
@@ -231,9 +230,9 @@ void av1_convolve_2d_sr_sse2(const uint8_t *src, int src_stride, uint8_t *dst,
filter_params_x, filter_params_y, subpel_x_qn,
subpel_y_qn, conv_params);
} else {
- av1_convolve_2d_sr_12tap_sse2(src, src_stride, dst, dst_stride, w, h,
- filter_params_x, filter_params_y,
- subpel_x_qn, subpel_y_qn, conv_params);
+ convolve_2d_sr_12tap_sse2(src, src_stride, dst, dst_stride, w, h,
+ filter_params_x, filter_params_y, subpel_x_qn,
+ subpel_y_qn, conv_params);
}
} else {
const int bd = 8;
diff --git a/third_party/aom/av1/common/x86/convolve_sse2.c b/third_party/aom/av1/common/x86/convolve_sse2.c
index 012e75c1ae..6383567a48 100644
--- a/third_party/aom/av1/common/x86/convolve_sse2.c
+++ b/third_party/aom/av1/common/x86/convolve_sse2.c
@@ -75,10 +75,10 @@ static INLINE __m128i convolve_hi_y(const __m128i *const s,
return convolve(ss, coeffs);
}
-void av1_convolve_y_sr_12tap_sse2(const uint8_t *src, int src_stride,
- uint8_t *dst, int dst_stride, int w, int h,
- const InterpFilterParams *filter_params_y,
- int subpel_y_qn) {
+static void convolve_y_sr_12tap_sse2(const uint8_t *src, int src_stride,
+ uint8_t *dst, int dst_stride, int w, int h,
+ const InterpFilterParams *filter_params_y,
+ int subpel_y_qn) {
const int fo_vert = filter_params_y->taps / 2 - 1;
const uint8_t *src_ptr = src - fo_vert * src_stride;
const __m128i round_const = _mm_set1_epi32((1 << FILTER_BITS) >> 1);
@@ -185,8 +185,8 @@ void av1_convolve_y_sr_sse2(const uint8_t *src, int src_stride, uint8_t *dst,
av1_convolve_y_sr_c(src, src_stride, dst, dst_stride, w, h,
filter_params_y, subpel_y_qn);
} else {
- av1_convolve_y_sr_12tap_sse2(src, src_stride, dst, dst_stride, w, h,
- filter_params_y, subpel_y_qn);
+ convolve_y_sr_12tap_sse2(src, src_stride, dst, dst_stride, w, h,
+ filter_params_y, subpel_y_qn);
}
} else {
const int fo_vert = filter_params_y->taps / 2 - 1;
@@ -337,11 +337,11 @@ void av1_convolve_y_sr_sse2(const uint8_t *src, int src_stride, uint8_t *dst,
}
}
-void av1_convolve_x_sr_12tap_sse2(const uint8_t *src, int src_stride,
- uint8_t *dst, int dst_stride, int w, int h,
- const InterpFilterParams *filter_params_x,
- int subpel_x_qn,
- ConvolveParams *conv_params) {
+static void convolve_x_sr_12tap_sse2(const uint8_t *src, int src_stride,
+ uint8_t *dst, int dst_stride, int w, int h,
+ const InterpFilterParams *filter_params_x,
+ int subpel_x_qn,
+ ConvolveParams *conv_params) {
const int fo_horiz = filter_params_x->taps / 2 - 1;
const uint8_t *src_ptr = src - fo_horiz;
const int bits = FILTER_BITS - conv_params->round_0;
@@ -402,8 +402,8 @@ void av1_convolve_x_sr_sse2(const uint8_t *src, int src_stride, uint8_t *dst,
av1_convolve_x_sr_c(src, src_stride, dst, dst_stride, w, h,
filter_params_x, subpel_x_qn, conv_params);
} else {
- av1_convolve_x_sr_12tap_sse2(src, src_stride, dst, dst_stride, w, h,
- filter_params_x, subpel_x_qn, conv_params);
+ convolve_x_sr_12tap_sse2(src, src_stride, dst, dst_stride, w, h,
+ filter_params_x, subpel_x_qn, conv_params);
}
} else {
const int fo_horiz = filter_params_x->taps / 2 - 1;
diff --git a/third_party/aom/av1/common/x86/jnt_convolve_sse2.c b/third_party/aom/av1/common/x86/jnt_convolve_sse2.c
index 8c5d9918fb..d5d2db7455 100644
--- a/third_party/aom/av1/common/x86/jnt_convolve_sse2.c
+++ b/third_party/aom/av1/common/x86/jnt_convolve_sse2.c
@@ -375,232 +375,3 @@ void av1_dist_wtd_convolve_y_sse2(const uint8_t *src, int src_stride,
} while (j < w);
}
}
-
-void av1_dist_wtd_convolve_2d_sse2(const uint8_t *src, int src_stride,
- uint8_t *dst0, int dst_stride0, int w, int h,
- const InterpFilterParams *filter_params_x,
- const InterpFilterParams *filter_params_y,
- const int subpel_x_qn, const int subpel_y_qn,
- ConvolveParams *conv_params) {
- CONV_BUF_TYPE *dst = conv_params->dst;
- int dst_stride = conv_params->dst_stride;
- const int bd = 8;
-
- DECLARE_ALIGNED(16, int16_t,
- im_block[(MAX_SB_SIZE + MAX_FILTER_TAP - 1) * MAX_SB_SIZE]);
- int im_h = h + filter_params_y->taps - 1;
- int im_stride = MAX_SB_SIZE;
- int i, j;
- const int fo_vert = filter_params_y->taps / 2 - 1;
- const int fo_horiz = filter_params_x->taps / 2 - 1;
- const int do_average = conv_params->do_average;
- const int use_dist_wtd_comp_avg = conv_params->use_dist_wtd_comp_avg;
- const uint8_t *const src_ptr = src - fo_vert * src_stride - fo_horiz;
-
- const __m128i zero = _mm_setzero_si128();
-
- const int w0 = conv_params->fwd_offset;
- const int w1 = conv_params->bck_offset;
- const __m128i wt0 = _mm_set1_epi16(w0);
- const __m128i wt1 = _mm_set1_epi16(w1);
- const __m128i wt = _mm_unpacklo_epi16(wt0, wt1);
-
- const int offset_0 =
- bd + 2 * FILTER_BITS - conv_params->round_0 - conv_params->round_1;
- const int offset = (1 << offset_0) + (1 << (offset_0 - 1));
- const __m128i offset_const = _mm_set1_epi16(offset);
- const int rounding_shift =
- 2 * FILTER_BITS - conv_params->round_0 - conv_params->round_1;
- const __m128i rounding_const = _mm_set1_epi16((1 << rounding_shift) >> 1);
-
- /* Horizontal filter */
- {
- const int16_t *x_filter = av1_get_interp_filter_subpel_kernel(
- filter_params_x, subpel_x_qn & SUBPEL_MASK);
- const __m128i coeffs_x = _mm_loadu_si128((__m128i *)x_filter);
-
- // coeffs 0 1 0 1 2 3 2 3
- const __m128i tmp_0 = _mm_unpacklo_epi32(coeffs_x, coeffs_x);
- // coeffs 4 5 4 5 6 7 6 7
- const __m128i tmp_1 = _mm_unpackhi_epi32(coeffs_x, coeffs_x);
-
- // coeffs 0 1 0 1 0 1 0 1
- const __m128i coeff_01 = _mm_unpacklo_epi64(tmp_0, tmp_0);
- // coeffs 2 3 2 3 2 3 2 3
- const __m128i coeff_23 = _mm_unpackhi_epi64(tmp_0, tmp_0);
- // coeffs 4 5 4 5 4 5 4 5
- const __m128i coeff_45 = _mm_unpacklo_epi64(tmp_1, tmp_1);
- // coeffs 6 7 6 7 6 7 6 7
- const __m128i coeff_67 = _mm_unpackhi_epi64(tmp_1, tmp_1);
-
- const __m128i round_const = _mm_set1_epi32(
- ((1 << conv_params->round_0) >> 1) + (1 << (bd + FILTER_BITS - 1)));
- const __m128i round_shift = _mm_cvtsi32_si128(conv_params->round_0);
-
- for (i = 0; i < im_h; ++i) {
- for (j = 0; j < w; j += 8) {
- __m128i temp_lo, temp_hi;
- const __m128i data =
- _mm_loadu_si128((__m128i *)&src_ptr[i * src_stride + j]);
-
- const __m128i src_lo = _mm_unpacklo_epi8(data, zero);
- const __m128i src_hi = _mm_unpackhi_epi8(data, zero);
-
- // Filter even-index pixels
- const __m128i res_0 = _mm_madd_epi16(src_lo, coeff_01);
- temp_lo = _mm_srli_si128(src_lo, 4);
- temp_hi = _mm_slli_si128(src_hi, 12);
- const __m128i src_2 = _mm_or_si128(temp_hi, temp_lo);
- const __m128i res_2 = _mm_madd_epi16(src_2, coeff_23);
- temp_lo = _mm_srli_si128(src_lo, 8);
- temp_hi = _mm_slli_si128(src_hi, 8);
- const __m128i src_4 = _mm_or_si128(temp_hi, temp_lo);
- const __m128i res_4 = _mm_madd_epi16(src_4, coeff_45);
- temp_lo = _mm_srli_si128(src_lo, 12);
- temp_hi = _mm_slli_si128(src_hi, 4);
- const __m128i src_6 = _mm_or_si128(temp_hi, temp_lo);
- const __m128i res_6 = _mm_madd_epi16(src_6, coeff_67);
-
- __m128i res_even = _mm_add_epi32(_mm_add_epi32(res_0, res_4),
- _mm_add_epi32(res_2, res_6));
- res_even =
- _mm_sra_epi32(_mm_add_epi32(res_even, round_const), round_shift);
-
- // Filter odd-index pixels
- temp_lo = _mm_srli_si128(src_lo, 2);
- temp_hi = _mm_slli_si128(src_hi, 14);
- const __m128i src_1 = _mm_or_si128(temp_hi, temp_lo);
- const __m128i res_1 = _mm_madd_epi16(src_1, coeff_01);
- temp_lo = _mm_srli_si128(src_lo, 6);
- temp_hi = _mm_slli_si128(src_hi, 10);
- const __m128i src_3 = _mm_or_si128(temp_hi, temp_lo);
- const __m128i res_3 = _mm_madd_epi16(src_3, coeff_23);
- temp_lo = _mm_srli_si128(src_lo, 10);
- temp_hi = _mm_slli_si128(src_hi, 6);
- const __m128i src_5 = _mm_or_si128(temp_hi, temp_lo);
- const __m128i res_5 = _mm_madd_epi16(src_5, coeff_45);
- temp_lo = _mm_srli_si128(src_lo, 14);
- temp_hi = _mm_slli_si128(src_hi, 2);
- const __m128i src_7 = _mm_or_si128(temp_hi, temp_lo);
- const __m128i res_7 = _mm_madd_epi16(src_7, coeff_67);
-
- __m128i res_odd = _mm_add_epi32(_mm_add_epi32(res_1, res_5),
- _mm_add_epi32(res_3, res_7));
- res_odd =
- _mm_sra_epi32(_mm_add_epi32(res_odd, round_const), round_shift);
-
- // Pack in the column order 0, 2, 4, 6, 1, 3, 5, 7
- __m128i res = _mm_packs_epi32(res_even, res_odd);
- _mm_store_si128((__m128i *)&im_block[i * im_stride + j], res);
- }
- }
- }
-
- /* Vertical filter */
- {
- const int16_t *y_filter = av1_get_interp_filter_subpel_kernel(
- filter_params_y, subpel_y_qn & SUBPEL_MASK);
- const __m128i coeffs_y = _mm_loadu_si128((__m128i *)y_filter);
-
- // coeffs 0 1 0 1 2 3 2 3
- const __m128i tmp_0 = _mm_unpacklo_epi32(coeffs_y, coeffs_y);
- // coeffs 4 5 4 5 6 7 6 7
- const __m128i tmp_1 = _mm_unpackhi_epi32(coeffs_y, coeffs_y);
-
- // coeffs 0 1 0 1 0 1 0 1
- const __m128i coeff_01 = _mm_unpacklo_epi64(tmp_0, tmp_0);
- // coeffs 2 3 2 3 2 3 2 3
- const __m128i coeff_23 = _mm_unpackhi_epi64(tmp_0, tmp_0);
- // coeffs 4 5 4 5 4 5 4 5
- const __m128i coeff_45 = _mm_unpacklo_epi64(tmp_1, tmp_1);
- // coeffs 6 7 6 7 6 7 6 7
- const __m128i coeff_67 = _mm_unpackhi_epi64(tmp_1, tmp_1);
-
- const __m128i round_const = _mm_set1_epi32(
- ((1 << conv_params->round_1) >> 1) -
- (1 << (bd + 2 * FILTER_BITS - conv_params->round_0 - 1)));
- const __m128i round_shift = _mm_cvtsi32_si128(conv_params->round_1);
-
- for (i = 0; i < h; ++i) {
- for (j = 0; j < w; j += 8) {
- // Filter even-index pixels
- const int16_t *data = &im_block[i * im_stride + j];
- const __m128i src_0 =
- _mm_unpacklo_epi16(*(__m128i *)(data + 0 * im_stride),
- *(__m128i *)(data + 1 * im_stride));
- const __m128i src_2 =
- _mm_unpacklo_epi16(*(__m128i *)(data + 2 * im_stride),
- *(__m128i *)(data + 3 * im_stride));
- const __m128i src_4 =
- _mm_unpacklo_epi16(*(__m128i *)(data + 4 * im_stride),
- *(__m128i *)(data + 5 * im_stride));
- const __m128i src_6 =
- _mm_unpacklo_epi16(*(__m128i *)(data + 6 * im_stride),
- *(__m128i *)(data + 7 * im_stride));
-
- const __m128i res_0 = _mm_madd_epi16(src_0, coeff_01);
- const __m128i res_2 = _mm_madd_epi16(src_2, coeff_23);
- const __m128i res_4 = _mm_madd_epi16(src_4, coeff_45);
- const __m128i res_6 = _mm_madd_epi16(src_6, coeff_67);
-
- const __m128i res_even = _mm_add_epi32(_mm_add_epi32(res_0, res_2),
- _mm_add_epi32(res_4, res_6));
-
- // Filter odd-index pixels
- const __m128i src_1 =
- _mm_unpackhi_epi16(*(__m128i *)(data + 0 * im_stride),
- *(__m128i *)(data + 1 * im_stride));
- const __m128i src_3 =
- _mm_unpackhi_epi16(*(__m128i *)(data + 2 * im_stride),
- *(__m128i *)(data + 3 * im_stride));
- const __m128i src_5 =
- _mm_unpackhi_epi16(*(__m128i *)(data + 4 * im_stride),
- *(__m128i *)(data + 5 * im_stride));
- const __m128i src_7 =
- _mm_unpackhi_epi16(*(__m128i *)(data + 6 * im_stride),
- *(__m128i *)(data + 7 * im_stride));
-
- const __m128i res_1 = _mm_madd_epi16(src_1, coeff_01);
- const __m128i res_3 = _mm_madd_epi16(src_3, coeff_23);
- const __m128i res_5 = _mm_madd_epi16(src_5, coeff_45);
- const __m128i res_7 = _mm_madd_epi16(src_7, coeff_67);
-
- const __m128i res_odd = _mm_add_epi32(_mm_add_epi32(res_1, res_3),
- _mm_add_epi32(res_5, res_7));
-
- // Rearrange pixels back into the order 0 ... 7
- const __m128i res_lo = _mm_unpacklo_epi32(res_even, res_odd);
- const __m128i res_hi = _mm_unpackhi_epi32(res_even, res_odd);
-
- const __m128i res_lo_round =
- _mm_sra_epi32(_mm_add_epi32(res_lo, round_const), round_shift);
- const __m128i res_hi_round =
- _mm_sra_epi32(_mm_add_epi32(res_hi, round_const), round_shift);
-
- const __m128i res_16b = _mm_packs_epi32(res_lo_round, res_hi_round);
- const __m128i res_unsigned = _mm_add_epi16(res_16b, offset_const);
-
- // Accumulate values into the destination buffer
- if (do_average) {
- const __m128i data_ref_0 =
- _mm_loadu_si128((__m128i *)(&dst[i * dst_stride + j]));
-
- const __m128i comp_avg_res =
- comp_avg(&data_ref_0, &res_unsigned, &wt, use_dist_wtd_comp_avg);
-
- const __m128i round_result = convolve_rounding(
- &comp_avg_res, &offset_const, &rounding_const, rounding_shift);
-
- const __m128i res_8 = _mm_packus_epi16(round_result, round_result);
-
- if (w > 4)
- _mm_storel_epi64((__m128i *)(&dst0[i * dst_stride0 + j]), res_8);
- else
- *(int *)(&dst0[i * dst_stride0 + j]) = _mm_cvtsi128_si32(res_8);
- } else {
- _mm_store_si128((__m128i *)(&dst[i * dst_stride + j]), res_unsigned);
- }
- }
- }
- }
-}
diff --git a/third_party/aom/av1/decoder/decodeframe.c b/third_party/aom/av1/decoder/decodeframe.c
index bb09347e1c..c027308ff3 100644
--- a/third_party/aom/av1/decoder/decodeframe.c
+++ b/third_party/aom/av1/decoder/decodeframe.c
@@ -14,20 +14,23 @@
#include <stddef.h>
#include "config/aom_config.h"
-#include "config/aom_dsp_rtcd.h"
#include "config/aom_scale_rtcd.h"
-#include "config/av1_rtcd.h"
#include "aom/aom_codec.h"
+#include "aom/aom_image.h"
+#include "aom/internal/aom_codec_internal.h"
#include "aom_dsp/aom_dsp_common.h"
#include "aom_dsp/binary_codes_reader.h"
#include "aom_dsp/bitreader.h"
#include "aom_dsp/bitreader_buffer.h"
+#include "aom_dsp/txfm_common.h"
#include "aom_mem/aom_mem.h"
#include "aom_ports/aom_timer.h"
#include "aom_ports/mem.h"
#include "aom_ports/mem_ops.h"
#include "aom_scale/aom_scale.h"
+#include "aom_scale/yv12config.h"
+#include "aom_util/aom_pthread.h"
#include "aom_util/aom_thread.h"
#if CONFIG_BITSTREAM_DEBUG || CONFIG_MISMATCH_DEBUG
@@ -35,33 +38,41 @@
#endif // CONFIG_BITSTREAM_DEBUG || CONFIG_MISMATCH_DEBUG
#include "av1/common/alloccommon.h"
+#include "av1/common/av1_common_int.h"
+#include "av1/common/blockd.h"
#include "av1/common/cdef.h"
#include "av1/common/cfl.h"
-#if CONFIG_INSPECTION
-#include "av1/decoder/inspection.h"
-#endif
+#include "av1/common/common_data.h"
#include "av1/common/common.h"
#include "av1/common/entropy.h"
#include "av1/common/entropymode.h"
#include "av1/common/entropymv.h"
+#include "av1/common/enums.h"
#include "av1/common/frame_buffers.h"
#include "av1/common/idct.h"
+#include "av1/common/mv.h"
#include "av1/common/mvref_common.h"
+#include "av1/common/obmc.h"
#include "av1/common/pred_common.h"
#include "av1/common/quant_common.h"
#include "av1/common/reconinter.h"
#include "av1/common/reconintra.h"
#include "av1/common/resize.h"
+#include "av1/common/restoration.h"
+#include "av1/common/scale.h"
#include "av1/common/seg_common.h"
#include "av1/common/thread_common.h"
#include "av1/common/tile_common.h"
#include "av1/common/warped_motion.h"
-#include "av1/common/obmc.h"
+
#include "av1/decoder/decodeframe.h"
#include "av1/decoder/decodemv.h"
#include "av1/decoder/decoder.h"
#include "av1/decoder/decodetxb.h"
#include "av1/decoder/detokenize.h"
+#if CONFIG_INSPECTION
+#include "av1/decoder/inspection.h"
+#endif
#define ACCT_STR __func__
@@ -1935,8 +1946,8 @@ static AOM_INLINE void setup_buffer_pool(AV1_COMMON *cm) {
&cm->cur_frame->buf, cm->width, cm->height, seq_params->subsampling_x,
seq_params->subsampling_y, seq_params->use_highbitdepth,
AOM_DEC_BORDER_IN_PIXELS, cm->features.byte_alignment,
- &cm->cur_frame->raw_frame_buffer, pool->get_fb_cb, pool->cb_priv, 0,
- 0)) {
+ &cm->cur_frame->raw_frame_buffer, pool->get_fb_cb, pool->cb_priv,
+ false, 0)) {
unlock_buffer_pool(pool);
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate frame buffer");
@@ -2293,7 +2304,11 @@ static const uint8_t *get_ls_tile_buffers(
const int tile_col_size_bytes = pbi->tile_col_size_bytes;
const int tile_size_bytes = pbi->tile_size_bytes;
int tile_width, tile_height;
- av1_get_uniform_tile_size(cm, &tile_width, &tile_height);
+ if (!av1_get_uniform_tile_size(cm, &tile_width, &tile_height)) {
+ aom_internal_error(
+ &pbi->error, AOM_CODEC_CORRUPT_FRAME,
+ "Not all the tiles in the tile list have the same size.");
+ }
const int tile_copy_mode =
((AOMMAX(tile_width, tile_height) << MI_SIZE_LOG2) <= 256) ? 1 : 0;
// Read tile column sizes for all columns (we need the last tile buffer)
@@ -2302,8 +2317,16 @@ static const uint8_t *get_ls_tile_buffers(
size_t tile_col_size;
if (!is_last) {
+ if (tile_col_size_bytes > data_end - data) {
+ aom_internal_error(&pbi->error, AOM_CODEC_CORRUPT_FRAME,
+ "Not enough data to read tile_col_size");
+ }
tile_col_size = mem_get_varsize(data, tile_col_size_bytes);
data += tile_col_size_bytes;
+ if (tile_col_size > (size_t)(data_end - data)) {
+ aom_internal_error(&pbi->error, AOM_CODEC_CORRUPT_FRAME,
+ "tile_col_data_end[%d] is out of bound", c);
+ }
tile_col_data_end[c] = data + tile_col_size;
} else {
tile_col_size = data_end - data;
@@ -3871,8 +3894,8 @@ static AOM_INLINE void read_bitdepth(
#endif
}
-void av1_read_film_grain_params(AV1_COMMON *cm,
- struct aom_read_bit_buffer *rb) {
+static void read_film_grain_params(AV1_COMMON *cm,
+ struct aom_read_bit_buffer *rb) {
aom_film_grain_t *pars = &cm->film_grain_params;
const SequenceHeader *const seq_params = cm->seq_params;
@@ -4040,7 +4063,7 @@ static AOM_INLINE void read_film_grain(AV1_COMMON *cm,
struct aom_read_bit_buffer *rb) {
if (cm->seq_params->film_grain_params_present &&
(cm->show_frame || cm->showable_frame)) {
- av1_read_film_grain_params(cm, rb);
+ read_film_grain_params(cm, rb);
} else {
memset(&cm->film_grain_params, 0, sizeof(cm->film_grain_params));
}
@@ -4768,7 +4791,7 @@ static int read_uncompressed_header(AV1Decoder *pbi,
seq_params->max_frame_height, seq_params->subsampling_x,
seq_params->subsampling_y, seq_params->use_highbitdepth,
AOM_BORDER_IN_PIXELS, features->byte_alignment,
- &buf->raw_frame_buffer, pool->get_fb_cb, pool->cb_priv, 0,
+ &buf->raw_frame_buffer, pool->get_fb_cb, pool->cb_priv, false,
0)) {
decrease_ref_count(buf, pool);
unlock_buffer_pool(pool);
diff --git a/third_party/aom/av1/decoder/decodemv.h b/third_party/aom/av1/decoder/decodemv.h
index 3d8629c9a5..7e77c030f8 100644
--- a/third_party/aom/av1/decoder/decodemv.h
+++ b/third_party/aom/av1/decoder/decodemv.h
@@ -20,6 +20,8 @@
extern "C" {
#endif
+int av1_neg_deinterleave(int diff, int ref, int max);
+
void av1_read_mode_info(AV1Decoder *const pbi, DecoderCodingBlock *dcb,
aom_reader *r, int x_mis, int y_mis);
diff --git a/third_party/aom/av1/decoder/decoder.c b/third_party/aom/av1/decoder/decoder.c
index 32e94840be..a886ed469c 100644
--- a/third_party/aom/av1/decoder/decoder.c
+++ b/third_party/aom/av1/decoder/decoder.c
@@ -21,6 +21,7 @@
#include "aom_mem/aom_mem.h"
#include "aom_ports/aom_timer.h"
#include "aom_scale/aom_scale.h"
+#include "aom_util/aom_pthread.h"
#include "aom_util/aom_thread.h"
#include "av1/common/alloccommon.h"
diff --git a/third_party/aom/av1/decoder/dthread.h b/third_party/aom/av1/decoder/dthread.h
index f82b9d8ccf..b0f6fda829 100644
--- a/third_party/aom/av1/decoder/dthread.h
+++ b/third_party/aom/av1/decoder/dthread.h
@@ -14,7 +14,6 @@
#include "config/aom_config.h"
-#include "aom_util/aom_thread.h"
#include "aom/internal/aom_codec_internal.h"
#ifdef __cplusplus
diff --git a/third_party/aom/av1/decoder/obu.c b/third_party/aom/av1/decoder/obu.c
index 0e31ce9404..e0b2d87c32 100644
--- a/third_party/aom/av1/decoder/obu.c
+++ b/third_party/aom/av1/decoder/obu.c
@@ -367,16 +367,13 @@ static uint32_t read_one_tile_group_obu(
return header_size + tg_payload_size;
}
-static void alloc_tile_list_buffer(AV1Decoder *pbi) {
+static void alloc_tile_list_buffer(AV1Decoder *pbi, int tile_width_in_pixels,
+ int tile_height_in_pixels) {
// The resolution of the output frame is read out from the bitstream. The data
// are stored in the order of Y plane, U plane and V plane. As an example, for
// image format 4:2:0, the output frame of U plane and V plane is 1/4 of the
// output frame.
AV1_COMMON *const cm = &pbi->common;
- int tile_width, tile_height;
- av1_get_uniform_tile_size(cm, &tile_width, &tile_height);
- const int tile_width_in_pixels = tile_width * MI_SIZE;
- const int tile_height_in_pixels = tile_height * MI_SIZE;
const int output_frame_width =
(pbi->output_frame_width_in_tiles_minus_1 + 1) * tile_width_in_pixels;
const int output_frame_height =
@@ -396,7 +393,7 @@ static void alloc_tile_list_buffer(AV1Decoder *pbi) {
cm->seq_params->subsampling_y,
(cm->seq_params->use_highbitdepth &&
(cm->seq_params->bit_depth > AOM_BITS_8)),
- 0, cm->features.byte_alignment, 0, 0))
+ 0, cm->features.byte_alignment, false, 0))
aom_internal_error(&pbi->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate the tile list output buffer");
}
@@ -424,13 +421,10 @@ static void yv12_tile_copy(const YV12_BUFFER_CONFIG *src, int hstart1,
return;
}
-static void copy_decoded_tile_to_tile_list_buffer(AV1Decoder *pbi,
- int tile_idx) {
+static void copy_decoded_tile_to_tile_list_buffer(AV1Decoder *pbi, int tile_idx,
+ int tile_width_in_pixels,
+ int tile_height_in_pixels) {
AV1_COMMON *const cm = &pbi->common;
- int tile_width, tile_height;
- av1_get_uniform_tile_size(cm, &tile_width, &tile_height);
- const int tile_width_in_pixels = tile_width * MI_SIZE;
- const int tile_height_in_pixels = tile_height * MI_SIZE;
const int ssy = cm->seq_params->subsampling_y;
const int ssx = cm->seq_params->subsampling_x;
const int num_planes = av1_num_planes(cm);
@@ -501,13 +495,31 @@ static uint32_t read_and_decode_one_tile_list(AV1Decoder *pbi,
pbi->output_frame_width_in_tiles_minus_1 = aom_rb_read_literal(rb, 8);
pbi->output_frame_height_in_tiles_minus_1 = aom_rb_read_literal(rb, 8);
pbi->tile_count_minus_1 = aom_rb_read_literal(rb, 16);
+
+ // The output frame is used to store the decoded tile list. The decoded tile
+ // list has to fit into 1 output frame.
+ if ((pbi->tile_count_minus_1 + 1) >
+ (pbi->output_frame_width_in_tiles_minus_1 + 1) *
+ (pbi->output_frame_height_in_tiles_minus_1 + 1)) {
+ pbi->error.error_code = AOM_CODEC_CORRUPT_FRAME;
+ return 0;
+ }
+
if (pbi->tile_count_minus_1 > MAX_TILES - 1) {
pbi->error.error_code = AOM_CODEC_CORRUPT_FRAME;
return 0;
}
+ int tile_width, tile_height;
+ if (!av1_get_uniform_tile_size(cm, &tile_width, &tile_height)) {
+ pbi->error.error_code = AOM_CODEC_CORRUPT_FRAME;
+ return 0;
+ }
+ const int tile_width_in_pixels = tile_width * MI_SIZE;
+ const int tile_height_in_pixels = tile_height * MI_SIZE;
+
// Allocate output frame buffer for the tile list.
- alloc_tile_list_buffer(pbi);
+ alloc_tile_list_buffer(pbi, tile_width_in_pixels, tile_height_in_pixels);
uint32_t tile_list_info_bytes = 4;
tile_list_payload_size += tile_list_info_bytes;
@@ -558,7 +570,8 @@ static uint32_t read_and_decode_one_tile_list(AV1Decoder *pbi,
assert(data <= data_end);
// Copy the decoded tile to the tile list output buffer.
- copy_decoded_tile_to_tile_list_buffer(pbi, tile_idx);
+ copy_decoded_tile_to_tile_list_buffer(pbi, tile_idx, tile_width_in_pixels,
+ tile_height_in_pixels);
tile_idx++;
}
diff --git a/third_party/aom/av1/encoder/allintra_vis.c b/third_party/aom/av1/encoder/allintra_vis.c
index 8dcef5fc85..87becb80ef 100644
--- a/third_party/aom/av1/encoder/allintra_vis.c
+++ b/third_party/aom/av1/encoder/allintra_vis.c
@@ -13,6 +13,8 @@
#include "config/aom_config.h"
+#include "aom_util/aom_pthread.h"
+
#if CONFIG_TFLITE
#include "tensorflow/lite/c/c_api.h"
#include "av1/encoder/deltaq4_model.c"
@@ -588,7 +590,7 @@ void av1_set_mb_wiener_variance(AV1_COMP *cpi) {
&cm->cur_frame->buf, cm->width, cm->height, seq_params->subsampling_x,
seq_params->subsampling_y, seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels, cm->features.byte_alignment, NULL, NULL,
- NULL, cpi->image_pyramid_levels, 0))
+ NULL, cpi->alloc_pyramid, 0))
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate frame buffer");
av1_alloc_mb_wiener_var_pred_buf(&cpi->common, &cpi->td);
diff --git a/third_party/aom/av1/encoder/aq_cyclicrefresh.c b/third_party/aom/av1/encoder/aq_cyclicrefresh.c
index f48ff11e51..1aa8dde323 100644
--- a/third_party/aom/av1/encoder/aq_cyclicrefresh.c
+++ b/third_party/aom/av1/encoder/aq_cyclicrefresh.c
@@ -15,6 +15,7 @@
#include "av1/common/pred_common.h"
#include "av1/common/seg_common.h"
#include "av1/encoder/aq_cyclicrefresh.h"
+#include "av1/encoder/encoder_utils.h"
#include "av1/encoder/ratectrl.h"
#include "av1/encoder/segmentation.h"
#include "av1/encoder/tokenize.h"
@@ -295,6 +296,7 @@ static void cyclic_refresh_update_map(AV1_COMP *const cpi) {
const CommonModeInfoParams *const mi_params = &cm->mi_params;
CYCLIC_REFRESH *const cr = cpi->cyclic_refresh;
unsigned char *const seg_map = cpi->enc_seg.map;
+ unsigned char *const active_map_4x4 = cpi->active_map.map;
int i, block_count, bl_index, sb_rows, sb_cols, sbs_in_frame;
int xmis, ymis, x, y;
uint64_t sb_sad = 0;
@@ -302,7 +304,12 @@ static void cyclic_refresh_update_map(AV1_COMP *const cpi) {
uint64_t thresh_sad = INT64_MAX;
const int mi_rows = mi_params->mi_rows, mi_cols = mi_params->mi_cols;
const int mi_stride = mi_cols;
- memset(seg_map, CR_SEGMENT_ID_BASE, mi_rows * mi_cols);
+ // Don't set seg_map to 0 if active_maps is enabled. Active_maps will set
+ // seg_map to either 7 or 0 (AM_SEGMENT_ID_INACTIVE/ACTIVE), and cyclic
+ // refresh set below (segment 1 or 2) will only be set for ACTIVE blocks.
+ if (!cpi->active_map.enabled) {
+ memset(seg_map, CR_SEGMENT_ID_BASE, mi_rows * mi_cols);
+ }
sb_cols = (mi_cols + cm->seq_params->mib_size - 1) / cm->seq_params->mib_size;
sb_rows = (mi_rows + cm->seq_params->mib_size - 1) / cm->seq_params->mib_size;
sbs_in_frame = sb_cols * sb_rows;
@@ -357,7 +364,10 @@ static void cyclic_refresh_update_map(AV1_COMP *const cpi) {
// for possible boost/refresh (segment 1). The segment id may get
// reset to 0 later if block gets coded anything other than low motion.
// If the block_sad (sb_sad) is very low label it for refresh anyway.
- if (cr->map[bl_index2] == 0 || sb_sad < thresh_sad_low) {
+ // If active_maps is enabled, only allow for setting on ACTIVE blocks.
+ if ((cr->map[bl_index2] == 0 || sb_sad < thresh_sad_low) &&
+ (!cpi->active_map.enabled ||
+ active_map_4x4[bl_index2] == AM_SEGMENT_ID_ACTIVE)) {
sum_map += 4;
} else if (cr->map[bl_index2] < 0) {
cr->map[bl_index2]++;
@@ -380,7 +390,8 @@ static void cyclic_refresh_update_map(AV1_COMP *const cpi) {
cr->sb_index = i;
if (cr->target_num_seg_blocks == 0) {
// Disable segmentation, seg_map is already set to 0 above.
- av1_disable_segmentation(&cm->seg);
+ // Don't disable if active_map is being used.
+ if (!cpi->active_map.enabled) av1_disable_segmentation(&cm->seg);
}
}
@@ -423,8 +434,6 @@ void av1_cyclic_refresh_update_parameters(AV1_COMP *const cpi) {
// function av1_cyclic_reset_segment_skip(). Skipping over
// 4x4 will therefore have small bdrate loss (~0.2%), so
// we use it only for speed > 9 for now.
- // Also if loop-filter deltas is applied via segment, then
- // we need to set cr->skip_over4x4 = 1.
cr->skip_over4x4 = (cpi->oxcf.speed > 9) ? 1 : 0;
// should we enable cyclic refresh on this frame.
@@ -450,6 +459,15 @@ void av1_cyclic_refresh_update_parameters(AV1_COMP *const cpi) {
else
cr->percent_refresh = 10 + cr->percent_refresh_adjustment;
+ if (cpi->active_map.enabled) {
+ // Scale down the percent_refresh to target the active blocks only.
+ cr->percent_refresh =
+ cr->percent_refresh * (100 - cpi->rc.percent_blocks_inactive) / 100;
+ if (cr->percent_refresh == 0) {
+ cr->apply_cyclic_refresh = 0;
+ }
+ }
+
cr->max_qdelta_perc = 60;
cr->time_for_refresh = 0;
cr->use_block_sad_scene_det =
@@ -543,10 +561,14 @@ void av1_cyclic_refresh_setup(AV1_COMP *const cpi) {
if (resolution_change) av1_cyclic_refresh_reset_resize(cpi);
if (!cr->apply_cyclic_refresh) {
- // Set segmentation map to 0 and disable.
- unsigned char *const seg_map = cpi->enc_seg.map;
- memset(seg_map, 0, cm->mi_params.mi_rows * cm->mi_params.mi_cols);
- av1_disable_segmentation(&cm->seg);
+ // Don't disable and set seg_map to 0 if active_maps is enabled, unless
+ // whole frame is set as inactive (since we only apply cyclic_refresh to
+ // active blocks).
+ if (!cpi->active_map.enabled || cpi->rc.percent_blocks_inactive == 100) {
+ unsigned char *const seg_map = cpi->enc_seg.map;
+ memset(seg_map, 0, cm->mi_params.mi_rows * cm->mi_params.mi_cols);
+ av1_disable_segmentation(&cm->seg);
+ }
if (frame_is_intra_only(cm) || scene_change_detected ||
cpi->ppi->rtc_ref.bias_recovery_frame) {
cr->sb_index = 0;
@@ -574,9 +596,11 @@ void av1_cyclic_refresh_setup(AV1_COMP *const cpi) {
cr->thresh_rate_sb = INT64_MAX;
}
// Set up segmentation.
- // Clear down the segment map.
av1_enable_segmentation(&cm->seg);
- av1_clearall_segfeatures(seg);
+ if (!cpi->active_map.enabled) {
+ // Clear down the segment map, only if active_maps is not enabled.
+ av1_clearall_segfeatures(seg);
+ }
// Note: setting temporal_update has no effect, as the seg-map coding method
// (temporal or spatial) is determined in
@@ -644,6 +668,10 @@ void av1_cyclic_refresh_reset_resize(AV1_COMP *const cpi) {
int av1_cyclic_refresh_disable_lf_cdef(AV1_COMP *const cpi) {
CYCLIC_REFRESH *const cr = cpi->cyclic_refresh;
const int qindex = cpi->common.quant_params.base_qindex;
+ if (cpi->active_map.enabled &&
+ cpi->rc.percent_blocks_inactive >
+ cpi->sf.rt_sf.thresh_active_maps_skip_lf_cdef)
+ return 1;
if (cpi->rc.frames_since_key > 30 && cr->percent_refresh > 0 &&
cr->counter_encode_maxq_scene_change > 300 / cr->percent_refresh &&
cpi->rc.frame_source_sad < 1000 &&
diff --git a/third_party/aom/av1/encoder/arm/neon/av1_error_sve.c b/third_party/aom/av1/encoder/arm/neon/av1_error_sve.c
index 63aad0b785..52803a9838 100644
--- a/third_party/aom/av1/encoder/arm/neon/av1_error_sve.c
+++ b/third_party/aom/av1/encoder/arm/neon/av1_error_sve.c
@@ -14,7 +14,7 @@
#include "config/aom_config.h"
#include "aom_dsp/aom_dsp_common.h"
-#include "aom_dsp/arm/dot_sve.h"
+#include "aom_dsp/arm/aom_neon_sve_bridge.h"
#include "aom_dsp/arm/mem_neon.h"
int64_t av1_block_error_sve(const tran_low_t *coeff, const tran_low_t *dqcoeff,
diff --git a/third_party/aom/av1/encoder/arm/neon/temporal_filter_neon_dotprod.c b/third_party/aom/av1/encoder/arm/neon/temporal_filter_neon_dotprod.c
index 5a52e701a2..919521fec7 100644
--- a/third_party/aom/av1/encoder/arm/neon/temporal_filter_neon_dotprod.c
+++ b/third_party/aom/av1/encoder/arm/neon/temporal_filter_neon_dotprod.c
@@ -23,7 +23,15 @@
#define SSE_STRIDE (BW + 4)
// clang-format off
+// Table used to pad the first and last columns and apply the sliding window.
+DECLARE_ALIGNED(16, static const uint8_t, kLoadPad[4][16]) = {
+ { 2, 2, 2, 3, 4, 255, 255, 255, 255, 2, 2, 3, 4, 5, 255, 255 },
+ { 255, 255, 2, 3, 4, 5, 6, 255, 255, 255, 255, 3, 4, 5, 6, 7 },
+ { 0, 1, 2, 3, 4, 255, 255, 255, 255, 1, 2, 3, 4, 5, 255, 255 },
+ { 255, 255, 2, 3, 4, 5, 5, 255, 255, 255, 255, 3, 4, 5, 5, 5 }
+};
+// For columns that don't need to be padded it's just a simple mask.
DECLARE_ALIGNED(16, static const uint8_t, kSlidingWindowMask[]) = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
@@ -56,22 +64,6 @@ static INLINE void get_abs_diff(const uint8_t *frame1, const uint32_t stride1,
} while (++i < block_height);
}
-static INLINE uint8x16_t load_and_pad(const uint8_t *src, const uint32_t col,
- const uint32_t block_width) {
- uint8x8_t s = vld1_u8(src);
-
- if (col == 0) {
- const uint8_t lane2 = vget_lane_u8(s, 2);
- s = vset_lane_u8(lane2, s, 0);
- s = vset_lane_u8(lane2, s, 1);
- } else if (col >= block_width - 4) {
- const uint8_t lane5 = vget_lane_u8(s, 5);
- s = vset_lane_u8(lane5, s, 6);
- s = vset_lane_u8(lane5, s, 7);
- }
- return vcombine_u8(s, s);
-}
-
static void apply_temporal_filter(
const uint8_t *frame, const unsigned int stride, const uint32_t block_width,
const uint32_t block_height, const int *subblock_mses,
@@ -84,6 +76,10 @@ static void apply_temporal_filter(
uint32_t acc_5x5_neon[BH][BW];
const uint8x16x2_t vmask = vld1q_u8_x2(kSlidingWindowMask);
+ const uint8x16_t pad_tbl0 = vld1q_u8(kLoadPad[0]);
+ const uint8x16_t pad_tbl1 = vld1q_u8(kLoadPad[1]);
+ const uint8x16_t pad_tbl2 = vld1q_u8(kLoadPad[2]);
+ const uint8x16_t pad_tbl3 = vld1q_u8(kLoadPad[3]);
// Traverse 4 columns at a time - first and last two columns need padding.
for (uint32_t col = 0; col < block_width; col += 4) {
@@ -92,9 +88,18 @@ static void apply_temporal_filter(
// Load, pad (for first and last two columns) and mask 3 rows from the top.
for (int i = 2; i < 5; i++) {
- const uint8x16_t s = load_and_pad(src, col, block_width);
- vsrc[i][0] = vandq_u8(s, vmask.val[0]);
- vsrc[i][1] = vandq_u8(s, vmask.val[1]);
+ uint8x8_t s = vld1_u8(src);
+ uint8x16_t s_dup = vcombine_u8(s, s);
+ if (col == 0) {
+ vsrc[i][0] = vqtbl1q_u8(s_dup, pad_tbl0);
+ vsrc[i][1] = vqtbl1q_u8(s_dup, pad_tbl1);
+ } else if (col >= block_width - 4) {
+ vsrc[i][0] = vqtbl1q_u8(s_dup, pad_tbl2);
+ vsrc[i][1] = vqtbl1q_u8(s_dup, pad_tbl3);
+ } else {
+ vsrc[i][0] = vandq_u8(s_dup, vmask.val[0]);
+ vsrc[i][1] = vandq_u8(s_dup, vmask.val[1]);
+ }
src += SSE_STRIDE;
}
@@ -130,9 +135,18 @@ static void apply_temporal_filter(
if (row <= block_height - 4) {
// Load next row into the bottom of the sliding window.
- uint8x16_t s = load_and_pad(src, col, block_width);
- vsrc[4][0] = vandq_u8(s, vmask.val[0]);
- vsrc[4][1] = vandq_u8(s, vmask.val[1]);
+ uint8x8_t s = vld1_u8(src);
+ uint8x16_t s_dup = vcombine_u8(s, s);
+ if (col == 0) {
+ vsrc[4][0] = vqtbl1q_u8(s_dup, pad_tbl0);
+ vsrc[4][1] = vqtbl1q_u8(s_dup, pad_tbl1);
+ } else if (col >= block_width - 4) {
+ vsrc[4][0] = vqtbl1q_u8(s_dup, pad_tbl2);
+ vsrc[4][1] = vqtbl1q_u8(s_dup, pad_tbl3);
+ } else {
+ vsrc[4][0] = vandq_u8(s_dup, vmask.val[0]);
+ vsrc[4][1] = vandq_u8(s_dup, vmask.val[1]);
+ }
src += SSE_STRIDE;
} else {
// Pad the bottom 2 rows.
diff --git a/third_party/aom/av1/encoder/arm/neon/wedge_utils_sve.c b/third_party/aom/av1/encoder/arm/neon/wedge_utils_sve.c
new file mode 100644
index 0000000000..521601a3f3
--- /dev/null
+++ b/third_party/aom/av1/encoder/arm/neon/wedge_utils_sve.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2024, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#include <arm_neon.h>
+#include <assert.h>
+
+#include "aom_dsp/arm/aom_neon_sve_bridge.h"
+#include "aom_dsp/arm/sum_neon.h"
+#include "av1/common/reconinter.h"
+
+uint64_t av1_wedge_sse_from_residuals_sve(const int16_t *r1, const int16_t *d,
+ const uint8_t *m, int N) {
+ assert(N % 64 == 0);
+
+ // Predicate pattern with first 8 elements true.
+ const svbool_t pattern = svptrue_pat_b16(SV_VL8);
+ int64x2_t sse[2] = { vdupq_n_s64(0), vdupq_n_s64(0) };
+
+ int i = 0;
+ do {
+ int32x4_t sum[4];
+ int16x8_t sum_s16[2];
+
+ const int16x8_t r1_l = vld1q_s16(r1 + i);
+ const int16x8_t r1_h = vld1q_s16(r1 + i + 8);
+ const int16x8_t d_l = vld1q_s16(d + i);
+ const int16x8_t d_h = vld1q_s16(d + i + 8);
+
+ // Use a zero-extending load to widen the vector elements.
+ const int16x8_t m_l = svget_neonq_s16(svld1ub_s16(pattern, m + i));
+ const int16x8_t m_h = svget_neonq_s16(svld1ub_s16(pattern, m + i + 8));
+
+ sum[0] = vshll_n_s16(vget_low_s16(r1_l), WEDGE_WEIGHT_BITS);
+ sum[1] = vshll_n_s16(vget_high_s16(r1_l), WEDGE_WEIGHT_BITS);
+ sum[2] = vshll_n_s16(vget_low_s16(r1_h), WEDGE_WEIGHT_BITS);
+ sum[3] = vshll_n_s16(vget_high_s16(r1_h), WEDGE_WEIGHT_BITS);
+
+ sum[0] = vmlal_s16(sum[0], vget_low_s16(m_l), vget_low_s16(d_l));
+ sum[1] = vmlal_s16(sum[1], vget_high_s16(m_l), vget_high_s16(d_l));
+ sum[2] = vmlal_s16(sum[2], vget_low_s16(m_h), vget_low_s16(d_h));
+ sum[3] = vmlal_s16(sum[3], vget_high_s16(m_h), vget_high_s16(d_h));
+
+ sum_s16[0] = vcombine_s16(vqmovn_s32(sum[0]), vqmovn_s32(sum[1]));
+ sum_s16[1] = vcombine_s16(vqmovn_s32(sum[2]), vqmovn_s32(sum[3]));
+
+ sse[0] = aom_sdotq_s16(sse[0], sum_s16[0], sum_s16[0]);
+ sse[1] = aom_sdotq_s16(sse[1], sum_s16[1], sum_s16[1]);
+
+ i += 16;
+ } while (i < N);
+
+ const uint64_t csse =
+ (uint64_t)horizontal_add_s64x2(vaddq_s64(sse[0], sse[1]));
+ return ROUND_POWER_OF_TWO(csse, 2 * WEDGE_WEIGHT_BITS);
+}
+
+int8_t av1_wedge_sign_from_residuals_sve(const int16_t *ds, const uint8_t *m,
+ int N, int64_t limit) {
+ assert(N % 16 == 0);
+
+ // Predicate pattern with first 8 elements true.
+ svbool_t pattern = svptrue_pat_b16(SV_VL8);
+ int64x2_t acc_l = vdupq_n_s64(0);
+ int64x2_t acc_h = vdupq_n_s64(0);
+
+ do {
+ const int16x8_t ds_l = vld1q_s16(ds);
+ const int16x8_t ds_h = vld1q_s16(ds + 8);
+
+ // Use a zero-extending load to widen the vector elements.
+ const int16x8_t m_l = svget_neonq_s16(svld1ub_s16(pattern, m));
+ const int16x8_t m_h = svget_neonq_s16(svld1ub_s16(pattern, m + 8));
+
+ acc_l = aom_sdotq_s16(acc_l, ds_l, m_l);
+ acc_h = aom_sdotq_s16(acc_h, ds_h, m_h);
+
+ ds += 16;
+ m += 16;
+ N -= 16;
+ } while (N != 0);
+
+ const int64x2_t sum = vaddq_s64(acc_l, acc_h);
+ return horizontal_add_s64x2(sum) > limit;
+}
diff --git a/third_party/aom/av1/encoder/av1_temporal_denoiser.c b/third_party/aom/av1/encoder/av1_temporal_denoiser.c
index 3012df6311..d4a1625612 100644
--- a/third_party/aom/av1/encoder/av1_temporal_denoiser.c
+++ b/third_party/aom/av1/encoder/av1_temporal_denoiser.c
@@ -489,7 +489,7 @@ static int av1_denoiser_realloc_svc_helper(AV1_COMMON *cm,
&denoiser->running_avg_y[fb_idx], cm->width, cm->height,
cm->seq_params->subsampling_x, cm->seq_params->subsampling_y,
cm->seq_params->use_highbitdepth, AOM_BORDER_IN_PIXELS,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
if (fail) {
av1_denoiser_free(denoiser);
return 1;
@@ -577,7 +577,7 @@ int av1_denoiser_alloc(AV1_COMMON *cm, struct SVC *svc, AV1_DENOISER *denoiser,
fail = aom_alloc_frame_buffer(
&denoiser->running_avg_y[i + denoiser->num_ref_frames * layer],
denoise_width, denoise_height, ssx, ssy, use_highbitdepth, border,
- legacy_byte_alignment, 0, 0);
+ legacy_byte_alignment, false, 0);
if (fail) {
av1_denoiser_free(denoiser);
return 1;
@@ -589,7 +589,7 @@ int av1_denoiser_alloc(AV1_COMMON *cm, struct SVC *svc, AV1_DENOISER *denoiser,
fail = aom_alloc_frame_buffer(
&denoiser->mc_running_avg_y[layer], denoise_width, denoise_height, ssx,
- ssy, use_highbitdepth, border, legacy_byte_alignment, 0, 0);
+ ssy, use_highbitdepth, border, legacy_byte_alignment, false, 0);
if (fail) {
av1_denoiser_free(denoiser);
return 1;
@@ -600,7 +600,7 @@ int av1_denoiser_alloc(AV1_COMMON *cm, struct SVC *svc, AV1_DENOISER *denoiser,
// layer.
fail = aom_alloc_frame_buffer(&denoiser->last_source, width, height, ssx, ssy,
use_highbitdepth, border, legacy_byte_alignment,
- 0, 0);
+ false, 0);
if (fail) {
av1_denoiser_free(denoiser);
return 1;
diff --git a/third_party/aom/av1/encoder/bitstream.c b/third_party/aom/av1/encoder/bitstream.c
index 219784fedf..9981871147 100644
--- a/third_party/aom/av1/encoder/bitstream.c
+++ b/third_party/aom/av1/encoder/bitstream.c
@@ -3391,8 +3391,8 @@ int av1_write_uleb_obu_size(size_t obu_header_size, size_t obu_payload_size,
return AOM_CODEC_OK;
}
-size_t av1_obu_memmove(size_t obu_header_size, size_t obu_payload_size,
- uint8_t *data) {
+static size_t obu_memmove(size_t obu_header_size, size_t obu_payload_size,
+ uint8_t *data) {
const size_t length_field_size = aom_uleb_size_in_bytes(obu_payload_size);
const size_t move_dst_offset = length_field_size + obu_header_size;
const size_t move_src_offset = obu_header_size;
@@ -3581,7 +3581,7 @@ static void write_large_scale_tile_obu_size(
*total_size += lst_obu->tg_hdr_size;
const uint32_t obu_payload_size = *total_size - lst_obu->tg_hdr_size;
const size_t length_field_size =
- av1_obu_memmove(lst_obu->tg_hdr_size, obu_payload_size, dst);
+ obu_memmove(lst_obu->tg_hdr_size, obu_payload_size, dst);
if (av1_write_uleb_obu_size(lst_obu->tg_hdr_size, obu_payload_size, dst) !=
AOM_CODEC_OK)
assert(0);
@@ -3806,7 +3806,7 @@ void av1_write_last_tile_info(
const uint32_t obu_payload_size =
(uint32_t)(*curr_tg_data_size) - obu_header_size;
const size_t length_field_size =
- av1_obu_memmove(obu_header_size, obu_payload_size, curr_tg_start);
+ obu_memmove(obu_header_size, obu_payload_size, curr_tg_start);
if (av1_write_uleb_obu_size(obu_header_size, obu_payload_size,
curr_tg_start) != AOM_CODEC_OK) {
assert(0);
@@ -4015,8 +4015,8 @@ static void write_tile_obu_size(AV1_COMP *const cpi, uint8_t *const dst,
// to pack the smaller bitstream of such frames. This function computes the
// number of required number of workers based on setup time overhead and job
// dispatch time overhead for given tiles and available workers.
-int calc_pack_bs_mt_workers(const TileDataEnc *tile_data, int num_tiles,
- int avail_workers, bool pack_bs_mt_enabled) {
+static int calc_pack_bs_mt_workers(const TileDataEnc *tile_data, int num_tiles,
+ int avail_workers, bool pack_bs_mt_enabled) {
if (!pack_bs_mt_enabled) return 1;
uint64_t frame_abs_sum_level = 0;
@@ -4141,8 +4141,7 @@ static size_t av1_write_metadata_array(AV1_COMP *const cpi, uint8_t *dst) {
OBU_METADATA, 0, dst);
obu_payload_size =
av1_write_metadata_obu(current_metadata, dst + obu_header_size);
- length_field_size =
- av1_obu_memmove(obu_header_size, obu_payload_size, dst);
+ length_field_size = obu_memmove(obu_header_size, obu_payload_size, dst);
if (av1_write_uleb_obu_size(obu_header_size, obu_payload_size, dst) ==
AOM_CODEC_OK) {
const size_t obu_size = obu_header_size + obu_payload_size;
@@ -4192,7 +4191,7 @@ int av1_pack_bitstream(AV1_COMP *const cpi, uint8_t *dst, size_t *size,
obu_payload_size =
av1_write_sequence_header_obu(cm->seq_params, data + obu_header_size);
const size_t length_field_size =
- av1_obu_memmove(obu_header_size, obu_payload_size, data);
+ obu_memmove(obu_header_size, obu_payload_size, data);
if (av1_write_uleb_obu_size(obu_header_size, obu_payload_size, data) !=
AOM_CODEC_OK) {
return AOM_CODEC_ERROR;
@@ -4217,7 +4216,7 @@ int av1_pack_bitstream(AV1_COMP *const cpi, uint8_t *dst, size_t *size,
obu_payload_size = write_frame_header_obu(cpi, &cpi->td.mb.e_mbd, &saved_wb,
data + obu_header_size, 1);
- length_field = av1_obu_memmove(obu_header_size, obu_payload_size, data);
+ length_field = obu_memmove(obu_header_size, obu_payload_size, data);
if (av1_write_uleb_obu_size(obu_header_size, obu_payload_size, data) !=
AOM_CODEC_OK) {
return AOM_CODEC_ERROR;
diff --git a/third_party/aom/av1/encoder/bitstream.h b/third_party/aom/av1/encoder/bitstream.h
index 12e8a630db..d037039593 100644
--- a/third_party/aom/av1/encoder/bitstream.h
+++ b/third_party/aom/av1/encoder/bitstream.h
@@ -21,6 +21,7 @@ extern "C" {
#include "av1/common/enums.h"
#include "av1/encoder/level.h"
#include "aom_dsp/bitwriter.h"
+#include "aom_util/aom_pthread.h"
struct aom_write_bit_buffer;
struct AV1_COMP;
diff --git a/third_party/aom/av1/encoder/block.h b/third_party/aom/av1/encoder/block.h
index 33d2d8c2a0..1baf3f942e 100644
--- a/third_party/aom/av1/encoder/block.h
+++ b/third_party/aom/av1/encoder/block.h
@@ -1348,6 +1348,9 @@ typedef struct macroblock {
//! Motion vector from superblock MV derived from int_pro_motion() in
// the variance_partitioning.
int_mv sb_me_mv;
+ //! Flag to indicate if a fixed partition should be used, only if the
+ // speed feature rt_sf->use_fast_fixed_part is enabled.
+ int sb_force_fixed_part;
//! SSE of the current predictor.
unsigned int pred_sse[REF_FRAMES];
//! Prediction for ML based partition.
diff --git a/third_party/aom/av1/encoder/cnn.c b/third_party/aom/av1/encoder/cnn.c
index 598b362753..b019ace685 100644
--- a/third_party/aom/av1/encoder/cnn.c
+++ b/third_party/aom/av1/encoder/cnn.c
@@ -138,14 +138,16 @@ static bool concat_tensor(const TENSOR *src, TENSOR *dst) {
return true;
}
-int check_tensor_equal_dims(TENSOR *t1, TENSOR *t2) {
+#ifndef NDEBUG
+static int check_tensor_equal_dims(TENSOR *t1, TENSOR *t2) {
return (t1->width == t2->width && t1->height == t2->height);
}
-int check_tensor_equal_size(TENSOR *t1, TENSOR *t2) {
+static int check_tensor_equal_size(TENSOR *t1, TENSOR *t2) {
return (t1->channels == t2->channels && t1->width == t2->width &&
t1->height == t2->height);
}
+#endif // NDEBUG
void av1_find_cnn_layer_output_size(int in_width, int in_height,
const CNN_LAYER_CONFIG *layer_config,
@@ -189,8 +191,8 @@ void av1_find_cnn_layer_output_size(int in_width, int in_height,
}
}
-void find_cnn_out_channels(const CNN_LAYER_CONFIG *layer_config,
- int channels_per_branch[]) {
+static void find_cnn_out_channels(const CNN_LAYER_CONFIG *layer_config,
+ int channels_per_branch[]) {
int branch = layer_config->branch;
const CNN_BRANCH_CONFIG *branch_config = &layer_config->branch_config;
for (int b = 0; b < CNN_MAX_BRANCHES; ++b) {
diff --git a/third_party/aom/av1/encoder/encode_strategy.c b/third_party/aom/av1/encoder/encode_strategy.c
index 35ca83c3f4..db77dc0e3c 100644
--- a/third_party/aom/av1/encoder/encode_strategy.c
+++ b/third_party/aom/av1/encoder/encode_strategy.c
@@ -712,20 +712,6 @@ int av1_get_refresh_frame_flags(
}
#if !CONFIG_REALTIME_ONLY
-void setup_mi(AV1_COMP *const cpi, YV12_BUFFER_CONFIG *src) {
- AV1_COMMON *const cm = &cpi->common;
- const int num_planes = av1_num_planes(cm);
- MACROBLOCK *const x = &cpi->td.mb;
- MACROBLOCKD *const xd = &x->e_mbd;
-
- av1_setup_src_planes(x, src, 0, 0, num_planes, cm->seq_params->sb_size);
-
- av1_setup_block_planes(xd, cm->seq_params->subsampling_x,
- cm->seq_params->subsampling_y, num_planes);
-
- set_mi_offsets(&cm->mi_params, xd, 0, 0);
-}
-
// Apply temporal filtering to source frames and encode the filtered frame.
// If the current frame does not require filtering, this function is identical
// to av1_encode() except that tpl is not performed.
@@ -819,7 +805,7 @@ static int denoise_and_encode(AV1_COMP *const cpi, uint8_t *const dest,
oxcf->frm_dim_cfg.height, cm->seq_params->subsampling_x,
cm->seq_params->subsampling_y, cm->seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels, cm->features.byte_alignment, NULL, NULL,
- NULL, cpi->image_pyramid_levels, 0);
+ NULL, cpi->alloc_pyramid, 0);
if (ret)
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate tf_buf_second_arf");
@@ -923,7 +909,7 @@ static int denoise_and_encode(AV1_COMP *const cpi, uint8_t *const dest,
if (apply_filtering && is_psnr_calc_enabled(cpi)) {
cpi->source = av1_realloc_and_scale_if_required(
cm, source_buffer, &cpi->scaled_source, cm->features.interp_filter, 0,
- false, true, cpi->oxcf.border_in_pixels, cpi->image_pyramid_levels);
+ false, true, cpi->oxcf.border_in_pixels, cpi->alloc_pyramid);
cpi->unscaled_source = source_buffer;
}
#if CONFIG_COLLECT_COMPONENT_TIMING
@@ -1702,8 +1688,7 @@ int av1_encode_strategy(AV1_COMP *const cpi, size_t *const size,
// This is used in rtc temporal filter case. Use true source in the PSNR
// calculation.
- if (is_psnr_calc_enabled(cpi) && cpi->sf.rt_sf.use_rtc_tf &&
- cpi->common.current_frame.frame_type != KEY_FRAME) {
+ if (is_psnr_calc_enabled(cpi) && cpi->sf.rt_sf.use_rtc_tf) {
assert(cpi->orig_source.buffer_alloc_sz > 0);
cpi->source = &cpi->orig_source;
}
@@ -1758,9 +1743,9 @@ int av1_encode_strategy(AV1_COMP *const cpi, size_t *const size,
cpi->svc.temporal_layer_id == 0 &&
cpi->unscaled_source->y_width == cpi->svc.source_last_TL0.y_width &&
cpi->unscaled_source->y_height == cpi->svc.source_last_TL0.y_height) {
- aom_yv12_copy_y(cpi->unscaled_source, &cpi->svc.source_last_TL0);
- aom_yv12_copy_u(cpi->unscaled_source, &cpi->svc.source_last_TL0);
- aom_yv12_copy_v(cpi->unscaled_source, &cpi->svc.source_last_TL0);
+ aom_yv12_copy_y(cpi->unscaled_source, &cpi->svc.source_last_TL0, 1);
+ aom_yv12_copy_u(cpi->unscaled_source, &cpi->svc.source_last_TL0, 1);
+ aom_yv12_copy_v(cpi->unscaled_source, &cpi->svc.source_last_TL0, 1);
}
return AOM_CODEC_OK;
diff --git a/third_party/aom/av1/encoder/encodeframe.c b/third_party/aom/av1/encoder/encodeframe.c
index e2213a8355..a9214f77c2 100644
--- a/third_party/aom/av1/encoder/encodeframe.c
+++ b/third_party/aom/av1/encoder/encodeframe.c
@@ -23,7 +23,7 @@
#include "aom_dsp/binary_codes_writer.h"
#include "aom_ports/mem.h"
#include "aom_ports/aom_timer.h"
-
+#include "aom_util/aom_pthread.h"
#if CONFIG_MISMATCH_DEBUG
#include "aom_util/debug_util.h"
#endif // CONFIG_MISMATCH_DEBUG
@@ -536,8 +536,8 @@ static AOM_INLINE void encode_nonrd_sb(AV1_COMP *cpi, ThreadData *td,
#endif
// Set the partition
if (sf->part_sf.partition_search_type == FIXED_PARTITION || seg_skip ||
- (sf->rt_sf.use_fast_fixed_part &&
- x->content_state_sb.source_sad_nonrd < kMedSad)) {
+ (sf->rt_sf.use_fast_fixed_part && x->sb_force_fixed_part == 1 &&
+ !frame_is_intra_only(cm))) {
// set a fixed-size partition
av1_set_offsets(cpi, tile_info, x, mi_row, mi_col, sb_size);
BLOCK_SIZE bsize_select = sf->part_sf.fixed_partition_size;
@@ -1054,8 +1054,13 @@ static AOM_INLINE bool is_calc_src_content_needed(AV1_COMP *cpi,
// The threshold is determined based on kLowSad and kHighSad threshold and
// test results.
- const uint64_t thresh_low = 15000;
- const uint64_t thresh_high = 40000;
+ uint64_t thresh_low = 15000;
+ uint64_t thresh_high = 40000;
+
+ if (cpi->sf.rt_sf.increase_source_sad_thresh) {
+ thresh_low = thresh_low << 1;
+ thresh_high = thresh_high << 1;
+ }
if (avg_64x64_blk_sad > thresh_low && avg_64x64_blk_sad < thresh_high) {
do_calc_src_content = false;
@@ -1203,6 +1208,7 @@ static AOM_INLINE void encode_sb_row(AV1_COMP *cpi, ThreadData *td,
x->sb_me_block = 0;
x->sb_me_partition = 0;
x->sb_me_mv.as_int = 0;
+ x->sb_force_fixed_part = 1;
if (cpi->oxcf.mode == ALLINTRA) {
x->intra_sb_rdmult_modifier = 128;
@@ -1231,7 +1237,7 @@ static AOM_INLINE void encode_sb_row(AV1_COMP *cpi, ThreadData *td,
// Grade the temporal variation of the sb, the grade will be used to decide
// fast mode search strategy for coding blocks
- grade_source_content_sb(cpi, x, tile_data, mi_row, mi_col);
+ if (!seg_skip) grade_source_content_sb(cpi, x, tile_data, mi_row, mi_col);
// encode the superblock
if (use_nonrd_mode) {
@@ -2337,7 +2343,7 @@ void av1_encode_frame(AV1_COMP *cpi) {
// a source or a ref frame should have an image pyramid allocated.
// Check here so that issues can be caught early in debug mode
#if !defined(NDEBUG) && !CONFIG_REALTIME_ONLY
- if (cpi->image_pyramid_levels > 0) {
+ if (cpi->alloc_pyramid) {
assert(cpi->source->y_pyramid);
for (int ref_frame = LAST_FRAME; ref_frame <= ALTREF_FRAME; ++ref_frame) {
const RefCntBuffer *const buf = get_ref_frame_buf(cm, ref_frame);
diff --git a/third_party/aom/av1/encoder/encodeframe_utils.c b/third_party/aom/av1/encoder/encodeframe_utils.c
index 949837184a..a8e4a88396 100644
--- a/third_party/aom/av1/encoder/encodeframe_utils.c
+++ b/third_party/aom/av1/encoder/encodeframe_utils.c
@@ -15,6 +15,7 @@
#include "av1/encoder/encoder.h"
#include "av1/encoder/encodeframe_utils.h"
+#include "av1/encoder/encoder_utils.h"
#include "av1/encoder/rdopt.h"
void av1_set_ssim_rdmult(const AV1_COMP *const cpi, int *errorperbit,
@@ -306,6 +307,7 @@ void av1_update_state(const AV1_COMP *const cpi, ThreadData *td,
// Else for cyclic refresh mode update the segment map, set the segment id
// and then update the quantizer.
if (cpi->oxcf.q_cfg.aq_mode == CYCLIC_REFRESH_AQ &&
+ mi_addr->segment_id != AM_SEGMENT_ID_INACTIVE &&
!cpi->rc.rtc_external_ratectrl) {
av1_cyclic_refresh_update_segment(cpi, x, mi_row, mi_col, bsize,
ctx->rd_stats.rate, ctx->rd_stats.dist,
@@ -1431,6 +1433,10 @@ void av1_source_content_sb(AV1_COMP *cpi, MACROBLOCK *x, TileDataEnc *tile_data,
if ((tmp_sse - tmp_variance) < (sum_sq_thresh >> 1))
x->content_state_sb.low_sumdiff = 1;
+ if (tmp_sse > ((avg_source_sse_threshold_high * 7) >> 3) &&
+ !x->content_state_sb.lighting_change && !x->content_state_sb.low_sumdiff)
+ x->sb_force_fixed_part = 0;
+
if (!cpi->sf.rt_sf.use_rtc_tf || cpi->rc.high_source_sad ||
cpi->rc.frame_source_sad > 20000 || cpi->svc.number_spatial_layers > 1)
return;
diff --git a/third_party/aom/av1/encoder/encoder.c b/third_party/aom/av1/encoder/encoder.c
index fe053af5cc..1ddbfda08b 100644
--- a/third_party/aom/av1/encoder/encoder.c
+++ b/third_party/aom/av1/encoder/encoder.c
@@ -35,6 +35,7 @@
#include "aom_ports/aom_timer.h"
#include "aom_ports/mem.h"
#include "aom_scale/aom_scale.h"
+#include "aom_util/aom_pthread.h"
#if CONFIG_BITSTREAM_DEBUG
#include "aom_util/debug_util.h"
#endif // CONFIG_BITSTREAM_DEBUG
@@ -152,24 +153,33 @@ int av1_set_active_map(AV1_COMP *cpi, unsigned char *new_map_16x16, int rows,
unsigned char *const active_map_4x4 = cpi->active_map.map;
const int mi_rows = mi_params->mi_rows;
const int mi_cols = mi_params->mi_cols;
- const int row_scale = mi_size_high_log2[BLOCK_16X16];
- const int col_scale = mi_size_wide_log2[BLOCK_16X16];
cpi->active_map.update = 0;
- assert(mi_rows % 2 == 0);
- assert(mi_cols % 2 == 0);
+ cpi->rc.percent_blocks_inactive = 0;
+ assert(mi_rows % 2 == 0 && mi_rows > 0);
+ assert(mi_cols % 2 == 0 && mi_cols > 0);
if (new_map_16x16) {
- for (int r = 0; r < (mi_rows >> row_scale); ++r) {
- for (int c = 0; c < (mi_cols >> col_scale); ++c) {
- const uint8_t val = new_map_16x16[r * cols + c]
+ int num_samples = 0;
+ int num_blocks_inactive = 0;
+ for (int r = 0; r < mi_rows; r += 4) {
+ for (int c = 0; c < mi_cols; c += 4) {
+ const uint8_t val = new_map_16x16[(r >> 2) * cols + (c >> 2)]
? AM_SEGMENT_ID_ACTIVE
: AM_SEGMENT_ID_INACTIVE;
- active_map_4x4[(2 * r + 0) * mi_cols + (c + 0)] = val;
- active_map_4x4[(2 * r + 0) * mi_cols + (c + 1)] = val;
- active_map_4x4[(2 * r + 1) * mi_cols + (c + 0)] = val;
- active_map_4x4[(2 * r + 1) * mi_cols + (c + 1)] = val;
+ num_samples++;
+ if (val == AM_SEGMENT_ID_INACTIVE) num_blocks_inactive++;
+ const int row_max = AOMMIN(4, mi_rows - r);
+ const int col_max = AOMMIN(4, mi_cols - c);
+ for (int x = 0; x < row_max; ++x) {
+ for (int y = 0; y < col_max; ++y) {
+ active_map_4x4[(r + x) * mi_cols + (c + y)] = val;
+ }
+ }
}
}
cpi->active_map.enabled = 1;
+ cpi->active_map.update = 1;
+ cpi->rc.percent_blocks_inactive =
+ (num_blocks_inactive * 100) / num_samples;
}
return 0;
}
@@ -943,14 +953,9 @@ void av1_change_config(struct AV1_COMP *cpi, const AV1EncoderConfig *oxcf,
#if CONFIG_REALTIME_ONLY
assert(!oxcf->tool_cfg.enable_global_motion);
- cpi->image_pyramid_levels = 0;
+ cpi->alloc_pyramid = false;
#else
- if (oxcf->tool_cfg.enable_global_motion) {
- cpi->image_pyramid_levels =
- global_motion_pyr_levels[default_global_motion_method];
- } else {
- cpi->image_pyramid_levels = 0;
- }
+ cpi->alloc_pyramid = oxcf->tool_cfg.enable_global_motion;
#endif // CONFIG_REALTIME_ONLY
}
@@ -2208,7 +2213,7 @@ void av1_set_frame_size(AV1_COMP *cpi, int width, int height) {
&cm->cur_frame->buf, cm->width, cm->height, seq_params->subsampling_x,
seq_params->subsampling_y, seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels, cm->features.byte_alignment, NULL, NULL,
- NULL, cpi->image_pyramid_levels, 0))
+ NULL, cpi->alloc_pyramid, 0))
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate frame buffer");
@@ -2389,7 +2394,10 @@ static void loopfilter_frame(AV1_COMP *cpi, AV1_COMMON *cm) {
const int use_loopfilter =
is_loopfilter_used(cm) && !cpi->mt_info.pipeline_lpf_mt_with_enc;
- const int use_cdef = is_cdef_used(cm);
+ const int use_cdef =
+ is_cdef_used(cm) && (!cpi->active_map.enabled ||
+ cpi->rc.percent_blocks_inactive <=
+ cpi->sf.rt_sf.thresh_active_maps_skip_lf_cdef);
const int use_superres = av1_superres_scaled(cm);
const int use_restoration = is_restoration_used(cm);
@@ -2498,7 +2506,8 @@ static int encode_without_recode(AV1_COMP *cpi) {
&cpi->svc.source_last_TL0, cpi->oxcf.frm_dim_cfg.width,
cpi->oxcf.frm_dim_cfg.height, seq_params->subsampling_x,
seq_params->subsampling_y, seq_params->use_highbitdepth,
- cpi->oxcf.border_in_pixels, cm->features.byte_alignment, 0, 0)) {
+ cpi->oxcf.border_in_pixels, cm->features.byte_alignment, false,
+ 0)) {
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate buffer for source_last_TL0");
}
@@ -2547,7 +2556,7 @@ static int encode_without_recode(AV1_COMP *cpi) {
cpi->source = av1_realloc_and_scale_if_required(
cm, unscaled, &cpi->scaled_source, filter_scaler, phase_scaler, true,
- false, cpi->oxcf.border_in_pixels, cpi->image_pyramid_levels);
+ false, cpi->oxcf.border_in_pixels, cpi->alloc_pyramid);
if (frame_is_intra_only(cm) || resize_pending != 0) {
const int current_size =
(cm->mi_params.mi_rows * cm->mi_params.mi_cols) >> 2;
@@ -2570,7 +2579,7 @@ static int encode_without_recode(AV1_COMP *cpi) {
cpi->last_source = av1_realloc_and_scale_if_required(
cm, cpi->unscaled_last_source, &cpi->scaled_last_source, filter_scaler,
phase_scaler, true, false, cpi->oxcf.border_in_pixels,
- cpi->image_pyramid_levels);
+ cpi->alloc_pyramid);
}
if (cpi->sf.rt_sf.use_temporal_noise_estimate) {
@@ -2647,12 +2656,8 @@ static int encode_without_recode(AV1_COMP *cpi) {
av1_setup_frame(cpi);
}
}
-
- if (q_cfg->aq_mode == CYCLIC_REFRESH_AQ) {
- suppress_active_map(cpi);
- av1_cyclic_refresh_setup(cpi);
- }
av1_apply_active_map(cpi);
+ if (q_cfg->aq_mode == CYCLIC_REFRESH_AQ) av1_cyclic_refresh_setup(cpi);
if (cm->seg.enabled) {
if (!cm->seg.update_data && cm->prev_frame) {
segfeatures_copy(&cm->seg, &cm->prev_frame->seg);
@@ -2667,26 +2672,26 @@ static int encode_without_recode(AV1_COMP *cpi) {
cm->cur_frame->seg.enabled = cm->seg.enabled;
// This is for rtc temporal filtering case.
- if (is_psnr_calc_enabled(cpi) && cpi->sf.rt_sf.use_rtc_tf &&
- cm->current_frame.frame_type != KEY_FRAME) {
+ if (is_psnr_calc_enabled(cpi) && cpi->sf.rt_sf.use_rtc_tf) {
const SequenceHeader *seq_params = cm->seq_params;
if (cpi->orig_source.buffer_alloc_sz == 0 ||
- cpi->last_source->y_width != cpi->source->y_width ||
- cpi->last_source->y_height != cpi->source->y_height) {
+ cpi->rc.prev_coded_width != cpi->oxcf.frm_dim_cfg.width ||
+ cpi->rc.prev_coded_height != cpi->oxcf.frm_dim_cfg.height) {
// Allocate a source buffer to store the true source for psnr calculation.
if (aom_alloc_frame_buffer(
&cpi->orig_source, cpi->oxcf.frm_dim_cfg.width,
cpi->oxcf.frm_dim_cfg.height, seq_params->subsampling_x,
seq_params->subsampling_y, seq_params->use_highbitdepth,
- cpi->oxcf.border_in_pixels, cm->features.byte_alignment, 0, 0))
+ cpi->oxcf.border_in_pixels, cm->features.byte_alignment, false,
+ 0))
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate scaled buffer");
}
- aom_yv12_copy_y(cpi->source, &cpi->orig_source);
- aom_yv12_copy_u(cpi->source, &cpi->orig_source);
- aom_yv12_copy_v(cpi->source, &cpi->orig_source);
+ aom_yv12_copy_y(cpi->source, &cpi->orig_source, 1);
+ aom_yv12_copy_u(cpi->source, &cpi->orig_source, 1);
+ aom_yv12_copy_v(cpi->source, &cpi->orig_source, 1);
}
#if CONFIG_COLLECT_COMPONENT_TIMING
@@ -2725,9 +2730,9 @@ static int encode_without_recode(AV1_COMP *cpi) {
(cm->width != cpi->unscaled_source->y_crop_width ||
cm->height != cpi->unscaled_source->y_crop_height)) {
cpi->scaled_last_source_available = 1;
- aom_yv12_copy_y(&cpi->scaled_source, &cpi->scaled_last_source);
- aom_yv12_copy_u(&cpi->scaled_source, &cpi->scaled_last_source);
- aom_yv12_copy_v(&cpi->scaled_source, &cpi->scaled_last_source);
+ aom_yv12_copy_y(&cpi->scaled_source, &cpi->scaled_last_source, 1);
+ aom_yv12_copy_u(&cpi->scaled_source, &cpi->scaled_last_source, 1);
+ aom_yv12_copy_v(&cpi->scaled_source, &cpi->scaled_last_source, 1);
}
#if CONFIG_COLLECT_COMPONENT_TIMING
@@ -2846,7 +2851,7 @@ static int encode_with_recode_loop(AV1_COMP *cpi, size_t *size, uint8_t *dest) {
}
cpi->source = av1_realloc_and_scale_if_required(
cm, cpi->unscaled_source, &cpi->scaled_source, EIGHTTAP_REGULAR, 0,
- false, false, cpi->oxcf.border_in_pixels, cpi->image_pyramid_levels);
+ false, false, cpi->oxcf.border_in_pixels, cpi->alloc_pyramid);
#if CONFIG_TUNE_BUTTERAUGLI
if (oxcf->tune_cfg.tuning == AOM_TUNE_BUTTERAUGLI) {
@@ -2866,7 +2871,7 @@ static int encode_with_recode_loop(AV1_COMP *cpi, size_t *size, uint8_t *dest) {
cpi->last_source = av1_realloc_and_scale_if_required(
cm, cpi->unscaled_last_source, &cpi->scaled_last_source,
EIGHTTAP_REGULAR, 0, false, false, cpi->oxcf.border_in_pixels,
- cpi->image_pyramid_levels);
+ cpi->alloc_pyramid);
}
int scale_references = 0;
@@ -4042,7 +4047,7 @@ int av1_encode(AV1_COMP *const cpi, uint8_t *const dest,
}
#if CONFIG_DENOISE
-static int apply_denoise_2d(AV1_COMP *cpi, YV12_BUFFER_CONFIG *sd,
+static int apply_denoise_2d(AV1_COMP *cpi, const YV12_BUFFER_CONFIG *sd,
int block_size, float noise_level,
int64_t time_stamp, int64_t end_time) {
AV1_COMMON *const cm = &cpi->common;
@@ -4077,7 +4082,7 @@ static int apply_denoise_2d(AV1_COMP *cpi, YV12_BUFFER_CONFIG *sd,
#endif
int av1_receive_raw_frame(AV1_COMP *cpi, aom_enc_frame_flags_t frame_flags,
- YV12_BUFFER_CONFIG *sd, int64_t time_stamp,
+ const YV12_BUFFER_CONFIG *sd, int64_t time_stamp,
int64_t end_time) {
AV1_COMMON *const cm = &cpi->common;
const SequenceHeader *const seq_params = cm->seq_params;
@@ -4139,8 +4144,7 @@ int av1_receive_raw_frame(AV1_COMP *cpi, aom_enc_frame_flags_t frame_flags,
#endif // CONFIG_DENOISE
if (av1_lookahead_push(cpi->ppi->lookahead, sd, time_stamp, end_time,
- use_highbitdepth, cpi->image_pyramid_levels,
- frame_flags)) {
+ use_highbitdepth, cpi->alloc_pyramid, frame_flags)) {
aom_set_error(cm->error, AOM_CODEC_ERROR, "av1_lookahead_push() failed");
res = -1;
}
diff --git a/third_party/aom/av1/encoder/encoder.h b/third_party/aom/av1/encoder/encoder.h
index e87ab9be1f..4de5d426ce 100644
--- a/third_party/aom/av1/encoder/encoder.h
+++ b/third_party/aom/av1/encoder/encoder.h
@@ -21,6 +21,7 @@
#include "config/aom_config.h"
#include "aom/aomcx.h"
+#include "aom_util/aom_pthread.h"
#include "av1/common/alloccommon.h"
#include "av1/common/av1_common_int.h"
@@ -3631,10 +3632,10 @@ typedef struct AV1_COMP {
unsigned int zeromv_skip_thresh_exit_part[BLOCK_SIZES_ALL];
/*!
- * Number of downsampling pyramid levels to allocate for each frame
+ * Should we allocate a downsampling pyramid for each frame buffer?
* This is currently only used for global motion
*/
- int image_pyramid_levels;
+ bool alloc_pyramid;
#if CONFIG_SALIENCY_MAP
/*!
@@ -3808,7 +3809,7 @@ int av1_init_parallel_frame_context(const AV1_COMP_DATA *const first_cpi_data,
* copy of the pointer.
*/
int av1_receive_raw_frame(AV1_COMP *cpi, aom_enc_frame_flags_t frame_flags,
- YV12_BUFFER_CONFIG *sd, int64_t time_stamp,
+ const YV12_BUFFER_CONFIG *sd, int64_t time_stamp,
int64_t end_time_stamp);
/*!\brief Encode a frame
@@ -4310,7 +4311,7 @@ static AOM_INLINE int is_psnr_calc_enabled(const AV1_COMP *cpi) {
const AV1_COMMON *const cm = &cpi->common;
return cpi->ppi->b_calculate_psnr && !is_stat_generation_stage(cpi) &&
- cm->show_frame;
+ cm->show_frame && !cpi->is_dropped_frame;
}
static INLINE int is_frame_resize_pending(const AV1_COMP *const cpi) {
diff --git a/third_party/aom/av1/encoder/encoder_alloc.h b/third_party/aom/av1/encoder/encoder_alloc.h
index ce48496d48..f24d4b0a10 100644
--- a/third_party/aom/av1/encoder/encoder_alloc.h
+++ b/third_party/aom/av1/encoder/encoder_alloc.h
@@ -439,8 +439,7 @@ static AOM_INLINE YV12_BUFFER_CONFIG *realloc_and_scale_source(
&cpi->scaled_source, scaled_width, scaled_height,
cm->seq_params->subsampling_x, cm->seq_params->subsampling_y,
cm->seq_params->use_highbitdepth, AOM_BORDER_IN_PIXELS,
- cm->features.byte_alignment, NULL, NULL, NULL,
- cpi->image_pyramid_levels, 0))
+ cm->features.byte_alignment, NULL, NULL, NULL, cpi->alloc_pyramid, 0))
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to reallocate scaled source buffer");
assert(cpi->scaled_source.y_crop_width == scaled_width);
diff --git a/third_party/aom/av1/encoder/encoder_utils.c b/third_party/aom/av1/encoder/encoder_utils.c
index c35873d207..1f81a530c9 100644
--- a/third_party/aom/av1/encoder/encoder_utils.c
+++ b/third_party/aom/av1/encoder/encoder_utils.c
@@ -9,8 +9,11 @@
* PATENTS file, you can obtain it at www.aomedia.org/license/patent.
*/
+#include <string.h>
+
#include "aom/aomcx.h"
+#include "av1/common/av1_common_int.h"
#include "av1/encoder/bitstream.h"
#include "av1/encoder/encodeframe.h"
#include "av1/encoder/encoder.h"
@@ -421,11 +424,13 @@ void av1_apply_active_map(AV1_COMP *cpi) {
struct segmentation *const seg = &cpi->common.seg;
unsigned char *const seg_map = cpi->enc_seg.map;
const unsigned char *const active_map = cpi->active_map.map;
- int i;
assert(AM_SEGMENT_ID_ACTIVE == CR_SEGMENT_ID_BASE);
- if (frame_is_intra_only(&cpi->common)) {
+ // Disable the active_maps on intra_only frames or if the
+ // input map for the current frame has no inactive blocks.
+ if (frame_is_intra_only(&cpi->common) ||
+ cpi->rc.percent_blocks_inactive == 0) {
cpi->active_map.enabled = 0;
cpi->active_map.update = 1;
}
@@ -434,8 +439,7 @@ void av1_apply_active_map(AV1_COMP *cpi) {
if (cpi->active_map.enabled) {
const int num_mis =
cpi->common.mi_params.mi_rows * cpi->common.mi_params.mi_cols;
- for (i = 0; i < num_mis; ++i)
- if (seg_map[i] == AM_SEGMENT_ID_ACTIVE) seg_map[i] = active_map[i];
+ memcpy(seg_map, active_map, sizeof(active_map[0]) * num_mis);
av1_enable_segmentation(seg);
av1_enable_segfeature(seg, AM_SEGMENT_ID_INACTIVE, SEG_LVL_SKIP);
av1_enable_segfeature(seg, AM_SEGMENT_ID_INACTIVE, SEG_LVL_ALT_LF_Y_H);
@@ -725,7 +729,7 @@ void av1_scale_references(AV1_COMP *cpi, const InterpFilter filter,
RefCntBuffer *ref_fb = get_ref_frame_buf(cm, ref_frame);
if (aom_yv12_realloc_with_new_border(
&ref_fb->buf, AOM_BORDER_IN_PIXELS,
- cm->features.byte_alignment, cpi->image_pyramid_levels,
+ cm->features.byte_alignment, cpi->alloc_pyramid,
num_planes) != 0) {
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate frame buffer");
@@ -749,7 +753,7 @@ void av1_scale_references(AV1_COMP *cpi, const InterpFilter filter,
&new_fb->buf, cm->width, cm->height,
cm->seq_params->subsampling_x, cm->seq_params->subsampling_y,
cm->seq_params->use_highbitdepth, AOM_BORDER_IN_PIXELS,
- cm->features.byte_alignment, NULL, NULL, NULL, 0, 0)) {
+ cm->features.byte_alignment, NULL, NULL, NULL, false, 0)) {
if (force_scaling) {
// Release the reference acquired in the get_free_fb() call above.
--new_fb->ref_count;
@@ -1087,12 +1091,12 @@ void av1_determine_sc_tools_with_encoding(AV1_COMP *cpi, const int q_orig) {
cpi->source = av1_realloc_and_scale_if_required(
cm, cpi->unscaled_source, &cpi->scaled_source, cm->features.interp_filter,
- 0, false, false, cpi->oxcf.border_in_pixels, cpi->image_pyramid_levels);
+ 0, false, false, cpi->oxcf.border_in_pixels, cpi->alloc_pyramid);
if (cpi->unscaled_last_source != NULL) {
cpi->last_source = av1_realloc_and_scale_if_required(
cm, cpi->unscaled_last_source, &cpi->scaled_last_source,
cm->features.interp_filter, 0, false, false, cpi->oxcf.border_in_pixels,
- cpi->image_pyramid_levels);
+ cpi->alloc_pyramid);
}
av1_setup_frame(cpi);
diff --git a/third_party/aom/av1/encoder/encodetxb.c b/third_party/aom/av1/encoder/encodetxb.c
index 5fe2a497c7..701c5489fe 100644
--- a/third_party/aom/av1/encoder/encodetxb.c
+++ b/third_party/aom/av1/encoder/encodetxb.c
@@ -134,14 +134,14 @@ int av1_get_eob_pos_token(const int eob, int *const extra) {
}
#if CONFIG_ENTROPY_STATS
-void av1_update_eob_context(int cdf_idx, int eob, TX_SIZE tx_size,
- TX_CLASS tx_class, PLANE_TYPE plane,
- FRAME_CONTEXT *ec_ctx, FRAME_COUNTS *counts,
- uint8_t allow_update_cdf) {
+static void update_eob_context(int cdf_idx, int eob, TX_SIZE tx_size,
+ TX_CLASS tx_class, PLANE_TYPE plane,
+ FRAME_CONTEXT *ec_ctx, FRAME_COUNTS *counts,
+ uint8_t allow_update_cdf) {
#else
-void av1_update_eob_context(int eob, TX_SIZE tx_size, TX_CLASS tx_class,
- PLANE_TYPE plane, FRAME_CONTEXT *ec_ctx,
- uint8_t allow_update_cdf) {
+static void update_eob_context(int eob, TX_SIZE tx_size, TX_CLASS tx_class,
+ PLANE_TYPE plane, FRAME_CONTEXT *ec_ctx,
+ uint8_t allow_update_cdf) {
#endif
int eob_extra;
const int eob_pt = av1_get_eob_pos_token(eob, &eob_extra);
@@ -623,11 +623,11 @@ void av1_update_and_record_txb_context(int plane, int block, int blk_row,
td->rd_counts.tx_type_used[tx_size][tx_type]++;
#if CONFIG_ENTROPY_STATS
- av1_update_eob_context(cdf_idx, eob, tx_size, tx_class, plane_type, ec_ctx,
- td->counts, allow_update_cdf);
+ update_eob_context(cdf_idx, eob, tx_size, tx_class, plane_type, ec_ctx,
+ td->counts, allow_update_cdf);
#else
- av1_update_eob_context(eob, tx_size, tx_class, plane_type, ec_ctx,
- allow_update_cdf);
+ update_eob_context(eob, tx_size, tx_class, plane_type, ec_ctx,
+ allow_update_cdf);
#endif
DECLARE_ALIGNED(16, int8_t, coeff_contexts[MAX_TX_SQUARE]);
@@ -785,8 +785,8 @@ void av1_record_txb_context(int plane, int block, int blk_row, int blk_col,
#if CONFIG_ENTROPY_STATS
FRAME_CONTEXT *ec_ctx = xd->tile_ctx;
- av1_update_eob_context(cdf_idx, eob, tx_size, tx_class, plane_type, ec_ctx,
- td->counts, 0 /*allow_update_cdf*/);
+ update_eob_context(cdf_idx, eob, tx_size, tx_class, plane_type, ec_ctx,
+ td->counts, 0 /*allow_update_cdf*/);
DECLARE_ALIGNED(16, int8_t, coeff_contexts[MAX_TX_SQUARE]);
av1_get_nz_map_contexts(levels, scan, eob, tx_size, tx_class,
diff --git a/third_party/aom/av1/encoder/ethread.c b/third_party/aom/av1/encoder/ethread.c
index d6a806d504..755535ba51 100644
--- a/third_party/aom/av1/encoder/ethread.c
+++ b/third_party/aom/av1/encoder/ethread.c
@@ -12,6 +12,8 @@
#include <assert.h>
#include <stdbool.h>
+#include "aom_util/aom_pthread.h"
+
#include "av1/common/warped_motion.h"
#include "av1/common/thread_common.h"
@@ -1415,7 +1417,7 @@ static AOM_INLINE void sync_fpmt_workers(AV1_PRIMARY *ppi,
int num_workers = ppi->p_mt_info.p_num_workers;
int had_error = 0;
// Points to error in the earliest display order frame in the parallel set.
- const struct aom_internal_error_info *error;
+ const struct aom_internal_error_info *error = NULL;
// Encoding ends.
for (int i = num_workers - 1; i >= 0; --i) {
@@ -2227,8 +2229,8 @@ void av1_tpl_dealloc(AV1TplRowMultiThreadSync *tpl_sync) {
}
// Allocate memory for tpl row synchronization.
-void av1_tpl_alloc(AV1TplRowMultiThreadSync *tpl_sync, AV1_COMMON *cm,
- int mb_rows) {
+static void av1_tpl_alloc(AV1TplRowMultiThreadSync *tpl_sync, AV1_COMMON *cm,
+ int mb_rows) {
tpl_sync->rows = mb_rows;
#if CONFIG_MULTITHREAD
{
diff --git a/third_party/aom/av1/encoder/firstpass.c b/third_party/aom/av1/encoder/firstpass.c
index e20b6c177e..b94a50714a 100644
--- a/third_party/aom/av1/encoder/firstpass.c
+++ b/third_party/aom/av1/encoder/firstpass.c
@@ -22,6 +22,7 @@
#include "aom_ports/mem.h"
#include "aom_scale/aom_scale.h"
#include "aom_scale/yv12config.h"
+#include "aom_util/aom_pthread.h"
#include "av1/common/entropymv.h"
#include "av1/common/quant_common.h"
diff --git a/third_party/aom/av1/encoder/global_motion.c b/third_party/aom/av1/encoder/global_motion.c
index 73910de121..0ae47809c6 100644
--- a/third_party/aom/av1/encoder/global_motion.c
+++ b/third_party/aom/av1/encoder/global_motion.c
@@ -30,83 +30,6 @@
// Border over which to compute the global motion
#define ERRORADV_BORDER 0
-/* clang-format off */
-// Error metric used for global motion evaluation.
-// For 8-bit input, the pixel error used to index this table will always
-// be between -255 and +255. But for 10- and 12-bit input, we use interpolation
-// which means that we need to support indices of -256 and +256 as well.
-// Therefore, the table is offset so that logical index 0 corresponds to
-// error_measure_lut[256].
-const int error_measure_lut[513] = {
- // pow 0.7
- 16384, 16384, 16339, 16294, 16249, 16204, 16158, 16113,
- 16068, 16022, 15977, 15932, 15886, 15840, 15795, 15749,
- 15703, 15657, 15612, 15566, 15520, 15474, 15427, 15381,
- 15335, 15289, 15242, 15196, 15149, 15103, 15056, 15010,
- 14963, 14916, 14869, 14822, 14775, 14728, 14681, 14634,
- 14587, 14539, 14492, 14445, 14397, 14350, 14302, 14254,
- 14206, 14159, 14111, 14063, 14015, 13967, 13918, 13870,
- 13822, 13773, 13725, 13676, 13628, 13579, 13530, 13481,
- 13432, 13383, 13334, 13285, 13236, 13187, 13137, 13088,
- 13038, 12988, 12939, 12889, 12839, 12789, 12739, 12689,
- 12639, 12588, 12538, 12487, 12437, 12386, 12335, 12285,
- 12234, 12183, 12132, 12080, 12029, 11978, 11926, 11875,
- 11823, 11771, 11719, 11667, 11615, 11563, 11511, 11458,
- 11406, 11353, 11301, 11248, 11195, 11142, 11089, 11036,
- 10982, 10929, 10875, 10822, 10768, 10714, 10660, 10606,
- 10552, 10497, 10443, 10388, 10333, 10279, 10224, 10168,
- 10113, 10058, 10002, 9947, 9891, 9835, 9779, 9723,
- 9666, 9610, 9553, 9497, 9440, 9383, 9326, 9268,
- 9211, 9153, 9095, 9037, 8979, 8921, 8862, 8804,
- 8745, 8686, 8627, 8568, 8508, 8449, 8389, 8329,
- 8269, 8208, 8148, 8087, 8026, 7965, 7903, 7842,
- 7780, 7718, 7656, 7593, 7531, 7468, 7405, 7341,
- 7278, 7214, 7150, 7086, 7021, 6956, 6891, 6826,
- 6760, 6695, 6628, 6562, 6495, 6428, 6361, 6293,
- 6225, 6157, 6089, 6020, 5950, 5881, 5811, 5741,
- 5670, 5599, 5527, 5456, 5383, 5311, 5237, 5164,
- 5090, 5015, 4941, 4865, 4789, 4713, 4636, 4558,
- 4480, 4401, 4322, 4242, 4162, 4080, 3998, 3916,
- 3832, 3748, 3663, 3577, 3490, 3402, 3314, 3224,
- 3133, 3041, 2948, 2854, 2758, 2661, 2562, 2461,
- 2359, 2255, 2148, 2040, 1929, 1815, 1698, 1577,
- 1452, 1323, 1187, 1045, 894, 731, 550, 339,
- 0, 339, 550, 731, 894, 1045, 1187, 1323,
- 1452, 1577, 1698, 1815, 1929, 2040, 2148, 2255,
- 2359, 2461, 2562, 2661, 2758, 2854, 2948, 3041,
- 3133, 3224, 3314, 3402, 3490, 3577, 3663, 3748,
- 3832, 3916, 3998, 4080, 4162, 4242, 4322, 4401,
- 4480, 4558, 4636, 4713, 4789, 4865, 4941, 5015,
- 5090, 5164, 5237, 5311, 5383, 5456, 5527, 5599,
- 5670, 5741, 5811, 5881, 5950, 6020, 6089, 6157,
- 6225, 6293, 6361, 6428, 6495, 6562, 6628, 6695,
- 6760, 6826, 6891, 6956, 7021, 7086, 7150, 7214,
- 7278, 7341, 7405, 7468, 7531, 7593, 7656, 7718,
- 7780, 7842, 7903, 7965, 8026, 8087, 8148, 8208,
- 8269, 8329, 8389, 8449, 8508, 8568, 8627, 8686,
- 8745, 8804, 8862, 8921, 8979, 9037, 9095, 9153,
- 9211, 9268, 9326, 9383, 9440, 9497, 9553, 9610,
- 9666, 9723, 9779, 9835, 9891, 9947, 10002, 10058,
- 10113, 10168, 10224, 10279, 10333, 10388, 10443, 10497,
- 10552, 10606, 10660, 10714, 10768, 10822, 10875, 10929,
- 10982, 11036, 11089, 11142, 11195, 11248, 11301, 11353,
- 11406, 11458, 11511, 11563, 11615, 11667, 11719, 11771,
- 11823, 11875, 11926, 11978, 12029, 12080, 12132, 12183,
- 12234, 12285, 12335, 12386, 12437, 12487, 12538, 12588,
- 12639, 12689, 12739, 12789, 12839, 12889, 12939, 12988,
- 13038, 13088, 13137, 13187, 13236, 13285, 13334, 13383,
- 13432, 13481, 13530, 13579, 13628, 13676, 13725, 13773,
- 13822, 13870, 13918, 13967, 14015, 14063, 14111, 14159,
- 14206, 14254, 14302, 14350, 14397, 14445, 14492, 14539,
- 14587, 14634, 14681, 14728, 14775, 14822, 14869, 14916,
- 14963, 15010, 15056, 15103, 15149, 15196, 15242, 15289,
- 15335, 15381, 15427, 15474, 15520, 15566, 15612, 15657,
- 15703, 15749, 15795, 15840, 15886, 15932, 15977, 16022,
- 16068, 16113, 16158, 16204, 16249, 16294, 16339, 16384,
- 16384,
-};
-/* clang-format on */
-
int av1_is_enough_erroradvantage(double best_erroradvantage, int params_cost) {
return best_erroradvantage < erroradv_tr &&
best_erroradvantage * params_cost < erroradv_prod_tr;
@@ -541,6 +464,11 @@ int64_t av1_refine_integerized_param(
}
wm->wmtype = get_wmtype(wm);
+ // Recompute shear params for the refined model
+ // This should never fail, because we only ever consider warp-able models
+ if (!av1_get_shear_params(wm)) {
+ assert(0);
+ }
return best_error;
}
diff --git a/third_party/aom/av1/encoder/global_motion.h b/third_party/aom/av1/encoder/global_motion.h
index 8c9c60f0f5..de46a0e1f2 100644
--- a/third_party/aom/av1/encoder/global_motion.h
+++ b/third_party/aom/av1/encoder/global_motion.h
@@ -15,6 +15,7 @@
#include "aom/aom_integer.h"
#include "aom_dsp/flow_estimation/flow_estimation.h"
#include "aom_scale/yv12config.h"
+#include "aom_util/aom_pthread.h"
#include "aom_util/aom_thread.h"
#ifdef __cplusplus
@@ -97,37 +98,6 @@ void av1_compute_feature_segmentation_map(uint8_t *segment_map, int width,
int height, int *inliers,
int num_inliers);
-extern const int error_measure_lut[513];
-
-static INLINE int error_measure(int err) {
- return error_measure_lut[256 + err];
-}
-
-#if CONFIG_AV1_HIGHBITDEPTH
-static INLINE int highbd_error_measure(int err, int bd) {
- const int b = bd - 8;
- const int bmask = (1 << b) - 1;
- const int v = (1 << b);
-
- // Split error into two parts and do an interpolated table lookup
- // To compute the table index and interpolation value, we want to calculate
- // the quotient and remainder of err / 2^b. But it is very important that
- // the division must round down, and the remainder must be positive,
- // ie. in the range [0, 2^b).
- //
- // In C, the >> and & operators do what we want, but the / and % operators
- // give the wrong results for negative inputs. So we must use >> and & here.
- //
- // For example, if bd == 10 and err == -5, compare the results:
- // (-5) >> 2 = -2, (-5) & 3 = 3
- // vs. (-5) / 4 = -1, (-5) % 4 = -1
- const int e1 = err >> b;
- const int e2 = err & bmask;
- return error_measure_lut[256 + e1] * (v - e2) +
- error_measure_lut[257 + e1] * e2;
-}
-#endif // CONFIG_AV1_HIGHBITDEPTH
-
int64_t av1_segmented_frame_error(int use_hbd, int bd, const uint8_t *ref,
int ref_stride, uint8_t *dst, int dst_stride,
int p_width, int p_height,
diff --git a/third_party/aom/av1/encoder/global_motion_facade.c b/third_party/aom/av1/encoder/global_motion_facade.c
index 02a4e70ed3..687eeee18a 100644
--- a/third_party/aom/av1/encoder/global_motion_facade.c
+++ b/third_party/aom/av1/encoder/global_motion_facade.c
@@ -89,6 +89,7 @@ static AOM_INLINE void compute_global_motion_for_ref_frame(
assert(ref_buf[frame] != NULL);
int bit_depth = cpi->common.seq_params->bit_depth;
GlobalMotionMethod global_motion_method = default_global_motion_method;
+ int downsample_level = cpi->sf.gm_sf.downsample_level;
int num_refinements = cpi->sf.gm_sf.num_refinement_steps;
bool mem_alloc_failed = false;
@@ -99,9 +100,10 @@ static AOM_INLINE void compute_global_motion_for_ref_frame(
double best_erroradv = erroradv_tr;
for (TransformationType model = FIRST_GLOBAL_TRANS_TYPE;
model <= LAST_GLOBAL_TRANS_TYPE; ++model) {
- if (!aom_compute_global_motion(
- model, cpi->source, ref_buf[frame], bit_depth, global_motion_method,
- motion_models, RANSAC_NUM_MOTIONS, &mem_alloc_failed)) {
+ if (!aom_compute_global_motion(model, cpi->source, ref_buf[frame],
+ bit_depth, global_motion_method,
+ downsample_level, motion_models,
+ RANSAC_NUM_MOTIONS, &mem_alloc_failed)) {
if (mem_alloc_failed) {
aom_internal_error(error_info, AOM_CODEC_MEM_ERROR,
"Failed to allocate global motion buffers");
@@ -115,6 +117,9 @@ static AOM_INLINE void compute_global_motion_for_ref_frame(
WarpedMotionParams tmp_wm_params;
av1_convert_model_to_params(motion_models[i].params, &tmp_wm_params);
+ // Check that the generated model is warp-able
+ if (!av1_get_shear_params(&tmp_wm_params)) continue;
+
// Skip models that we won't use (IDENTITY or TRANSLATION)
//
// For IDENTITY type models, we don't need to evaluate anything because
@@ -151,6 +156,14 @@ static AOM_INLINE void compute_global_motion_for_ref_frame(
double erroradvantage = (double)warp_error / ref_frame_error;
+ // Check that the model signaling cost is not too high
+ if (!av1_is_enough_erroradvantage(
+ erroradvantage,
+ gm_get_params_cost(&tmp_wm_params, ref_params,
+ cm->features.allow_high_precision_mv))) {
+ continue;
+ }
+
if (erroradvantage < best_erroradv) {
best_erroradv = erroradvantage;
// Save the wm_params modified by
@@ -161,34 +174,6 @@ static AOM_INLINE void compute_global_motion_for_ref_frame(
}
}
}
-
- if (!av1_get_shear_params(&cm->global_motion[frame]))
- cm->global_motion[frame] = default_warp_params;
-
-#if 0
- // We never choose translational models, so this code is disabled
- if (cm->global_motion[frame].wmtype == TRANSLATION) {
- cm->global_motion[frame].wmmat[0] =
- convert_to_trans_prec(cm->features.allow_high_precision_mv,
- cm->global_motion[frame].wmmat[0]) *
- GM_TRANS_ONLY_DECODE_FACTOR;
- cm->global_motion[frame].wmmat[1] =
- convert_to_trans_prec(cm->features.allow_high_precision_mv,
- cm->global_motion[frame].wmmat[1]) *
- GM_TRANS_ONLY_DECODE_FACTOR;
- }
-#endif
-
- if (cm->global_motion[frame].wmtype == IDENTITY) return;
-
- // If the best error advantage found doesn't meet the threshold for
- // this motion type, revert to IDENTITY.
- if (!av1_is_enough_erroradvantage(
- best_erroradv,
- gm_get_params_cost(&cm->global_motion[frame], ref_params,
- cm->features.allow_high_precision_mv))) {
- cm->global_motion[frame] = default_warp_params;
- }
}
// Computes global motion for the given reference frame.
diff --git a/third_party/aom/av1/encoder/k_means_template.h b/third_party/aom/av1/encoder/k_means_template.h
index 4be2038a6f..239029345d 100644
--- a/third_party/aom/av1/encoder/k_means_template.h
+++ b/third_party/aom/av1/encoder/k_means_template.h
@@ -24,6 +24,9 @@
#define RENAME_(x, y) AV1_K_MEANS_RENAME(x, y)
#define RENAME(x) RENAME_(x, AV1_K_MEANS_DIM)
+#define K_MEANS_RENAME_C(x, y) x##_dim##y##_c
+#define RENAME_C_(x, y) K_MEANS_RENAME_C(x, y)
+#define RENAME_C(x) RENAME_C_(x, AV1_K_MEANS_DIM)
// Though we want to compute the smallest L2 norm, in 1 dimension,
// it is equivalent to find the smallest L1 norm and then square it.
@@ -41,8 +44,8 @@ static int RENAME(calc_dist)(const int16_t *p1, const int16_t *p2) {
#endif
}
-void RENAME(av1_calc_indices)(const int16_t *data, const int16_t *centroids,
- uint8_t *indices, int64_t *dist, int n, int k) {
+void RENAME_C(av1_calc_indices)(const int16_t *data, const int16_t *centroids,
+ uint8_t *indices, int64_t *dist, int n, int k) {
if (dist) {
*dist = 0;
}
@@ -149,3 +152,6 @@ void RENAME(av1_k_means)(const int16_t *data, int16_t *centroids,
}
#undef RENAME_
#undef RENAME
+#undef K_MEANS_RENAME_C
+#undef RENAME_C_
+#undef RENAME_C
diff --git a/third_party/aom/av1/encoder/lookahead.c b/third_party/aom/av1/encoder/lookahead.c
index 9ef9b88675..476c91ab95 100644
--- a/third_party/aom/av1/encoder/lookahead.c
+++ b/third_party/aom/av1/encoder/lookahead.c
@@ -46,7 +46,7 @@ struct lookahead_ctx *av1_lookahead_init(
unsigned int width, unsigned int height, unsigned int subsampling_x,
unsigned int subsampling_y, int use_highbitdepth, unsigned int depth,
const int border_in_pixels, int byte_alignment, int num_lap_buffers,
- bool is_all_intra, int num_pyramid_levels) {
+ bool is_all_intra, bool alloc_pyramid) {
int lag_in_frames = AOMMAX(1, depth);
// For all-intra frame encoding, previous source frames are not required.
@@ -82,7 +82,7 @@ struct lookahead_ctx *av1_lookahead_init(
if (aom_realloc_frame_buffer(
&ctx->buf[i].img, width, height, subsampling_x, subsampling_y,
use_highbitdepth, border_in_pixels, byte_alignment, NULL, NULL,
- NULL, num_pyramid_levels, 0)) {
+ NULL, alloc_pyramid, 0)) {
goto fail;
}
}
@@ -100,7 +100,7 @@ int av1_lookahead_full(const struct lookahead_ctx *ctx) {
int av1_lookahead_push(struct lookahead_ctx *ctx, const YV12_BUFFER_CONFIG *src,
int64_t ts_start, int64_t ts_end, int use_highbitdepth,
- int num_pyramid_levels, aom_enc_frame_flags_t flags) {
+ bool alloc_pyramid, aom_enc_frame_flags_t flags) {
int width = src->y_crop_width;
int height = src->y_crop_height;
int uv_width = src->uv_crop_width;
@@ -124,9 +124,9 @@ int av1_lookahead_push(struct lookahead_ctx *ctx, const YV12_BUFFER_CONFIG *src,
height != buf->img.y_crop_height ||
uv_width != buf->img.uv_crop_width ||
uv_height != buf->img.uv_crop_height;
- larger_dimensions = width > buf->img.y_width || height > buf->img.y_height ||
- uv_width > buf->img.uv_width ||
- uv_height > buf->img.uv_height;
+ larger_dimensions =
+ width > buf->img.y_crop_width || height > buf->img.y_crop_height ||
+ uv_width > buf->img.uv_crop_width || uv_height > buf->img.uv_crop_height;
assert(!larger_dimensions || new_dimensions);
if (larger_dimensions) {
@@ -134,11 +134,15 @@ int av1_lookahead_push(struct lookahead_ctx *ctx, const YV12_BUFFER_CONFIG *src,
memset(&new_img, 0, sizeof(new_img));
if (aom_alloc_frame_buffer(&new_img, width, height, subsampling_x,
subsampling_y, use_highbitdepth,
- AOM_BORDER_IN_PIXELS, 0, num_pyramid_levels, 0))
+ AOM_BORDER_IN_PIXELS, 0, alloc_pyramid, 0))
return 1;
aom_free_frame_buffer(&buf->img);
buf->img = new_img;
} else if (new_dimensions) {
+ buf->img.y_width = src->y_width;
+ buf->img.y_height = src->y_height;
+ buf->img.uv_width = src->uv_width;
+ buf->img.uv_height = src->uv_height;
buf->img.y_crop_width = src->y_crop_width;
buf->img.y_crop_height = src->y_crop_height;
buf->img.uv_crop_width = src->uv_crop_width;
@@ -146,7 +150,6 @@ int av1_lookahead_push(struct lookahead_ctx *ctx, const YV12_BUFFER_CONFIG *src,
buf->img.subsampling_x = src->subsampling_x;
buf->img.subsampling_y = src->subsampling_y;
}
- // Partial copy not implemented yet
av1_copy_and_extend_frame(src, &buf->img);
buf->ts_start = ts_start;
diff --git a/third_party/aom/av1/encoder/lookahead.h b/third_party/aom/av1/encoder/lookahead.h
index c0e6d222f5..41eca87fa3 100644
--- a/third_party/aom/av1/encoder/lookahead.h
+++ b/third_party/aom/av1/encoder/lookahead.h
@@ -70,7 +70,7 @@ struct lookahead_ctx *av1_lookahead_init(
unsigned int width, unsigned int height, unsigned int subsampling_x,
unsigned int subsampling_y, int use_highbitdepth, unsigned int depth,
const int border_in_pixels, int byte_alignment, int num_lap_buffers,
- bool is_all_intra, int num_pyramid_levels);
+ bool is_all_intra, bool alloc_pyramid);
/**\brief Destroys the lookahead stage
*/
@@ -85,18 +85,18 @@ int av1_lookahead_full(const struct lookahead_ctx *ctx);
* This function will copy the source image into a new framebuffer with
* the expected stride/border.
*
- * \param[in] ctx Pointer to the lookahead context
- * \param[in] src Pointer to the image to enqueue
- * \param[in] ts_start Timestamp for the start of this frame
- * \param[in] ts_end Timestamp for the end of this frame
- * \param[in] use_highbitdepth Tell if HBD is used
- * \param[in] num_pyramid_levels Number of pyramid levels to allocate
- for each frame buffer
- * \param[in] flags Flags set on this frame
+ * \param[in] ctx Pointer to the lookahead context
+ * \param[in] src Pointer to the image to enqueue
+ * \param[in] ts_start Timestamp for the start of this frame
+ * \param[in] ts_end Timestamp for the end of this frame
+ * \param[in] use_highbitdepth Tell if HBD is used
+ * \param[in] alloc_pyramid Whether to allocate a downsampling pyramid
+ * for each frame buffer
+ * \param[in] flags Flags set on this frame
*/
int av1_lookahead_push(struct lookahead_ctx *ctx, const YV12_BUFFER_CONFIG *src,
int64_t ts_start, int64_t ts_end, int use_highbitdepth,
- int num_pyramid_levels, aom_enc_frame_flags_t flags);
+ bool alloc_pyramid, aom_enc_frame_flags_t flags);
/**\brief Get the next source buffer to encode
*
diff --git a/third_party/aom/av1/encoder/nonrd_pickmode.c b/third_party/aom/av1/encoder/nonrd_pickmode.c
index f939b6d1fa..57c74f66d5 100644
--- a/third_party/aom/av1/encoder/nonrd_pickmode.c
+++ b/third_party/aom/av1/encoder/nonrd_pickmode.c
@@ -2357,6 +2357,10 @@ static AOM_FORCE_INLINE bool skip_inter_mode_nonrd(
*ref_frame2 = NONE_FRAME;
}
+ if (segfeature_active(&cm->seg, segment_id, SEG_LVL_SKIP) &&
+ (*this_mode != GLOBALMV || *ref_frame != LAST_FRAME))
+ return true;
+
if (x->sb_me_block && *ref_frame == LAST_FRAME) {
// We want to make sure to test the superblock MV:
// so don't skip (return false) for NEAREST_LAST or NEAR_LAST if they
@@ -3241,7 +3245,8 @@ void av1_nonrd_pick_inter_mode_sb(AV1_COMP *cpi, TileDataEnc *tile_data,
inter_pred_params_sr.conv_params =
get_conv_params(/*do_average=*/0, AOM_PLANE_Y, xd->bd);
- x->block_is_zero_sad = x->content_state_sb.source_sad_nonrd == kZeroSad;
+ x->block_is_zero_sad = x->content_state_sb.source_sad_nonrd == kZeroSad ||
+ segfeature_active(&cm->seg, segment_id, SEG_LVL_SKIP);
if (cpi->oxcf.tune_cfg.content == AOM_CONTENT_SCREEN &&
!x->force_zeromv_skip_for_blk &&
x->content_state_sb.source_sad_nonrd != kZeroSad &&
diff --git a/third_party/aom/av1/encoder/palette.c b/third_party/aom/av1/encoder/palette.c
index 7f79e9596e..45b56199c6 100644
--- a/third_party/aom/av1/encoder/palette.c
+++ b/third_party/aom/av1/encoder/palette.c
@@ -480,7 +480,7 @@ struct ColorCount {
int count;
};
-int color_count_comp(const void *c1, const void *c2) {
+static int color_count_comp(const void *c1, const void *c2) {
const struct ColorCount *color_count1 = (const struct ColorCount *)c1;
const struct ColorCount *color_count2 = (const struct ColorCount *)c2;
if (color_count1->count > color_count2->count) return -1;
diff --git a/third_party/aom/av1/encoder/palette.h b/third_party/aom/av1/encoder/palette.h
index 7da863a0cc..30886d37ae 100644
--- a/third_party/aom/av1/encoder/palette.h
+++ b/third_party/aom/av1/encoder/palette.h
@@ -26,7 +26,7 @@ struct PICK_MODE_CONTEXT;
struct macroblock;
/*!\cond */
-#define AV1_K_MEANS_RENAME(func, dim) func##_dim##dim##_c
+#define AV1_K_MEANS_RENAME(func, dim) func##_dim##dim
void AV1_K_MEANS_RENAME(av1_k_means, 1)(const int16_t *data, int16_t *centroids,
uint8_t *indices, int n, int k,
diff --git a/third_party/aom/av1/encoder/partition_search.c b/third_party/aom/av1/encoder/partition_search.c
index 1c17b09ee1..61d49a23f2 100644
--- a/third_party/aom/av1/encoder/partition_search.c
+++ b/third_party/aom/av1/encoder/partition_search.c
@@ -2144,8 +2144,9 @@ static void encode_b_nonrd(const AV1_COMP *const cpi, TileDataEnc *tile_data,
}
if (tile_data->allow_update_cdf) update_stats(&cpi->common, td);
}
- if (cpi->oxcf.q_cfg.aq_mode == CYCLIC_REFRESH_AQ && mbmi->skip_txfm &&
- !cpi->rc.rtc_external_ratectrl && cm->seg.enabled)
+ if ((cpi->oxcf.q_cfg.aq_mode == CYCLIC_REFRESH_AQ ||
+ cpi->active_map.enabled) &&
+ mbmi->skip_txfm && !cpi->rc.rtc_external_ratectrl && cm->seg.enabled)
av1_cyclic_reset_segment_skip(cpi, x, mi_row, mi_col, bsize, dry_run);
// TODO(Ravi/Remya): Move this copy function to a better logical place
// This function will copy the best mode information from block
@@ -2254,6 +2255,8 @@ static void pick_sb_modes_nonrd(AV1_COMP *const cpi, TileDataEnc *tile_data,
const AQ_MODE aq_mode = cpi->oxcf.q_cfg.aq_mode;
TxfmSearchInfo *txfm_info = &x->txfm_search_info;
int i;
+ const int seg_skip =
+ segfeature_active(&cm->seg, mbmi->segment_id, SEG_LVL_SKIP);
// This is only needed for real time/allintra row-mt enabled multi-threaded
// encoding with cost update frequency set to COST_UPD_TILE/COST_UPD_OFF.
@@ -2276,15 +2279,17 @@ static void pick_sb_modes_nonrd(AV1_COMP *const cpi, TileDataEnc *tile_data,
}
for (i = 0; i < 2; ++i) pd[i].color_index_map = ctx->color_index_map[i];
- x->force_zeromv_skip_for_blk =
- get_force_zeromv_skip_flag_for_blk(cpi, x, bsize);
+ if (!seg_skip) {
+ x->force_zeromv_skip_for_blk =
+ get_force_zeromv_skip_flag_for_blk(cpi, x, bsize);
- // Source variance may be already compute at superblock level, so no need
- // to recompute, unless bsize < sb_size or source_variance is not yet set.
- if (!x->force_zeromv_skip_for_blk &&
- (x->source_variance == UINT_MAX || bsize < cm->seq_params->sb_size))
- x->source_variance = av1_get_perpixel_variance_facade(
- cpi, xd, &x->plane[0].src, bsize, AOM_PLANE_Y);
+ // Source variance may be already compute at superblock level, so no need
+ // to recompute, unless bsize < sb_size or source_variance is not yet set.
+ if (!x->force_zeromv_skip_for_blk &&
+ (x->source_variance == UINT_MAX || bsize < cm->seq_params->sb_size))
+ x->source_variance = av1_get_perpixel_variance_facade(
+ cpi, xd, &x->plane[0].src, bsize, AOM_PLANE_Y);
+ }
// Save rdmult before it might be changed, so it can be restored later.
const int orig_rdmult = x->rdmult;
@@ -2305,16 +2310,13 @@ static void pick_sb_modes_nonrd(AV1_COMP *const cpi, TileDataEnc *tile_data,
#if CONFIG_COLLECT_COMPONENT_TIMING
start_timing(cpi, nonrd_pick_inter_mode_sb_time);
#endif
- if (segfeature_active(&cm->seg, mbmi->segment_id, SEG_LVL_SKIP)) {
- RD_STATS invalid_rd;
- av1_invalid_rd_stats(&invalid_rd);
- // TODO(kyslov): add av1_nonrd_pick_inter_mode_sb_seg_skip
- av1_rd_pick_inter_mode_sb_seg_skip(cpi, tile_data, x, mi_row, mi_col,
- rd_cost, bsize, ctx,
- invalid_rd.rdcost);
- } else {
- av1_nonrd_pick_inter_mode_sb(cpi, tile_data, x, rd_cost, bsize, ctx);
+ if (seg_skip) {
+ x->force_zeromv_skip_for_blk = 1;
+ // TODO(marpan): Consider adding a function for nonrd:
+ // av1_nonrd_pick_inter_mode_sb_seg_skip(), instead of setting
+ // x->force_zeromv_skip flag and entering av1_nonrd_pick_inter_mode_sb().
}
+ av1_nonrd_pick_inter_mode_sb(cpi, tile_data, x, rd_cost, bsize, ctx);
#if CONFIG_COLLECT_COMPONENT_TIMING
end_timing(cpi, nonrd_pick_inter_mode_sb_time);
#endif
@@ -2322,10 +2324,12 @@ static void pick_sb_modes_nonrd(AV1_COMP *const cpi, TileDataEnc *tile_data,
if (cpi->sf.rt_sf.skip_cdef_sb) {
// cdef_strength is initialized to 1 which means skip_cdef, and is updated
// here. Check to see is skipping cdef is allowed.
+ // Always allow cdef_skip for seg_skip = 1.
const int allow_cdef_skipping =
- cpi->rc.frames_since_key > 10 && !cpi->rc.high_source_sad &&
- !(x->color_sensitivity[COLOR_SENS_IDX(AOM_PLANE_U)] ||
- x->color_sensitivity[COLOR_SENS_IDX(AOM_PLANE_V)]);
+ seg_skip ||
+ (cpi->rc.frames_since_key > 10 && !cpi->rc.high_source_sad &&
+ !(x->color_sensitivity[COLOR_SENS_IDX(AOM_PLANE_U)] ||
+ x->color_sensitivity[COLOR_SENS_IDX(AOM_PLANE_V)]));
// Find the corresponding 64x64 block. It'll be the 128x128 block if that's
// the block size.
diff --git a/third_party/aom/av1/encoder/partition_strategy.c b/third_party/aom/av1/encoder/partition_strategy.c
index ce06313579..1d62f128c7 100644
--- a/third_party/aom/av1/encoder/partition_strategy.c
+++ b/third_party/aom/av1/encoder/partition_strategy.c
@@ -1761,7 +1761,7 @@ void av1_prune_partitions_by_max_min_bsize(SuperBlockEnc *sb_enc,
// Decide whether to evaluate the AB partition specified by part_type based on
// split and HORZ/VERT info
-int evaluate_ab_partition_based_on_split(
+static int evaluate_ab_partition_based_on_split(
const PC_TREE *pc_tree, PARTITION_TYPE rect_part,
const RD_RECT_PART_WIN_INFO *rect_part_win_info, int qindex, int split_idx1,
int split_idx2) {
diff --git a/third_party/aom/av1/encoder/pass2_strategy.c b/third_party/aom/av1/encoder/pass2_strategy.c
index a9442ffc1a..bd8620c2be 100644
--- a/third_party/aom/av1/encoder/pass2_strategy.c
+++ b/third_party/aom/av1/encoder/pass2_strategy.c
@@ -158,28 +158,12 @@ static int frame_max_bits(const RATE_CONTROL *rc,
return (int)max_bits;
}
-static const double q_pow_term[(QINDEX_RANGE >> 5) + 1] = { 0.65, 0.70, 0.75,
- 0.80, 0.85, 0.90,
- 0.95, 0.95, 0.95 };
-#define ERR_DIVISOR 96.0
-static double calc_correction_factor(double err_per_mb, int q) {
- const double error_term = err_per_mb / ERR_DIVISOR;
- const int index = q >> 5;
- // Adjustment to power term based on qindex
- const double power_term =
- q_pow_term[index] +
- (((q_pow_term[index + 1] - q_pow_term[index]) * (q % 32)) / 32.0);
- assert(error_term >= 0.0);
- return fclamp(pow(error_term, power_term), 0.05, 5.0);
-}
-
// Based on history adjust expectations of bits per macroblock.
static void twopass_update_bpm_factor(AV1_COMP *cpi, int rate_err_tol) {
TWO_PASS *const twopass = &cpi->ppi->twopass;
const PRIMARY_RATE_CONTROL *const p_rc = &cpi->ppi->p_rc;
// Based on recent history adjust expectations of bits per macroblock.
- double damp_fac = AOMMAX(5.0, rate_err_tol / 10.0);
double rate_err_factor = 1.0;
const double adj_limit = AOMMAX(0.2, (double)(100 - rate_err_tol) / 200.0);
const double min_fac = 1.0 - adj_limit;
@@ -214,9 +198,7 @@ static void twopass_update_bpm_factor(AV1_COMP *cpi, int rate_err_tol) {
}
int err_estimate = p_rc->rate_error_estimate;
- int64_t bits_left = twopass->bits_left;
int64_t total_actual_bits = p_rc->total_actual_bits;
- int64_t bits_off_target = p_rc->vbr_bits_off_target;
double rolling_arf_group_actual_bits =
(double)twopass->rolling_arf_group_actual_bits;
double rolling_arf_group_target_bits =
@@ -231,10 +213,6 @@ static void twopass_update_bpm_factor(AV1_COMP *cpi, int rate_err_tol) {
: 0;
total_actual_bits = simulate_parallel_frame ? p_rc->temp_total_actual_bits
: p_rc->total_actual_bits;
- bits_off_target = simulate_parallel_frame ? p_rc->temp_vbr_bits_off_target
- : p_rc->vbr_bits_off_target;
- bits_left =
- simulate_parallel_frame ? p_rc->temp_bits_left : twopass->bits_left;
rolling_arf_group_target_bits =
(double)(simulate_parallel_frame
? p_rc->temp_rolling_arf_group_target_bits
@@ -247,21 +225,21 @@ static void twopass_update_bpm_factor(AV1_COMP *cpi, int rate_err_tol) {
: p_rc->rate_error_estimate;
#endif
- if (p_rc->bits_off_target && total_actual_bits > 0) {
- if (cpi->ppi->lap_enabled) {
- rate_err_factor = rolling_arf_group_actual_bits /
- DOUBLE_DIVIDE_CHECK(rolling_arf_group_target_bits);
+ if ((p_rc->bits_off_target && total_actual_bits > 0) &&
+ (rolling_arf_group_target_bits >= 1.0)) {
+ if (rolling_arf_group_actual_bits > rolling_arf_group_target_bits) {
+ double error_fraction =
+ (rolling_arf_group_actual_bits - rolling_arf_group_target_bits) /
+ rolling_arf_group_target_bits;
+ error_fraction = (error_fraction > 1.0) ? 1.0 : error_fraction;
+ rate_err_factor = 1.0 + error_fraction;
} else {
- rate_err_factor = 1.0 - ((double)(bits_off_target) /
- AOMMAX(total_actual_bits, bits_left));
+ double error_fraction =
+ (rolling_arf_group_target_bits - rolling_arf_group_actual_bits) /
+ rolling_arf_group_target_bits;
+ rate_err_factor = 1.0 - error_fraction;
}
- // Adjustment is damped if this is 1 pass with look ahead processing
- // (as there are only ever a few frames of data) and for all but the first
- // GOP in normal two pass.
- if ((twopass->bpm_factor != 1.0) || cpi->ppi->lap_enabled) {
- rate_err_factor = 1.0 + ((rate_err_factor - 1.0) / damp_fac);
- }
rate_err_factor = AOMMAX(min_fac, AOMMIN(max_fac, rate_err_factor));
}
@@ -270,36 +248,38 @@ static void twopass_update_bpm_factor(AV1_COMP *cpi, int rate_err_tol) {
if ((rate_err_factor < 1.0 && err_estimate >= 0) ||
(rate_err_factor > 1.0 && err_estimate <= 0)) {
twopass->bpm_factor *= rate_err_factor;
- if (rate_err_tol >= 100) {
- twopass->bpm_factor =
- AOMMAX(min_fac, AOMMIN(max_fac, twopass->bpm_factor));
- } else {
- twopass->bpm_factor = AOMMAX(0.1, AOMMIN(10.0, twopass->bpm_factor));
- }
+ twopass->bpm_factor = AOMMAX(min_fac, AOMMIN(max_fac, twopass->bpm_factor));
}
}
-static int qbpm_enumerator(int rate_err_tol) {
- return 1200000 + ((300000 * AOMMIN(75, AOMMAX(rate_err_tol - 25, 0))) / 75);
+static const double q_div_term[(QINDEX_RANGE >> 5) + 1] = { 32.0, 40.0, 46.0,
+ 52.0, 56.0, 60.0,
+ 64.0, 68.0, 72.0 };
+#define EPMB_SCALER 1250000
+static double calc_correction_factor(double err_per_mb, int q) {
+ double power_term = 0.90;
+ const int index = q >> 5;
+ const double divisor =
+ q_div_term[index] +
+ (((q_div_term[index + 1] - q_div_term[index]) * (q % 32)) / 32.0);
+ double error_term = EPMB_SCALER * pow(err_per_mb, power_term);
+ return error_term / divisor;
}
// Similar to find_qindex_by_rate() function in ratectrl.c, but includes
// calculation of a correction_factor.
static int find_qindex_by_rate_with_correction(
int desired_bits_per_mb, aom_bit_depth_t bit_depth, double error_per_mb,
- double group_weight_factor, int rate_err_tol, int best_qindex,
- int worst_qindex) {
+ double group_weight_factor, int best_qindex, int worst_qindex) {
assert(best_qindex <= worst_qindex);
int low = best_qindex;
int high = worst_qindex;
while (low < high) {
const int mid = (low + high) >> 1;
- const double mid_factor = calc_correction_factor(error_per_mb, mid);
+ const double q_factor = calc_correction_factor(error_per_mb, mid);
const double q = av1_convert_qindex_to_q(mid, bit_depth);
- const int enumerator = qbpm_enumerator(rate_err_tol);
- const int mid_bits_per_mb =
- (int)((enumerator * mid_factor * group_weight_factor) / q);
+ const int mid_bits_per_mb = (int)((q_factor * group_weight_factor) / q);
if (mid_bits_per_mb > desired_bits_per_mb) {
low = mid + 1;
@@ -359,8 +339,8 @@ static int get_twopass_worst_quality(AV1_COMP *cpi, const double av_frame_err,
// content at the given rate.
int q = find_qindex_by_rate_with_correction(
target_norm_bits_per_mb, cpi->common.seq_params->bit_depth,
- av_err_per_mb, cpi->ppi->twopass.bpm_factor, rate_err_tol,
- rc->best_quality, rc->worst_quality);
+ av_err_per_mb, cpi->ppi->twopass.bpm_factor, rc->best_quality,
+ rc->worst_quality);
// Restriction on active max q for constrained quality mode.
if (rc_cfg->mode == AOM_CQ) q = AOMMAX(q, rc_cfg->cq_level);
@@ -4235,12 +4215,13 @@ void av1_twopass_postencode_update(AV1_COMP *cpi) {
twopass->kf_group_bits = AOMMAX(twopass->kf_group_bits, 0);
// If the rate control is drifting consider adjustment to min or maxq.
- if ((rc_cfg->mode != AOM_Q) && !cpi->rc.is_src_frame_alt_ref) {
+ if ((rc_cfg->mode != AOM_Q) && !cpi->rc.is_src_frame_alt_ref &&
+ (p_rc->rolling_target_bits > 0)) {
int minq_adj_limit;
int maxq_adj_limit;
minq_adj_limit =
(rc_cfg->mode == AOM_CQ ? MINQ_ADJ_LIMIT_CQ : MINQ_ADJ_LIMIT);
- maxq_adj_limit = rc->worst_quality - rc->active_worst_quality;
+ maxq_adj_limit = (rc->worst_quality - rc->active_worst_quality);
// Undershoot
if ((rc_cfg->under_shoot_pct < 100) &&
@@ -4252,8 +4233,9 @@ void av1_twopass_postencode_update(AV1_COMP *cpi) {
if ((pct_error >= rc_cfg->under_shoot_pct) &&
(p_rc->rate_error_estimate > 0)) {
twopass->extend_minq += 1;
+ twopass->extend_maxq -= 1;
}
- twopass->extend_maxq -= 1;
+
// Overshoot
} else if ((rc_cfg->over_shoot_pct < 100) &&
(p_rc->rolling_actual_bits > p_rc->rolling_target_bits)) {
@@ -4265,18 +4247,8 @@ void av1_twopass_postencode_update(AV1_COMP *cpi) {
if ((pct_error >= rc_cfg->over_shoot_pct) &&
(p_rc->rate_error_estimate < 0)) {
twopass->extend_maxq += 1;
+ twopass->extend_minq -= 1;
}
- twopass->extend_minq -= 1;
- } else {
- // Adjustment for extreme local overshoot.
- // Only applies when normal adjustment above is not used (e.g.
- // when threshold is set to 100).
- if (rc->projected_frame_size > (2 * rc->base_frame_target) &&
- rc->projected_frame_size > (2 * rc->avg_frame_bandwidth))
- ++twopass->extend_maxq;
- // Unwind extreme overshoot adjustment.
- else if (p_rc->rolling_target_bits > p_rc->rolling_actual_bits)
- --twopass->extend_maxq;
}
twopass->extend_minq =
clamp(twopass->extend_minq, -minq_adj_limit, minq_adj_limit);
diff --git a/third_party/aom/av1/encoder/pickcdef.c b/third_party/aom/av1/encoder/pickcdef.c
index 232a2f9edb..ed5fa55f17 100644
--- a/third_party/aom/av1/encoder/pickcdef.c
+++ b/third_party/aom/av1/encoder/pickcdef.c
@@ -894,7 +894,7 @@ void av1_cdef_search(AV1_COMP *cpi) {
int rdmult = cpi->td.mb.rdmult;
for (int i = 0; i <= 3; i++) {
if (i > max_signaling_bits) break;
- int best_lev0[CDEF_MAX_STRENGTHS];
+ int best_lev0[CDEF_MAX_STRENGTHS] = { 0 };
int best_lev1[CDEF_MAX_STRENGTHS] = { 0 };
const int nb_strengths = 1 << i;
uint64_t tot_mse;
diff --git a/third_party/aom/av1/encoder/picklpf.c b/third_party/aom/av1/encoder/picklpf.c
index 9084d3f13a..a504535028 100644
--- a/third_party/aom/av1/encoder/picklpf.c
+++ b/third_party/aom/av1/encoder/picklpf.c
@@ -27,12 +27,25 @@
#include "av1/encoder/encoder.h"
#include "av1/encoder/picklpf.h"
+// AV1 loop filter applies to the whole frame according to mi_rows and mi_cols,
+// which are calculated based on aligned width and aligned height,
+// In addition, if super res is enabled, it copies the whole frame
+// according to the aligned width and height (av1_superres_upscale()).
+// So we need to copy the whole filtered region, instead of the cropped region.
+// For example, input image size is: 160x90.
+// Then src->y_crop_width = 160, src->y_crop_height = 90.
+// The aligned frame size is: src->y_width = 160, src->y_height = 96.
+// AV1 aligns frame size to a multiple of 8, if there is
+// chroma subsampling, it is able to ensure the chroma is also
+// an integer number of mi units. mi unit is 4x4, 8 = 4 * 2, and 2 luma mi
+// units correspond to 1 chroma mi unit if there is subsampling.
+// See: aom_realloc_frame_buffer() in yv12config.c.
static void yv12_copy_plane(const YV12_BUFFER_CONFIG *src_bc,
YV12_BUFFER_CONFIG *dst_bc, int plane) {
switch (plane) {
- case 0: aom_yv12_copy_y(src_bc, dst_bc); break;
- case 1: aom_yv12_copy_u(src_bc, dst_bc); break;
- case 2: aom_yv12_copy_v(src_bc, dst_bc); break;
+ case 0: aom_yv12_copy_y(src_bc, dst_bc, 0); break;
+ case 1: aom_yv12_copy_u(src_bc, dst_bc, 0); break;
+ case 2: aom_yv12_copy_v(src_bc, dst_bc, 0); break;
default: assert(plane >= 0 && plane <= 2); break;
}
}
@@ -311,7 +324,7 @@ void av1_pick_filter_level(const YV12_BUFFER_CONFIG *sd, AV1_COMP *cpi,
&cpi->last_frame_uf, cm->width, cm->height,
seq_params->subsampling_x, seq_params->subsampling_y,
seq_params->use_highbitdepth, cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, NULL, NULL, NULL, 0, 0))
+ cm->features.byte_alignment, NULL, NULL, NULL, false, 0))
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate last frame buffer");
diff --git a/third_party/aom/av1/encoder/pickrst.c b/third_party/aom/av1/encoder/pickrst.c
index 6429064175..b0d0d0bb78 100644
--- a/third_party/aom/av1/encoder/pickrst.c
+++ b/third_party/aom/av1/encoder/pickrst.c
@@ -1103,6 +1103,39 @@ static INLINE int wrap_index(int i, int wiener_win) {
return (i >= wiener_halfwin1 ? wiener_win - 1 - i : i);
}
+// Splits each w[i] into smaller components w1[i] and w2[i] such that
+// w[i] = w1[i] * WIENER_TAP_SCALE_FACTOR + w2[i].
+static INLINE void split_wiener_filter_coefficients(int wiener_win,
+ const int32_t *w,
+ int32_t *w1, int32_t *w2) {
+ for (int i = 0; i < wiener_win; i++) {
+ w1[i] = w[i] / WIENER_TAP_SCALE_FACTOR;
+ w2[i] = w[i] - w1[i] * WIENER_TAP_SCALE_FACTOR;
+ assert(w[i] == w1[i] * WIENER_TAP_SCALE_FACTOR + w2[i]);
+ }
+}
+
+// Calculates x * w / WIENER_TAP_SCALE_FACTOR, where
+// w = w1 * WIENER_TAP_SCALE_FACTOR + w2.
+//
+// The multiplication x * w may overflow, so we multiply x by the components of
+// w (w1 and w2) and combine the multiplication with the division.
+static INLINE int64_t multiply_and_scale(int64_t x, int32_t w1, int32_t w2) {
+ // Let y = x * w / WIENER_TAP_SCALE_FACTOR
+ // = x * (w1 * WIENER_TAP_SCALE_FACTOR + w2) / WIENER_TAP_SCALE_FACTOR
+ const int64_t y = x * w1 + x * w2 / WIENER_TAP_SCALE_FACTOR;
+ // Double-check the calculation using __int128.
+ // TODO(wtc): Remove after 2024-04-30.
+#if !defined(NDEBUG) && defined(__GNUC__) && defined(__LP64__)
+ const int32_t w = w1 * WIENER_TAP_SCALE_FACTOR + w2;
+ const __int128 z = (__int128)x * w / WIENER_TAP_SCALE_FACTOR;
+ assert(z >= INT64_MIN);
+ assert(z <= INT64_MAX);
+ assert(y == (int64_t)z);
+#endif
+ return y;
+}
+
// Solve linear equations to find Wiener filter tap values
// Taps are output scaled by WIENER_FILT_STEP
static int linsolve_wiener(int n, int64_t *A, int stride, int64_t *b,
@@ -1175,10 +1208,12 @@ static int linsolve_wiener(int n, int64_t *A, int stride, int64_t *b,
// Fix vector b, update vector a
static AOM_INLINE void update_a_sep_sym(int wiener_win, int64_t **Mc,
- int64_t **Hc, int32_t *a, int32_t *b) {
+ int64_t **Hc, int32_t *a,
+ const int32_t *b) {
int i, j;
int64_t S[WIENER_WIN];
int64_t A[WIENER_HALFWIN1], B[WIENER_HALFWIN1 * WIENER_HALFWIN1];
+ int32_t b1[WIENER_WIN], b2[WIENER_WIN];
const int wiener_win2 = wiener_win * wiener_win;
const int wiener_halfwin1 = (wiener_win >> 1) + 1;
memset(A, 0, sizeof(A));
@@ -1189,16 +1224,7 @@ static AOM_INLINE void update_a_sep_sym(int wiener_win, int64_t **Mc,
A[jj] += Mc[i][j] * b[i] / WIENER_TAP_SCALE_FACTOR;
}
}
-
- // b/274668506: This is the dual branch for the issue in b/272139363. The fix
- // is similar. See comments in update_b_sep_sym() below.
- int32_t max_b_l = 0;
- for (int l = 0; l < wiener_win; ++l) {
- const int32_t abs_b_l = abs(b[l]);
- if (abs_b_l > max_b_l) max_b_l = abs_b_l;
- }
- const int scale_threshold = 128 * WIENER_TAP_SCALE_FACTOR;
- const int scaler = max_b_l < scale_threshold ? 1 : 4;
+ split_wiener_filter_coefficients(wiener_win, b, b1, b2);
for (i = 0; i < wiener_win; i++) {
for (j = 0; j < wiener_win; j++) {
@@ -1207,10 +1233,17 @@ static AOM_INLINE void update_a_sep_sym(int wiener_win, int64_t **Mc,
const int kk = wrap_index(k, wiener_win);
for (l = 0; l < wiener_win; ++l) {
const int ll = wrap_index(l, wiener_win);
- B[ll * wiener_halfwin1 + kk] +=
- Hc[j * wiener_win + i][k * wiener_win2 + l] * b[i] /
- (scaler * WIENER_TAP_SCALE_FACTOR) * b[j] /
- (WIENER_TAP_SCALE_FACTOR / scaler);
+ // Calculate
+ // B[ll * wiener_halfwin1 + kk] +=
+ // Hc[j * wiener_win + i][k * wiener_win2 + l] * b[i] /
+ // WIENER_TAP_SCALE_FACTOR * b[j] / WIENER_TAP_SCALE_FACTOR;
+ //
+ // The last multiplication may overflow, so we combine the last
+ // multiplication with the last division.
+ const int64_t x = Hc[j * wiener_win + i][k * wiener_win2 + l] * b[i] /
+ WIENER_TAP_SCALE_FACTOR;
+ // b[j] = b1[j] * WIENER_TAP_SCALE_FACTOR + b2[j]
+ B[ll * wiener_halfwin1 + kk] += multiply_and_scale(x, b1[j], b2[j]);
}
}
}
@@ -1246,10 +1279,12 @@ static AOM_INLINE void update_a_sep_sym(int wiener_win, int64_t **Mc,
// Fix vector a, update vector b
static AOM_INLINE void update_b_sep_sym(int wiener_win, int64_t **Mc,
- int64_t **Hc, int32_t *a, int32_t *b) {
+ int64_t **Hc, const int32_t *a,
+ int32_t *b) {
int i, j;
int64_t S[WIENER_WIN];
int64_t A[WIENER_HALFWIN1], B[WIENER_HALFWIN1 * WIENER_HALFWIN1];
+ int32_t a1[WIENER_WIN], a2[WIENER_WIN];
const int wiener_win2 = wiener_win * wiener_win;
const int wiener_halfwin1 = (wiener_win >> 1) + 1;
memset(A, 0, sizeof(A));
@@ -1260,32 +1295,7 @@ static AOM_INLINE void update_b_sep_sym(int wiener_win, int64_t **Mc,
A[ii] += Mc[i][j] * a[j] / WIENER_TAP_SCALE_FACTOR;
}
}
-
- // b/272139363: The computation,
- // Hc[i * wiener_win + j][k * wiener_win2 + l] * a[k] /
- // WIENER_TAP_SCALE_FACTOR * a[l] / WIENER_TAP_SCALE_FACTOR;
- // may generate a signed-integer-overflow. Conditionally scale the terms to
- // avoid a potential overflow.
- //
- // Hc contains accumulated correlation statistics and it is desired to leave
- // as much room as possible for Hc. It was experimentally observed that the
- // primary issue manifests itself with the second, a[l], multiply. For
- // max_a_l < WIENER_TAP_SCALE_FACTOR the first multiply with a[k] should not
- // increase dynamic range and the second multiply should hence be safe.
- // Thereafter a safe scale_threshold depends on the actual operational range
- // of Hc. The largest scale_threshold is expected to depend on bit-depth
- // (av1_compute_stats_highbd_c() scales highbd to 8-bit) and maximum
- // restoration-unit size (256), leading up to 32-bit positive numbers in Hc.
- // Noting that the caller, wiener_decompose_sep_sym(), initializes a[...]
- // to a range smaller than 16 bits, the scale_threshold is set as below for
- // convenience.
- int32_t max_a_l = 0;
- for (int l = 0; l < wiener_win; ++l) {
- const int32_t abs_a_l = abs(a[l]);
- if (abs_a_l > max_a_l) max_a_l = abs_a_l;
- }
- const int scale_threshold = 128 * WIENER_TAP_SCALE_FACTOR;
- const int scaler = max_a_l < scale_threshold ? 1 : 4;
+ split_wiener_filter_coefficients(wiener_win, a, a1, a2);
for (i = 0; i < wiener_win; i++) {
const int ii = wrap_index(i, wiener_win);
@@ -1294,10 +1304,17 @@ static AOM_INLINE void update_b_sep_sym(int wiener_win, int64_t **Mc,
int k, l;
for (k = 0; k < wiener_win; ++k) {
for (l = 0; l < wiener_win; ++l) {
- B[jj * wiener_halfwin1 + ii] +=
- Hc[i * wiener_win + j][k * wiener_win2 + l] * a[k] /
- (scaler * WIENER_TAP_SCALE_FACTOR) * a[l] /
- (WIENER_TAP_SCALE_FACTOR / scaler);
+ // Calculate
+ // B[jj * wiener_halfwin1 + ii] +=
+ // Hc[i * wiener_win + j][k * wiener_win2 + l] * a[k] /
+ // WIENER_TAP_SCALE_FACTOR * a[l] / WIENER_TAP_SCALE_FACTOR;
+ //
+ // The last multiplication may overflow, so we combine the last
+ // multiplication with the last division.
+ const int64_t x = Hc[i * wiener_win + j][k * wiener_win2 + l] * a[k] /
+ WIENER_TAP_SCALE_FACTOR;
+ // a[l] = a1[l] * WIENER_TAP_SCALE_FACTOR + a2[l]
+ B[jj * wiener_halfwin1 + ii] += multiply_and_scale(x, a1[l], a2[l]);
}
}
}
@@ -2050,7 +2067,7 @@ void av1_pick_filter_restoration(const YV12_BUFFER_CONFIG *src, AV1_COMP *cpi) {
&cpi->trial_frame_rst, cm->superres_upscaled_width,
cm->superres_upscaled_height, seq_params->subsampling_x,
seq_params->subsampling_y, highbd, AOM_RESTORATION_FRAME_BORDER,
- cm->features.byte_alignment, NULL, NULL, NULL, 0, 0))
+ cm->features.byte_alignment, NULL, NULL, NULL, false, 0))
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate trial restored frame buffer");
diff --git a/third_party/aom/av1/encoder/ratectrl.c b/third_party/aom/av1/encoder/ratectrl.c
index df86380272..7639484df5 100644
--- a/third_party/aom/av1/encoder/ratectrl.c
+++ b/third_party/aom/av1/encoder/ratectrl.c
@@ -30,6 +30,7 @@
#include "av1/common/seg_common.h"
#include "av1/encoder/encodemv.h"
+#include "av1/encoder/encoder_utils.h"
#include "av1/encoder/encode_strategy.h"
#include "av1/encoder/gop_structure.h"
#include "av1/encoder/random.h"
@@ -405,10 +406,10 @@ void av1_primary_rc_init(const AV1EncoderConfig *oxcf,
p_rc->rate_correction_factors[KF_STD] = 1.0;
p_rc->bits_off_target = p_rc->starting_buffer_level;
- p_rc->rolling_target_bits =
- (int)(oxcf->rc_cfg.target_bandwidth / oxcf->input_cfg.init_framerate);
- p_rc->rolling_actual_bits =
- (int)(oxcf->rc_cfg.target_bandwidth / oxcf->input_cfg.init_framerate);
+ p_rc->rolling_target_bits = AOMMAX(
+ 1, (int)(oxcf->rc_cfg.target_bandwidth / oxcf->input_cfg.init_framerate));
+ p_rc->rolling_actual_bits = AOMMAX(
+ 1, (int)(oxcf->rc_cfg.target_bandwidth / oxcf->input_cfg.init_framerate));
}
void av1_rc_init(const AV1EncoderConfig *oxcf, RATE_CONTROL *rc) {
@@ -439,6 +440,7 @@ void av1_rc_init(const AV1EncoderConfig *oxcf, RATE_CONTROL *rc) {
rc->rtc_external_ratectrl = 0;
rc->frame_level_fast_extra_bits = 0;
rc->use_external_qp_one_pass = 0;
+ rc->percent_blocks_inactive = 0;
}
static bool check_buffer_below_thresh(AV1_COMP *cpi, int64_t buffer_level,
@@ -1719,41 +1721,39 @@ static void adjust_active_best_and_worst_quality(const AV1_COMP *cpi,
const AV1_COMMON *const cm = &cpi->common;
const RATE_CONTROL *const rc = &cpi->rc;
const PRIMARY_RATE_CONTROL *const p_rc = &cpi->ppi->p_rc;
- const RefreshFrameInfo *const refresh_frame = &cpi->refresh_frame;
int active_best_quality = *active_best;
int active_worst_quality = *active_worst;
#if CONFIG_FPMT_TEST
- const int simulate_parallel_frame =
- cpi->ppi->gf_group.frame_parallel_level[cpi->gf_frame_index] > 0 &&
- cpi->ppi->fpmt_unit_test_cfg == PARALLEL_SIMULATION_ENCODE;
- int extend_minq = simulate_parallel_frame ? p_rc->temp_extend_minq
- : cpi->ppi->twopass.extend_minq;
- int extend_maxq = simulate_parallel_frame ? p_rc->temp_extend_maxq
- : cpi->ppi->twopass.extend_maxq;
#endif
// Extension to max or min Q if undershoot or overshoot is outside
// the permitted range.
if (cpi->oxcf.rc_cfg.mode != AOM_Q) {
+#if CONFIG_FPMT_TEST
+ const int simulate_parallel_frame =
+ cpi->ppi->gf_group.frame_parallel_level[cpi->gf_frame_index] > 0 &&
+ cpi->ppi->fpmt_unit_test_cfg == PARALLEL_SIMULATION_ENCODE;
+ const int extend_minq = simulate_parallel_frame
+ ? p_rc->temp_extend_minq
+ : cpi->ppi->twopass.extend_minq;
+ const int extend_maxq = simulate_parallel_frame
+ ? p_rc->temp_extend_maxq
+ : cpi->ppi->twopass.extend_maxq;
+ const RefreshFrameInfo *const refresh_frame = &cpi->refresh_frame;
if (frame_is_intra_only(cm) ||
(!rc->is_src_frame_alt_ref &&
(refresh_frame->golden_frame || is_intrl_arf_boost ||
refresh_frame->alt_ref_frame))) {
-#if CONFIG_FPMT_TEST
active_best_quality -= extend_minq;
active_worst_quality += (extend_maxq / 2);
-#else
- active_best_quality -= cpi->ppi->twopass.extend_minq / 4;
- active_worst_quality += (cpi->ppi->twopass.extend_maxq / 2);
-#endif
} else {
-#if CONFIG_FPMT_TEST
active_best_quality -= extend_minq / 2;
active_worst_quality += extend_maxq;
+ }
#else
- active_best_quality -= cpi->ppi->twopass.extend_minq / 4;
- active_worst_quality += cpi->ppi->twopass.extend_maxq;
+ (void)is_intrl_arf_boost;
+ active_best_quality -= cpi->ppi->twopass.extend_minq / 8;
+ active_worst_quality += cpi->ppi->twopass.extend_maxq / 4;
#endif
- }
}
#ifndef STRICT_RC
@@ -2991,6 +2991,24 @@ void av1_set_rtc_reference_structure_one_layer(AV1_COMP *cpi, int gf_update) {
cpi->rt_reduce_num_ref_buffers &= (rtc_ref->ref_idx[2] < 7);
}
+static int set_block_is_active(unsigned char *const active_map_4x4, int mi_cols,
+ int mi_rows, int sbi_col, int sbi_row, int sh,
+ int num_4x4) {
+ int r = sbi_row << sh;
+ int c = sbi_col << sh;
+ const int row_max = AOMMIN(num_4x4, mi_rows - r);
+ const int col_max = AOMMIN(num_4x4, mi_cols - c);
+ // Active map is set for 16x16 blocks, so only need to
+ // check over16x16,
+ for (int x = 0; x < row_max; x += 4) {
+ for (int y = 0; y < col_max; y += 4) {
+ if (active_map_4x4[(r + x) * mi_cols + (c + y)] == AM_SEGMENT_ID_ACTIVE)
+ return 1;
+ }
+ }
+ return 0;
+}
+
/*!\brief Check for scene detection, for 1 pass real-time mode.
*
* Compute average source sad (temporal sad: between current source and
@@ -3093,11 +3111,26 @@ static void rc_scene_detection_onepass_rt(AV1_COMP *cpi,
sizeof(*cpi->src_sad_blk_64x64)));
}
}
+ const CommonModeInfoParams *const mi_params = &cpi->common.mi_params;
+ const int mi_cols = mi_params->mi_cols;
+ const int mi_rows = mi_params->mi_rows;
+ int sh = (cm->seq_params->sb_size == BLOCK_128X128) ? 5 : 4;
+ int num_4x4 = (cm->seq_params->sb_size == BLOCK_128X128) ? 32 : 16;
+ unsigned char *const active_map_4x4 = cpi->active_map.map;
// Avoid bottom and right border.
for (int sbi_row = 0; sbi_row < sb_rows - border; ++sbi_row) {
for (int sbi_col = 0; sbi_col < sb_cols; ++sbi_col) {
- tmp_sad = cpi->ppi->fn_ptr[bsize].sdf(src_y, src_ystride, last_src_y,
- last_src_ystride);
+ int block_is_active = 1;
+ if (cpi->active_map.enabled && rc->percent_blocks_inactive > 0) {
+ block_is_active = set_block_is_active(active_map_4x4, mi_cols, mi_rows,
+ sbi_col, sbi_row, sh, num_4x4);
+ }
+ if (block_is_active) {
+ tmp_sad = cpi->ppi->fn_ptr[bsize].sdf(src_y, src_ystride, last_src_y,
+ last_src_ystride);
+ } else {
+ tmp_sad = 0;
+ }
if (cpi->src_sad_blk_64x64 != NULL)
cpi->src_sad_blk_64x64[sbi_col + sbi_row * sb_cols] = tmp_sad;
if (check_light_change) {
@@ -3456,8 +3489,13 @@ void av1_get_one_pass_rt_params(AV1_COMP *cpi, FRAME_TYPE *const frame_type,
}
}
}
- // Check for scene change: for SVC check on base spatial layer only.
- if (cpi->sf.rt_sf.check_scene_detection && svc->spatial_layer_id == 0) {
+ if (cpi->active_map.enabled && cpi->rc.percent_blocks_inactive == 100) {
+ rc->frame_source_sad = 0;
+ rc->avg_source_sad = (3 * rc->avg_source_sad + rc->frame_source_sad) >> 2;
+ rc->percent_blocks_with_motion = 0;
+ rc->high_source_sad = 0;
+ } else if (cpi->sf.rt_sf.check_scene_detection &&
+ svc->spatial_layer_id == 0) {
if (rc->prev_coded_width == cm->width &&
rc->prev_coded_height == cm->height) {
rc_scene_detection_onepass_rt(cpi, frame_input);
@@ -3522,6 +3560,10 @@ void av1_get_one_pass_rt_params(AV1_COMP *cpi, FRAME_TYPE *const frame_type,
}
}
+#define CHECK_INTER_LAYER_PRED(ref_frame) \
+ ((cpi->ref_frame_flags & av1_ref_frame_flag_list[ref_frame]) && \
+ (av1_check_ref_is_low_spatial_res_super_frame(cpi, ref_frame)))
+
int av1_encodedframe_overshoot_cbr(AV1_COMP *cpi, int *q) {
AV1_COMMON *const cm = &cpi->common;
PRIMARY_RATE_CONTROL *const p_rc = &cpi->ppi->p_rc;
@@ -3532,12 +3574,26 @@ int av1_encodedframe_overshoot_cbr(AV1_COMP *cpi, int *q) {
int target_bits_per_mb;
double q2;
int enumerator;
+ int inter_layer_pred_on = 0;
int is_screen_content = (cpi->oxcf.tune_cfg.content == AOM_CONTENT_SCREEN);
- *q = (3 * cpi->rc.worst_quality + *q) >> 2;
- // For screen content use the max-q set by the user to allow for less
- // overshoot on slide changes.
- if (is_screen_content) *q = cpi->rc.worst_quality;
cpi->cyclic_refresh->counter_encode_maxq_scene_change = 0;
+ if (cpi->svc.spatial_layer_id > 0) {
+ // For spatial layers: check if inter-layer (spatial) prediction is used
+ // (check if any reference is being used that is the lower spatial layer),
+ inter_layer_pred_on = CHECK_INTER_LAYER_PRED(LAST_FRAME) ||
+ CHECK_INTER_LAYER_PRED(GOLDEN_FRAME) ||
+ CHECK_INTER_LAYER_PRED(ALTREF_FRAME);
+ }
+ // If inter-layer prediction is on: we expect to pull up the quality from
+ // the lower spatial layer, so we can use a lower q.
+ if (cpi->svc.spatial_layer_id > 0 && inter_layer_pred_on) {
+ *q = (cpi->rc.worst_quality + *q) >> 1;
+ } else {
+ *q = (3 * cpi->rc.worst_quality + *q) >> 2;
+ // For screen content use the max-q set by the user to allow for less
+ // overshoot on slide changes.
+ if (is_screen_content) *q = cpi->rc.worst_quality;
+ }
// Adjust avg_frame_qindex, buffer_level, and rate correction factors, as
// these parameters will affect QP selection for subsequent frames. If they
// have settled down to a very different (low QP) state, then not adjusting
@@ -3566,8 +3622,10 @@ int av1_encodedframe_overshoot_cbr(AV1_COMP *cpi, int *q) {
rate_correction_factor;
}
// For temporal layers: reset the rate control parameters across all
- // temporal layers.
- if (cpi->svc.number_temporal_layers > 1) {
+ // temporal layers. Only do it for spatial enhancement layers when
+ // inter_layer_pred_on is not set (off).
+ if (cpi->svc.number_temporal_layers > 1 &&
+ (cpi->svc.spatial_layer_id == 0 || inter_layer_pred_on == 0)) {
SVC *svc = &cpi->svc;
for (int tl = 0; tl < svc->number_temporal_layers; ++tl) {
int sl = svc->spatial_layer_id;
diff --git a/third_party/aom/av1/encoder/ratectrl.h b/third_party/aom/av1/encoder/ratectrl.h
index 6802ad42d0..5121a909f4 100644
--- a/third_party/aom/av1/encoder/ratectrl.h
+++ b/third_party/aom/av1/encoder/ratectrl.h
@@ -249,6 +249,9 @@ typedef struct {
// signals if number of blocks with motion is high
int percent_blocks_with_motion;
+ // signals percentage of 16x16 blocks that are inactive, via active_maps
+ int percent_blocks_inactive;
+
// Maximum value of source sad across all blocks of frame.
uint64_t max_block_source_sad;
diff --git a/third_party/aom/av1/encoder/speed_features.c b/third_party/aom/av1/encoder/speed_features.c
index 63d69cadc5..256b6fc9eb 100644
--- a/third_party/aom/av1/encoder/speed_features.c
+++ b/third_party/aom/av1/encoder/speed_features.c
@@ -1177,6 +1177,7 @@ static void set_good_speed_features_framesize_independent(
sf->mv_sf.subpel_search_method = SUBPEL_TREE_PRUNED_MORE;
sf->gm_sf.prune_zero_mv_with_sse = 2;
+ sf->gm_sf.downsample_level = 1;
sf->part_sf.simple_motion_search_prune_agg =
allow_screen_content_tools ? SIMPLE_AGG_LVL0 : SIMPLE_AGG_LVL2;
@@ -1282,6 +1283,8 @@ static void set_good_speed_features_framesize_independent(
sf->hl_sf.disable_extra_sc_testing = 1;
sf->hl_sf.second_alt_ref_filtering = 0;
+ sf->gm_sf.downsample_level = 2;
+
sf->inter_sf.prune_inter_modes_based_on_tpl = boosted ? 0 : 3;
sf->inter_sf.selective_ref_frame = 6;
sf->inter_sf.prune_single_ref = is_boosted_arf2_bwd_type ? 0 : 2;
@@ -1465,6 +1468,7 @@ static void set_rt_speed_feature_framesize_dependent(const AV1_COMP *const cpi,
if (is_360p_or_larger) {
sf->part_sf.fixed_partition_size = BLOCK_32X32;
sf->rt_sf.use_fast_fixed_part = 1;
+ sf->mv_sf.subpel_force_stop = HALF_PEL;
}
sf->rt_sf.increase_source_sad_thresh = 1;
sf->rt_sf.part_early_exit_zeromv = 2;
@@ -1472,6 +1476,7 @@ static void set_rt_speed_feature_framesize_dependent(const AV1_COMP *const cpi,
for (int i = 0; i < BLOCK_SIZES; ++i) {
sf->rt_sf.intra_y_mode_bsize_mask_nrd[i] = INTRA_DC;
}
+ sf->rt_sf.hybrid_intra_pickmode = 0;
}
// Setting for SVC, or when the ref_frame_config control is
// used to set the reference structure.
@@ -1572,13 +1577,13 @@ static void set_rt_speed_feature_framesize_dependent(const AV1_COMP *const cpi,
sf->rt_sf.screen_content_cdef_filter_qindex_thresh = 80;
sf->rt_sf.part_early_exit_zeromv = 1;
sf->rt_sf.nonrd_aggressive_skip = 1;
+ sf->rt_sf.thresh_active_maps_skip_lf_cdef = 90;
}
if (speed >= 11) {
sf->rt_sf.skip_lf_screen = 2;
sf->rt_sf.skip_cdef_sb = 2;
sf->rt_sf.part_early_exit_zeromv = 2;
sf->rt_sf.prune_palette_nonrd = 1;
- sf->rt_sf.set_zeromv_skip_based_on_source_sad = 2;
sf->rt_sf.increase_color_thresh_palette = 0;
}
sf->rt_sf.use_nonrd_altref_frame = 0;
@@ -1974,6 +1979,7 @@ static AOM_INLINE void init_gm_sf(GLOBAL_MOTION_SPEED_FEATURES *gm_sf) {
gm_sf->prune_ref_frame_for_gm_search = 0;
gm_sf->prune_zero_mv_with_sse = 0;
gm_sf->disable_gm_search_based_on_stats = 0;
+ gm_sf->downsample_level = 0;
gm_sf->num_refinement_steps = GM_MAX_REFINEMENT_STEPS;
}
@@ -2270,6 +2276,7 @@ static AOM_INLINE void init_rt_sf(REAL_TIME_SPEED_FEATURES *rt_sf) {
rt_sf->part_early_exit_zeromv = 0;
rt_sf->sse_early_term_inter_search = EARLY_TERM_DISABLED;
rt_sf->skip_lf_screen = 0;
+ rt_sf->thresh_active_maps_skip_lf_cdef = 100;
rt_sf->sad_based_adp_altref_lag = 0;
rt_sf->partition_direct_merging = 0;
rt_sf->var_part_based_on_qidx = 0;
diff --git a/third_party/aom/av1/encoder/speed_features.h b/third_party/aom/av1/encoder/speed_features.h
index 60c000e4f4..d59cb38a71 100644
--- a/third_party/aom/av1/encoder/speed_features.h
+++ b/third_party/aom/av1/encoder/speed_features.h
@@ -587,6 +587,9 @@ typedef struct GLOBAL_MOTION_SPEED_FEATURES {
// GF group
int disable_gm_search_based_on_stats;
+ // Downsampling pyramid level to use for global motion estimation
+ int downsample_level;
+
// Number of refinement steps to apply after initial model generation
int num_refinement_steps;
} GLOBAL_MOTION_SPEED_FEATURES;
@@ -1771,6 +1774,10 @@ typedef struct REAL_TIME_SPEED_FEATURES {
// where rc->high_source_sad = 0 (no slide-changes).
int skip_lf_screen;
+ // Threshold on the active/inactive region percent to disable
+ // the loopfilter and cdef. Setting to 100 disables this feature.
+ int thresh_active_maps_skip_lf_cdef;
+
// For nonrd: early exit out of variance partition that sets the
// block size to superblock size, and sets mode to zeromv-last skip.
// 0: disabled
diff --git a/third_party/aom/av1/encoder/superres_scale.c b/third_party/aom/av1/encoder/superres_scale.c
index 3b47909b15..41225d55ae 100644
--- a/third_party/aom/av1/encoder/superres_scale.c
+++ b/third_party/aom/av1/encoder/superres_scale.c
@@ -404,7 +404,7 @@ void av1_superres_post_encode(AV1_COMP *cpi) {
assert(!is_lossless_requested(&cpi->oxcf.rc_cfg));
assert(!cm->features.all_lossless);
- av1_superres_upscale(cm, NULL, cpi->image_pyramid_levels);
+ av1_superres_upscale(cm, NULL, cpi->alloc_pyramid);
// If regular resizing is occurring the source will need to be downscaled to
// match the upscaled superres resolution. Otherwise the original source is
diff --git a/third_party/aom/av1/encoder/svc_layercontext.c b/third_party/aom/av1/encoder/svc_layercontext.c
index 2c99cb89b8..33da3afbd3 100644
--- a/third_party/aom/av1/encoder/svc_layercontext.c
+++ b/third_party/aom/av1/encoder/svc_layercontext.c
@@ -203,8 +203,10 @@ void av1_update_temporal_layer_framerate(AV1_COMP *const cpi) {
}
}
-static AOM_INLINE bool check_ref_is_low_spatial_res_super_frame(
- int ref_frame, const SVC *svc, const RTC_REF *rtc_ref) {
+bool av1_check_ref_is_low_spatial_res_super_frame(AV1_COMP *const cpi,
+ int ref_frame) {
+ SVC *svc = &cpi->svc;
+ RTC_REF *const rtc_ref = &cpi->ppi->rtc_ref;
int ref_frame_idx = rtc_ref->ref_idx[ref_frame - 1];
return rtc_ref->buffer_time_index[ref_frame_idx] == svc->current_superframe &&
rtc_ref->buffer_spatial_layer[ref_frame_idx] <=
@@ -253,13 +255,13 @@ void av1_restore_layer_context(AV1_COMP *const cpi) {
// previous spatial layer(s) at the same time (current_superframe).
if (rtc_ref->set_ref_frame_config && svc->force_zero_mode_spatial_ref &&
cpi->sf.rt_sf.use_nonrd_pick_mode) {
- if (check_ref_is_low_spatial_res_super_frame(LAST_FRAME, svc, rtc_ref)) {
+ if (av1_check_ref_is_low_spatial_res_super_frame(cpi, LAST_FRAME)) {
svc->skip_mvsearch_last = 1;
}
- if (check_ref_is_low_spatial_res_super_frame(GOLDEN_FRAME, svc, rtc_ref)) {
+ if (av1_check_ref_is_low_spatial_res_super_frame(cpi, GOLDEN_FRAME)) {
svc->skip_mvsearch_gf = 1;
}
- if (check_ref_is_low_spatial_res_super_frame(ALTREF_FRAME, svc, rtc_ref)) {
+ if (av1_check_ref_is_low_spatial_res_super_frame(cpi, ALTREF_FRAME)) {
svc->skip_mvsearch_altref = 1;
}
}
diff --git a/third_party/aom/av1/encoder/svc_layercontext.h b/third_party/aom/av1/encoder/svc_layercontext.h
index 93118be2d4..d56ea77791 100644
--- a/third_party/aom/av1/encoder/svc_layercontext.h
+++ b/third_party/aom/av1/encoder/svc_layercontext.h
@@ -223,6 +223,21 @@ void av1_update_layer_context_change_config(struct AV1_COMP *const cpi,
*/
void av1_update_temporal_layer_framerate(struct AV1_COMP *const cpi);
+/*!\brief Prior to check if reference is lower spatial layer at the same
+ * timestamp/superframe.
+ *
+ * \ingroup SVC
+ * \callgraph
+ * \callergraph
+ *
+ * \param[in] cpi Top level encoder structure
+ * \param[in] ref_frame Reference frame
+ *
+ * \return True if the ref_frame if lower spatial layer, otherwise false.
+ */
+bool av1_check_ref_is_low_spatial_res_super_frame(struct AV1_COMP *const cpi,
+ int ref_frame);
+
/*!\brief Prior to encoding the frame, set the layer context, for the current
layer to be encoded, to the cpi struct.
*
diff --git a/third_party/aom/av1/encoder/temporal_filter.c b/third_party/aom/av1/encoder/temporal_filter.c
index 7d4d25de6a..e8cc145030 100644
--- a/third_party/aom/av1/encoder/temporal_filter.c
+++ b/third_party/aom/av1/encoder/temporal_filter.c
@@ -463,12 +463,12 @@ static void tf_build_predictor(const YV12_BUFFER_CONFIG *ref_frame,
// Returns:
// Nothing will be returned. But the content to which `accum` and `pred`
// point will be modified.
-void tf_apply_temporal_filter_self(const YV12_BUFFER_CONFIG *ref_frame,
- const MACROBLOCKD *mbd,
- const BLOCK_SIZE block_size,
- const int mb_row, const int mb_col,
- const int num_planes, uint32_t *accum,
- uint16_t *count) {
+static void tf_apply_temporal_filter_self(const YV12_BUFFER_CONFIG *ref_frame,
+ const MACROBLOCKD *mbd,
+ const BLOCK_SIZE block_size,
+ const int mb_row, const int mb_col,
+ const int num_planes, uint32_t *accum,
+ uint16_t *count) {
// Block information.
const int mb_height = block_size_high[block_size];
const int mb_width = block_size_wide[block_size];
@@ -564,9 +564,10 @@ static INLINE void compute_square_diff(const uint8_t *ref, const int ref_offset,
// Returns:
// Nothing will be returned. But the content to which `luma_sse_sum` points
// will be modified.
-void compute_luma_sq_error_sum(uint32_t *square_diff, uint32_t *luma_sse_sum,
- int block_height, int block_width,
- int ss_x_shift, int ss_y_shift) {
+static void compute_luma_sq_error_sum(uint32_t *square_diff,
+ uint32_t *luma_sse_sum, int block_height,
+ int block_width, int ss_x_shift,
+ int ss_y_shift) {
for (int i = 0; i < block_height; ++i) {
for (int j = 0; j < block_width; ++j) {
for (int ii = 0; ii < (1 << ss_y_shift); ++ii) {
@@ -1456,7 +1457,7 @@ bool av1_tf_info_alloc(TEMPORAL_FILTER_INFO *tf_info, const AV1_COMP *cpi) {
oxcf->frm_dim_cfg.height, seq_params->subsampling_x,
seq_params->subsampling_y, seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels, cm->features.byte_alignment, NULL, NULL,
- NULL, cpi->image_pyramid_levels, 0)) {
+ NULL, cpi->alloc_pyramid, 0)) {
return false;
}
}
diff --git a/third_party/aom/av1/encoder/temporal_filter.h b/third_party/aom/av1/encoder/temporal_filter.h
index 6504b91b66..a40fb039b9 100644
--- a/third_party/aom/av1/encoder/temporal_filter.h
+++ b/third_party/aom/av1/encoder/temporal_filter.h
@@ -14,6 +14,8 @@
#include <stdbool.h>
+#include "aom_util/aom_pthread.h"
+
#ifdef __cplusplus
extern "C" {
#endif
diff --git a/third_party/aom/av1/encoder/tpl_model.c b/third_party/aom/av1/encoder/tpl_model.c
index ca60e4981e..86f5485a26 100644
--- a/third_party/aom/av1/encoder/tpl_model.c
+++ b/third_party/aom/av1/encoder/tpl_model.c
@@ -19,6 +19,7 @@
#include "config/aom_scale_rtcd.h"
#include "aom/aom_codec.h"
+#include "aom_util/aom_pthread.h"
#include "av1/common/av1_common_int.h"
#include "av1/common/enums.h"
@@ -193,7 +194,7 @@ void av1_setup_tpl_buffers(AV1_PRIMARY *const ppi,
&tpl_data->tpl_rec_pool[frame], width, height,
seq_params->subsampling_x, seq_params->subsampling_y,
seq_params->use_highbitdepth, tpl_data->border_in_pixels,
- byte_alignment, 0, alloc_y_plane_only))
+ byte_alignment, false, alloc_y_plane_only))
aom_internal_error(&ppi->error, AOM_CODEC_MEM_ERROR,
"Failed to allocate frame buffer");
}
diff --git a/third_party/aom/av1/encoder/tpl_model.h b/third_party/aom/av1/encoder/tpl_model.h
index bcd58216c5..0150c702f9 100644
--- a/third_party/aom/av1/encoder/tpl_model.h
+++ b/third_party/aom/av1/encoder/tpl_model.h
@@ -30,6 +30,7 @@ struct TPL_INFO;
#include "config/aom_config.h"
#include "aom_scale/yv12config.h"
+#include "aom_util/aom_pthread.h"
#include "av1/common/mv.h"
#include "av1/common/scale.h"
diff --git a/third_party/aom/av1/encoder/tune_butteraugli.c b/third_party/aom/av1/encoder/tune_butteraugli.c
index 92fc4b2a92..4381af6a8b 100644
--- a/third_party/aom/av1/encoder/tune_butteraugli.c
+++ b/third_party/aom/av1/encoder/tune_butteraugli.c
@@ -209,7 +209,7 @@ void av1_setup_butteraugli_source(AV1_COMP *cpi) {
if (dst->buffer_alloc_sz == 0) {
aom_alloc_frame_buffer(
dst, width, height, ss_x, ss_y, cm->seq_params->use_highbitdepth,
- cpi->oxcf.border_in_pixels, cm->features.byte_alignment, 0, 0);
+ cpi->oxcf.border_in_pixels, cm->features.byte_alignment, false, 0);
}
av1_copy_and_extend_frame(cpi->source, dst);
@@ -218,7 +218,7 @@ void av1_setup_butteraugli_source(AV1_COMP *cpi) {
aom_alloc_frame_buffer(
resized_dst, width / resize_factor, height / resize_factor, ss_x, ss_y,
cm->seq_params->use_highbitdepth, cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
}
if (!av1_resize_and_extend_frame_nonnormative(
cpi->source, resized_dst, bit_depth, av1_num_planes(cm))) {
@@ -244,7 +244,7 @@ void av1_setup_butteraugli_rdmult_and_restore_source(AV1_COMP *cpi, double K) {
aom_alloc_frame_buffer(
&resized_recon, width / resize_factor, height / resize_factor, ss_x, ss_y,
cm->seq_params->use_highbitdepth, cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
copy_img(&cpi->common.cur_frame->buf, &resized_recon, width / resize_factor,
height / resize_factor);
@@ -267,12 +267,12 @@ void av1_setup_butteraugli_rdmult(AV1_COMP *cpi) {
cpi->source = av1_realloc_and_scale_if_required(
cm, cpi->unscaled_source, &cpi->scaled_source, cm->features.interp_filter,
- 0, false, false, cpi->oxcf.border_in_pixels, cpi->image_pyramid_levels);
+ 0, false, false, cpi->oxcf.border_in_pixels, cpi->alloc_pyramid);
if (cpi->unscaled_last_source != NULL) {
cpi->last_source = av1_realloc_and_scale_if_required(
cm, cpi->unscaled_last_source, &cpi->scaled_last_source,
cm->features.interp_filter, 0, false, false, cpi->oxcf.border_in_pixels,
- cpi->image_pyramid_levels);
+ cpi->alloc_pyramid);
}
av1_setup_butteraugli_source(cpi);
diff --git a/third_party/aom/av1/encoder/tune_vmaf.c b/third_party/aom/av1/encoder/tune_vmaf.c
index 4e5ffa387c..91db3db726 100644
--- a/third_party/aom/av1/encoder/tune_vmaf.c
+++ b/third_party/aom/av1/encoder/tune_vmaf.c
@@ -288,10 +288,10 @@ static AOM_INLINE void gaussian_blur(const int bit_depth,
}
}
-static AOM_INLINE double cal_approx_vmaf(const AV1_COMP *const cpi,
- double source_variance,
- YV12_BUFFER_CONFIG *const source,
- YV12_BUFFER_CONFIG *const sharpened) {
+static AOM_INLINE double cal_approx_vmaf(
+ const AV1_COMP *const cpi, double source_variance,
+ const YV12_BUFFER_CONFIG *const source,
+ const YV12_BUFFER_CONFIG *const sharpened) {
const int bit_depth = cpi->td.mb.e_mbd.bd;
const bool cal_vmaf_neg =
cpi->oxcf.tune_cfg.tuning == AOM_TUNE_VMAF_NEG_MAX_GAIN;
@@ -305,11 +305,11 @@ static AOM_INLINE double cal_approx_vmaf(const AV1_COMP *const cpi,
}
static double find_best_frame_unsharp_amount_loop(
- const AV1_COMP *const cpi, YV12_BUFFER_CONFIG *const source,
- YV12_BUFFER_CONFIG *const blurred, YV12_BUFFER_CONFIG *const sharpened,
- double best_vmaf, const double baseline_variance,
- const double unsharp_amount_start, const double step_size,
- const int max_loop_count, const double max_amount) {
+ const AV1_COMP *const cpi, const YV12_BUFFER_CONFIG *const source,
+ const YV12_BUFFER_CONFIG *const blurred,
+ const YV12_BUFFER_CONFIG *const sharpened, double best_vmaf,
+ const double baseline_variance, const double unsharp_amount_start,
+ const double step_size, const int max_loop_count, const double max_amount) {
const double min_amount = 0.0;
int loop_count = 0;
double approx_vmaf = best_vmaf;
@@ -328,13 +328,11 @@ static double find_best_frame_unsharp_amount_loop(
return AOMMIN(max_amount, AOMMAX(unsharp_amount, min_amount));
}
-static double find_best_frame_unsharp_amount(const AV1_COMP *const cpi,
- YV12_BUFFER_CONFIG *const source,
- YV12_BUFFER_CONFIG *const blurred,
- const double unsharp_amount_start,
- const double step_size,
- const int max_loop_count,
- const double max_filter_amount) {
+static double find_best_frame_unsharp_amount(
+ const AV1_COMP *const cpi, const YV12_BUFFER_CONFIG *const source,
+ const YV12_BUFFER_CONFIG *const blurred, const double unsharp_amount_start,
+ const double step_size, const int max_loop_count,
+ const double max_filter_amount) {
const AV1_COMMON *const cm = &cpi->common;
const int width = source->y_width;
const int height = source->y_height;
@@ -343,7 +341,7 @@ static double find_best_frame_unsharp_amount(const AV1_COMP *const cpi,
aom_alloc_frame_buffer(
&sharpened, width, height, source->subsampling_x, source->subsampling_y,
cm->seq_params->use_highbitdepth, cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
const double baseline_variance = frame_average_variance(cpi, source);
double unsharp_amount;
@@ -376,7 +374,7 @@ static double find_best_frame_unsharp_amount(const AV1_COMP *const cpi,
}
void av1_vmaf_neg_preprocessing(AV1_COMP *const cpi,
- YV12_BUFFER_CONFIG *const source) {
+ const YV12_BUFFER_CONFIG *const source) {
const AV1_COMMON *const cm = &cpi->common;
const int bit_depth = cpi->td.mb.e_mbd.bd;
const int width = source->y_width;
@@ -395,7 +393,7 @@ void av1_vmaf_neg_preprocessing(AV1_COMP *const cpi,
aom_alloc_frame_buffer(
&blurred, width, height, source->subsampling_x, source->subsampling_y,
cm->seq_params->use_highbitdepth, cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
gaussian_blur(bit_depth, source, &blurred);
unsharp(cpi, source, &blurred, source, best_frame_unsharp_amount);
@@ -403,7 +401,7 @@ void av1_vmaf_neg_preprocessing(AV1_COMP *const cpi,
}
void av1_vmaf_frame_preprocessing(AV1_COMP *const cpi,
- YV12_BUFFER_CONFIG *const source) {
+ const YV12_BUFFER_CONFIG *const source) {
const AV1_COMMON *const cm = &cpi->common;
const int bit_depth = cpi->td.mb.e_mbd.bd;
const int width = source->y_width;
@@ -415,11 +413,11 @@ void av1_vmaf_frame_preprocessing(AV1_COMP *const cpi,
aom_alloc_frame_buffer(
&source_extended, width, height, source->subsampling_x,
source->subsampling_y, cm->seq_params->use_highbitdepth,
- cpi->oxcf.border_in_pixels, cm->features.byte_alignment, 0, 0);
+ cpi->oxcf.border_in_pixels, cm->features.byte_alignment, false, 0);
aom_alloc_frame_buffer(
&blurred, width, height, source->subsampling_x, source->subsampling_y,
cm->seq_params->use_highbitdepth, cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
av1_copy_and_extend_frame(source, &source_extended);
gaussian_blur(bit_depth, &source_extended, &blurred);
@@ -442,7 +440,7 @@ void av1_vmaf_frame_preprocessing(AV1_COMP *const cpi,
}
void av1_vmaf_blk_preprocessing(AV1_COMP *const cpi,
- YV12_BUFFER_CONFIG *const source) {
+ const YV12_BUFFER_CONFIG *const source) {
const AV1_COMMON *const cm = &cpi->common;
const int width = source->y_width;
const int height = source->y_height;
@@ -455,11 +453,11 @@ void av1_vmaf_blk_preprocessing(AV1_COMP *const cpi,
memset(&source_extended, 0, sizeof(source_extended));
aom_alloc_frame_buffer(
&blurred, width, height, ss_x, ss_y, cm->seq_params->use_highbitdepth,
- cpi->oxcf.border_in_pixels, cm->features.byte_alignment, 0, 0);
+ cpi->oxcf.border_in_pixels, cm->features.byte_alignment, false, 0);
aom_alloc_frame_buffer(&source_extended, width, height, ss_x, ss_y,
cm->seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
av1_copy_and_extend_frame(source, &source_extended);
gaussian_blur(bit_depth, &source_extended, &blurred);
@@ -495,11 +493,11 @@ void av1_vmaf_blk_preprocessing(AV1_COMP *const cpi,
aom_alloc_frame_buffer(&source_block, block_w, block_h, ss_x, ss_y,
cm->seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
aom_alloc_frame_buffer(&blurred_block, block_w, block_h, ss_x, ss_y,
cm->seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
for (int row = 0; row < num_rows; ++row) {
for (int col = 0; col < num_cols; ++col) {
@@ -622,7 +620,7 @@ void av1_set_mb_vmaf_rdmult_scaling(AV1_COMP *cpi) {
aom_alloc_frame_buffer(
&resized_source, y_width / resize_factor, y_height / resize_factor, ss_x,
ss_y, cm->seq_params->use_highbitdepth, cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
if (!av1_resize_and_extend_frame_nonnormative(
cpi->source, &resized_source, bit_depth, av1_num_planes(cm))) {
aom_internal_error(cm->error, AOM_CODEC_MEM_ERROR,
@@ -643,7 +641,7 @@ void av1_set_mb_vmaf_rdmult_scaling(AV1_COMP *cpi) {
aom_alloc_frame_buffer(&blurred, resized_y_width, resized_y_height, ss_x,
ss_y, cm->seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
gaussian_blur(bit_depth, &resized_source, &blurred);
YV12_BUFFER_CONFIG recon;
@@ -651,7 +649,7 @@ void av1_set_mb_vmaf_rdmult_scaling(AV1_COMP *cpi) {
aom_alloc_frame_buffer(&recon, resized_y_width, resized_y_height, ss_x, ss_y,
cm->seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
aom_yv12_copy_frame(&resized_source, &recon, 1);
VmafContext *vmaf_context;
@@ -830,15 +828,15 @@ static double calc_vmaf_motion_score(const AV1_COMP *const cpi,
aom_alloc_frame_buffer(&blurred_cur, y_width, y_height, ss_x, ss_y,
cm->seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
aom_alloc_frame_buffer(&blurred_last, y_width, y_height, ss_x, ss_y,
cm->seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
aom_alloc_frame_buffer(&blurred_next, y_width, y_height, ss_x, ss_y,
cm->seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
gaussian_blur(bit_depth, cur, &blurred_cur);
gaussian_blur(bit_depth, last, &blurred_last);
@@ -881,8 +879,8 @@ static double calc_vmaf_motion_score(const AV1_COMP *const cpi,
}
static AOM_INLINE void get_neighbor_frames(const AV1_COMP *const cpi,
- YV12_BUFFER_CONFIG **last,
- YV12_BUFFER_CONFIG **next) {
+ const YV12_BUFFER_CONFIG **last,
+ const YV12_BUFFER_CONFIG **next) {
const AV1_COMMON *const cm = &cpi->common;
const GF_GROUP *gf_group = &cpi->ppi->gf_group;
const int src_index =
@@ -920,7 +918,7 @@ int av1_get_vmaf_base_qindex(const AV1_COMP *const cpi, int current_qindex) {
if (approx_sse < sse_threshold || approx_dvmaf < vmaf_threshold) {
return current_qindex;
}
- YV12_BUFFER_CONFIG *cur_buf = cpi->source;
+ const YV12_BUFFER_CONFIG *cur_buf = cpi->source;
if (cm->show_frame == 0) {
const int src_index = gf_group->arf_src_offset[cpi->gf_frame_index];
struct lookahead_entry *cur_entry = av1_lookahead_peek(
@@ -929,7 +927,7 @@ int av1_get_vmaf_base_qindex(const AV1_COMP *const cpi, int current_qindex) {
}
assert(cur_buf);
- YV12_BUFFER_CONFIG *next_buf, *last_buf;
+ const YV12_BUFFER_CONFIG *next_buf, *last_buf;
get_neighbor_frames(cpi, &last_buf, &next_buf);
assert(last_buf);
@@ -954,8 +952,8 @@ int av1_get_vmaf_base_qindex(const AV1_COMP *const cpi, int current_qindex) {
static AOM_INLINE double cal_approx_score(
AV1_COMP *const cpi, double src_variance, double new_variance,
- double src_score, YV12_BUFFER_CONFIG *const src,
- YV12_BUFFER_CONFIG *const recon_sharpened) {
+ double src_score, const YV12_BUFFER_CONFIG *const src,
+ const YV12_BUFFER_CONFIG *const recon_sharpened) {
double score;
const uint32_t bit_depth = cpi->td.mb.e_mbd.bd;
const bool cal_vmaf_neg =
@@ -967,11 +965,12 @@ static AOM_INLINE double cal_approx_score(
static double find_best_frame_unsharp_amount_loop_neg(
AV1_COMP *const cpi, double src_variance, double base_score,
- YV12_BUFFER_CONFIG *const src, YV12_BUFFER_CONFIG *const recon,
- YV12_BUFFER_CONFIG *const ref, YV12_BUFFER_CONFIG *const src_blurred,
- YV12_BUFFER_CONFIG *const recon_blurred,
- YV12_BUFFER_CONFIG *const src_sharpened,
- YV12_BUFFER_CONFIG *const recon_sharpened, FULLPEL_MV *mvs,
+ const YV12_BUFFER_CONFIG *const src, const YV12_BUFFER_CONFIG *const recon,
+ const YV12_BUFFER_CONFIG *const ref,
+ const YV12_BUFFER_CONFIG *const src_blurred,
+ const YV12_BUFFER_CONFIG *const recon_blurred,
+ const YV12_BUFFER_CONFIG *const src_sharpened,
+ const YV12_BUFFER_CONFIG *const recon_sharpened, FULLPEL_MV *mvs,
double best_score, const double unsharp_amount_start,
const double step_size, const int max_loop_count, const double max_amount) {
const double min_amount = 0.0;
@@ -999,8 +998,8 @@ static double find_best_frame_unsharp_amount_loop_neg(
}
static double find_best_frame_unsharp_amount_neg(
- AV1_COMP *const cpi, YV12_BUFFER_CONFIG *const src,
- YV12_BUFFER_CONFIG *const recon, YV12_BUFFER_CONFIG *const ref,
+ AV1_COMP *const cpi, const YV12_BUFFER_CONFIG *const src,
+ const YV12_BUFFER_CONFIG *const recon, const YV12_BUFFER_CONFIG *const ref,
double base_score, const double unsharp_amount_start,
const double step_size, const int max_loop_count,
const double max_filter_amount) {
@@ -1023,18 +1022,18 @@ static double find_best_frame_unsharp_amount_neg(
aom_alloc_frame_buffer(&recon_sharpened, width, height, ss_x, ss_y,
cm->seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
aom_alloc_frame_buffer(&src_sharpened, width, height, ss_x, ss_y,
cm->seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
aom_alloc_frame_buffer(&recon_blurred, width, height, ss_x, ss_y,
cm->seq_params->use_highbitdepth,
cpi->oxcf.border_in_pixels,
- cm->features.byte_alignment, 0, 0);
+ cm->features.byte_alignment, false, 0);
aom_alloc_frame_buffer(
&src_blurred, width, height, ss_x, ss_y, cm->seq_params->use_highbitdepth,
- cpi->oxcf.border_in_pixels, cm->features.byte_alignment, 0, 0);
+ cpi->oxcf.border_in_pixels, cm->features.byte_alignment, false, 0);
gaussian_blur(bit_depth, recon, &recon_blurred);
gaussian_blur(bit_depth, src, &src_blurred);
@@ -1076,8 +1075,8 @@ static double find_best_frame_unsharp_amount_neg(
}
void av1_update_vmaf_curve(AV1_COMP *cpi) {
- YV12_BUFFER_CONFIG *source = cpi->source;
- YV12_BUFFER_CONFIG *recon = &cpi->common.cur_frame->buf;
+ const YV12_BUFFER_CONFIG *source = cpi->source;
+ const YV12_BUFFER_CONFIG *recon = &cpi->common.cur_frame->buf;
const int bit_depth = cpi->td.mb.e_mbd.bd;
const GF_GROUP *const gf_group = &cpi->ppi->gf_group;
const int layer_depth =
@@ -1099,7 +1098,7 @@ void av1_update_vmaf_curve(AV1_COMP *cpi) {
}
if (cpi->oxcf.tune_cfg.tuning == AOM_TUNE_VMAF_NEG_MAX_GAIN) {
- YV12_BUFFER_CONFIG *last, *next;
+ const YV12_BUFFER_CONFIG *last, *next;
get_neighbor_frames(cpi, &last, &next);
double best_unsharp_amount_start =
get_layer_value(cpi->vmaf_info.last_frame_unsharp_amount, layer_depth);
diff --git a/third_party/aom/av1/encoder/tune_vmaf.h b/third_party/aom/av1/encoder/tune_vmaf.h
index a04a29e6fe..404fd1029a 100644
--- a/third_party/aom/av1/encoder/tune_vmaf.h
+++ b/third_party/aom/av1/encoder/tune_vmaf.h
@@ -43,13 +43,13 @@ typedef struct {
struct AV1_COMP;
void av1_vmaf_blk_preprocessing(struct AV1_COMP *cpi,
- YV12_BUFFER_CONFIG *source);
+ const YV12_BUFFER_CONFIG *source);
void av1_vmaf_frame_preprocessing(struct AV1_COMP *cpi,
- YV12_BUFFER_CONFIG *source);
+ const YV12_BUFFER_CONFIG *source);
void av1_vmaf_neg_preprocessing(struct AV1_COMP *cpi,
- YV12_BUFFER_CONFIG *source);
+ const YV12_BUFFER_CONFIG *source);
void av1_set_mb_vmaf_rdmult_scaling(struct AV1_COMP *cpi);
diff --git a/third_party/aom/av1/encoder/tx_search.c b/third_party/aom/av1/encoder/tx_search.c
index 7292c01191..5dcc08c0ff 100644
--- a/third_party/aom/av1/encoder/tx_search.c
+++ b/third_party/aom/av1/encoder/tx_search.c
@@ -1109,13 +1109,11 @@ static INLINE void dist_block_tx_domain(MACROBLOCK *x, int plane, int block,
*out_sse = RIGHT_SIGNED_SHIFT(this_sse, shift);
}
-uint16_t prune_txk_type_separ(const AV1_COMP *cpi, MACROBLOCK *x, int plane,
- int block, TX_SIZE tx_size, int blk_row,
- int blk_col, BLOCK_SIZE plane_bsize, int *txk_map,
- int16_t allowed_tx_mask, int prune_factor,
- const TXB_CTX *const txb_ctx,
- int reduced_tx_set_used, int64_t ref_best_rd,
- int num_sel) {
+static uint16_t prune_txk_type_separ(
+ const AV1_COMP *cpi, MACROBLOCK *x, int plane, int block, TX_SIZE tx_size,
+ int blk_row, int blk_col, BLOCK_SIZE plane_bsize, int *txk_map,
+ int16_t allowed_tx_mask, int prune_factor, const TXB_CTX *const txb_ctx,
+ int reduced_tx_set_used, int64_t ref_best_rd, int num_sel) {
const AV1_COMMON *cm = &cpi->common;
MACROBLOCKD *xd = &x->e_mbd;
@@ -1255,11 +1253,12 @@ uint16_t prune_txk_type_separ(const AV1_COMP *cpi, MACROBLOCK *x, int plane,
return prune;
}
-uint16_t prune_txk_type(const AV1_COMP *cpi, MACROBLOCK *x, int plane,
- int block, TX_SIZE tx_size, int blk_row, int blk_col,
- BLOCK_SIZE plane_bsize, int *txk_map,
- uint16_t allowed_tx_mask, int prune_factor,
- const TXB_CTX *const txb_ctx, int reduced_tx_set_used) {
+static uint16_t prune_txk_type(const AV1_COMP *cpi, MACROBLOCK *x, int plane,
+ int block, TX_SIZE tx_size, int blk_row,
+ int blk_col, BLOCK_SIZE plane_bsize,
+ int *txk_map, uint16_t allowed_tx_mask,
+ int prune_factor, const TXB_CTX *const txb_ctx,
+ int reduced_tx_set_used) {
const AV1_COMMON *cm = &cpi->common;
MACROBLOCKD *xd = &x->e_mbd;
int tx_type;
diff --git a/third_party/aom/av1/encoder/x86/av1_fwd_txfm_sse2.c b/third_party/aom/av1/encoder/x86/av1_fwd_txfm_sse2.c
index a4def754b0..31cc37db7a 100644
--- a/third_party/aom/av1/encoder/x86/av1_fwd_txfm_sse2.c
+++ b/third_party/aom/av1/encoder/x86/av1_fwd_txfm_sse2.c
@@ -2638,6 +2638,11 @@ void av1_lowbd_fwd_txfm2d_16x64_sse2(const int16_t *input, int32_t *output,
}
}
+// Include top-level function only for 32-bit x86, to support Valgrind.
+// For normal use, we require SSE4.1, so av1_lowbd_fwd_txfm_sse4_1 will be used
+// instead of this function. However, 32-bit Valgrind does not support SSE4.1,
+// so we include a fallback to SSE2 to improve performance
+#if AOM_ARCH_X86
static FwdTxfm2dFunc fwd_txfm2d_func_ls[TX_SIZES_ALL] = {
av1_lowbd_fwd_txfm2d_4x4_sse2, // 4x4 transform
av1_lowbd_fwd_txfm2d_8x8_sse2, // 8x8 transform
@@ -2671,3 +2676,4 @@ void av1_lowbd_fwd_txfm_sse2(const int16_t *src_diff, tran_low_t *coeff,
fwd_txfm2d_func(src_diff, coeff, diff_stride, txfm_param->tx_type,
txfm_param->bd);
}
+#endif // AOM_ARCH_X86
diff --git a/third_party/aom/av1/encoder/x86/cnn_avx2.c b/third_party/aom/av1/encoder/x86/cnn_avx2.c
index ee93b3d5a0..9c26a56641 100644
--- a/third_party/aom/av1/encoder/x86/cnn_avx2.c
+++ b/third_party/aom/av1/encoder/x86/cnn_avx2.c
@@ -466,7 +466,7 @@ static INLINE void cnn_convolve_no_maxpool_padding_valid_layer2_avx2(
// As per the layer config set by av1_intra_mode_cnn_partition_cnn_config,
// the filter_width and filter_height are equal to 2 for layer >= 1. So
// convolution happens at 2x2 for layer >= 1.
-void cnn_convolve_no_maxpool_padding_valid_2x2_avx2(
+static void cnn_convolve_no_maxpool_padding_valid_2x2_avx2(
const float **input, int in_width, int in_height, int in_stride,
const CNN_LAYER_CONFIG *const layer_config, float **output, int out_stride,
int start_idx, const int cstep, const int channel_step) {
diff --git a/third_party/aom/build/cmake/aom_config_defaults.cmake b/third_party/aom/build/cmake/aom_config_defaults.cmake
index da7de4b0f4..980dfb9327 100644
--- a/third_party/aom/build/cmake/aom_config_defaults.cmake
+++ b/third_party/aom/build/cmake/aom_config_defaults.cmake
@@ -37,6 +37,7 @@ set_aom_detect_var(HAVE_NEON_DOTPROD 0
set_aom_detect_var(HAVE_NEON_I8MM 0
"Enables Armv8.2-A Neon i8mm intrinsics optimizations.")
set_aom_detect_var(HAVE_SVE 0 "Enables Armv8.2-A SVE intrinsics optimizations.")
+set_aom_detect_var(HAVE_SVE2 0 "Enables Armv9-A SVE2 intrinsics optimizations.")
# PPC feature flags.
set_aom_detect_var(HAVE_VSX 0 "Enables VSX optimizations.")
@@ -84,6 +85,9 @@ set_aom_config_var(CONFIG_AV1_TEMPORAL_DENOISING 0
set_aom_config_var(CONFIG_MULTITHREAD 1 "Multithread support.")
set_aom_config_var(CONFIG_OS_SUPPORT 0 "Internal flag.")
set_aom_config_var(CONFIG_PIC 0 "Build with PIC enabled.")
+set_aom_config_var(CONFIG_QUANT_MATRIX 1
+ "Build with quantization matrices for AV1 encoder."
+ "AV1 decoder is always built with quantization matrices.")
set_aom_config_var(CONFIG_REALTIME_ONLY 0
"Build for RTC-only. See aomcx.h for all disabled features.")
set_aom_config_var(CONFIG_RUNTIME_CPU_DETECT 1 "Runtime CPU detection support.")
@@ -209,6 +213,8 @@ set_aom_option_var(
"Enables Armv8.2-A Neon i8mm optimizations on AArch64 targets." ON)
set_aom_option_var(ENABLE_SVE
"Enables Armv8.2-A SVE optimizations on AArch64 targets." ON)
+set_aom_option_var(ENABLE_SVE2
+ "Enables Armv9-A SVE2 optimizations on AArch64 targets." ON)
# VSX intrinsics flags.
set_aom_option_var(ENABLE_VSX "Enables VSX optimizations on PowerPC targets."
diff --git a/third_party/aom/build/cmake/aom_configure.cmake b/third_party/aom/build/cmake/aom_configure.cmake
index 917e7cac5d..304d90d1e1 100644
--- a/third_party/aom/build/cmake/aom_configure.cmake
+++ b/third_party/aom/build/cmake/aom_configure.cmake
@@ -320,6 +320,10 @@ else()
# minimum supported C++ version. If Clang is using this Standard Library
# implementation, it cannot target C++11.
require_cxx_flag_nomsvc("-std=c++14" YES)
+ elseif(CYGWIN AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ # The GNU C++ compiler in Cygwin needs the -std=gnu++11 flag to make the
+ # POSIX function declarations visible in the Standard C Library headers.
+ require_cxx_flag_nomsvc("-std=gnu++11" YES)
else()
require_cxx_flag_nomsvc("-std=c++11" YES)
endif()
@@ -393,6 +397,13 @@ else()
endif()
add_compiler_flag_if_supported("-D_LARGEFILE_SOURCE")
add_compiler_flag_if_supported("-D_FILE_OFFSET_BITS=64")
+
+ # Do not allow implicit vector type conversions on Clang builds (this is
+ # already the default on GCC builds).
+ if(CMAKE_C_COMPILER_ID MATCHES "Clang")
+ # Clang 8.0.1 (in Cygwin) doesn't support -flax-vector-conversions=none.
+ add_compiler_flag_if_supported("-flax-vector-conversions=none")
+ endif()
endif()
# Prior to r23, or with ANDROID_USE_LEGACY_TOOLCHAIN_FILE set,
diff --git a/third_party/aom/build/cmake/compiler_flags.cmake b/third_party/aom/build/cmake/compiler_flags.cmake
index f008b964f5..3afcd50b5c 100644
--- a/third_party/aom/build/cmake/compiler_flags.cmake
+++ b/third_party/aom/build/cmake/compiler_flags.cmake
@@ -176,11 +176,11 @@ function(require_cxx_flag cxx_flag update_cxx_flags)
endif()
unset(HAVE_CXX_FLAG CACHE)
- message("Checking C compiler flag support for: " ${cxx_flag})
+ message("Checking C++ compiler flag support for: " ${cxx_flag})
check_cxx_compiler_flag("${cxx_flag}" HAVE_CXX_FLAG)
if(NOT HAVE_CXX_FLAG)
message(
- FATAL_ERROR "${PROJECT_NAME} requires support for C flag: ${cxx_flag}.")
+ FATAL_ERROR "${PROJECT_NAME} requires support for C++ flag: ${cxx_flag}.")
endif()
if(NOT "${AOM_EXE_LINKER_FLAGS}" STREQUAL "")
diff --git a/third_party/aom/build/cmake/cpu.cmake b/third_party/aom/build/cmake/cpu.cmake
index a9b7a67070..489dbcbf44 100644
--- a/third_party/aom/build/cmake/cpu.cmake
+++ b/third_party/aom/build/cmake/cpu.cmake
@@ -14,11 +14,12 @@ if("${AOM_TARGET_CPU}" STREQUAL "arm64")
set(AOM_ARCH_AARCH64 1)
set(RTCD_ARCH_ARM "yes")
- set(ARM64_FLAVORS "NEON;ARM_CRC32;NEON_DOTPROD;NEON_I8MM;SVE")
+ set(ARM64_FLAVORS "NEON;ARM_CRC32;NEON_DOTPROD;NEON_I8MM;SVE;SVE2")
set(AOM_ARM_CRC32_DEFAULT_FLAG "-march=armv8-a+crc")
set(AOM_NEON_DOTPROD_DEFAULT_FLAG "-march=armv8.2-a+dotprod")
set(AOM_NEON_I8MM_DEFAULT_FLAG "-march=armv8.2-a+dotprod+i8mm")
set(AOM_SVE_DEFAULT_FLAG "-march=armv8.2-a+dotprod+i8mm+sve")
+ set(AOM_SVE2_DEFAULT_FLAG "-march=armv9-a+sve2") # SVE2 is a v9-only feature
# Check that the compiler flag to enable each flavor is supported by the
# compiler. This may not be the case for new architecture features on old
@@ -26,16 +27,27 @@ if("${AOM_TARGET_CPU}" STREQUAL "arm64")
foreach(flavor ${ARM64_FLAVORS})
if(ENABLE_${flavor} AND NOT DEFINED AOM_${flavor}_FLAG)
set(AOM_${flavor}_FLAG "${AOM_${flavor}_DEFAULT_FLAG}")
+ string(TOLOWER "${flavor}" flavor_lower)
+
+ # Do not use check_c_compiler_flag here since the regex used to match
+ # against stderr does not recognise the "invalid feature modifier" error
+ # produced by certain versions of GCC, leading to the feature being
+ # incorrectly marked as available.
+ set(OLD_CMAKE_REQURED_FLAGS ${CMAKE_REQUIRED_FLAGS})
+ set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${AOM_${flavor}_FLAG}")
unset(FLAG_SUPPORTED)
- check_c_compiler_flag("${AOM_${flavor}_FLAG}" FLAG_SUPPORTED)
+ aom_check_source_compiles("arm_feature_flag_${flavor_lower}_available"
+ "static void function(void) {}" FLAG_SUPPORTED)
+ set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQURED_FLAGS})
+
if(NOT ${FLAG_SUPPORTED})
set(ENABLE_${flavor} 0)
endif()
endif()
endforeach()
- # SVE requires that the Neon-SVE bridge header is also available.
- if(ENABLE_SVE)
+ # SVE and SVE2 require that the Neon-SVE bridge header is also available.
+ if(ENABLE_SVE OR ENABLE_SVE2)
set(OLD_CMAKE_REQURED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} ${AOM_SVE_FLAG}")
aom_check_source_compiles("arm_neon_sve_bridge_available" "
@@ -47,6 +59,7 @@ if("${AOM_TARGET_CPU}" STREQUAL "arm64")
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQURED_FLAGS})
if(HAVE_SVE_HEADERS EQUAL 0)
set(ENABLE_SVE 0)
+ set(ENABLE_SVE2 0)
endif()
endif()
diff --git a/third_party/aom/build/cmake/rtcd.pl b/third_party/aom/build/cmake/rtcd.pl
index 1cf52f076c..f4a70842d0 100755
--- a/third_party/aom/build/cmake/rtcd.pl
+++ b/third_party/aom/build/cmake/rtcd.pl
@@ -392,7 +392,7 @@ if ($opts{arch} eq 'x86') {
@ALL_ARCHS = filter(qw/neon/);
arm;
} elsif ($opts{arch} eq 'arm64' ) {
- @ALL_ARCHS = filter(qw/neon arm_crc32 neon_dotprod neon_i8mm sve/);
+ @ALL_ARCHS = filter(qw/neon arm_crc32 neon_dotprod neon_i8mm sve sve2/);
@REQUIRES = filter(qw/neon/);
&require(@REQUIRES);
arm;
diff --git a/third_party/aom/doc/dev_guide/av1_encoder.dox b/third_party/aom/doc/dev_guide/av1_encoder.dox
index 0f7e8f87e2..a40b58933b 100644
--- a/third_party/aom/doc/dev_guide/av1_encoder.dox
+++ b/third_party/aom/doc/dev_guide/av1_encoder.dox
@@ -1313,6 +1313,34 @@ Related functions:
All the related functions are listed in \ref coefficient_coding.
+\section architecture_simd SIMD usage
+
+In order to efficiently encode video on modern platforms, it is necessary to
+implement optimized versions of many core encoding and decoding functions using
+architecture-specific SIMD instructions.
+
+Functions which have optimized implementations will have multiple variants
+in the code, each suffixed with the name of the appropriate instruction set.
+There will additionally be an `_c` version, which acts as a reference
+implementation which the SIMD variants can be tested against.
+
+As different machines with the same nominal architecture may support different
+subsets of SIMD instructions, we have dynamic CPU detection logic which chooses
+the appropriate functions to use at run time. This process is handled by
+`build/cmake/rtcd.pl`, with function definitions in the files
+`*_rtcd_defs.pl` elsewhere in the codebase.
+
+Currently SIMD is supported on the following platforms:
+
+- x86: Requires SSE4.1 or above
+
+- Arm: Requires Neon (Armv7-A and above)
+
+We aim to provide implementations of all performance-critical functions which
+are compatible with the instruction sets listed above. Additional SIMD
+extensions (e.g. AVX on x86, SVE on Arm) are also used to provide even
+greater performance where available.
+
*/
/*!\defgroup encoder_algo Encoder Algorithm
diff --git a/third_party/aom/examples/av1_dec_fuzzer.cc b/third_party/aom/examples/av1_dec_fuzzer.cc
index 9b9a0b9cb6..e9388b7062 100644
--- a/third_party/aom/examples/av1_dec_fuzzer.cc
+++ b/third_party/aom/examples/av1_dec_fuzzer.cc
@@ -34,6 +34,14 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
return 0;
}
+ // Abusing the four unused bytes at the end of the IVF file header as a source
+ // of random bits.
+ unsigned int tile_mode = (data[IVF_FILE_HDR_SZ - 1] & 2) != 0;
+ unsigned int ext_tile_debug = (data[IVF_FILE_HDR_SZ - 1] & 4) != 0;
+ unsigned int is_annexb = (data[IVF_FILE_HDR_SZ - 1] & 8) != 0;
+ int output_all_layers = (data[IVF_FILE_HDR_SZ - 1] & 0x10) != 0;
+ int operating_point = data[IVF_FILE_HDR_SZ - 2] & 0x1F;
+
aom_codec_iface_t *codec_interface = aom_codec_av1_dx();
aom_codec_ctx_t codec;
// Set thread count in the range [1, 64].
@@ -42,6 +50,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (aom_codec_dec_init(&codec, codec_interface, &cfg, 0)) {
return 0;
}
+ AOM_CODEC_CONTROL_TYPECHECKED(&codec, AV1_SET_TILE_MODE, tile_mode);
+ AOM_CODEC_CONTROL_TYPECHECKED(&codec, AV1D_EXT_TILE_DEBUG, ext_tile_debug);
+ AOM_CODEC_CONTROL_TYPECHECKED(&codec, AV1D_SET_IS_ANNEXB, is_annexb);
+ AOM_CODEC_CONTROL_TYPECHECKED(&codec, AV1D_SET_OUTPUT_ALL_LAYERS,
+ output_all_layers);
+ AOM_CODEC_CONTROL_TYPECHECKED(&codec, AV1D_SET_OPERATING_POINT,
+ operating_point);
data += IVF_FILE_HDR_SZ;
size -= IVF_FILE_HDR_SZ;
diff --git a/third_party/aom/examples/svc_encoder_rtc.cc b/third_party/aom/examples/svc_encoder_rtc.cc
index 2c041081e5..c751e9868c 100644
--- a/third_party/aom/examples/svc_encoder_rtc.cc
+++ b/third_party/aom/examples/svc_encoder_rtc.cc
@@ -1442,6 +1442,35 @@ static int qindex_to_quantizer(int qindex) {
return 63;
}
+static void set_active_map(const aom_codec_enc_cfg_t *cfg,
+ aom_codec_ctx_t *codec, int frame_cnt) {
+ aom_active_map_t map = { 0, 0, 0 };
+
+ map.rows = (cfg->g_h + 15) / 16;
+ map.cols = (cfg->g_w + 15) / 16;
+
+ map.active_map = (uint8_t *)malloc(map.rows * map.cols);
+ if (!map.active_map) die("Failed to allocate active map");
+
+ // Example map for testing.
+ for (unsigned int i = 0; i < map.rows; ++i) {
+ for (unsigned int j = 0; j < map.cols; ++j) {
+ int index = map.cols * i + j;
+ map.active_map[index] = 1;
+ if (frame_cnt < 300) {
+ if (i < map.rows / 2 && j < map.cols / 2) map.active_map[index] = 0;
+ } else if (frame_cnt >= 300) {
+ if (i < map.rows / 2 && j >= map.cols / 2) map.active_map[index] = 0;
+ }
+ }
+ }
+
+ if (aom_codec_control(codec, AOME_SET_ACTIVEMAP, &map))
+ die_codec(codec, "Failed to set active map");
+
+ free(map.active_map);
+}
+
int main(int argc, const char **argv) {
AppInput app_input;
AvxVideoWriter *outfile[AOM_MAX_LAYERS] = { NULL };
@@ -1494,6 +1523,9 @@ int main(int argc, const char **argv) {
// Flag to test setting speed per layer.
const int test_speed_per_layer = 0;
+ // Flag for testing active maps.
+ const int test_active_maps = 0;
+
/* Setup default input stream settings */
app_input.input_ctx.framerate.numerator = 30;
app_input.input_ctx.framerate.denominator = 1;
@@ -1874,6 +1906,8 @@ int main(int argc, const char **argv) {
}
}
+ if (test_active_maps) set_active_map(&cfg, &codec, frame_cnt);
+
// Do the layer encode.
aom_usec_timer_start(&timer);
if (aom_codec_encode(&codec, frame_avail ? &raw : NULL, pts, 1, flags))
diff --git a/third_party/aom/libs.doxy_template b/third_party/aom/libs.doxy_template
index ba77751a50..01da81ac0c 100644
--- a/third_party/aom/libs.doxy_template
+++ b/third_party/aom/libs.doxy_template
@@ -1219,15 +1219,6 @@ HTML_COLORSTYLE_SAT = 100
HTML_COLORSTYLE_GAMMA = 80
-# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting this
-# to YES can help to show when doxygen was last run and thus if the
-# documentation is up to date.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_TIMESTAMP = NO
-
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
# documentation will contain a main index with vertical navigation menus that
# are dynamically created via Javascript. If disabled, the navigation index will
@@ -1509,17 +1500,6 @@ EXT_LINKS_IN_WINDOW = NO
FORMULA_FONTSIZE = 10
-# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are not
-# supported properly for IE 6.0, but are supported on all modern browsers.
-#
-# Note that when changing this option you need to delete any form_*.png files in
-# the HTML output directory before the changes have effect.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_TRANSPARENT = YES
-
# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
# https://www.mathjax.org) which uses client side Javascript for the rendering
# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
@@ -1820,14 +1800,6 @@ LATEX_HIDE_INDICES = NO
LATEX_BIB_STYLE = plain
-# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
-# page will contain the date and time when the page was generated. Setting this
-# to NO can help when comparing the output of multiple runs.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_TIMESTAMP = NO
-
# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
# path from which the emoji images will be read. If a relative path is entered,
# it will be relative to the LATEX_OUTPUT directory. If left blank the
@@ -2167,23 +2139,6 @@ HAVE_DOT = NO
DOT_NUM_THREADS = 0
-# When you want a differently looking font in the dot files that doxygen
-# generates you can specify the font name using DOT_FONTNAME. You need to make
-# sure dot is able to find the font, which can be done by putting it in a
-# standard location or by setting the DOTFONTPATH environment variable or by
-# setting DOT_FONTPATH to the directory containing the font.
-# The default value is: Helvetica.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTNAME = Helvetica
-
-# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
-# dot graphs.
-# Minimum value: 4, maximum value: 24, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTSIZE = 10
-
# By default doxygen will tell dot to use the default font as specified with
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
# the path where dot can find it using this tag.
@@ -2401,18 +2356,6 @@ DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not seem
-# to support this out of the box.
-#
-# Warning: Depending on the platform used, enabling this option may lead to
-# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
-# read).
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_TRANSPARENT = NO
-
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
diff --git a/third_party/aom/test/active_map_test.cc b/third_party/aom/test/active_map_test.cc
index 979ee6b8b3..de16541281 100644
--- a/third_party/aom/test/active_map_test.cc
+++ b/third_party/aom/test/active_map_test.cc
@@ -19,8 +19,10 @@
namespace {
+// Params: test mode, speed, aq_mode and screen_content mode.
class ActiveMapTest
- : public ::libaom_test::CodecTestWith2Params<libaom_test::TestMode, int>,
+ : public ::libaom_test::CodecTestWith4Params<libaom_test::TestMode, int,
+ int, int>,
public ::libaom_test::EncoderTest {
protected:
static const int kWidth = 208;
@@ -32,6 +34,8 @@ class ActiveMapTest
void SetUp() override {
InitializeConfig(GET_PARAM(1));
cpu_used_ = GET_PARAM(2);
+ aq_mode_ = GET_PARAM(3);
+ screen_mode_ = GET_PARAM(4);
}
void PreEncodeFrameHook(::libaom_test::VideoSource *video,
@@ -41,6 +45,9 @@ class ActiveMapTest
encoder->Control(AV1E_SET_ALLOW_WARPED_MOTION, 0);
encoder->Control(AV1E_SET_ENABLE_GLOBAL_MOTION, 0);
encoder->Control(AV1E_SET_ENABLE_OBMC, 0);
+ encoder->Control(AV1E_SET_AQ_MODE, aq_mode_);
+ encoder->Control(AV1E_SET_TUNE_CONTENT, screen_mode_);
+ if (screen_mode_) encoder->Control(AV1E_SET_ENABLE_PALETTE, 1);
} else if (video->frame() == 3) {
aom_active_map_t map = aom_active_map_t();
/* clang-format off */
@@ -79,19 +86,22 @@ class ActiveMapTest
cfg_.g_pass = AOM_RC_ONE_PASS;
cfg_.rc_end_usage = AOM_CBR;
cfg_.kf_max_dist = 90000;
- ::libaom_test::I420VideoSource video("hantro_odd.yuv", kWidth, kHeight, 30,
- 1, 0, 20);
+ ::libaom_test::I420VideoSource video("hantro_odd.yuv", kWidth, kHeight, 100,
+ 1, 0, 100);
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
}
int cpu_used_;
+ int aq_mode_;
+ int screen_mode_;
};
TEST_P(ActiveMapTest, Test) { DoTest(); }
AV1_INSTANTIATE_TEST_SUITE(ActiveMapTest,
::testing::Values(::libaom_test::kRealTime),
- ::testing::Range(5, 9));
+ ::testing::Range(5, 12), ::testing::Values(0, 3),
+ ::testing::Values(0, 1));
} // namespace
diff --git a/third_party/aom/test/aom_image_test.cc b/third_party/aom/test/aom_image_test.cc
index ad48e73e3d..03f4373f35 100644
--- a/third_party/aom/test/aom_image_test.cc
+++ b/third_party/aom/test/aom_image_test.cc
@@ -47,6 +47,16 @@ TEST(AomImageTest, AomImgSetRectOverflow) {
0);
}
+TEST(AomImageTest, AomImgAllocNone) {
+ const int kWidth = 128;
+ const int kHeight = 128;
+
+ aom_image_t img;
+ aom_img_fmt_t format = AOM_IMG_FMT_NONE;
+ unsigned int align = 32;
+ ASSERT_EQ(aom_img_alloc(&img, format, kWidth, kHeight, align), nullptr);
+}
+
TEST(AomImageTest, AomImgAllocNv12) {
const int kWidth = 128;
const int kHeight = 128;
@@ -54,7 +64,7 @@ TEST(AomImageTest, AomImgAllocNv12) {
aom_image_t img;
aom_img_fmt_t format = AOM_IMG_FMT_NV12;
unsigned int align = 32;
- EXPECT_NE(aom_img_alloc(&img, format, kWidth, kHeight, align), nullptr);
+ EXPECT_EQ(aom_img_alloc(&img, format, kWidth, kHeight, align), &img);
EXPECT_EQ(img.stride[AOM_PLANE_U], img.stride[AOM_PLANE_Y]);
EXPECT_EQ(img.stride[AOM_PLANE_V], 0);
EXPECT_EQ(img.planes[AOM_PLANE_V], nullptr);
diff --git a/third_party/aom/test/av1_convolve_test.cc b/third_party/aom/test/av1_convolve_test.cc
index 5bbac21803..b2392276cc 100644
--- a/third_party/aom/test/av1_convolve_test.cc
+++ b/third_party/aom/test/av1_convolve_test.cc
@@ -631,6 +631,11 @@ INSTANTIATE_TEST_SUITE_P(NEON, AV1ConvolveXHighbdTest,
BuildHighbdParams(av1_highbd_convolve_x_sr_neon));
#endif
+#if HAVE_SVE2
+INSTANTIATE_TEST_SUITE_P(SVE2, AV1ConvolveXHighbdTest,
+ BuildHighbdParams(av1_highbd_convolve_x_sr_sve2));
+#endif
+
/////////////////////////////////////////////////////////////////
// Single reference convolve-x IntraBC functions (high bit-depth)
/////////////////////////////////////////////////////////////////
@@ -998,6 +1003,11 @@ INSTANTIATE_TEST_SUITE_P(NEON, AV1ConvolveYHighbdTest,
BuildHighbdParams(av1_highbd_convolve_y_sr_neon));
#endif
+#if HAVE_SVE2
+INSTANTIATE_TEST_SUITE_P(SVE2, AV1ConvolveYHighbdTest,
+ BuildHighbdParams(av1_highbd_convolve_y_sr_sve2));
+#endif
+
/////////////////////////////////////////////////////////////////
// Single reference convolve-y IntraBC functions (high bit-depth)
/////////////////////////////////////////////////////////////////
@@ -1523,6 +1533,11 @@ INSTANTIATE_TEST_SUITE_P(NEON, AV1Convolve2DHighbdTest,
BuildHighbdParams(av1_highbd_convolve_2d_sr_neon));
#endif
+#if HAVE_SVE2
+INSTANTIATE_TEST_SUITE_P(SVE2, AV1Convolve2DHighbdTest,
+ BuildHighbdParams(av1_highbd_convolve_2d_sr_sve2));
+#endif
+
//////////////////////////////////////////////////////////////////
// Single reference convolve-2d IntraBC functions (high bit-depth)
//////////////////////////////////////////////////////////////////
@@ -1943,6 +1958,12 @@ INSTANTIATE_TEST_SUITE_P(
BuildHighbdLumaParams(av1_highbd_dist_wtd_convolve_x_neon));
#endif
+#if HAVE_SVE2
+INSTANTIATE_TEST_SUITE_P(
+ SVE2, AV1ConvolveXHighbdCompoundTest,
+ BuildHighbdLumaParams(av1_highbd_dist_wtd_convolve_x_sve2));
+#endif
+
#endif // CONFIG_AV1_HIGHBITDEPTH
////////////////////////////////////////////////
@@ -2023,6 +2044,12 @@ INSTANTIATE_TEST_SUITE_P(
BuildHighbdLumaParams(av1_highbd_dist_wtd_convolve_y_neon));
#endif
+#if HAVE_SVE2
+INSTANTIATE_TEST_SUITE_P(
+ SVE2, AV1ConvolveYHighbdCompoundTest,
+ BuildHighbdLumaParams(av1_highbd_dist_wtd_convolve_y_sve2));
+#endif
+
#endif // CONFIG_AV1_HIGHBITDEPTH
//////////////////////////////////////////////////////
@@ -2312,11 +2339,6 @@ TEST_P(AV1Convolve2DCompoundTest, RunTest) { RunTest(); }
INSTANTIATE_TEST_SUITE_P(C, AV1Convolve2DCompoundTest,
BuildLowbdLumaParams(av1_dist_wtd_convolve_2d_c));
-#if HAVE_SSE2
-INSTANTIATE_TEST_SUITE_P(SSE2, AV1Convolve2DCompoundTest,
- BuildLowbdLumaParams(av1_dist_wtd_convolve_2d_sse2));
-#endif
-
#if HAVE_SSSE3
INSTANTIATE_TEST_SUITE_P(SSSE3, AV1Convolve2DCompoundTest,
BuildLowbdLumaParams(av1_dist_wtd_convolve_2d_ssse3));
@@ -2442,6 +2464,12 @@ INSTANTIATE_TEST_SUITE_P(
BuildHighbdLumaParams(av1_highbd_dist_wtd_convolve_2d_neon));
#endif
+#if HAVE_SVE2
+INSTANTIATE_TEST_SUITE_P(
+ SVE2, AV1Convolve2DHighbdCompoundTest,
+ BuildHighbdLumaParams(av1_highbd_dist_wtd_convolve_2d_sve2));
+#endif
+
#endif // CONFIG_AV1_HIGHBITDEPTH
} // namespace
diff --git a/third_party/aom/test/av1_fwd_txfm2d_test.cc b/third_party/aom/test/av1_fwd_txfm2d_test.cc
index 2ed5d94db3..4a5a634545 100644
--- a/third_party/aom/test/av1_fwd_txfm2d_test.cc
+++ b/third_party/aom/test/av1_fwd_txfm2d_test.cc
@@ -443,7 +443,7 @@ using ::testing::Combine;
using ::testing::Values;
using ::testing::ValuesIn;
-#if HAVE_SSE2
+#if AOM_ARCH_X86 && HAVE_SSE2
static TX_SIZE fwd_txfm_for_sse2[] = {
TX_4X4,
TX_8X8,
@@ -469,15 +469,14 @@ static TX_SIZE fwd_txfm_for_sse2[] = {
INSTANTIATE_TEST_SUITE_P(SSE2, AV1FwdTxfm2dTest,
Combine(ValuesIn(fwd_txfm_for_sse2),
Values(av1_lowbd_fwd_txfm_sse2)));
-#endif // HAVE_SSE2
+#endif // AOM_ARCH_X86 && HAVE_SSE2
#if HAVE_SSE4_1
-static TX_SIZE fwd_txfm_for_sse41[] = {
- TX_4X4,
- TX_64X64,
- TX_32X64,
- TX_64X32,
-};
+static TX_SIZE fwd_txfm_for_sse41[] = { TX_4X4, TX_8X8, TX_16X16, TX_32X32,
+ TX_64X64, TX_4X8, TX_8X4, TX_8X16,
+ TX_16X8, TX_16X32, TX_32X16, TX_32X64,
+ TX_64X32, TX_4X16, TX_16X4, TX_8X32,
+ TX_32X8, TX_16X64, TX_64X16 };
INSTANTIATE_TEST_SUITE_P(SSE4_1, AV1FwdTxfm2dTest,
Combine(ValuesIn(fwd_txfm_for_sse41),
diff --git a/third_party/aom/test/av1_wedge_utils_test.cc b/third_party/aom/test/av1_wedge_utils_test.cc
index 1055ff35b2..2234561b7d 100644
--- a/third_party/aom/test/av1_wedge_utils_test.cc
+++ b/third_party/aom/test/av1_wedge_utils_test.cc
@@ -408,4 +408,16 @@ INSTANTIATE_TEST_SUITE_P(
av1_wedge_compute_delta_squares_avx2)));
#endif // HAVE_AVX2
+#if HAVE_SVE
+INSTANTIATE_TEST_SUITE_P(
+ SVE, WedgeUtilsSSEOptTest,
+ ::testing::Values(TestFuncsFSSE(av1_wedge_sse_from_residuals_c,
+ av1_wedge_sse_from_residuals_sve)));
+
+INSTANTIATE_TEST_SUITE_P(
+ SVE, WedgeUtilsSignOptTest,
+ ::testing::Values(TestFuncsFSign(av1_wedge_sign_from_residuals_c,
+ av1_wedge_sign_from_residuals_sve)));
+#endif // HAVE_SVE
+
} // namespace
diff --git a/third_party/aom/test/cdef_test.cc b/third_party/aom/test/cdef_test.cc
index ad54407ca7..ac0591f6a8 100644
--- a/third_party/aom/test/cdef_test.cc
+++ b/third_party/aom/test/cdef_test.cc
@@ -614,7 +614,7 @@ TEST_P(CDEFCopyRect16to16Test, TestSIMDNoMismatch) {
using std::make_tuple;
-#if (HAVE_SSE2 || HAVE_SSSE3 || HAVE_SSE4_1 || HAVE_AVX2 || HAVE_NEON)
+#if ((AOM_ARCH_X86 && HAVE_SSSE3) || HAVE_SSE4_1 || HAVE_AVX2 || HAVE_NEON)
static const CdefFilterBlockFunctions kCdefFilterFuncC[] = {
{ &cdef_filter_8_0_c, &cdef_filter_8_1_c, &cdef_filter_8_2_c,
&cdef_filter_8_3_c }
@@ -626,50 +626,7 @@ static const CdefFilterBlockFunctions kCdefFilterHighbdFuncC[] = {
};
#endif
-#if HAVE_SSE2
-static const CdefFilterBlockFunctions kCdefFilterFuncSse2[] = {
- { &cdef_filter_8_0_sse2, &cdef_filter_8_1_sse2, &cdef_filter_8_2_sse2,
- &cdef_filter_8_3_sse2 }
-};
-
-static const CdefFilterBlockFunctions kCdefFilterHighbdFuncSse2[] = {
- { &cdef_filter_16_0_sse2, &cdef_filter_16_1_sse2, &cdef_filter_16_2_sse2,
- &cdef_filter_16_3_sse2 }
-};
-
-INSTANTIATE_TEST_SUITE_P(
- SSE2, CDEFBlockTest,
- ::testing::Combine(::testing::ValuesIn(kCdefFilterFuncSse2),
- ::testing::ValuesIn(kCdefFilterFuncC),
- ::testing::Values(BLOCK_4X4, BLOCK_4X8, BLOCK_8X4,
- BLOCK_8X8),
- ::testing::Range(0, 16), ::testing::Values(8)));
-INSTANTIATE_TEST_SUITE_P(
- SSE2, CDEFBlockHighbdTest,
- ::testing::Combine(::testing::ValuesIn(kCdefFilterHighbdFuncSse2),
- ::testing::ValuesIn(kCdefFilterHighbdFuncC),
- ::testing::Values(BLOCK_4X4, BLOCK_4X8, BLOCK_8X4,
- BLOCK_8X8),
- ::testing::Range(0, 16), ::testing::Range(10, 13, 2)));
-INSTANTIATE_TEST_SUITE_P(SSE2, CDEFFindDirTest,
- ::testing::Values(make_tuple(&cdef_find_dir_sse2,
- &cdef_find_dir_c)));
-INSTANTIATE_TEST_SUITE_P(SSE2, CDEFFindDirDualTest,
- ::testing::Values(make_tuple(&cdef_find_dir_dual_sse2,
- &cdef_find_dir_dual_c)));
-
-INSTANTIATE_TEST_SUITE_P(
- SSE2, CDEFCopyRect8to16Test,
- ::testing::Values(make_tuple(&cdef_copy_rect8_8bit_to_16bit_c,
- &cdef_copy_rect8_8bit_to_16bit_sse2)));
-
-INSTANTIATE_TEST_SUITE_P(
- SSE2, CDEFCopyRect16to16Test,
- ::testing::Values(make_tuple(&cdef_copy_rect8_16bit_to_16bit_c,
- &cdef_copy_rect8_16bit_to_16bit_sse2)));
-#endif
-
-#if HAVE_SSSE3
+#if AOM_ARCH_X86 && HAVE_SSSE3
static const CdefFilterBlockFunctions kCdefFilterFuncSsse3[] = {
{ &cdef_filter_8_0_ssse3, &cdef_filter_8_1_ssse3, &cdef_filter_8_2_ssse3,
&cdef_filter_8_3_ssse3 }
@@ -843,30 +800,7 @@ INSTANTIATE_TEST_SUITE_P(
#endif
// Test speed for all supported architectures
-#if HAVE_SSE2
-INSTANTIATE_TEST_SUITE_P(
- SSE2, CDEFSpeedTest,
- ::testing::Combine(::testing::ValuesIn(kCdefFilterFuncSse2),
- ::testing::ValuesIn(kCdefFilterFuncC),
- ::testing::Values(BLOCK_4X4, BLOCK_4X8, BLOCK_8X4,
- BLOCK_8X8),
- ::testing::Range(0, 16), ::testing::Values(8)));
-INSTANTIATE_TEST_SUITE_P(
- SSE2, CDEFSpeedHighbdTest,
- ::testing::Combine(::testing::ValuesIn(kCdefFilterHighbdFuncSse2),
- ::testing::ValuesIn(kCdefFilterHighbdFuncC),
- ::testing::Values(BLOCK_4X4, BLOCK_4X8, BLOCK_8X4,
- BLOCK_8X8),
- ::testing::Range(0, 16), ::testing::Values(10)));
-INSTANTIATE_TEST_SUITE_P(SSE2, CDEFFindDirSpeedTest,
- ::testing::Values(make_tuple(&cdef_find_dir_sse2,
- &cdef_find_dir_c)));
-INSTANTIATE_TEST_SUITE_P(SSE2, CDEFFindDirDualSpeedTest,
- ::testing::Values(make_tuple(&cdef_find_dir_dual_sse2,
- &cdef_find_dir_dual_c)));
-#endif
-
-#if HAVE_SSSE3
+#if AOM_ARCH_X86 && HAVE_SSSE3
INSTANTIATE_TEST_SUITE_P(
SSSE3, CDEFSpeedTest,
::testing::Combine(::testing::ValuesIn(kCdefFilterFuncSsse3),
diff --git a/third_party/aom/test/convolve_test.cc b/third_party/aom/test/convolve_test.cc
index c97f814057..cab590927b 100644
--- a/third_party/aom/test/convolve_test.cc
+++ b/third_party/aom/test/convolve_test.cc
@@ -773,6 +773,17 @@ WRAP(convolve8_vert_neon, 10)
WRAP(convolve8_horiz_neon, 12)
WRAP(convolve8_vert_neon, 12)
#endif // HAVE_NEON
+
+#if HAVE_SVE
+WRAP(convolve8_horiz_sve, 8)
+WRAP(convolve8_vert_sve, 8)
+
+WRAP(convolve8_horiz_sve, 10)
+WRAP(convolve8_vert_sve, 10)
+
+WRAP(convolve8_horiz_sve, 12)
+WRAP(convolve8_vert_sve, 12)
+#endif // HAVE_SVE
#endif // CONFIG_AV1_HIGHBITDEPTH
#undef WRAP
@@ -832,12 +843,6 @@ const ConvolveParam kArrayHighbdConvolve_sse2[] = {
INSTANTIATE_TEST_SUITE_P(SSE2, HighbdConvolveTest,
::testing::ValuesIn(kArrayHighbdConvolve_sse2));
#endif
-const ConvolveFunctions convolve8_sse2(aom_convolve8_horiz_sse2,
- aom_convolve8_vert_sse2, 0);
-const ConvolveParam kArrayConvolve_sse2[] = { ALL_SIZES(convolve8_sse2) };
-
-INSTANTIATE_TEST_SUITE_P(SSE2, LowbdConvolveTest,
- ::testing::ValuesIn(kArrayConvolve_sse2));
#endif
#if HAVE_SSSE3
@@ -919,4 +924,22 @@ INSTANTIATE_TEST_SUITE_P(NEON_I8MM, LowbdConvolveTest,
::testing::ValuesIn(kArray_Convolve8_neon_i8mm));
#endif // HAVE_NEON_I8MM
+#if HAVE_SVE
+#if CONFIG_AV1_HIGHBITDEPTH
+const ConvolveFunctions wrap_convolve8_sve(wrap_convolve8_horiz_sve_8,
+ wrap_convolve8_vert_sve_8, 8);
+const ConvolveFunctions wrap_convolve10_sve(wrap_convolve8_horiz_sve_10,
+ wrap_convolve8_vert_sve_10, 10);
+const ConvolveFunctions wrap_convolve12_sve(wrap_convolve8_horiz_sve_12,
+ wrap_convolve8_vert_sve_12, 12);
+const ConvolveParam kArray_HighbdConvolve8_sve[] = {
+ ALL_SIZES_64(wrap_convolve8_sve), ALL_SIZES_64(wrap_convolve10_sve),
+ ALL_SIZES_64(wrap_convolve12_sve)
+};
+
+INSTANTIATE_TEST_SUITE_P(SVE, HighbdConvolveTest,
+ ::testing::ValuesIn(kArray_HighbdConvolve8_sve));
+#endif
+#endif // HAVE_SVE
+
} // namespace
diff --git a/third_party/aom/test/corner_match_test.cc b/third_party/aom/test/corner_match_test.cc
index 9733732180..895c8ad7d3 100644
--- a/third_party/aom/test/corner_match_test.cc
+++ b/third_party/aom/test/corner_match_test.cc
@@ -27,13 +27,19 @@ namespace AV1CornerMatch {
using libaom_test::ACMRandom;
-typedef double (*ComputeCrossCorrFunc)(const unsigned char *im1, int stride1,
- int x1, int y1, const unsigned char *im2,
- int stride2, int x2, int y2);
+typedef bool (*ComputeMeanStddevFunc)(const unsigned char *frame, int stride,
+ int x, int y, double *mean,
+ double *one_over_stddev);
+typedef double (*ComputeCorrFunc)(const unsigned char *frame1, int stride1,
+ int x1, int y1, double mean1,
+ double one_over_stddev1,
+ const unsigned char *frame2, int stride2,
+ int x2, int y2, double mean2,
+ double one_over_stddev2);
using std::make_tuple;
using std::tuple;
-typedef tuple<int, ComputeCrossCorrFunc> CornerMatchParam;
+typedef tuple<int, ComputeMeanStddevFunc, ComputeCorrFunc> CornerMatchParam;
class AV1CornerMatchTest : public ::testing::TestWithParam<CornerMatchParam> {
public:
@@ -41,8 +47,11 @@ class AV1CornerMatchTest : public ::testing::TestWithParam<CornerMatchParam> {
void SetUp() override;
protected:
- void RunCheckOutput(int run_times);
- ComputeCrossCorrFunc target_func;
+ void GenerateInput(uint8_t *input1, uint8_t *input2, int w, int h, int mode);
+ void RunCheckOutput();
+ void RunSpeedTest();
+ ComputeMeanStddevFunc target_compute_mean_stddev_func;
+ ComputeCorrFunc target_compute_corr_func;
libaom_test::ACMRandom rnd_;
};
@@ -51,14 +60,31 @@ GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AV1CornerMatchTest);
AV1CornerMatchTest::~AV1CornerMatchTest() = default;
void AV1CornerMatchTest::SetUp() {
rnd_.Reset(ACMRandom::DeterministicSeed());
- target_func = GET_PARAM(1);
+ target_compute_mean_stddev_func = GET_PARAM(1);
+ target_compute_corr_func = GET_PARAM(2);
}
-void AV1CornerMatchTest::RunCheckOutput(int run_times) {
+void AV1CornerMatchTest::GenerateInput(uint8_t *input1, uint8_t *input2, int w,
+ int h, int mode) {
+ if (mode == 0) {
+ for (int i = 0; i < h; ++i)
+ for (int j = 0; j < w; ++j) {
+ input1[i * w + j] = rnd_.Rand8();
+ input2[i * w + j] = rnd_.Rand8();
+ }
+ } else if (mode == 1) {
+ for (int i = 0; i < h; ++i)
+ for (int j = 0; j < w; ++j) {
+ int v = rnd_.Rand8();
+ input1[i * w + j] = v;
+ input2[i * w + j] = (v / 2) + (rnd_.Rand8() & 15);
+ }
+ }
+}
+
+void AV1CornerMatchTest::RunCheckOutput() {
const int w = 128, h = 128;
- const int num_iters = 10000;
- int i, j;
- aom_usec_timer ref_timer, test_timer;
+ const int num_iters = 1000;
std::unique_ptr<uint8_t[]> input1(new (std::nothrow) uint8_t[w * h]);
std::unique_ptr<uint8_t[]> input2(new (std::nothrow) uint8_t[w * h]);
@@ -69,76 +95,139 @@ void AV1CornerMatchTest::RunCheckOutput(int run_times) {
// i) Random data, should have correlation close to 0
// ii) Linearly related data + noise, should have correlation close to 1
int mode = GET_PARAM(0);
- if (mode == 0) {
- for (i = 0; i < h; ++i)
- for (j = 0; j < w; ++j) {
- input1[i * w + j] = rnd_.Rand8();
- input2[i * w + j] = rnd_.Rand8();
- }
- } else if (mode == 1) {
- for (i = 0; i < h; ++i)
- for (j = 0; j < w; ++j) {
- int v = rnd_.Rand8();
- input1[i * w + j] = v;
- input2[i * w + j] = (v / 2) + (rnd_.Rand8() & 15);
- }
+ GenerateInput(&input1[0], &input2[0], w, h, mode);
+
+ for (int i = 0; i < num_iters; ++i) {
+ int x1 = MATCH_SZ_BY2 + rnd_.PseudoUniform(w + 1 - MATCH_SZ);
+ int y1 = MATCH_SZ_BY2 + rnd_.PseudoUniform(h + 1 - MATCH_SZ);
+ int x2 = MATCH_SZ_BY2 + rnd_.PseudoUniform(w + 1 - MATCH_SZ);
+ int y2 = MATCH_SZ_BY2 + rnd_.PseudoUniform(h + 1 - MATCH_SZ);
+
+ double c_mean1, c_one_over_stddev1, c_mean2, c_one_over_stddev2;
+ bool c_valid1 = aom_compute_mean_stddev_c(input1.get(), w, x1, y1, &c_mean1,
+ &c_one_over_stddev1);
+ bool c_valid2 = aom_compute_mean_stddev_c(input2.get(), w, x2, y2, &c_mean2,
+ &c_one_over_stddev2);
+
+ double simd_mean1, simd_one_over_stddev1, simd_mean2, simd_one_over_stddev2;
+ bool simd_valid1 = target_compute_mean_stddev_func(
+ input1.get(), w, x1, y1, &simd_mean1, &simd_one_over_stddev1);
+ bool simd_valid2 = target_compute_mean_stddev_func(
+ input2.get(), w, x2, y2, &simd_mean2, &simd_one_over_stddev2);
+
+ // Run the correlation calculation even if one of the "valid" flags is
+ // false, i.e. if one of the patches doesn't have enough variance. This is
+ // safe because any potential division by 0 is caught in
+ // aom_compute_mean_stddev(), and one_over_stddev is set to 0 instead.
+ // This causes aom_compute_correlation() to return 0, without causing a
+ // division by 0.
+ const double c_corr = aom_compute_correlation_c(
+ input1.get(), w, x1, y1, c_mean1, c_one_over_stddev1, input2.get(), w,
+ x2, y2, c_mean2, c_one_over_stddev2);
+ const double simd_corr = target_compute_corr_func(
+ input1.get(), w, x1, y1, c_mean1, c_one_over_stddev1, input2.get(), w,
+ x2, y2, c_mean2, c_one_over_stddev2);
+
+ ASSERT_EQ(simd_valid1, c_valid1);
+ ASSERT_EQ(simd_valid2, c_valid2);
+ ASSERT_EQ(simd_mean1, c_mean1);
+ ASSERT_EQ(simd_one_over_stddev1, c_one_over_stddev1);
+ ASSERT_EQ(simd_mean2, c_mean2);
+ ASSERT_EQ(simd_one_over_stddev2, c_one_over_stddev2);
+ ASSERT_EQ(simd_corr, c_corr);
}
+}
- for (i = 0; i < num_iters; ++i) {
- int x1 = MATCH_SZ_BY2 + rnd_.PseudoUniform(w - 2 * MATCH_SZ_BY2);
- int y1 = MATCH_SZ_BY2 + rnd_.PseudoUniform(h - 2 * MATCH_SZ_BY2);
- int x2 = MATCH_SZ_BY2 + rnd_.PseudoUniform(w - 2 * MATCH_SZ_BY2);
- int y2 = MATCH_SZ_BY2 + rnd_.PseudoUniform(h - 2 * MATCH_SZ_BY2);
-
- double res_c = av1_compute_cross_correlation_c(input1.get(), w, x1, y1,
- input2.get(), w, x2, y2);
- double res_simd =
- target_func(input1.get(), w, x1, y1, input2.get(), w, x2, y2);
-
- if (run_times > 1) {
- aom_usec_timer_start(&ref_timer);
- for (j = 0; j < run_times; j++) {
- av1_compute_cross_correlation_c(input1.get(), w, x1, y1, input2.get(),
- w, x2, y2);
- }
- aom_usec_timer_mark(&ref_timer);
- const int elapsed_time_c =
- static_cast<int>(aom_usec_timer_elapsed(&ref_timer));
+void AV1CornerMatchTest::RunSpeedTest() {
+ const int w = 16, h = 16;
+ const int num_iters = 1000000;
+ aom_usec_timer ref_timer, test_timer;
- aom_usec_timer_start(&test_timer);
- for (j = 0; j < run_times; j++) {
- target_func(input1.get(), w, x1, y1, input2.get(), w, x2, y2);
- }
- aom_usec_timer_mark(&test_timer);
- const int elapsed_time_simd =
- static_cast<int>(aom_usec_timer_elapsed(&test_timer));
-
- printf(
- "c_time=%d \t simd_time=%d \t "
- "gain=%d\n",
- elapsed_time_c, elapsed_time_simd,
- (elapsed_time_c / elapsed_time_simd));
- } else {
- ASSERT_EQ(res_simd, res_c);
- }
+ std::unique_ptr<uint8_t[]> input1(new (std::nothrow) uint8_t[w * h]);
+ std::unique_ptr<uint8_t[]> input2(new (std::nothrow) uint8_t[w * h]);
+ ASSERT_NE(input1, nullptr);
+ ASSERT_NE(input2, nullptr);
+
+ // Test the two extreme cases:
+ // i) Random data, should have correlation close to 0
+ // ii) Linearly related data + noise, should have correlation close to 1
+ int mode = GET_PARAM(0);
+ GenerateInput(&input1[0], &input2[0], w, h, mode);
+
+ // Time aom_compute_mean_stddev()
+ double c_mean1, c_one_over_stddev1, c_mean2, c_one_over_stddev2;
+ aom_usec_timer_start(&ref_timer);
+ for (int i = 0; i < num_iters; i++) {
+ aom_compute_mean_stddev_c(input1.get(), w, 0, 0, &c_mean1,
+ &c_one_over_stddev1);
+ aom_compute_mean_stddev_c(input2.get(), w, 0, 0, &c_mean2,
+ &c_one_over_stddev2);
+ }
+ aom_usec_timer_mark(&ref_timer);
+ int elapsed_time_c = static_cast<int>(aom_usec_timer_elapsed(&ref_timer));
+
+ double simd_mean1, simd_one_over_stddev1, simd_mean2, simd_one_over_stddev2;
+ aom_usec_timer_start(&test_timer);
+ for (int i = 0; i < num_iters; i++) {
+ target_compute_mean_stddev_func(input1.get(), w, 0, 0, &simd_mean1,
+ &simd_one_over_stddev1);
+ target_compute_mean_stddev_func(input2.get(), w, 0, 0, &simd_mean2,
+ &simd_one_over_stddev2);
+ }
+ aom_usec_timer_mark(&test_timer);
+ int elapsed_time_simd = static_cast<int>(aom_usec_timer_elapsed(&test_timer));
+
+ printf(
+ "aom_compute_mean_stddev(): c_time=%6d simd_time=%6d "
+ "gain=%.3f\n",
+ elapsed_time_c, elapsed_time_simd,
+ (elapsed_time_c / (double)elapsed_time_simd));
+
+ // Time aom_compute_correlation
+ aom_usec_timer_start(&ref_timer);
+ for (int i = 0; i < num_iters; i++) {
+ aom_compute_correlation_c(input1.get(), w, 0, 0, c_mean1,
+ c_one_over_stddev1, input2.get(), w, 0, 0,
+ c_mean2, c_one_over_stddev2);
+ }
+ aom_usec_timer_mark(&ref_timer);
+ elapsed_time_c = static_cast<int>(aom_usec_timer_elapsed(&ref_timer));
+
+ aom_usec_timer_start(&test_timer);
+ for (int i = 0; i < num_iters; i++) {
+ target_compute_corr_func(input1.get(), w, 0, 0, c_mean1, c_one_over_stddev1,
+ input2.get(), w, 0, 0, c_mean2,
+ c_one_over_stddev2);
}
+ aom_usec_timer_mark(&test_timer);
+ elapsed_time_simd = static_cast<int>(aom_usec_timer_elapsed(&test_timer));
+
+ printf(
+ "aom_compute_correlation(): c_time=%6d simd_time=%6d "
+ "gain=%.3f\n",
+ elapsed_time_c, elapsed_time_simd,
+ (elapsed_time_c / (double)elapsed_time_simd));
}
-TEST_P(AV1CornerMatchTest, CheckOutput) { RunCheckOutput(1); }
-TEST_P(AV1CornerMatchTest, DISABLED_Speed) { RunCheckOutput(100000); }
+TEST_P(AV1CornerMatchTest, CheckOutput) { RunCheckOutput(); }
+TEST_P(AV1CornerMatchTest, DISABLED_Speed) { RunSpeedTest(); }
#if HAVE_SSE4_1
INSTANTIATE_TEST_SUITE_P(
SSE4_1, AV1CornerMatchTest,
- ::testing::Values(make_tuple(0, &av1_compute_cross_correlation_sse4_1),
- make_tuple(1, &av1_compute_cross_correlation_sse4_1)));
+ ::testing::Values(make_tuple(0, &aom_compute_mean_stddev_sse4_1,
+ &aom_compute_correlation_sse4_1),
+ make_tuple(1, &aom_compute_mean_stddev_sse4_1,
+ &aom_compute_correlation_sse4_1)));
#endif
#if HAVE_AVX2
INSTANTIATE_TEST_SUITE_P(
AVX2, AV1CornerMatchTest,
- ::testing::Values(make_tuple(0, &av1_compute_cross_correlation_avx2),
- make_tuple(1, &av1_compute_cross_correlation_avx2)));
+ ::testing::Values(make_tuple(0, &aom_compute_mean_stddev_avx2,
+ &aom_compute_correlation_avx2),
+ make_tuple(1, &aom_compute_mean_stddev_avx2,
+ &aom_compute_correlation_avx2)));
#endif
} // namespace AV1CornerMatch
diff --git a/third_party/aom/test/disflow_test.cc b/third_party/aom/test/disflow_test.cc
index 124c9a96c7..4f004480e2 100644
--- a/third_party/aom/test/disflow_test.cc
+++ b/third_party/aom/test/disflow_test.cc
@@ -114,6 +114,11 @@ INSTANTIATE_TEST_SUITE_P(SSE4_1, ComputeFlowTest,
::testing::Values(aom_compute_flow_at_point_sse4_1));
#endif
+#if HAVE_AVX2
+INSTANTIATE_TEST_SUITE_P(AVX2, ComputeFlowTest,
+ ::testing::Values(aom_compute_flow_at_point_avx2));
+#endif
+
#if HAVE_NEON
INSTANTIATE_TEST_SUITE_P(NEON, ComputeFlowTest,
::testing::Values(aom_compute_flow_at_point_neon));
diff --git a/third_party/aom/test/encode_api_test.cc b/third_party/aom/test/encode_api_test.cc
index 605743f9be..a7d5b3aa3c 100644
--- a/third_party/aom/test/encode_api_test.cc
+++ b/third_party/aom/test/encode_api_test.cc
@@ -10,6 +10,8 @@
*/
#include <cassert>
+#include <climits>
+#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <tuple>
@@ -556,6 +558,83 @@ TEST(EncodeAPI, Buganizer310457427) {
encoder.Encode(false);
}
+TEST(EncodeAPI, PtsSmallerThanInitialPts) {
+ // Initialize libaom encoder.
+ aom_codec_iface_t *const iface = aom_codec_av1_cx();
+ aom_codec_ctx_t enc;
+ aom_codec_enc_cfg_t cfg;
+
+ ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
+ AOM_CODEC_OK);
+
+ cfg.g_w = 1280;
+ cfg.g_h = 720;
+ cfg.rc_target_bitrate = 1000;
+
+ ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
+
+ // Create input image.
+ aom_image_t *const image =
+ CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
+ ASSERT_NE(image, nullptr);
+
+ // Encode frame.
+ ASSERT_EQ(aom_codec_encode(&enc, image, 12, 1, 0), AOM_CODEC_OK);
+ ASSERT_EQ(aom_codec_encode(&enc, image, 13, 1, 0), AOM_CODEC_OK);
+ // pts (10) is smaller than the initial pts (12).
+ ASSERT_EQ(aom_codec_encode(&enc, image, 10, 1, 0), AOM_CODEC_INVALID_PARAM);
+
+ // Free resources.
+ aom_img_free(image);
+ aom_codec_destroy(&enc);
+}
+
+TEST(EncodeAPI, PtsOrDurationTooBig) {
+ // Initialize libaom encoder.
+ aom_codec_iface_t *const iface = aom_codec_av1_cx();
+ aom_codec_ctx_t enc;
+ aom_codec_enc_cfg_t cfg;
+
+ ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_REALTIME),
+ AOM_CODEC_OK);
+
+ cfg.g_w = 1280;
+ cfg.g_h = 720;
+ cfg.rc_target_bitrate = 1000;
+
+ ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
+
+ // Create input image.
+ aom_image_t *const image =
+ CreateGrayImage(AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h);
+ ASSERT_NE(image, nullptr);
+
+ // Encode frame.
+ ASSERT_EQ(aom_codec_encode(&enc, image, 0, 1, 0), AOM_CODEC_OK);
+ // pts, when converted to ticks, is too big.
+ ASSERT_EQ(aom_codec_encode(&enc, image, INT64_MAX / 1000000 + 1, 1, 0),
+ AOM_CODEC_INVALID_PARAM);
+#if ULONG_MAX > INT64_MAX
+ // duration is too big.
+ ASSERT_EQ(aom_codec_encode(&enc, image, 0, (1ul << 63), 0),
+ AOM_CODEC_INVALID_PARAM);
+ // pts + duration is too big.
+ ASSERT_EQ(aom_codec_encode(&enc, image, 1, INT64_MAX, 0),
+ AOM_CODEC_INVALID_PARAM);
+#endif
+ // pts + duration, when converted to ticks, is too big.
+#if ULONG_MAX > INT64_MAX
+ ASSERT_EQ(aom_codec_encode(&enc, image, 0, 0x1c0a0a1a3232, 0),
+ AOM_CODEC_INVALID_PARAM);
+#endif
+ ASSERT_EQ(aom_codec_encode(&enc, image, INT64_MAX / 1000000, 1, 0),
+ AOM_CODEC_INVALID_PARAM);
+
+ // Free resources.
+ aom_img_free(image);
+ aom_codec_destroy(&enc);
+}
+
class EncodeAPIParameterized
: public testing::TestWithParam<std::tuple<
/*usage=*/unsigned int, /*speed=*/int, /*aq_mode=*/unsigned int>> {};
diff --git a/third_party/aom/test/hbd_metrics_test.cc b/third_party/aom/test/hbd_metrics_test.cc
index 303d580c4a..71c816f1cc 100644
--- a/third_party/aom/test/hbd_metrics_test.cc
+++ b/third_party/aom/test/hbd_metrics_test.cc
@@ -112,10 +112,10 @@ class HBDMetricsTestBase {
memset(&hbd_src, 0, sizeof(hbd_src));
memset(&hbd_dst, 0, sizeof(hbd_dst));
- aom_alloc_frame_buffer(&lbd_src, width, height, 1, 1, 0, 32, 16, 0, 0);
- aom_alloc_frame_buffer(&lbd_dst, width, height, 1, 1, 0, 32, 16, 0, 0);
- aom_alloc_frame_buffer(&hbd_src, width, height, 1, 1, 1, 32, 16, 0, 0);
- aom_alloc_frame_buffer(&hbd_dst, width, height, 1, 1, 1, 32, 16, 0, 0);
+ aom_alloc_frame_buffer(&lbd_src, width, height, 1, 1, 0, 32, 16, false, 0);
+ aom_alloc_frame_buffer(&lbd_dst, width, height, 1, 1, 0, 32, 16, false, 0);
+ aom_alloc_frame_buffer(&hbd_src, width, height, 1, 1, 1, 32, 16, false, 0);
+ aom_alloc_frame_buffer(&hbd_dst, width, height, 1, 1, 1, 32, 16, false, 0);
memset(lbd_src.buffer_alloc, kPixFiller, lbd_src.buffer_alloc_sz);
while (i < lbd_src.buffer_alloc_sz) {
diff --git a/third_party/aom/test/level_test.cc b/third_party/aom/test/level_test.cc
index a7c26d2305..6d59f45272 100644
--- a/third_party/aom/test/level_test.cc
+++ b/third_party/aom/test/level_test.cc
@@ -135,12 +135,12 @@ TEST_P(LevelTest, TestLevelMonitoringLowBitrate) {
// To save run time, we only test speed 4.
if (cpu_used_ == 4) {
libaom_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288,
- 30, 1, 0, 40);
+ 30, 1, 0, 30);
target_level_ = kLevelKeepStats;
cfg_.rc_target_bitrate = 1000;
- cfg_.g_limit = 40;
+ cfg_.g_limit = 30;
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
- ASSERT_EQ(level_[0], 0);
+ ASSERT_LE(level_[0], 0);
}
}
@@ -148,12 +148,12 @@ TEST_P(LevelTest, TestLevelMonitoringHighBitrate) {
// To save run time, we only test speed 4.
if (cpu_used_ == 4) {
libaom_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288,
- 30, 1, 0, 40);
+ 30, 1, 0, 30);
target_level_ = kLevelKeepStats;
cfg_.rc_target_bitrate = 4000;
- cfg_.g_limit = 40;
+ cfg_.g_limit = 30;
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
- ASSERT_EQ(level_[0], 4);
+ ASSERT_LE(level_[0], 4);
}
}
@@ -166,7 +166,7 @@ TEST_P(LevelTest, TestTargetLevel0) {
target_level_ = target_level;
cfg_.rc_target_bitrate = 4000;
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
- ASSERT_EQ(level_[0], target_level);
+ ASSERT_LE(level_[0], target_level);
}
}
diff --git a/third_party/aom/test/quantize_func_test.cc b/third_party/aom/test/quantize_func_test.cc
index 328d5b10df..61f26ea57f 100644
--- a/third_party/aom/test/quantize_func_test.cc
+++ b/third_party/aom/test/quantize_func_test.cc
@@ -19,6 +19,7 @@
#include "config/av1_rtcd.h"
#include "aom/aom_codec.h"
+#include "aom_dsp/txfm_common.h"
#include "aom_ports/aom_timer.h"
#include "av1/encoder/encoder.h"
#include "av1/common/scan.h"
@@ -482,9 +483,9 @@ const QuantizeParam<LPQuantizeFunc> kLPQParamArrayAvx2[] = {
make_tuple(&av1_quantize_lp_c, &av1_quantize_lp_avx2,
static_cast<TX_SIZE>(TX_16X16), TYPE_FP, AOM_BITS_8),
make_tuple(&av1_quantize_lp_c, &av1_quantize_lp_avx2,
- static_cast<TX_SIZE>(TX_32X32), TYPE_FP, AOM_BITS_8),
+ static_cast<TX_SIZE>(TX_8X8), TYPE_FP, AOM_BITS_8),
make_tuple(&av1_quantize_lp_c, &av1_quantize_lp_avx2,
- static_cast<TX_SIZE>(TX_64X64), TYPE_FP, AOM_BITS_8)
+ static_cast<TX_SIZE>(TX_4X4), TYPE_FP, AOM_BITS_8)
};
INSTANTIATE_TEST_SUITE_P(AVX2, LowPrecisionQuantizeTest,
@@ -704,9 +705,9 @@ const QuantizeParam<LPQuantizeFunc> kLPQParamArrayNEON[] = {
make_tuple(av1_quantize_lp_c, av1_quantize_lp_neon,
static_cast<TX_SIZE>(TX_16X16), TYPE_FP, AOM_BITS_8),
make_tuple(av1_quantize_lp_c, av1_quantize_lp_neon,
- static_cast<TX_SIZE>(TX_32X32), TYPE_FP, AOM_BITS_8),
+ static_cast<TX_SIZE>(TX_8X8), TYPE_FP, AOM_BITS_8),
make_tuple(av1_quantize_lp_c, av1_quantize_lp_neon,
- static_cast<TX_SIZE>(TX_64X64), TYPE_FP, AOM_BITS_8)
+ static_cast<TX_SIZE>(TX_4X4), TYPE_FP, AOM_BITS_8)
};
INSTANTIATE_TEST_SUITE_P(NEON, LowPrecisionQuantizeTest,
diff --git a/third_party/aom/test/resize_test.cc b/third_party/aom/test/resize_test.cc
index 755d4e3d02..a84a4654a8 100644
--- a/third_party/aom/test/resize_test.cc
+++ b/third_party/aom/test/resize_test.cc
@@ -15,7 +15,6 @@
#include "aom/aomcx.h"
#include "aom_dsp/aom_dsp_common.h"
#include "av1/encoder/encoder.h"
-#include "common/tools_common.h"
#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
#include "test/codec_factory.h"
#include "test/encode_test_driver.h"
@@ -690,6 +689,45 @@ TEST_P(ResizeRealtimeTest, TestExternalResizeWorks) {
}
}
+TEST_P(ResizeRealtimeTest, TestExternalResizeWorksUsePSNR) {
+ ResizingVideoSource video;
+ video.flag_codec_ = 1;
+ change_bitrate_ = false;
+ set_scale_mode_ = false;
+ set_scale_mode2_ = false;
+ set_scale_mode3_ = false;
+ mismatch_psnr_ = 0.0;
+ mismatch_nframes_ = 0;
+ init_flags_ = AOM_CODEC_USE_PSNR;
+ cfg_.rc_dropframe_thresh = 30;
+ DefaultConfig();
+ // Test external resizing with start resolution equal to
+ // 1. kInitialWidth and kInitialHeight
+ // 2. down-scaled kInitialWidth and kInitialHeight
+ for (int i = 0; i < 2; i++) {
+ video.change_start_resln_ = static_cast<bool>(i);
+
+ ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+
+ // Check we decoded the same number of frames as we attempted to encode
+ ASSERT_EQ(frame_info_list_.size(), video.limit());
+ for (const auto &info : frame_info_list_) {
+ const unsigned int frame = static_cast<unsigned>(info.pts);
+ unsigned int expected_w;
+ unsigned int expected_h;
+ ScaleForFrameNumber(frame, kInitialWidth, kInitialHeight,
+ video.flag_codec_, video.change_start_resln_,
+ &expected_w, &expected_h);
+ EXPECT_EQ(expected_w, info.w)
+ << "Frame " << frame << " had unexpected width";
+ EXPECT_EQ(expected_h, info.h)
+ << "Frame " << frame << " had unexpected height";
+ EXPECT_EQ(static_cast<unsigned int>(0), GetMismatchFrames());
+ }
+ frame_info_list_.clear();
+ }
+}
+
// Verify the dynamic resizer behavior for real time, 1 pass CBR mode.
// Run at low bitrate, with resize_allowed = 1, and verify that we get
// one resize down event.
diff --git a/third_party/aom/test/sad_test.cc b/third_party/aom/test/sad_test.cc
index 521274863c..64cf8006be 100644
--- a/third_party/aom/test/sad_test.cc
+++ b/third_party/aom/test/sad_test.cc
@@ -3202,6 +3202,7 @@ const SadSkipMxNx4Param skip_x4d_avx2_tests[] = {
make_tuple(32, 8, &aom_sad_skip_32x8x4d_avx2, -1),
make_tuple(16, 64, &aom_sad_skip_16x64x4d_avx2, -1),
+ make_tuple(16, 4, &aom_sad_skip_16x4x4d_avx2, -1),
#endif
};
@@ -3294,6 +3295,7 @@ const SadMxNx4Param x3d_avx2_tests[] = {
#if !CONFIG_REALTIME_ONLY
make_tuple(32, 8, &aom_sad32x8x3d_avx2, -1),
make_tuple(64, 16, &aom_sad64x16x3d_avx2, -1),
+ make_tuple(16, 4, &aom_sad16x4x3d_avx2, -1),
#endif // !CONFIG_REALTIME_ONLY
#if CONFIG_AV1_HIGHBITDEPTH
diff --git a/third_party/aom/test/segment_binarization_sync.cc b/third_party/aom/test/segment_binarization_sync.cc
index bd8cf11410..108e66a838 100644
--- a/third_party/aom/test/segment_binarization_sync.cc
+++ b/third_party/aom/test/segment_binarization_sync.cc
@@ -10,15 +10,14 @@
*/
#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
+
+#include "av1/common/seg_common.h"
+#include "av1/decoder/decodemv.h"
+#include "av1/encoder/bitstream.h"
#include "test/acm_random.h"
using libaom_test::ACMRandom;
-extern "C" {
-int av1_neg_interleave(int x, int ref, int max);
-int av1_neg_deinterleave(int diff, int ref, int max);
-}
-
namespace {
struct Segment {
@@ -28,8 +27,6 @@ struct Segment {
};
Segment GenerateSegment(int seed) {
- static const int MAX_SEGMENTS = 8;
-
ACMRandom rnd_(seed);
Segment segment;
diff --git a/third_party/aom/test/sharpness_test.cc b/third_party/aom/test/sharpness_test.cc
index 64465c88eb..054fbcc660 100644
--- a/third_party/aom/test/sharpness_test.cc
+++ b/third_party/aom/test/sharpness_test.cc
@@ -30,7 +30,7 @@ const std::unordered_map<
kPsnrThreshold = { { static_cast<int>(::libaom_test::kTwoPassGood),
{ { 2, { { 2, 37.6 }, { 5, 37.6 } } },
{ 4, { { 2, 37.5 }, { 5, 37.5 } } },
- { 6, { { 2, 37.5 }, { 5, 37.5 } } } } },
+ { 6, { { 2, 37.4 }, { 5, 37.4 } } } } },
{ static_cast<int>(::libaom_test::kAllIntra),
{ { 3, { { 2, 42.2 }, { 5, 42.2 } } },
{ 6, { { 2, 41.8 }, { 4, 41.9 }, { 5, 41.9 } } },
diff --git a/third_party/aom/test/test.cmake b/third_party/aom/test/test.cmake
index ce94a5a657..e2f5da570d 100644
--- a/third_party/aom/test/test.cmake
+++ b/third_party/aom/test/test.cmake
@@ -28,8 +28,7 @@ function(add_to_libaom_test_srcs src_list_name)
set(AOM_TEST_SOURCE_VARS "${AOM_TEST_SOURCE_VARS}" PARENT_SCOPE)
endfunction()
-list(APPEND AOM_UNIT_TEST_WRAPPER_SOURCES "${AOM_GEN_SRC_DIR}/usage_exit.c"
- "${AOM_ROOT}/test/test_libaom.cc")
+list(APPEND AOM_UNIT_TEST_WRAPPER_SOURCES "${AOM_ROOT}/test/test_libaom.cc")
add_to_libaom_test_srcs(AOM_UNIT_TEST_WRAPPER_SOURCES)
list(APPEND AOM_UNIT_TEST_COMMON_SOURCES
@@ -102,7 +101,7 @@ add_to_libaom_test_srcs(AOM_UNIT_TEST_ENCODER_SOURCES)
list(APPEND AOM_ENCODE_PERF_TEST_SOURCES "${AOM_ROOT}/test/encode_perf_test.cc")
list(APPEND AOM_UNIT_TEST_WEBM_SOURCES "${AOM_ROOT}/test/webm_video_source.h")
add_to_libaom_test_srcs(AOM_UNIT_TEST_WEBM_SOURCES)
-list(APPEND AOM_TEST_INTRA_PRED_SPEED_SOURCES "${AOM_GEN_SRC_DIR}/usage_exit.c"
+list(APPEND AOM_TEST_INTRA_PRED_SPEED_SOURCES
"${AOM_ROOT}/test/test_intra_pred_speed.cc")
if(CONFIG_AV1_DECODER)
@@ -277,24 +276,24 @@ if(NOT BUILD_SHARED_LIBS)
list(APPEND AOM_UNIT_TEST_COMMON_SOURCES
"${AOM_ROOT}/test/coding_path_sync.cc")
endif()
- if(CONFIG_REALTIME_ONLY)
- list(REMOVE_ITEM AOM_UNIT_TEST_COMMON_SOURCES
- "${AOM_ROOT}/test/altref_test.cc"
- "${AOM_ROOT}/test/av1_encoder_parms_get_to_decoder.cc"
- "${AOM_ROOT}/test/av1_ext_tile_test.cc"
- "${AOM_ROOT}/test/cnn_test.cc"
- "${AOM_ROOT}/test/decode_multithreaded_test.cc"
- "${AOM_ROOT}/test/error_resilience_test.cc"
- "${AOM_ROOT}/test/kf_test.cc"
- "${AOM_ROOT}/test/lossless_test.cc"
- "${AOM_ROOT}/test/sb_multipass_test.cc"
- "${AOM_ROOT}/test/sb_qp_sweep_test.cc"
- "${AOM_ROOT}/test/selfguided_filter_test.cc"
- "${AOM_ROOT}/test/screen_content_test.cc"
- "${AOM_ROOT}/test/still_picture_test.cc"
- "${AOM_ROOT}/test/tile_independence_test.cc"
- "${AOM_ROOT}/test/tpl_model_test.cc")
- endif()
+ endif()
+ if(CONFIG_REALTIME_ONLY)
+ list(REMOVE_ITEM AOM_UNIT_TEST_COMMON_SOURCES
+ "${AOM_ROOT}/test/altref_test.cc"
+ "${AOM_ROOT}/test/av1_encoder_parms_get_to_decoder.cc"
+ "${AOM_ROOT}/test/av1_ext_tile_test.cc"
+ "${AOM_ROOT}/test/cnn_test.cc"
+ "${AOM_ROOT}/test/decode_multithreaded_test.cc"
+ "${AOM_ROOT}/test/error_resilience_test.cc"
+ "${AOM_ROOT}/test/kf_test.cc"
+ "${AOM_ROOT}/test/lossless_test.cc"
+ "${AOM_ROOT}/test/sb_multipass_test.cc"
+ "${AOM_ROOT}/test/sb_qp_sweep_test.cc"
+ "${AOM_ROOT}/test/selfguided_filter_test.cc"
+ "${AOM_ROOT}/test/screen_content_test.cc"
+ "${AOM_ROOT}/test/still_picture_test.cc"
+ "${AOM_ROOT}/test/tile_independence_test.cc"
+ "${AOM_ROOT}/test/tpl_model_test.cc")
endif()
if(CONFIG_FPMT_TEST AND (NOT CONFIG_REALTIME_ONLY))
@@ -462,6 +461,7 @@ function(setup_aom_test_targets)
add_executable(test_libaom ${AOM_UNIT_TEST_WRAPPER_SOURCES}
$<TARGET_OBJECTS:aom_common_app_util>
+ $<TARGET_OBJECTS:aom_usage_exit>
$<TARGET_OBJECTS:test_aom_common>)
set_property(TARGET test_libaom PROPERTY FOLDER ${AOM_IDE_TEST_FOLDER})
list(APPEND AOM_APP_TARGETS test_libaom)
@@ -484,9 +484,9 @@ function(setup_aom_test_targets)
endif()
if(NOT BUILD_SHARED_LIBS)
- add_executable(test_intra_pred_speed
- ${AOM_TEST_INTRA_PRED_SPEED_SOURCES}
- $<TARGET_OBJECTS:aom_common_app_util>)
+ add_executable(test_intra_pred_speed ${AOM_TEST_INTRA_PRED_SPEED_SOURCES}
+ $<TARGET_OBJECTS:aom_common_app_util>
+ $<TARGET_OBJECTS:aom_usage_exit>)
set_property(TARGET test_intra_pred_speed
PROPERTY FOLDER ${AOM_IDE_TEST_FOLDER})
target_link_libraries(test_intra_pred_speed ${AOM_LIB_LINK_TYPE} aom
diff --git a/third_party/aom/test/test_libaom.cc b/third_party/aom/test/test_libaom.cc
index fbd7f2e380..26abbb0a06 100644
--- a/third_party/aom/test/test_libaom.cc
+++ b/third_party/aom/test/test_libaom.cc
@@ -62,6 +62,7 @@ int main(int argc, char **argv) {
if (!(caps & HAS_NEON_DOTPROD)) append_negative_gtest_filter("NEON_DOTPROD");
if (!(caps & HAS_NEON_I8MM)) append_negative_gtest_filter("NEON_I8MM");
if (!(caps & HAS_SVE)) append_negative_gtest_filter("SVE");
+ if (!(caps & HAS_SVE2)) append_negative_gtest_filter("SVE2");
#elif AOM_ARCH_ARM
const int caps = aom_arm_cpu_caps();
if (!(caps & HAS_NEON)) append_negative_gtest_filter("NEON");
diff --git a/third_party/aom/test/variance_test.cc b/third_party/aom/test/variance_test.cc
index e31f8f820c..261c080028 100644
--- a/third_party/aom/test/variance_test.cc
+++ b/third_party/aom/test/variance_test.cc
@@ -2785,64 +2785,6 @@ const GetSseSumParamsDual kArrayGetSseSum16x16Dual_sse2[] = {
INSTANTIATE_TEST_SUITE_P(SSE2, GetSseSum16x16DualTest,
::testing::ValuesIn(kArrayGetSseSum16x16Dual_sse2));
-const SubpelVarianceParams kArraySubpelVariance_sse2[] = {
- SubpelVarianceParams(7, 7, &aom_sub_pixel_variance128x128_sse2, 0),
- SubpelVarianceParams(7, 6, &aom_sub_pixel_variance128x64_sse2, 0),
- SubpelVarianceParams(6, 7, &aom_sub_pixel_variance64x128_sse2, 0),
- SubpelVarianceParams(6, 6, &aom_sub_pixel_variance64x64_sse2, 0),
- SubpelVarianceParams(6, 5, &aom_sub_pixel_variance64x32_sse2, 0),
- SubpelVarianceParams(5, 6, &aom_sub_pixel_variance32x64_sse2, 0),
- SubpelVarianceParams(5, 5, &aom_sub_pixel_variance32x32_sse2, 0),
- SubpelVarianceParams(5, 4, &aom_sub_pixel_variance32x16_sse2, 0),
- SubpelVarianceParams(4, 5, &aom_sub_pixel_variance16x32_sse2, 0),
- SubpelVarianceParams(4, 4, &aom_sub_pixel_variance16x16_sse2, 0),
- SubpelVarianceParams(4, 3, &aom_sub_pixel_variance16x8_sse2, 0),
- SubpelVarianceParams(3, 4, &aom_sub_pixel_variance8x16_sse2, 0),
- SubpelVarianceParams(3, 3, &aom_sub_pixel_variance8x8_sse2, 0),
- SubpelVarianceParams(3, 2, &aom_sub_pixel_variance8x4_sse2, 0),
- SubpelVarianceParams(2, 3, &aom_sub_pixel_variance4x8_sse2, 0),
- SubpelVarianceParams(2, 2, &aom_sub_pixel_variance4x4_sse2, 0),
-#if !CONFIG_REALTIME_ONLY
- SubpelVarianceParams(6, 4, &aom_sub_pixel_variance64x16_sse2, 0),
- SubpelVarianceParams(4, 6, &aom_sub_pixel_variance16x64_sse2, 0),
- SubpelVarianceParams(5, 3, &aom_sub_pixel_variance32x8_sse2, 0),
- SubpelVarianceParams(3, 5, &aom_sub_pixel_variance8x32_sse2, 0),
- SubpelVarianceParams(4, 2, &aom_sub_pixel_variance16x4_sse2, 0),
- SubpelVarianceParams(2, 4, &aom_sub_pixel_variance4x16_sse2, 0),
-#endif
-};
-INSTANTIATE_TEST_SUITE_P(SSE2, AvxSubpelVarianceTest,
- ::testing::ValuesIn(kArraySubpelVariance_sse2));
-
-const SubpelAvgVarianceParams kArraySubpelAvgVariance_sse2[] = {
- SubpelAvgVarianceParams(7, 7, &aom_sub_pixel_avg_variance128x128_sse2, 0),
- SubpelAvgVarianceParams(7, 6, &aom_sub_pixel_avg_variance128x64_sse2, 0),
- SubpelAvgVarianceParams(6, 7, &aom_sub_pixel_avg_variance64x128_sse2, 0),
- SubpelAvgVarianceParams(6, 6, &aom_sub_pixel_avg_variance64x64_sse2, 0),
- SubpelAvgVarianceParams(6, 5, &aom_sub_pixel_avg_variance64x32_sse2, 0),
- SubpelAvgVarianceParams(5, 6, &aom_sub_pixel_avg_variance32x64_sse2, 0),
- SubpelAvgVarianceParams(5, 5, &aom_sub_pixel_avg_variance32x32_sse2, 0),
- SubpelAvgVarianceParams(5, 4, &aom_sub_pixel_avg_variance32x16_sse2, 0),
- SubpelAvgVarianceParams(4, 5, &aom_sub_pixel_avg_variance16x32_sse2, 0),
- SubpelAvgVarianceParams(4, 4, &aom_sub_pixel_avg_variance16x16_sse2, 0),
- SubpelAvgVarianceParams(4, 3, &aom_sub_pixel_avg_variance16x8_sse2, 0),
- SubpelAvgVarianceParams(3, 4, &aom_sub_pixel_avg_variance8x16_sse2, 0),
- SubpelAvgVarianceParams(3, 3, &aom_sub_pixel_avg_variance8x8_sse2, 0),
- SubpelAvgVarianceParams(3, 2, &aom_sub_pixel_avg_variance8x4_sse2, 0),
- SubpelAvgVarianceParams(2, 3, &aom_sub_pixel_avg_variance4x8_sse2, 0),
- SubpelAvgVarianceParams(2, 2, &aom_sub_pixel_avg_variance4x4_sse2, 0),
-#if !CONFIG_REALTIME_ONLY
- SubpelAvgVarianceParams(6, 4, &aom_sub_pixel_avg_variance64x16_sse2, 0),
- SubpelAvgVarianceParams(4, 6, &aom_sub_pixel_avg_variance16x64_sse2, 0),
- SubpelAvgVarianceParams(5, 3, &aom_sub_pixel_avg_variance32x8_sse2, 0),
- SubpelAvgVarianceParams(3, 5, &aom_sub_pixel_avg_variance8x32_sse2, 0),
- SubpelAvgVarianceParams(4, 2, &aom_sub_pixel_avg_variance16x4_sse2, 0),
- SubpelAvgVarianceParams(2, 4, &aom_sub_pixel_avg_variance4x16_sse2, 0),
-#endif
-};
-INSTANTIATE_TEST_SUITE_P(SSE2, AvxSubpelAvgVarianceTest,
- ::testing::ValuesIn(kArraySubpelAvgVariance_sse2));
-
#if CONFIG_AV1_HIGHBITDEPTH
#if HAVE_SSE2
INSTANTIATE_TEST_SUITE_P(
@@ -2852,6 +2794,15 @@ INSTANTIATE_TEST_SUITE_P(
MseHBDWxHParams(2, 3, &aom_mse_wxh_16bit_highbd_sse2, 10),
MseHBDWxHParams(2, 2, &aom_mse_wxh_16bit_highbd_sse2,
10)));
+
+INSTANTIATE_TEST_SUITE_P(
+ SSE2, AvxHBDMseTest,
+ ::testing::Values(MseParams(4, 4, &aom_highbd_12_mse16x16_sse2, 12),
+ MseParams(3, 3, &aom_highbd_12_mse8x8_sse2, 12),
+ MseParams(4, 4, &aom_highbd_10_mse16x16_sse2, 10),
+ MseParams(3, 3, &aom_highbd_10_mse8x8_sse2, 10),
+ MseParams(4, 4, &aom_highbd_8_mse16x16_sse2, 8),
+ MseParams(3, 3, &aom_highbd_8_mse8x8_sse2, 8)));
#endif // HAVE_SSE2
#if HAVE_SSE4_1
INSTANTIATE_TEST_SUITE_P(
@@ -2878,14 +2829,11 @@ INSTANTIATE_TEST_SUITE_P(
12)));
#endif // HAVE_SSE4_1
+#if HAVE_AVX2
INSTANTIATE_TEST_SUITE_P(
- SSE2, AvxHBDMseTest,
- ::testing::Values(MseParams(4, 4, &aom_highbd_12_mse16x16_sse2, 12),
- MseParams(3, 3, &aom_highbd_12_mse8x8_sse2, 12),
- MseParams(4, 4, &aom_highbd_10_mse16x16_sse2, 10),
- MseParams(3, 3, &aom_highbd_10_mse8x8_sse2, 10),
- MseParams(4, 4, &aom_highbd_8_mse16x16_sse2, 8),
- MseParams(3, 3, &aom_highbd_8_mse8x8_sse2, 8)));
+ AVX2, AvxHBDMseTest,
+ ::testing::Values(MseParams(4, 4, &aom_highbd_10_mse16x16_avx2, 10)));
+#endif // HAVE_AVX2
const VarianceParams kArrayHBDVariance_sse2[] = {
VarianceParams(7, 7, &aom_highbd_12_variance128x128_sse2, 12),
diff --git a/third_party/aom/test/wiener_test.cc b/third_party/aom/test/wiener_test.cc
index 7eb6372aaa..b995c84d8f 100644
--- a/third_party/aom/test/wiener_test.cc
+++ b/third_party/aom/test/wiener_test.cc
@@ -1075,6 +1075,233 @@ TEST(SearchWienerTest, 12bitSignedIntegerOverflowInUpdateBSepSym) {
EXPECT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
}
+// A test that reproduces crbug.com/oss-fuzz/66474: signed integer overflow in
+// update_b_sep_sym().
+TEST(SearchWienerTest, 12bitSignedIntegerOverflowInUpdateBSepSym2) {
+ constexpr int kWidth = 510;
+ constexpr int kHeight = 3;
+ static const uint16_t buffer[kWidth * kHeight] = {
+ // Y plane:
+ 2136, 4095, 0, 0, 0, 4095, 4095, 0, 4095, 4095, 329, 0,
+ 4095, 0, 4095, 2587, 0, 0, 0, 4095, 0, 0, 0, 0,
+ 4095, 0, 4095, 878, 0, 4095, 0, 4095, 1474, 0, 573, 0,
+ 2401, 0, 1663, 4095, 0, 9, 3381, 0, 1084, 0, 270, 0,
+ 4095, 4095, 4095, 3992, 4095, 2047, 0, 0, 0, 4095, 41, 0,
+ 2726, 279, 0, 0, 4095, 0, 0, 1437, 0, 4095, 4095, 0,
+ 0, 0, 4095, 1683, 183, 3976, 3052, 0, 4095, 0, 0, 0,
+ 4095, 4095, 1882, 4095, 0, 4095, 83, 4095, 0, 4095, 0, 0,
+ 4095, 4095, 0, 0, 1637, 4095, 0, 4095, 0, 4095, 4095, 4095,
+ 0, 4095, 197, 4095, 563, 0, 3696, 3073, 3670, 0, 4095, 4095,
+ 0, 0, 0, 4095, 0, 0, 0, 0, 4095, 4095, 0, 0,
+ 0, 3539, 3468, 0, 2856, 3880, 0, 0, 1350, 2358, 4095, 802,
+ 4051, 0, 4095, 4095, 4095, 1677, 4095, 1135, 0, 4095, 0, 0,
+ 0, 618, 4095, 4095, 4095, 0, 2080, 4095, 0, 0, 1917, 0,
+ 0, 4095, 1937, 2835, 4095, 4095, 4095, 4095, 0, 4095, 4095, 3938,
+ 1707, 0, 0, 0, 4095, 448, 4095, 0, 1000, 2481, 3408, 0,
+ 0, 4095, 0, 3176, 0, 4095, 0, 4095, 4095, 4095, 0, 160,
+ 222, 1134, 4095, 4095, 0, 3539, 4095, 569, 3364, 0, 4095, 3687,
+ 0, 4095, 0, 0, 473, 0, 0, 4095, 298, 0, 3126, 4095,
+ 3854, 424, 0, 0, 4095, 3893, 0, 0, 175, 2774, 0, 4095,
+ 0, 2661, 950, 4095, 0, 1553, 0, 4095, 0, 4095, 4095, 2767,
+ 3630, 799, 255, 0, 4095, 0, 0, 4095, 2375, 0, 0, 0,
+ 0, 4095, 4095, 0, 0, 0, 1404, 4095, 4095, 4095, 4095, 2317,
+ 4095, 1227, 2205, 775, 0, 4095, 0, 0, 797, 1125, 736, 1773,
+ 2996, 4095, 2822, 4095, 4095, 0, 0, 0, 919, 0, 968, 3426,
+ 2702, 2613, 3647, 0, 0, 4095, 4095, 129, 4095, 0, 0, 4095,
+ 0, 0, 3632, 0, 3275, 123, 4095, 1566, 0, 0, 0, 1609,
+ 0, 1466, 4095, 577, 4095, 4095, 0, 4095, 1103, 1103, 4095, 0,
+ 1909, 0, 4095, 0, 4095, 4095, 227, 0, 4095, 2168, 4095, 374,
+ 4095, 4095, 4095, 0, 0, 0, 4095, 2066, 4095, 4095, 1475, 0,
+ 1959, 673, 4095, 0, 4095, 4095, 4095, 1142, 0, 464, 1819, 2033,
+ 4095, 0, 2212, 4095, 4095, 3961, 0, 4095, 0, 2838, 0, 4095,
+ 4095, 4095, 4095, 0, 3796, 3379, 2208, 0, 4095, 4095, 1943, 478,
+ 3573, 4095, 1763, 0, 0, 4095, 4095, 4095, 4095, 2061, 3346, 4095,
+ 0, 0, 4095, 0, 4095, 4095, 4095, 3738, 4095, 4095, 0, 4095,
+ 0, 425, 0, 0, 0, 927, 0, 0, 1814, 966, 4095, 0,
+ 0, 3185, 570, 3883, 2932, 0, 1413, 4095, 4095, 4095, 4095, 2477,
+ 2270, 4095, 2531, 4095, 1936, 3110, 99, 3936, 4095, 1315, 4095, 0,
+ 4095, 3564, 4095, 0, 0, 2797, 4095, 0, 1598, 0, 0, 3064,
+ 3526, 4095, 4095, 0, 3473, 3661, 0, 2388, 0, 4095, 639, 4095,
+ 0, 4095, 2390, 3715, 4095, 0, 0, 0, 740, 4095, 1432, 0,
+ 0, 0, 4057, 0, 0, 757, 4095, 4095, 0, 1437, 0, 0,
+ 4095, 0, 0, 0, 0, 0, 272, 4095, 4095, 4095, 2175, 4058,
+ 0, 4095, 4095, 4095, 3959, 3535, 0, 4095, 0, 0, 4095, 4095,
+ 4095, 4095, 0, 0, 4095, 4095, 4095, 3440, 3811, 0, 4095, 4095,
+ 4095, 4095, 0, 4095, 3193, 3674, 2819, 4095, 4095, 4048, 0, 0,
+ 4037, 4095, 3110, 4095, 1003, 0, 3650, 4095, 4095, 3154, 0, 1274,
+ 2192, 4095, 0, 4095, 0, 2814, 981, 370, 1407, 0, 4095, 1518,
+ 4095, 0, 0, 0, 0, 4095, 1577, 0, 4095, 0, 2607, 4095,
+ 3583, 0, 0, 4095, 1983, 1498, 4095, 4095, 2645, 4095, 4095, 3480,
+ 2587, 4095, 0, 0, 0, 0, 4095, 0, 4095, 4095, 0, 284,
+ 3973, 0, 0, 3677, 2463, 4095, 1338, 0, 4095, 0, 0, 4095,
+ 212, 2000, 4095, 4095, 0, 4095, 3780, 2039, 4095, 2453, 4095, 2050,
+ 2660, 1, 3839, 5, 1, 505, 809, 2907, 0, 0, 0, 1421,
+ 4095, 0, 0, 4095, 4095, 4095, 552, 0, 0, 4095, 3056, 0,
+ 0, 0, 0, 0, 4095, 0, 3386, 0, 0, 0, 4095, 0,
+ 0, 3404, 2702, 3534, 4095, 3562, 0, 4095, 4095, 150, 4095, 0,
+ 0, 3599, 4095, 4095, 0, 0, 0, 4095, 4095, 2093, 4095, 3753,
+ 3754, 4095, 0, 4095, 2733, 4095, 4095, 0, 0, 4095, 0, 0,
+ 0, 1496, 4095, 2366, 2936, 2494, 4095, 744, 1173, 4095, 0, 0,
+ 0, 1966, 4095, 4095, 0, 178, 3254, 4095, 4095, 995, 4095, 2083,
+ 0, 2639, 4095, 3422, 4095, 4095, 4095, 0, 842, 4095, 4095, 552,
+ 3681, 4095, 0, 1075, 2631, 554, 0, 0, 4095, 0, 0, 0,
+ 4095, 4095, 0, 0, 0, 2234, 0, 1098, 4095, 3164, 4095, 0,
+ 2748, 0, 0, 0, 4095, 4095, 4095, 1724, 891, 3496, 3964, 4095,
+ 0, 0, 1923, 4095, 4095, 4095, 3118, 0, 0, 0, 4095, 4095,
+ 0, 0, 3856, 4095, 0, 0, 4095, 4095, 2647, 0, 2089, 4095,
+ 471, 0, 4095, 0, 0, 0, 4095, 0, 1263, 2969, 289, 0,
+ 0, 4095, 289, 0, 0, 2965, 0, 0, 3280, 2279, 4091, 5,
+ 512, 1776, 4, 2046, 3994, 1, 4095, 898, 4095, 0, 0, 0,
+ 0, 4095, 0, 4095, 4095, 1930, 0, 0, 3725, 4095, 4095, 0,
+ 2593, 4095, 0, 4095, 984, 0, 4095, 2388, 0, 0, 4095, 4095,
+ 3341, 4095, 0, 2787, 0, 831, 2978, 4095, 0, 0, 0, 4095,
+ 1624, 4095, 1054, 1039, 0, 89, 3565, 0, 4095, 468, 0, 4095,
+ 4095, 0, 4095, 4095, 0, 3907, 0, 0, 0, 0, 0, 0,
+ 4095, 1898, 2178, 4095, 0, 3708, 2825, 0, 4095, 0, 4095, 4095,
+ 0, 0, 811, 1078, 0, 4095, 0, 3478, 0, 0, 1127, 0,
+ 504, 4095, 4095, 2006, 4095, 0, 2666, 1172, 4095, 4095, 4095, 4095,
+ 4095, 0, 199, 4095, 0, 2355, 2650, 2961, 0, 0, 0, 4095,
+ 4095, 0, 4095, 0, 4095, 1477, 0, 0, 1946, 0, 3352, 1988,
+ 0, 0, 2321, 4095, 0, 4095, 3367, 0, 0, 4095, 4095, 1946,
+ 0, 4034, 0, 0, 4095, 4095, 0, 0, 0, 0, 4095, 973,
+ 1734, 3966, 4095, 0, 3780, 1242, 0, 4095, 1301, 0, 1513, 4095,
+ 1079, 4095, 0, 0, 1316, 4095, 4095, 675, 2713, 2006, 4095, 4095,
+ 0, 0, 4095, 4095, 0, 3542, 4095, 0, 2365, 130, 4095, 2919,
+ 0, 4095, 3434, 0, 905, 4095, 673, 4095, 4095, 0, 3923, 293,
+ 4095, 213, 4095, 4095, 1334, 4095, 0, 3317, 0, 0, 0, 4095,
+ 4095, 4095, 2598, 2010, 0, 0, 3507, 0, 0, 0, 489, 0,
+ 0, 1782, 2681, 3303, 4095, 4095, 1955, 4095, 4095, 4095, 203, 1973,
+ 4095, 4020, 0, 4095, 1538, 0, 373, 1934, 4095, 0, 4095, 2244,
+ 4095, 1936, 4095, 640, 0, 4095, 0, 0, 0, 3653, 4095, 1966,
+ 4095, 4095, 4095, 4095, 0, 4095, 843, 0, 4095, 4095, 4095, 1646,
+ 4095, 0, 0, 4095, 4095, 4095, 2164, 0, 0, 0, 2141, 4095,
+ 0, 903, 4095, 4095, 0, 624, 4095, 792, 0, 0, 0, 0,
+ 0, 0, 0, 4095, 0, 4095, 4095, 2466, 0, 3631, 0, 4095,
+ 4095, 4095, 0, 941, 4095, 4095, 1609, 4095, 4095, 0, 0, 2398,
+ 4095, 4095, 2579, 0, 4020, 3485, 0, 0, 4095, 0, 4095, 0,
+ 3158, 2355, 0, 4095, 4095, 4095, 0, 0, 4095, 0, 0, 4095,
+ 475, 2272, 1010, 0, 0, 4095, 0, 0, 4095, 841, 4095, 4095,
+ 4095, 4095, 0, 4095, 0, 1046, 4095, 1738, 708, 4095, 0, 4095,
+ 4095, 0, 4095, 4095, 0, 4095, 4095, 0, 0, 0, 4032, 0,
+ 2679, 0, 1564, 0, 0, 0, 659, 1915, 4095, 3682, 0, 3660,
+ 4095, 723, 1383, 2499, 1353, 4095, 0, 3898, 2322, 3798, 4095, 0,
+ 444, 2277, 3729, 4095, 4095, 4095, 3054, 387, 3309, 4048, 3793, 2842,
+ 2087, 0, 3274, 2454, 518, 0, 4095, 0, 4095, 4095, 3358, 4095,
+ 2083, 2105, 0, 0, 0, 1125, 2636, 0, 0, 0, 0, 736,
+ 0, 349, 0, 4095, 2031, 4095, 992, 0, 4095, 3284, 4095, 214,
+ 3692, 4010, 402, 0, 0, 3776, 4095, 4095, 4095, 4095, 803, 2095,
+ 3864, 4095, 3323, 0, 0, 361, 1634, 0, 983, 0, 1181, 4095,
+ 1791, 4095, 367, 792, 4095, 4095, 3315, 3149, 4095, 62, 4095, 1791,
+ 3708, 2030, 4095, 1237, 0, 4095, 4095, 0, 0, 0, 0, 4095,
+ 1902, 2257, 4095, 4095, 0, 0, 2929, 4095, 0, 4095, 2356, 4095,
+ 2877, 1296, 4095, 0, 0, 0, 1310, 1968, 820, 4095, 4095, 4095,
+ 4095, 4095, 0, 0, 4095, 4095, 4095, 2897, 1787, 2218, 0, 129,
+ 4095, 4095, 0, 4095, 2331, 4095, 4095, 3192, 4095, 1744, 755, 0,
+ 1905, 0, 4095, 4095, 4095, 0, 0, 4095, 4095, 4095, 0, 0,
+ 0, 1467, 266, 1719, 4095, 729, 4095, 4095, 2647, 3543, 3388, 3326,
+ 4095, 0, 4095, 4095, 4095, 1416, 4095, 2131, 810, 0, 0, 4095,
+ 4095, 1250, 0, 0, 4095, 2722, 1493, 4095, 0, 4095, 0, 2895,
+ 0, 3847, 0, 2078, 0, 0, 0, 4095, 4095, 4095, 4095, 0,
+ 4095, 2651, 4095, 4095, 351, 2675, 4095, 0, 858, 0, 0, 0,
+ 816, 4095, 0, 4095, 0, 3842, 1990, 593, 0, 0, 3992, 4095,
+ 4095, 0, 4095, 1314, 4095, 4095, 1864, 2561, 4095, 1339, 0, 4095,
+ 2201, 4095, 0, 1403, 0, 0, 4095, 4095, 4095, 0, 0, 0,
+ 0, 0, 0, 577, 4095, 995, 2534, 827, 1431, 4095, 4095, 778,
+ 1405, 0, 0, 4095, 0, 4095, 1327, 4095, 0, 2725, 3351, 3937,
+ 741, 0, 2690, 2849, 4095, 4095, 2151, 0, 4095, 0, 4095, 4095,
+ 4095, 1342, 142, 1920, 1007, 2001
+ };
+ unsigned char *img_data =
+ reinterpret_cast<unsigned char *>(const_cast<uint16_t *>(buffer));
+
+ aom_image_t img;
+ EXPECT_EQ(&img, aom_img_wrap(&img, AOM_IMG_FMT_I42016, kWidth, kHeight, 1,
+ img_data));
+ img.cp = AOM_CICP_CP_UNSPECIFIED;
+ img.tc = AOM_CICP_TC_UNSPECIFIED;
+ img.mc = AOM_CICP_MC_UNSPECIFIED;
+ img.monochrome = 1;
+ img.csp = AOM_CSP_UNKNOWN;
+ img.range = AOM_CR_FULL_RANGE;
+ img.planes[1] = img.planes[2] = nullptr;
+ img.stride[1] = img.stride[2] = 0;
+
+ aom_codec_iface_t *iface = aom_codec_av1_cx();
+ aom_codec_enc_cfg_t cfg;
+ EXPECT_EQ(AOM_CODEC_OK,
+ aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY));
+ cfg.rc_end_usage = AOM_Q;
+ cfg.g_profile = 2;
+ cfg.g_bit_depth = AOM_BITS_12;
+ cfg.g_input_bit_depth = 12;
+ cfg.g_w = kWidth;
+ cfg.g_h = kHeight;
+ cfg.g_lag_in_frames = 0;
+ cfg.g_threads = 53;
+ cfg.monochrome = 1;
+ cfg.rc_min_quantizer = 22;
+ cfg.rc_max_quantizer = 30;
+ aom_codec_ctx_t enc;
+ EXPECT_EQ(AOM_CODEC_OK,
+ aom_codec_enc_init(&enc, iface, &cfg, AOM_CODEC_USE_HIGHBITDEPTH));
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_control(&enc, AOME_SET_CQ_LEVEL, 26));
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_control(&enc, AV1E_SET_TILE_ROWS, 3));
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_control(&enc, AOME_SET_CPUUSED, 6));
+ EXPECT_EQ(AOM_CODEC_OK,
+ aom_codec_control(&enc, AV1E_SET_COLOR_RANGE, AOM_CR_FULL_RANGE));
+ EXPECT_EQ(AOM_CODEC_OK,
+ aom_codec_control(&enc, AOME_SET_TUNING, AOM_TUNE_SSIM));
+
+ // Encode frame
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, &img, 0, 1, 0));
+ aom_codec_iter_t iter = nullptr;
+ const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(&enc, &iter);
+ ASSERT_NE(pkt, nullptr);
+ EXPECT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
+ // pkt->data.frame.flags is 0x1f0011.
+ EXPECT_EQ(pkt->data.frame.flags & AOM_FRAME_IS_KEY, AOM_FRAME_IS_KEY);
+ pkt = aom_codec_get_cx_data(&enc, &iter);
+ EXPECT_EQ(pkt, nullptr);
+
+ // Encode frame
+ EXPECT_EQ(AOM_CODEC_OK,
+ aom_codec_encode(&enc, &img, 0, 1, AOM_EFLAG_FORCE_KF));
+ iter = nullptr;
+ pkt = aom_codec_get_cx_data(&enc, &iter);
+ ASSERT_NE(pkt, nullptr);
+ EXPECT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
+ // pkt->data.frame.flags is 0x1f0011.
+ EXPECT_EQ(pkt->data.frame.flags & AOM_FRAME_IS_KEY, AOM_FRAME_IS_KEY);
+ pkt = aom_codec_get_cx_data(&enc, &iter);
+ EXPECT_EQ(pkt, nullptr);
+
+ // Encode frame
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, &img, 0, 1, 0));
+ iter = nullptr;
+ pkt = aom_codec_get_cx_data(&enc, &iter);
+ ASSERT_NE(pkt, nullptr);
+ EXPECT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
+ pkt = aom_codec_get_cx_data(&enc, &iter);
+ EXPECT_EQ(pkt, nullptr);
+
+ // Encode frame
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, &img, 0, 1, 0));
+ iter = nullptr;
+ pkt = aom_codec_get_cx_data(&enc, &iter);
+ ASSERT_NE(pkt, nullptr);
+ EXPECT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
+ pkt = aom_codec_get_cx_data(&enc, &iter);
+ EXPECT_EQ(pkt, nullptr);
+
+ // Flush encoder
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, nullptr, 0, 1, 0));
+ iter = nullptr;
+ pkt = aom_codec_get_cx_data(&enc, &iter);
+ EXPECT_EQ(pkt, nullptr);
+
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc));
+}
+
// A test that reproduces b/272139363: signed integer overflow in
// update_b_sep_sym().
TEST(SearchWienerTest, 10bitSignedIntegerOverflowInUpdateBSepSym) {
@@ -1164,6 +1391,161 @@ TEST(SearchWienerTest, 10bitSignedIntegerOverflowInUpdateBSepSym) {
EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc));
}
+// A test that reproduces b/319140742: signed integer overflow in
+// update_b_sep_sym().
+TEST(SearchWienerTest, 10bitSignedIntegerOverflowInUpdateBSepSym2) {
+ constexpr int kWidth = 326;
+ constexpr int kHeight = 3;
+ static const uint16_t buffer[kWidth * kHeight] = {
+ // Y plane:
+ 1023, 1023, 0, 1023, 1023, 0, 623, 0, 0, 1023, 1023, 0,
+ 0, 0, 0, 523, 1023, 2, 0, 0, 863, 1023, 1023, 409,
+ 7, 1023, 0, 409, 1023, 0, 579, 1023, 1023, 1023, 0, 0,
+ 1023, 1023, 446, 1023, 1023, 0, 0, 1023, 0, 0, 829, 1023,
+ 0, 1023, 939, 0, 0, 23, 1022, 990, 1023, 0, 0, 4,
+ 0, 299, 0, 0, 1023, 1023, 629, 688, 1023, 1023, 266, 1023,
+ 865, 0, 413, 0, 267, 0, 0, 69, 1023, 866, 1023, 885,
+ 0, 762, 330, 382, 0, 1023, 1023, 734, 504, 899, 119, 0,
+ 378, 1011, 0, 0, 1023, 364, 0, 1023, 1023, 462, 1023, 0,
+ 504, 1023, 1023, 0, 695, 1023, 57, 1023, 1023, 362, 0, 0,
+ 0, 0, 1023, 1023, 387, 12, 929, 1023, 0, 194, 1023, 0,
+ 1023, 505, 0, 1023, 1023, 1023, 1023, 1023, 0, 0, 676, 0,
+ 6, 683, 70, 0, 0, 1023, 226, 1023, 320, 758, 0, 0,
+ 648, 1023, 867, 550, 630, 960, 1023, 1023, 1023, 0, 0, 822,
+ 0, 0, 0, 1023, 1011, 1023, 1023, 0, 0, 15, 30, 0,
+ 1023, 1023, 0, 0, 0, 84, 954, 1023, 933, 416, 333, 323,
+ 0, 0, 1023, 355, 1023, 176, 1023, 1023, 886, 87, 1023, 0,
+ 1023, 1023, 1023, 562, 0, 1023, 1023, 354, 0, 0, 1023, 0,
+ 86, 0, 0, 1023, 0, 1023, 192, 0, 1023, 0, 1023, 0,
+ 0, 0, 735, 1023, 1023, 1023, 0, 372, 988, 131, 1023, 1023,
+ 0, 1023, 1023, 1023, 1023, 970, 1023, 1023, 248, 757, 665, 330,
+ 223, 273, 0, 274, 1023, 0, 1023, 613, 786, 1023, 792, 0,
+ 390, 282, 0, 1023, 0, 1023, 0, 1023, 1023, 1023, 614, 993,
+ 135, 737, 662, 0, 1023, 524, 970, 1023, 0, 906, 1023, 1023,
+ 959, 1023, 1023, 1023, 1023, 836, 838, 0, 0, 0, 0, 0,
+ 1023, 917, 492, 290, 1023, 1023, 817, 1023, 0, 0, 588, 410,
+ 419, 0, 1023, 1023, 178, 0, 0, 563, 775, 977, 1023, 1023,
+ 0, 1023, 0, 370, 434, 1023, 963, 587, 0, 0, 1023, 1023,
+ 1023, 1023, 1023, 1023, 619, 0, 1023, 352, 1023, 0, 0, 0,
+ 133, 557, 36, 1023, 1023, 1023, 0, 469, 1023, 1023, 0, 900,
+ 59, 841, 1023, 886, 0, 193, 126, 263, 119, 629, 0, 1023,
+ 0, 1023, 0, 0, 478, 0, 1023, 63, 1023, 0, 0, 0,
+ 0, 0, 0, 0, 1023, 888, 1023, 905, 646, 0, 0, 1023,
+ 752, 1023, 1023, 0, 1023, 0, 0, 648, 1023, 0, 0, 838,
+ 0, 321, 1023, 475, 0, 215, 867, 1023, 0, 1023, 1023, 624,
+ 417, 1023, 426, 0, 0, 960, 1020, 839, 687, 1023, 161, 1023,
+ 1023, 1023, 1023, 968, 0, 95, 430, 0, 132, 1023, 1023, 113,
+ 0, 1023, 1023, 606, 1023, 0, 0, 31, 1023, 1023, 0, 180,
+ 140, 654, 1023, 1023, 1023, 1023, 1023, 779, 1023, 0, 0, 1023,
+ 1023, 1023, 0, 1023, 0, 0, 1023, 963, 723, 536, 1023, 0,
+ 0, 0, 337, 812, 0, 0, 0, 428, 48, 0, 321, 205,
+ 0, 587, 799, 272, 5, 1023, 322, 0, 761, 0, 749, 1023,
+ 0, 0, 1023, 1023, 1023, 1023, 242, 402, 98, 0, 1023, 884,
+ 219, 1023, 0, 1023, 0, 0, 0, 106, 1023, 0, 1023, 414,
+ 1023, 0, 1023, 619, 0, 0, 973, 854, 82, 1023, 1023, 1023,
+ 0, 1023, 1023, 0, 0, 588, 433, 0, 0, 961, 0, 0,
+ 0, 917, 859, 461, 455, 68, 1023, 409, 1023, 821, 1023, 487,
+ 1023, 0, 717, 0, 613, 0, 0, 840, 932, 782, 1023, 1023,
+ 576, 1023, 0, 1023, 1023, 187, 876, 162, 0, 1023, 1023, 946,
+ 873, 0, 0, 953, 0, 537, 0, 0, 1023, 193, 807, 756,
+ 0, 0, 1023, 732, 1023, 1023, 1023, 0, 0, 1023, 1023, 1023,
+ 1023, 1023, 119, 0, 0, 90, 1023, 0, 1023, 0, 0, 0,
+ 1023, 366, 1023, 655, 0, 58, 1023, 1023, 8, 1023, 1023, 24,
+ 1023, 103, 0, 0, 1023, 919, 1023, 566, 1023, 0, 0, 480,
+ 1023, 1023, 0, 0, 807, 0, 1023, 0, 273, 412, 632, 1023,
+ 1023, 1023, 10, 633, 1023, 692, 978, 0, 0, 1023, 1023, 1023,
+ 25, 494, 215, 0, 148, 1023, 840, 118, 1023, 1023, 999, 1023,
+ 1023, 1023, 0, 0, 1023, 435, 894, 0, 1023, 1023, 168, 1023,
+ 1023, 211, 1023, 1023, 656, 1023, 0, 0, 0, 744, 238, 1023,
+ 0, 196, 907, 0, 0, 0, 838, 726, 1023, 1023, 1023, 0,
+ 0, 0, 1023, 0, 1023, 1023, 1023, 0, 1023, 0, 0, 0,
+ 323, 1023, 1023, 0, 1023, 0, 0, 925, 582, 1023, 0, 685,
+ 1023, 661, 464, 0, 0, 0, 1023, 0, 807, 0, 1023, 1023,
+ 1023, 100, 0, 1023, 302, 1023, 1023, 1023, 616, 0, 1023, 0,
+ 0, 377, 1023, 1023, 1023, 0, 1023, 555, 1023, 784, 0, 0,
+ 1023, 0, 0, 1023, 755, 0, 839, 1023, 0, 0, 0, 1023,
+ 1023, 1023, 0, 1023, 413, 0, 1023, 1023, 384, 0, 823, 797,
+ 1023, 0, 1023, 0, 0, 1023, 1023, 1023, 1023, 0, 1023, 39,
+ 0, 473, 299, 0, 0, 1023, 567, 1023, 1023, 0, 0, 1023,
+ 650, 1023, 41, 1023, 0, 1023, 0, 1023, 0, 1023, 0, 0,
+ 444, 1023, 23, 0, 503, 97, 0, 1023, 0, 890, 59, 578,
+ 0, 201, 1023, 672, 1023, 593, 1023, 599, 213, 1023, 1023, 1023,
+ 986, 1023, 335, 1023, 457, 0, 888, 1023, 1023, 97, 308, 259,
+ 813, 1023, 1023, 1023, 0, 1023, 798, 907, 105, 0, 1023, 0,
+ 1023, 1023, 0, 970, 518, 0, 635, 0, 634, 329, 1023, 430,
+ 0, 17, 1023, 1023, 1023, 0, 0, 407, 1023, 1023, 0, 1023,
+ 0, 0, 0, 0, 1023, 1023, 1023, 402, 1023, 0, 0, 101,
+ 1023, 1023, 1023, 1023, 1023, 1023, 425, 791, 1023, 1023, 961, 0,
+ 0, 1023, 474, 1023, 1023, 1023, 1023, 468, 1023, 1023, 0, 1023,
+ 215, 0, 1023, 1023, 334, 463, 286, 1023, 0, 1023, 0, 1023,
+ 270, 401, 0, 0, 1023, 0, 794, 0, 0, 0, 1023, 0,
+ 1023, 172, 317, 905, 950, 0
+ };
+ unsigned char *img_data =
+ reinterpret_cast<unsigned char *>(const_cast<uint16_t *>(buffer));
+
+ aom_image_t img;
+ EXPECT_EQ(&img, aom_img_wrap(&img, AOM_IMG_FMT_I42016, kWidth, kHeight, 1,
+ img_data));
+ img.cp = AOM_CICP_CP_UNSPECIFIED;
+ img.tc = AOM_CICP_TC_UNSPECIFIED;
+ img.mc = AOM_CICP_MC_UNSPECIFIED;
+ img.monochrome = 1;
+ img.csp = AOM_CSP_UNKNOWN;
+ img.range = AOM_CR_FULL_RANGE;
+ img.planes[1] = img.planes[2] = nullptr;
+ img.stride[1] = img.stride[2] = 0;
+
+ aom_codec_iface_t *iface = aom_codec_av1_cx();
+ aom_codec_enc_cfg_t cfg;
+ EXPECT_EQ(AOM_CODEC_OK,
+ aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY));
+ cfg.rc_end_usage = AOM_Q;
+ cfg.g_profile = 0;
+ cfg.g_bit_depth = AOM_BITS_10;
+ cfg.g_input_bit_depth = 10;
+ cfg.g_w = kWidth;
+ cfg.g_h = kHeight;
+ cfg.g_threads = 6;
+ cfg.monochrome = 1;
+ cfg.rc_min_quantizer = 54;
+ cfg.rc_max_quantizer = 62;
+ aom_codec_ctx_t enc;
+ EXPECT_EQ(AOM_CODEC_OK,
+ aom_codec_enc_init(&enc, iface, &cfg, AOM_CODEC_USE_HIGHBITDEPTH));
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_control(&enc, AOME_SET_CQ_LEVEL, 58));
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_control(&enc, AV1E_SET_TILE_ROWS, 1));
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_control(&enc, AOME_SET_CPUUSED, 6));
+ EXPECT_EQ(AOM_CODEC_OK,
+ aom_codec_control(&enc, AV1E_SET_COLOR_RANGE, AOM_CR_FULL_RANGE));
+ EXPECT_EQ(AOM_CODEC_OK,
+ aom_codec_control(&enc, AOME_SET_TUNING, AOM_TUNE_SSIM));
+
+ // Encode frame
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, &img, 0, 1, 0));
+ aom_codec_iter_t iter = nullptr;
+ const aom_codec_cx_pkt_t *pkt = aom_codec_get_cx_data(&enc, &iter);
+ ASSERT_EQ(pkt, nullptr);
+
+ // Flush encoder
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, nullptr, 0, 1, 0));
+ iter = nullptr;
+ pkt = aom_codec_get_cx_data(&enc, &iter);
+ ASSERT_NE(pkt, nullptr);
+ EXPECT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
+ // pkt->data.frame.flags is 0x1f0011.
+ EXPECT_EQ(pkt->data.frame.flags & AOM_FRAME_IS_KEY, AOM_FRAME_IS_KEY);
+ pkt = aom_codec_get_cx_data(&enc, &iter);
+ EXPECT_EQ(pkt, nullptr);
+
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_encode(&enc, nullptr, 0, 1, 0));
+ iter = nullptr;
+ pkt = aom_codec_get_cx_data(&enc, &iter);
+ EXPECT_EQ(pkt, nullptr);
+
+ EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc));
+}
+
// A test that reproduces b/277121724: signed integer overflow in
// update_b_sep_sym().
TEST(SearchWienerTest, 8bitSignedIntegerOverflowInUpdateBSepSym) {
diff --git a/third_party/aom/third_party/libwebm/README.libaom b/third_party/aom/third_party/libwebm/README.libaom
index ee350a523a..1eb0ce9a94 100644
--- a/third_party/aom/third_party/libwebm/README.libaom
+++ b/third_party/aom/third_party/libwebm/README.libaom
@@ -1,5 +1,5 @@
URL: https://chromium.googlesource.com/webm/libwebm
-Version: 1930e3ca23b007f3ff11d98a570077be6201957e
+Version: affd7f4d9644aa2b65981fa6c7616400be760e6e
License: BSD
License File: LICENSE.TXT
diff --git a/third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxer.cc b/third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxer.cc
index faaf0165f4..21e51be474 100644
--- a/third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxer.cc
+++ b/third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxer.cc
@@ -65,7 +65,8 @@ bool StrCpy(const char* src, char** dst_ptr) {
if (dst == NULL)
return false;
- strcpy(dst, src); // NOLINT
+ memcpy(dst, src, size - 1);
+ dst[size - 1] = '\0';
return true;
}
@@ -919,11 +920,8 @@ void Track::set_codec_id(const char* codec_id) {
const size_t length = strlen(codec_id) + 1;
codec_id_ = new (std::nothrow) char[length]; // NOLINT
if (codec_id_) {
-#ifdef _MSC_VER
- strcpy_s(codec_id_, length, codec_id);
-#else
- strcpy(codec_id_, codec_id);
-#endif
+ memcpy(codec_id_, codec_id, length - 1);
+ codec_id_[length - 1] = '\0';
}
}
}
@@ -936,11 +934,8 @@ void Track::set_language(const char* language) {
const size_t length = strlen(language) + 1;
language_ = new (std::nothrow) char[length]; // NOLINT
if (language_) {
-#ifdef _MSC_VER
- strcpy_s(language_, length, language);
-#else
- strcpy(language_, language);
-#endif
+ memcpy(language_, language, length - 1);
+ language_[length - 1] = '\0';
}
}
}
@@ -952,11 +947,8 @@ void Track::set_name(const char* name) {
const size_t length = strlen(name) + 1;
name_ = new (std::nothrow) char[length]; // NOLINT
if (name_) {
-#ifdef _MSC_VER
- strcpy_s(name_, length, name);
-#else
- strcpy(name_, name);
-#endif
+ memcpy(name_, name, length - 1);
+ name_[length - 1] = '\0';
}
}
}
@@ -1559,11 +1551,8 @@ void VideoTrack::set_colour_space(const char* colour_space) {
const size_t length = strlen(colour_space) + 1;
colour_space_ = new (std::nothrow) char[length]; // NOLINT
if (colour_space_) {
-#ifdef _MSC_VER
- strcpy_s(colour_space_, length, colour_space);
-#else
- strcpy(colour_space_, colour_space);
-#endif
+ memcpy(colour_space_, colour_space, length - 1);
+ colour_space_[length - 1] = '\0';
}
}
}
@@ -2856,13 +2845,13 @@ bool SeekHead::AddSeekEntry(uint32_t id, uint64_t pos) {
uint32_t SeekHead::GetId(int index) const {
if (index < 0 || index >= kSeekEntryCount)
- return UINT_MAX;
+ return UINT32_MAX;
return seek_entry_id_[index];
}
uint64_t SeekHead::GetPosition(int index) const {
if (index < 0 || index >= kSeekEntryCount)
- return ULLONG_MAX;
+ return UINT64_MAX;
return seek_entry_pos_[index];
}
@@ -2896,7 +2885,7 @@ SegmentInfo::SegmentInfo()
muxing_app_(NULL),
timecode_scale_(1000000ULL),
writing_app_(NULL),
- date_utc_(LLONG_MIN),
+ date_utc_(INT64_MIN),
duration_pos_(-1) {}
SegmentInfo::~SegmentInfo() {
@@ -2927,11 +2916,8 @@ bool SegmentInfo::Init() {
if (!muxing_app_)
return false;
-#ifdef _MSC_VER
- strcpy_s(muxing_app_, app_len, temp);
-#else
- strcpy(muxing_app_, temp);
-#endif
+ memcpy(muxing_app_, temp, app_len - 1);
+ muxing_app_[app_len - 1] = '\0';
set_writing_app(temp);
if (!writing_app_)
@@ -2974,7 +2960,7 @@ bool SegmentInfo::Write(IMkvWriter* writer) {
if (duration_ > 0.0)
size +=
EbmlElementSize(libwebm::kMkvDuration, static_cast<float>(duration_));
- if (date_utc_ != LLONG_MIN)
+ if (date_utc_ != INT64_MIN)
size += EbmlDateElementSize(libwebm::kMkvDateUTC);
size += EbmlElementSize(libwebm::kMkvMuxingApp, muxing_app_);
size += EbmlElementSize(libwebm::kMkvWritingApp, writing_app_);
@@ -2999,7 +2985,7 @@ bool SegmentInfo::Write(IMkvWriter* writer) {
return false;
}
- if (date_utc_ != LLONG_MIN)
+ if (date_utc_ != INT64_MIN)
WriteEbmlDateElement(writer, libwebm::kMkvDateUTC, date_utc_);
if (!WriteEbmlElement(writer, libwebm::kMkvMuxingApp, muxing_app_))
@@ -3022,11 +3008,8 @@ void SegmentInfo::set_muxing_app(const char* app) {
if (!temp_str)
return;
-#ifdef _MSC_VER
- strcpy_s(temp_str, length, app);
-#else
- strcpy(temp_str, app);
-#endif
+ memcpy(temp_str, app, length - 1);
+ temp_str[length - 1] = '\0';
delete[] muxing_app_;
muxing_app_ = temp_str;
@@ -3040,11 +3023,8 @@ void SegmentInfo::set_writing_app(const char* app) {
if (!temp_str)
return;
-#ifdef _MSC_VER
- strcpy_s(temp_str, length, app);
-#else
- strcpy(temp_str, app);
-#endif
+ memcpy(temp_str, app, length - 1);
+ temp_str[length - 1] = '\0';
delete[] writing_app_;
writing_app_ = temp_str;
@@ -3628,19 +3608,17 @@ bool Segment::SetChunking(bool chunking, const char* filename) {
if (chunking_ && !strcmp(filename, chunking_base_name_))
return true;
- const size_t name_length = strlen(filename) + 1;
- char* const temp = new (std::nothrow) char[name_length]; // NOLINT
+ const size_t filename_length = strlen(filename);
+ char* const temp = new (std::nothrow) char[filename_length + 1]; // NOLINT
if (!temp)
return false;
-#ifdef _MSC_VER
- strcpy_s(temp, name_length, filename);
-#else
- strcpy(temp, filename);
-#endif
+ memcpy(temp, filename, filename_length);
+ temp[filename_length] = '\0';
delete[] chunking_base_name_;
chunking_base_name_ = temp;
+ // From this point, strlen(chunking_base_name_) == filename_length
if (!UpdateChunkName("chk", &chunk_name_))
return false;
@@ -3666,18 +3644,16 @@ bool Segment::SetChunking(bool chunking, const char* filename) {
if (!chunk_writer_cluster_->Open(chunk_name_))
return false;
- const size_t header_length = strlen(filename) + strlen(".hdr") + 1;
+ const size_t hdr_length = strlen(".hdr");
+ const size_t header_length = filename_length + hdr_length + 1;
char* const header = new (std::nothrow) char[header_length]; // NOLINT
if (!header)
return false;
-#ifdef _MSC_VER
- strcpy_s(header, header_length - strlen(".hdr"), chunking_base_name_);
- strcat_s(header, header_length, ".hdr");
-#else
- strcpy(header, chunking_base_name_);
- strcat(header, ".hdr");
-#endif
+ memcpy(header, chunking_base_name_, filename_length);
+ memcpy(&header[filename_length], ".hdr", hdr_length);
+ header[filename_length + hdr_length] = '\0';
+
if (!chunk_writer_header_->Open(header)) {
delete[] header;
return false;
@@ -4022,18 +3998,16 @@ bool Segment::UpdateChunkName(const char* ext, char** name) const {
snprintf(ext_chk, sizeof(ext_chk), "_%06d.%s", chunk_count_, ext);
#endif
- const size_t length = strlen(chunking_base_name_) + strlen(ext_chk) + 1;
+ const size_t chunking_base_name_length = strlen(chunking_base_name_);
+ const size_t ext_chk_length = strlen(ext_chk);
+ const size_t length = chunking_base_name_length + ext_chk_length + 1;
char* const str = new (std::nothrow) char[length]; // NOLINT
if (!str)
return false;
-#ifdef _MSC_VER
- strcpy_s(str, length - strlen(ext_chk), chunking_base_name_);
- strcat_s(str, length, ext_chk);
-#else
- strcpy(str, chunking_base_name_);
- strcat(str, ext_chk);
-#endif
+ memcpy(str, chunking_base_name_, chunking_base_name_length);
+ memcpy(&str[chunking_base_name_length], ext_chk, ext_chk_length);
+ str[chunking_base_name_length + ext_chk_length] = '\0';
delete[] * name;
*name = str;
diff --git a/third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxer.h b/third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxer.h
index 8602d82325..2c4bb9e93e 100644
--- a/third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxer.h
+++ b/third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxer.h
@@ -1481,7 +1481,7 @@ class SegmentInfo {
uint64_t timecode_scale_;
// Initially set to libwebm-%d.%d.%d.%d, major, minor, build, revision.
char* writing_app_;
- // LLONG_MIN when DateUTC is not set.
+ // INT64_MIN when DateUTC is not set.
int64_t date_utc_;
// The file position of the duration element.
diff --git a/third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxerutil.cc b/third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxerutil.cc
index 300b155797..f538310e21 100644
--- a/third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxerutil.cc
+++ b/third_party/aom/third_party/libwebm/mkvmuxer/mkvmuxerutil.cc
@@ -607,22 +607,18 @@ uint64 WriteVoidElement(IMkvWriter* writer, uint64 size) {
void GetVersion(int32* major, int32* minor, int32* build, int32* revision) {
*major = 0;
*minor = 3;
- *build = 1;
+ *build = 3;
*revision = 0;
}
uint64 MakeUID(unsigned int* seed) {
uint64 uid = 0;
-#ifdef __MINGW32__
- srand(*seed);
-#endif
-
for (int i = 0; i < 7; ++i) { // avoid problems with 8-byte values
uid <<= 8;
// TODO(fgalligan): Move random number generation to platform specific code.
-#ifdef _MSC_VER
+#ifdef _WIN32
(void)seed;
const int32 nn = rand();
#elif __ANDROID__
@@ -634,8 +630,6 @@ uint64 MakeUID(unsigned int* seed) {
close(fd);
}
const int32 nn = temp_num;
-#elif defined __MINGW32__
- const int32 nn = rand();
#else
const int32 nn = rand_r(seed);
#endif
diff --git a/third_party/aom/third_party/libwebm/mkvparser/mkvparser.cc b/third_party/aom/third_party/libwebm/mkvparser/mkvparser.cc
index 868afcb3ed..eddbc7eb50 100644
--- a/third_party/aom/third_party/libwebm/mkvparser/mkvparser.cc
+++ b/third_party/aom/third_party/libwebm/mkvparser/mkvparser.cc
@@ -55,7 +55,7 @@ Type* SafeArrayAlloc(unsigned long long num_elements,
void GetVersion(int& major, int& minor, int& build, int& revision) {
major = 1;
minor = 1;
- build = 1;
+ build = 3;
revision = 0;
}
@@ -246,7 +246,8 @@ long UnserializeFloat(IMkvReader* pReader, long long pos, long long size_,
if (size == 4) {
union {
float f;
- unsigned long ff;
+ uint32_t ff;
+ static_assert(sizeof(float) == sizeof(uint32_t), "");
};
ff = 0;
@@ -264,7 +265,8 @@ long UnserializeFloat(IMkvReader* pReader, long long pos, long long size_,
} else {
union {
double d;
- unsigned long long dd;
+ uint64_t dd;
+ static_assert(sizeof(double) == sizeof(uint64_t), "");
};
dd = 0;
@@ -4569,7 +4571,8 @@ int Track::Info::CopyStr(char* Info::*str, Info& dst_) const {
if (dst == NULL)
return -1;
- strcpy(dst, src);
+ memcpy(dst, src, len);
+ dst[len] = '\0';
return 0;
}
diff --git a/third_party/content_analysis_sdk/.gitignore b/third_party/content_analysis_sdk/.gitignore
deleted file mode 100644
index 0ab461830e..0000000000
--- a/third_party/content_analysis_sdk/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-.vscode/
-.ccls-cache/
-.cache/
-build/
-*.bak
-*.swp
diff --git a/third_party/content_analysis_sdk/agent/src/event_win.cc b/third_party/content_analysis_sdk/agent/src/event_win.cc
index 907bdfb858..99b4233237 100644
--- a/third_party/content_analysis_sdk/agent/src/event_win.cc
+++ b/third_party/content_analysis_sdk/agent/src/event_win.cc
@@ -3,7 +3,6 @@
// found in the LICENSE file.
#include <ios>
-#include <iostream>
#include <sstream>
#include <utility>
diff --git a/third_party/content_analysis_sdk/agent_improvements.patch b/third_party/content_analysis_sdk/agent_improvements.patch
new file mode 100644
index 0000000000..c1475caded
--- /dev/null
+++ b/third_party/content_analysis_sdk/agent_improvements.patch
@@ -0,0 +1,480 @@
+commit 4ad63eb3aa65ce7baa08190aac2770540dc25f43
+Author: Greg Stoll <gstoll@mozilla.com>
+Date: Wed, 27 Mar 2024 12:13:56 -0500
+
+ Mozilla improvements to content_analysis_sdk
+
+ - add ability for demo agent to block/warn/report specific regexes
+ - add ability for demo agent to chose a sequence of delays to apply
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 39477223f031c..5dacc81031117 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -203,6 +203,7 @@ add_executable(agent
+ ./demo/agent.cc
+ ./demo/handler.h
+ )
++target_compile_features(agent PRIVATE cxx_std_17)
+ target_include_directories(agent PRIVATE ${AGENT_INCLUDES})
+ target_link_libraries(agent PRIVATE cac_agent)
+
+diff --git a/agent/src/event_win.h b/agent/src/event_win.h
+index 9f8b6903566f2..f631f693dcd9c 100644
+--- a/agent/src/event_win.h
++++ b/agent/src/event_win.h
+@@ -28,6 +28,12 @@ class ContentAnalysisEventWin : public ContentAnalysisEventBase {
+ ResultCode Close() override;
+ ResultCode Send() override;
+ std::string DebugString() const override;
++ std::string SerializeStringToSendToBrowser() {
++ return agent_to_chrome()->SerializeAsString();
++ }
++ void SetResponseSent() { response_sent_ = true; }
++
++ HANDLE Pipe() const { return hPipe_; }
+
+ private:
+ void Shutdown();
+diff --git a/browser/src/client_win.cc b/browser/src/client_win.cc
+index 9d3d7e8c52662..039946d131398 100644
+--- a/browser/src/client_win.cc
++++ b/browser/src/client_win.cc
+@@ -418,7 +418,11 @@ DWORD ClientWin::ConnectToPipe(const std::string& pipename, HANDLE* handle) {
+
+ void ClientWin::Shutdown() {
+ if (hPipe_ != INVALID_HANDLE_VALUE) {
+- FlushFileBuffers(hPipe_);
++ // TODO: This trips the LateWriteObserver. We could move this earlier
++ // (before the LateWriteObserver is created) or just remove it, although
++ // the later could mean an ACK message is not processed by the agent
++ // in time.
++ // FlushFileBuffers(hPipe_);
+ CloseHandle(hPipe_);
+ hPipe_ = INVALID_HANDLE_VALUE;
+ }
+diff --git a/demo/agent.cc b/demo/agent.cc
+index ff8b93f647ebd..3e168b0915a0c 100644
+--- a/demo/agent.cc
++++ b/demo/agent.cc
+@@ -2,12 +2,18 @@
+ // Use of this source code is governed by a BSD-style license that can be
+ // found in the LICENSE file.
+
++#include <algorithm>
+ #include <fstream>
+ #include <iostream>
+ #include <string>
++#include <regex>
++#include <vector>
+
+ #include "content_analysis/sdk/analysis_agent.h"
+ #include "demo/handler.h"
++#include "demo/handler_misbehaving.h"
++
++using namespace content_analysis::sdk;
+
+ // Different paths are used depending on whether this agent should run as a
+ // use specific agent or not. These values are chosen to match the test
+@@ -19,19 +25,50 @@ constexpr char kPathSystem[] = "brcm_chrm_cas";
+ std::string path = kPathSystem;
+ bool use_queue = false;
+ bool user_specific = false;
+-unsigned long delay = 0; // In seconds.
++std::vector<unsigned long> delays = {0}; // In seconds.
+ unsigned long num_threads = 8u;
+ std::string save_print_data_path = "";
++RegexArray toBlock, toWarn, toReport;
++static bool useMisbehavingHandler = false;
++static std::string modeStr;
+
+ // Command line parameters.
+-constexpr const char* kArgDelaySpecific = "--delay=";
++constexpr const char* kArgDelaySpecific = "--delays=";
+ constexpr const char* kArgPath = "--path=";
+ constexpr const char* kArgQueued = "--queued";
+ constexpr const char* kArgThreads = "--threads=";
+ constexpr const char* kArgUserSpecific = "--user";
++constexpr const char* kArgToBlock = "--toblock=";
++constexpr const char* kArgToWarn = "--towarn=";
++constexpr const char* kArgToReport = "--toreport=";
++constexpr const char* kArgMisbehave = "--misbehave=";
+ constexpr const char* kArgHelp = "--help";
+ constexpr const char* kArgSavePrintRequestDataTo = "--save-print-request-data-to=";
+
++std::map<std::string, Mode> sStringToMode = {
++#define AGENT_MODE(name) {#name, Mode::Mode_##name},
++#include "modes.h"
++#undef AGENT_MODE
++};
++
++std::map<Mode, std::string> sModeToString = {
++#define AGENT_MODE(name) {Mode::Mode_##name, #name},
++#include "modes.h"
++#undef AGENT_MODE
++};
++
++std::vector<std::pair<std::string, std::regex>>
++ParseRegex(const std::string str) {
++ std::vector<std::pair<std::string, std::regex>> ret;
++ for (auto it = str.begin(); it != str.end(); /* nop */) {
++ auto it2 = std::find(it, str.end(), ',');
++ ret.push_back(std::make_pair(std::string(it, it2), std::regex(it, it2)));
++ it = it2 == str.end() ? it2 : it2 + 1;
++ }
++
++ return ret;
++}
++
+ bool ParseCommandLine(int argc, char* argv[]) {
+ for (int i = 1; i < argc; ++i) {
+ const std::string arg = argv[i];
+@@ -44,16 +81,38 @@ bool ParseCommandLine(int argc, char* argv[]) {
+ path = kPathUser;
+ user_specific = true;
+ } else if (arg.find(kArgDelaySpecific) == 0) {
+- delay = std::stoul(arg.substr(strlen(kArgDelaySpecific)));
++ std::string delaysStr = arg.substr(strlen(kArgDelaySpecific));
++ delays.clear();
++ size_t posStart = 0, posEnd;
++ unsigned long delay;
++ while ((posEnd = delaysStr.find(',', posStart)) != std::string::npos) {
++ delay = std::stoul(delaysStr.substr(posStart, posEnd - posStart));
++ if (delay > 30) {
++ delay = 30;
++ }
++ delays.push_back(delay);
++ posStart = posEnd + 1;
++ }
++ delay = std::stoul(delaysStr.substr(posStart));
+ if (delay > 30) {
+ delay = 30;
+ }
++ delays.push_back(delay);
+ } else if (arg.find(kArgPath) == 0) {
+ path = arg.substr(strlen(kArgPath));
+ } else if (arg.find(kArgQueued) == 0) {
+ use_queue = true;
+ } else if (arg.find(kArgThreads) == 0) {
+ num_threads = std::stoul(arg.substr(strlen(kArgThreads)));
++ } else if (arg.find(kArgToBlock) == 0) {
++ toBlock = ParseRegex(arg.substr(strlen(kArgToBlock)));
++ } else if (arg.find(kArgToWarn) == 0) {
++ toWarn = ParseRegex(arg.substr(strlen(kArgToWarn)));
++ } else if (arg.find(kArgToReport) == 0) {
++ toReport = ParseRegex(arg.substr(strlen(kArgToReport)));
++ } else if (arg.find(kArgMisbehave) == 0) {
++ modeStr = arg.substr(strlen(kArgMisbehave));
++ useMisbehavingHandler = true;
+ } else if (arg.find(kArgHelp) == 0) {
+ return false;
+ } else if (arg.find(kArgSavePrintRequestDataTo) == 0) {
+@@ -72,13 +131,17 @@ void PrintHelp() {
+ << "A simple agent to process content analysis requests." << std::endl
+ << "Data containing the string 'block' blocks the request data from being used." << std::endl
+ << std::endl << "Options:" << std::endl
+- << kArgDelaySpecific << "<delay> : Add a delay to request processing in seconds (max 30)." << std::endl
++ << kArgDelaySpecific << "<delay1,delay2,...> : Add delays to request processing in seconds. Delays are limited to 30 seconds and are applied round-robin to requests. Default is 0." << std::endl
+ << kArgPath << " <path> : Used the specified path instead of default. Must come after --user." << std::endl
+ << kArgQueued << " : Queue requests for processing in a background thread" << std::endl
+ << kArgThreads << " : When queued, number of threads in the request processing thread pool" << std::endl
+ << kArgUserSpecific << " : Make agent OS user specific." << std::endl
+ << kArgHelp << " : prints this help message" << std::endl
+- << kArgSavePrintRequestDataTo << " : saves the PDF data to the given file path for print requests";
++ << kArgSavePrintRequestDataTo << " : saves the PDF data to the given file path for print requests" << std::endl
++ << kArgToBlock << "<regex> : Regular expression matching file and text content to block." << std::endl
++ << kArgToWarn << "<regex> : Regular expression matching file and text content to warn about." << std::endl
++ << kArgToReport << "<regex> : Regular expression matching file and text content to report." << std::endl
++ << kArgMisbehave << "<mode> : Use 'misbehaving' agent in given mode for testing purposes." << std::endl;
+ }
+
+ int main(int argc, char* argv[]) {
+@@ -87,9 +150,17 @@ int main(int argc, char* argv[]) {
+ return 1;
+ }
+
+- auto handler = use_queue
+- ? std::make_unique<QueuingHandler>(num_threads, delay, save_print_data_path)
+- : std::make_unique<Handler>(delay, save_print_data_path);
++ auto handler =
++ useMisbehavingHandler
++ ? MisbehavingHandler::Create(modeStr, std::move(delays), save_print_data_path, std::move(toBlock), std::move(toWarn), std::move(toReport))
++ : use_queue
++ ? std::make_unique<QueuingHandler>(num_threads, std::move(delays), save_print_data_path, std::move(toBlock), std::move(toWarn), std::move(toReport))
++ : std::make_unique<Handler>(std::move(delays), save_print_data_path, std::move(toBlock), std::move(toWarn), std::move(toReport));
++
++ if (!handler) {
++ std::cout << "[Demo] Failed to construct handler." << std::endl;
++ return 1;
++ }
+
+ // Each agent uses a unique name to identify itself with Google Chrome.
+ content_analysis::sdk::ResultCode rc;
+diff --git a/demo/handler.h b/demo/handler.h
+index 9d1ccfdf9857a..88599963c51b0 100644
+--- a/demo/handler.h
++++ b/demo/handler.h
+@@ -7,31 +7,51 @@
+
+ #include <time.h>
+
++#include <algorithm>
++#include <atomic>
+ #include <chrono>
+ #include <cstdio>
+ #include <fstream>
+ #include <iostream>
++#include <optional>
+ #include <thread>
+ #include <utility>
++#include <regex>
+ #include <vector>
+
+ #include "content_analysis/sdk/analysis_agent.h"
+ #include "demo/atomic_output.h"
+ #include "demo/request_queue.h"
+
++using RegexArray = std::vector<std::pair<std::string, std::regex>>;
++
+ // An AgentEventHandler that dumps requests information to stdout and blocks
+ // any requests that have the keyword "block" in their data
+ class Handler : public content_analysis::sdk::AgentEventHandler {
+ public:
+ using Event = content_analysis::sdk::ContentAnalysisEvent;
+
+- Handler(unsigned long delay, const std::string& print_data_file_path) :
+- delay_(delay), print_data_file_path_(print_data_file_path) {
+- }
++ Handler(std::vector<unsigned long>&& delays, const std::string& print_data_file_path,
++ RegexArray&& toBlock = RegexArray(),
++ RegexArray&& toWarn = RegexArray(),
++ RegexArray&& toReport = RegexArray()) :
++ toBlock_(std::move(toBlock)), toWarn_(std::move(toWarn)), toReport_(std::move(toReport)),
++ delays_(std::move(delays)), print_data_file_path_(print_data_file_path) {}
+
+- unsigned long delay() { return delay_; }
++ const std::vector<unsigned long> delays() { return delays_; }
++ size_t nextDelayIndex() const { return nextDelayIndex_; }
+
+ protected:
++ // subclasses can override this
++ // returns whether the response has been set
++ virtual bool SetCustomResponse(AtomicCout& aout, std::unique_ptr<Event>& event) {
++ return false;
++ }
++ // subclasses can override this
++ // returns whether the response has been sent
++ virtual bool SendCustomResponse(std::unique_ptr<Event>& event) {
++ return false;
++ }
+ // Analyzes one request from Google Chrome and responds back to the browser
+ // with either an allow or block verdict.
+ void AnalyzeContent(AtomicCout& aout, std::unique_ptr<Event> event) {
+@@ -43,29 +63,25 @@ class Handler : public content_analysis::sdk::AgentEventHandler {
+
+ DumpEvent(aout.stream(), event.get());
+
+- bool block = false;
+ bool success = true;
+- unsigned long delay = delay_;
+-
+- if (event->GetRequest().has_text_content()) {
+- block = ShouldBlockRequest(
+- event->GetRequest().text_content());
+- GetFileSpecificDelay(event->GetRequest().text_content(), &delay);
+- } else if (event->GetRequest().has_file_path()) {
+- std::string content;
+- success =
+- ReadContentFromFile(event->GetRequest().file_path(),
+- &content);
+- if (success) {
+- block = ShouldBlockRequest(content);
+- GetFileSpecificDelay(content, &delay);
++ std::optional<content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action> caResponse;
++ bool setResponse = SetCustomResponse(aout, event);
++ if (!setResponse) {
++ caResponse = content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK;
++ if (event->GetRequest().has_text_content()) {
++ caResponse = DecideCAResponse(
++ event->GetRequest().text_content(), aout.stream());
++ } else if (event->GetRequest().has_file_path()) {
++ // TODO: Fix downloads to store file *first* so we can check contents.
++ // Until then, just check the file name:
++ caResponse = DecideCAResponse(
++ event->GetRequest().file_path(), aout.stream());
++ } else if (event->GetRequest().has_print_data()) {
++ // In the case of print request, normally the PDF bytes would be parsed
++ // for sensitive data violations. To keep this class simple, only the
++ // URL is checked for the word "block".
++ caResponse = DecideCAResponse(event->GetRequest().request_data().url(), aout.stream());
+ }
+- } else if (event->GetRequest().has_print_data()) {
+- // In the case of print request, normally the PDF bytes would be parsed
+- // for sensitive data violations. To keep this class simple, only the
+- // URL is checked for the word "block".
+- block = ShouldBlockRequest(event->GetRequest().request_data().url());
+- GetFileSpecificDelay(event->GetRequest().request_data().url(), &delay);
+ }
+
+ if (!success) {
+@@ -75,22 +91,44 @@ class Handler : public content_analysis::sdk::AgentEventHandler {
+ content_analysis::sdk::ContentAnalysisResponse::Result::FAILURE);
+ aout.stream() << " Verdict: failed to reach verdict: ";
+ aout.stream() << event->DebugString() << std::endl;
+- } else if (block) {
+- auto rc = content_analysis::sdk::SetEventVerdictToBlock(event.get());
+- aout.stream() << " Verdict: block";
+- if (rc != content_analysis::sdk::ResultCode::OK) {
+- aout.stream() << " error: "
+- << content_analysis::sdk::ResultCodeToString(rc) << std::endl;
+- aout.stream() << " " << event->DebugString() << std::endl;
++ } else {
++ aout.stream() << " Verdict: ";
++ if (caResponse) {
++ switch (caResponse.value()) {
++ case content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK:
++ aout.stream() << "BLOCK";
++ break;
++ case content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_WARN:
++ aout.stream() << "WARN";
++ break;
++ case content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_REPORT_ONLY:
++ aout.stream() << "REPORT_ONLY";
++ break;
++ case content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_ACTION_UNSPECIFIED:
++ aout.stream() << "ACTION_UNSPECIFIED";
++ break;
++ default:
++ aout.stream() << "<error>";
++ break;
++ }
++ auto rc =
++ content_analysis::sdk::SetEventVerdictTo(event.get(), caResponse.value());
++ if (rc != content_analysis::sdk::ResultCode::OK) {
++ aout.stream() << " error: "
++ << content_analysis::sdk::ResultCodeToString(rc) << std::endl;
++ aout.stream() << " " << event->DebugString() << std::endl;
++ }
++ aout.stream() << std::endl;
++ } else {
++ aout.stream() << " Verdict: allow" << std::endl;
+ }
+ aout.stream() << std::endl;
+- } else {
+- aout.stream() << " Verdict: allow" << std::endl;
+ }
+-
+ aout.stream() << std::endl;
+
+ // If a delay is specified, wait that much.
++ size_t nextDelayIndex = nextDelayIndex_.fetch_add(1);
++ unsigned long delay = delays_[nextDelayIndex % delays_.size()];
+ if (delay > 0) {
+ aout.stream() << "Delaying response to " << event->GetRequest().request_token()
+ << " for " << delay << "s" << std::endl<< std::endl;
+@@ -99,16 +137,19 @@ class Handler : public content_analysis::sdk::AgentEventHandler {
+ }
+
+ // Send the response back to Google Chrome.
+- auto rc = event->Send();
+- if (rc != content_analysis::sdk::ResultCode::OK) {
+- aout.stream() << "[Demo] Error sending response: "
+- << content_analysis::sdk::ResultCodeToString(rc)
+- << std::endl;
+- aout.stream() << event->DebugString() << std::endl;
++ bool sentCustomResponse = SendCustomResponse(event);
++ if (!sentCustomResponse) {
++ auto rc = event->Send();
++ if (rc != content_analysis::sdk::ResultCode::OK) {
++ aout.stream() << "[Demo] Error sending response: "
++ << content_analysis::sdk::ResultCodeToString(rc)
++ << std::endl;
++ aout.stream() << event->DebugString() << std::endl;
++ }
+ }
+ }
+
+- private:
++ protected:
+ void OnBrowserConnected(
+ const content_analysis::sdk::BrowserInfo& info) override {
+ AtomicCout aout;
+@@ -362,21 +403,40 @@ class Handler : public content_analysis::sdk::AgentEventHandler {
+ return true;
+ }
+
+- bool ShouldBlockRequest(const std::string& content) {
+- // Determines if the request should be blocked. For this simple example
+- // the content is blocked if the string "block" is found. Otherwise the
+- // content is allowed.
+- return content.find("block") != std::string::npos;
+- }
+-
+- void GetFileSpecificDelay(const std::string& content, unsigned long* delay) {
+- auto pos = content.find("delay=");
+- if (pos != std::string::npos) {
+- std::sscanf(content.substr(pos).c_str(), "delay=%lu", delay);
++ std::optional<content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action>
++ DecideCAResponse(const std::string& content, std::stringstream& stream) {
++ for (auto& r : toBlock_) {
++ if (std::regex_search(content, r.second)) {
++ stream << "'" << content << "' matches BLOCK regex '"
++ << r.first << "'" << std::endl;
++ return content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK;
++ }
+ }
++ for (auto& r : toWarn_) {
++ if (std::regex_search(content, r.second)) {
++ stream << "'" << content << "' matches WARN regex '"
++ << r.first << "'" << std::endl;
++ return content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_WARN;
++ }
++ }
++ for (auto& r : toReport_) {
++ if (std::regex_search(content, r.second)) {
++ stream << "'" << content << "' matches REPORT_ONLY regex '"
++ << r.first << "'" << std::endl;
++ return content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_REPORT_ONLY;
++ }
++ }
++ stream << "'" << content << "' was ALLOWed\n";
++ return {};
+ }
+
+- unsigned long delay_;
++ // For the demo, block any content that matches these wildcards.
++ RegexArray toBlock_;
++ RegexArray toWarn_;
++ RegexArray toReport_;
++
++ std::vector<unsigned long> delays_;
++ std::atomic<size_t> nextDelayIndex_;
+ std::string print_data_file_path_;
+ };
+
+@@ -384,8 +444,11 @@ class Handler : public content_analysis::sdk::AgentEventHandler {
+ // any requests that have the keyword "block" in their data
+ class QueuingHandler : public Handler {
+ public:
+- QueuingHandler(unsigned long threads, unsigned long delay, const std::string& print_data_file_path)
+- : Handler(delay, print_data_file_path) {
++ QueuingHandler(unsigned long threads, std::vector<unsigned long>&& delays, const std::string& print_data_file_path,
++ RegexArray&& toBlock = RegexArray(),
++ RegexArray&& toWarn = RegexArray(),
++ RegexArray&& toReport = RegexArray())
++ : Handler(std::move(delays), print_data_file_path, std::move(toBlock), std::move(toWarn), std::move(toReport)) {
+ StartBackgroundThreads(threads);
+ }
+
+@@ -421,6 +484,8 @@ class QueuingHandler : public Handler {
+ aout.stream() << std::endl << "----------" << std::endl;
+ aout.stream() << "Thread: " << std::this_thread::get_id()
+ << std::endl;
++ aout.stream() << "Delaying request processing for "
++ << handler->delays()[handler->nextDelayIndex() % handler->delays().size()] << "s" << std::endl << std::endl;
+ aout.flush();
+
+ handler->AnalyzeContent(aout, std::move(event));
+--
+2.42.0.windows.2
+
diff --git a/third_party/content_analysis_sdk/browser/src/client_mac.cc b/third_party/content_analysis_sdk/browser/src/client_mac.cc
index c6f5a798c1..fd0902e516 100644
--- a/third_party/content_analysis_sdk/browser/src/client_mac.cc
+++ b/third_party/content_analysis_sdk/browser/src/client_mac.cc
@@ -11,7 +11,7 @@ namespace sdk {
// static
std::unique_ptr<Client> Client::Create(Config config) {
- return std::make_unique<ClientMac>(std::move(config));
+ return nullptr;
}
ClientMac::ClientMac(Config config) : ClientBase(std::move(config)) {}
@@ -30,4 +30,4 @@ int ClientMac::CancelRequests(const ContentAnalysisCancelRequests& cancel) {
}
} // namespace sdk
-} // namespace content_analysis \ No newline at end of file
+} // namespace content_analysis
diff --git a/third_party/content_analysis_sdk/browser/src/client_posix.cc b/third_party/content_analysis_sdk/browser/src/client_posix.cc
index 14277724fd..bd62b845a0 100644
--- a/third_party/content_analysis_sdk/browser/src/client_posix.cc
+++ b/third_party/content_analysis_sdk/browser/src/client_posix.cc
@@ -11,7 +11,7 @@ namespace sdk {
// static
std::unique_ptr<Client> Client::Create(Config config) {
- return std::make_unique<ClientPosix>(std::move(config));
+ return nullptr;
}
ClientPosix::ClientPosix(Config config) : ClientBase(std::move(config)) {}
diff --git a/third_party/content_analysis_sdk/demo/agent.cc b/third_party/content_analysis_sdk/demo/agent.cc
index c3640018e6..3e168b0915 100644
--- a/third_party/content_analysis_sdk/demo/agent.cc
+++ b/third_party/content_analysis_sdk/demo/agent.cc
@@ -136,12 +136,12 @@ void PrintHelp() {
<< kArgQueued << " : Queue requests for processing in a background thread" << std::endl
<< kArgThreads << " : When queued, number of threads in the request processing thread pool" << std::endl
<< kArgUserSpecific << " : Make agent OS user specific." << std::endl
+ << kArgHelp << " : prints this help message" << std::endl
<< kArgSavePrintRequestDataTo << " : saves the PDF data to the given file path for print requests" << std::endl
<< kArgToBlock << "<regex> : Regular expression matching file and text content to block." << std::endl
<< kArgToWarn << "<regex> : Regular expression matching file and text content to warn about." << std::endl
<< kArgToReport << "<regex> : Regular expression matching file and text content to report." << std::endl
- << kArgMisbehave << "<mode> : Use 'misbehaving' agent in given mode for testing purposes." << std::endl
- << kArgHelp << " : prints this help message" << std::endl;
+ << kArgMisbehave << "<mode> : Use 'misbehaving' agent in given mode for testing purposes." << std::endl;
}
int main(int argc, char* argv[]) {
@@ -150,10 +150,9 @@ int main(int argc, char* argv[]) {
return 1;
}
- // TODO: Add toBlock, toWarn, toReport to QueueingHandler
auto handler =
useMisbehavingHandler
- ? MisbehavingHandler::Create(delays[0], modeStr)
+ ? MisbehavingHandler::Create(modeStr, std::move(delays), save_print_data_path, std::move(toBlock), std::move(toWarn), std::move(toReport))
: use_queue
? std::make_unique<QueuingHandler>(num_threads, std::move(delays), save_print_data_path, std::move(toBlock), std::move(toWarn), std::move(toReport))
: std::make_unique<Handler>(std::move(delays), save_print_data_path, std::move(toBlock), std::move(toWarn), std::move(toReport));
diff --git a/third_party/content_analysis_sdk/demo/client.cc b/third_party/content_analysis_sdk/demo/client.cc
index 5e47fca57f..84ca6e2356 100644
--- a/third_party/content_analysis_sdk/demo/client.cc
+++ b/third_party/content_analysis_sdk/demo/client.cc
@@ -317,7 +317,7 @@ void HandleRequest(const ContentAnalysisRequest& request) {
global_final_action = final_action;
} else {
int err = client->Acknowledge(
- BuildAcknowledgement(request.request_token(), final_action));
+ BuildAcknowledgement(response.request_token(), final_action));
if (err != 0) {
aout.stream() << "[Demo] Error sending ack " << request.request_token()
<< std::endl;
diff --git a/third_party/content_analysis_sdk/demo/handler.h b/third_party/content_analysis_sdk/demo/handler.h
index 1c9871bd08..88599963c5 100644
--- a/third_party/content_analysis_sdk/demo/handler.h
+++ b/third_party/content_analysis_sdk/demo/handler.h
@@ -10,6 +10,7 @@
#include <algorithm>
#include <atomic>
#include <chrono>
+#include <cstdio>
#include <fstream>
#include <iostream>
#include <optional>
@@ -41,34 +42,46 @@ class Handler : public content_analysis::sdk::AgentEventHandler {
size_t nextDelayIndex() const { return nextDelayIndex_; }
protected:
+ // subclasses can override this
+ // returns whether the response has been set
+ virtual bool SetCustomResponse(AtomicCout& aout, std::unique_ptr<Event>& event) {
+ return false;
+ }
+ // subclasses can override this
+ // returns whether the response has been sent
+ virtual bool SendCustomResponse(std::unique_ptr<Event>& event) {
+ return false;
+ }
// Analyzes one request from Google Chrome and responds back to the browser
// with either an allow or block verdict.
- void AnalyzeContent(std::stringstream& stream, std::unique_ptr<Event> event) {
+ void AnalyzeContent(AtomicCout& aout, std::unique_ptr<Event> event) {
// An event represents one content analysis request and response triggered
// by a user action in Google Chrome. The agent determines whether the
// user is allowed to perform the action by examining event->GetRequest().
// The verdict, which can be "allow" or "block" is written into
// event->GetResponse().
- DumpEvent(stream, event.get());
+ DumpEvent(aout.stream(), event.get());
bool success = true;
- std::optional<content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action> caResponse =
- content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK;
-
- if (event->GetRequest().has_text_content()) {
- caResponse = DecideCAResponse(
- event->GetRequest().text_content(), stream);
- } else if (event->GetRequest().has_file_path()) {
- // TODO: Fix downloads to store file *first* so we can check contents.
- // Until then, just check the file name:
- caResponse = DecideCAResponse(
- event->GetRequest().file_path(), stream);
- } else if (event->GetRequest().has_print_data()) {
- // In the case of print request, normally the PDF bytes would be parsed
- // for sensitive data violations. To keep this class simple, only the
- // URL is checked for the word "block".
- caResponse = DecideCAResponse(event->GetRequest().request_data().url(), stream);
+ std::optional<content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action> caResponse;
+ bool setResponse = SetCustomResponse(aout, event);
+ if (!setResponse) {
+ caResponse = content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK;
+ if (event->GetRequest().has_text_content()) {
+ caResponse = DecideCAResponse(
+ event->GetRequest().text_content(), aout.stream());
+ } else if (event->GetRequest().has_file_path()) {
+ // TODO: Fix downloads to store file *first* so we can check contents.
+ // Until then, just check the file name:
+ caResponse = DecideCAResponse(
+ event->GetRequest().file_path(), aout.stream());
+ } else if (event->GetRequest().has_print_data()) {
+ // In the case of print request, normally the PDF bytes would be parsed
+ // for sensitive data violations. To keep this class simple, only the
+ // URL is checked for the word "block".
+ caResponse = DecideCAResponse(event->GetRequest().request_data().url(), aout.stream());
+ }
}
if (!success) {
@@ -76,61 +89,67 @@ class Handler : public content_analysis::sdk::AgentEventHandler {
event->GetResponse(),
std::string(),
content_analysis::sdk::ContentAnalysisResponse::Result::FAILURE);
- stream << " Verdict: failed to reach verdict: ";
- stream << event->DebugString() << std::endl;
+ aout.stream() << " Verdict: failed to reach verdict: ";
+ aout.stream() << event->DebugString() << std::endl;
} else {
- stream << " Verdict: ";
+ aout.stream() << " Verdict: ";
if (caResponse) {
switch (caResponse.value()) {
case content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK:
- stream << "BLOCK";
+ aout.stream() << "BLOCK";
break;
case content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_WARN:
- stream << "WARN";
+ aout.stream() << "WARN";
break;
case content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_REPORT_ONLY:
- stream << "REPORT_ONLY";
+ aout.stream() << "REPORT_ONLY";
break;
case content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action_ACTION_UNSPECIFIED:
- stream << "ACTION_UNSPECIFIED";
+ aout.stream() << "ACTION_UNSPECIFIED";
break;
default:
- stream << "<error>";
+ aout.stream() << "<error>";
break;
}
auto rc =
content_analysis::sdk::SetEventVerdictTo(event.get(), caResponse.value());
if (rc != content_analysis::sdk::ResultCode::OK) {
- stream << " error: "
- << content_analysis::sdk::ResultCodeToString(rc) << std::endl;
- stream << " " << event->DebugString() << std::endl;
+ aout.stream() << " error: "
+ << content_analysis::sdk::ResultCodeToString(rc) << std::endl;
+ aout.stream() << " " << event->DebugString() << std::endl;
}
- stream << std::endl;
+ aout.stream() << std::endl;
} else {
- stream << " Verdict: allow" << std::endl;
+ aout.stream() << " Verdict: allow" << std::endl;
}
- stream << std::endl;
+ aout.stream() << std::endl;
}
- stream << std::endl;
+ aout.stream() << std::endl;
// If a delay is specified, wait that much.
size_t nextDelayIndex = nextDelayIndex_.fetch_add(1);
unsigned long delay = delays_[nextDelayIndex % delays_.size()];
if (delay > 0) {
+ aout.stream() << "Delaying response to " << event->GetRequest().request_token()
+ << " for " << delay << "s" << std::endl<< std::endl;
+ aout.flush();
std::this_thread::sleep_for(std::chrono::seconds(delay));
}
// Send the response back to Google Chrome.
- auto rc = event->Send();
- if (rc != content_analysis::sdk::ResultCode::OK) {
- stream << "[Demo] Error sending response: "
- << content_analysis::sdk::ResultCodeToString(rc)
- << std::endl;
- stream << event->DebugString() << std::endl;
+ bool sentCustomResponse = SendCustomResponse(event);
+ if (!sentCustomResponse) {
+ auto rc = event->Send();
+ if (rc != content_analysis::sdk::ResultCode::OK) {
+ aout.stream() << "[Demo] Error sending response: "
+ << content_analysis::sdk::ResultCodeToString(rc)
+ << std::endl;
+ aout.stream() << event->DebugString() << std::endl;
+ }
}
}
- private:
+ protected:
void OnBrowserConnected(
const content_analysis::sdk::BrowserInfo& info) override {
AtomicCout aout;
@@ -155,7 +174,7 @@ class Handler : public content_analysis::sdk::AgentEventHandler {
// In this example code, the event is handled synchronously.
AtomicCout aout;
aout.stream() << std::endl << "----------" << std::endl << std::endl;
- AnalyzeContent(aout.stream(), std::move(event));
+ AnalyzeContent(aout, std::move(event));
}
void OnResponseAcknowledged(
@@ -183,7 +202,7 @@ class Handler : public content_analysis::sdk::AgentEventHandler {
}
AtomicCout aout;
- aout.stream() << "Ack: " << ack.request_token() << std::endl;
+ aout.stream() << " Ack: " << ack.request_token() << std::endl;
aout.stream() << " Final action: " << final_action << std::endl;
}
void OnCancelRequests(
@@ -206,31 +225,62 @@ class Handler : public content_analysis::sdk::AgentEventHandler {
void DumpEvent(std::stringstream& stream, Event* event) {
time_t now = time(nullptr);
- stream << "Received at: " << ctime(&now); // Returned string includes \n.
+ stream << "Received at: " << ctime(&now); // Includes \n.
+ stream << "Received from: pid=" << event->GetBrowserInfo().pid
+ << " path=" << event->GetBrowserInfo().binary_path << std::endl;
const content_analysis::sdk::ContentAnalysisRequest& request =
event->GetRequest();
std::string connector = "<Unknown>";
if (request.has_analysis_connector()) {
- switch (request.analysis_connector())
- {
- case content_analysis::sdk::FILE_DOWNLOADED:
- connector = "download";
- break;
- case content_analysis::sdk::FILE_ATTACHED:
- connector = "attach";
- break;
- case content_analysis::sdk::BULK_DATA_ENTRY:
- connector = "bulk-data-entry";
- break;
- case content_analysis::sdk::PRINT:
- connector = "print";
- break;
- case content_analysis::sdk::FILE_TRANSFER:
- connector = "file-transfer";
- break;
- default:
- break;
+ switch (request.analysis_connector()) {
+ case content_analysis::sdk::FILE_DOWNLOADED:
+ connector = "download";
+ break;
+ case content_analysis::sdk::FILE_ATTACHED:
+ connector = "attach";
+ break;
+ case content_analysis::sdk::BULK_DATA_ENTRY:
+ connector = "bulk-data-entry";
+ break;
+ case content_analysis::sdk::PRINT:
+ connector = "print";
+ break;
+ case content_analysis::sdk::FILE_TRANSFER:
+ connector = "file-transfer";
+ break;
+ default:
+ break;
+ }
+ }
+ std::string reason;
+ if (request.has_reason()) {
+ using content_analysis::sdk::ContentAnalysisRequest;
+ switch (request.reason()) {
+ case content_analysis::sdk::ContentAnalysisRequest::UNKNOWN:
+ reason = "<Unknown>";
+ break;
+ case content_analysis::sdk::ContentAnalysisRequest::CLIPBOARD_PASTE:
+ reason = "CLIPBOARD_PASTE";
+ break;
+ case content_analysis::sdk::ContentAnalysisRequest::DRAG_AND_DROP:
+ reason = "DRAG_AND_DROP";
+ break;
+ case content_analysis::sdk::ContentAnalysisRequest::FILE_PICKER_DIALOG:
+ reason = "FILE_PICKER_DIALOG";
+ break;
+ case content_analysis::sdk::ContentAnalysisRequest::PRINT_PREVIEW_PRINT:
+ reason = "PRINT_PREVIEW_PRINT";
+ break;
+ case content_analysis::sdk::ContentAnalysisRequest::SYSTEM_DIALOG_PRINT:
+ reason = "SYSTEM_DIALOG_PRINT";
+ break;
+ case content_analysis::sdk::ContentAnalysisRequest::NORMAL_DOWNLOAD:
+ reason = "NORMAL_DOWNLOAD";
+ break;
+ case content_analysis::sdk::ContentAnalysisRequest::SAVE_AS_DOWNLOAD:
+ reason = "SAVE_AS_DOWNLOAD";
+ break;
}
}
@@ -252,11 +302,7 @@ class Handler : public content_analysis::sdk::AgentEventHandler {
std::string file_path =
request.has_file_path()
- ? request.file_path() : "<none>";
-
- std::string text_content =
- request.has_text_content()
- ? request.text_content() : "<none>";
+ ? request.file_path() : "None, bulk text entry or print";
std::string machine_user =
request.has_client_metadata() &&
@@ -282,14 +328,35 @@ class Handler : public content_analysis::sdk::AgentEventHandler {
stream << " Expires at: " << expires_at_str << " ("
<< secs_remaining << " seconds from now)" << std::endl;
stream << " Connector: " << connector << std::endl;
+ if (!reason.empty()) {
+ stream << " Reason: " << reason << std::endl;
+ }
stream << " URL: " << url << std::endl;
stream << " Tab title: " << tab_title << std::endl;
stream << " Filename: " << filename << std::endl;
stream << " Digest: " << digest << std::endl;
stream << " Filepath: " << file_path << std::endl;
- stream << " Text content: '" << text_content << "'" << std::endl;
stream << " Machine user: " << machine_user << std::endl;
stream << " Email: " << email << std::endl;
+
+ if (request.has_text_content() && !request.text_content().empty()) {
+ std::string prefix = " Pasted data: ";
+ std::string text_content = request.text_content();
+
+ // Truncate the text past 50 bytes to keep it to a reasonable length in
+ // the terminal window.
+ if (text_content.size() > 50) {
+ prefix = " Pasted data (truncated): ";
+ text_content = text_content.substr(0, 50) + "...";
+ }
+ stream << prefix
+ << text_content
+ << std::endl;
+ stream << " Pasted data size (bytes): "
+ << request.text_content().size()
+ << std::endl;
+ }
+
if (request.has_print_data() && !print_data_file_path_.empty()) {
if (request.request_data().has_print_metadata() &&
request.request_data().print_metadata().has_printer_name()) {
@@ -415,12 +482,13 @@ class QueuingHandler : public Handler {
AtomicCout aout;
aout.stream() << std::endl << "----------" << std::endl;
- aout.stream() << "Thread: " << std::this_thread::get_id() << std::endl;
+ aout.stream() << "Thread: " << std::this_thread::get_id()
+ << std::endl;
aout.stream() << "Delaying request processing for "
<< handler->delays()[handler->nextDelayIndex() % handler->delays().size()] << "s" << std::endl << std::endl;
aout.flush();
- handler->AnalyzeContent(aout.stream(), std::move(event));
+ handler->AnalyzeContent(aout, std::move(event));
}
return 0;
diff --git a/third_party/content_analysis_sdk/demo/handler_misbehaving.h b/third_party/content_analysis_sdk/demo/handler_misbehaving.h
index d303049d98..bb0b4f18ad 100644
--- a/third_party/content_analysis_sdk/demo/handler_misbehaving.h
+++ b/third_party/content_analysis_sdk/demo/handler_misbehaving.h
@@ -20,6 +20,7 @@
#include "content_analysis/sdk/analysis.pb.h"
#include "content_analysis/sdk/analysis_agent.h"
#include "agent/src/event_win.h"
+#include "handler.h"
enum class Mode {
// Have to use a "Mode_" prefix to avoid preprocessing problems in StringToMode
@@ -93,13 +94,18 @@ static DWORD WriteBigMessageToPipe(HANDLE pipe, const std::string& message) {
}
// An AgentEventHandler that does various misbehaving things
-class MisbehavingHandler final : public content_analysis::sdk::AgentEventHandler {
+class MisbehavingHandler final : public Handler {
public:
using Event = content_analysis::sdk::ContentAnalysisEvent;
static
- std::unique_ptr<AgentEventHandler> Create(unsigned long delay,
- const std::string& modeStr) {
+ std::unique_ptr<AgentEventHandler> Create(
+ const std::string& modeStr,
+ std::vector<unsigned long>&& delays,
+ const std::string& print_data_file_path,
+ RegexArray&& toBlock = RegexArray(),
+ RegexArray&& toWarn = RegexArray(),
+ RegexArray&& toReport = RegexArray()) {
auto it = sStringToMode.find(modeStr);
if (it == sStringToMode.end()) {
std::cout << "\"" << modeStr << "\""
@@ -107,11 +113,17 @@ class MisbehavingHandler final : public content_analysis::sdk::AgentEventHandler
return nullptr;
}
- return std::unique_ptr<AgentEventHandler>(new MisbehavingHandler(delay, it->second));
+ return std::unique_ptr<AgentEventHandler>(new MisbehavingHandler(it->second, std::move(delays), print_data_file_path, std::move(toBlock), std::move(toWarn), std::move(toReport)));
}
private:
- MisbehavingHandler(unsigned long delay, Mode mode) : delay_(delay), mode_(mode) {}
+ MisbehavingHandler(Mode mode, std::vector<unsigned long>&& delays, const std::string& print_data_file_path,
+ RegexArray&& toBlock = RegexArray(),
+ RegexArray&& toWarn = RegexArray(),
+ RegexArray&& toReport = RegexArray()) :
+ Handler(std::move(delays), print_data_file_path, std::move(toBlock), std::move(toWarn), std::move(toReport)),
+ mode_(mode) {}
+
template <size_t N>
DWORD SendBytesOverPipe(const unsigned char (&bytes)[N],
@@ -124,20 +136,11 @@ class MisbehavingHandler final : public content_analysis::sdk::AgentEventHandler
return WriteBigMessageToPipe(pipe, s);
}
- // Analyzes one request from Google Chrome and responds back to the browser
- // with either an allow or block verdict.
- void AnalyzeContent(std::unique_ptr<Event> event) {
- // An event represents one content analysis request and response triggered
- // by a user action in Google Chrome. The agent determines whether the
- // user is allowed to perform the action by examining event->GetRequest().
- // The verdict, which can be "allow" or "block" is written into
- // event->GetResponse().
-
+ bool SetCustomResponse(AtomicCout& aout, std::unique_ptr<Event>& event) override {
std::cout << std::endl << "----------" << std::endl << std::endl;
-
- DumpRequest(event->GetRequest());
std::cout << "Mode is " << sModeToString[mode_] << std::endl;
+ bool handled = true;
if (mode_ == Mode::Mode_largeResponse) {
for (size_t i = 0; i < 1000; ++i) {
content_analysis::sdk::ContentAnalysisResponse_Result* result =
@@ -177,7 +180,7 @@ class MisbehavingHandler final : public content_analysis::sdk::AgentEventHandler
event->GetResponse().clear_results();
} else if (mode_ == Mode::Mode_resultWithInvalidStatus) {
// This causes an assertion failure and the process exits
- // So we just serialize this ourselves below
+ // So we just serialize this ourselves in SendCustomResponse()
/*content_analysis::sdk::ContentAnalysisResponse_Result* result =
event->GetResponse().mutable_results(0);
result->set_status(
@@ -185,38 +188,12 @@ class MisbehavingHandler final : public content_analysis::sdk::AgentEventHandler
::content_analysis::sdk::ContentAnalysisResponse_Result_Status>(
100));*/
} else {
- bool block = false;
-
- if (event->GetRequest().has_text_content()) {
- block = ShouldBlockRequest(event->GetRequest().text_content());
- } else if (event->GetRequest().has_file_path()) {
- block = ShouldBlockRequest(event->GetRequest().file_path());
- }
-
- if (block) {
- auto rc = content_analysis::sdk::SetEventVerdictToBlock(event.get());
- std::cout << " Verdict: block";
- if (rc != content_analysis::sdk::ResultCode::OK) {
- std::cout << " error: "
- << content_analysis::sdk::ResultCodeToString(rc)
- << std::endl;
- std::cout << " " << event->DebugString() << std::endl;
- }
- std::cout << std::endl;
- } else {
- std::cout << " Verdict: allow" << std::endl;
- }
- }
-
- std::cout << std::endl;
-
- // If a delay is specified, wait that much.
- if (delay_ > 0) {
- std::cout << "[Demo] delaying request processing for " << delay_ << "s"
- << std::endl;
- std::this_thread::sleep_for(std::chrono::seconds(delay_));
+ handled = false;
}
+ return handled;
+ }
+ bool SendCustomResponse(std::unique_ptr<Event>& event) override {
if (mode_ == Mode::Mode_largeResponse) {
content_analysis::sdk::ContentAnalysisEventWin* eventWin =
static_cast<content_analysis::sdk::ContentAnalysisEventWin*>(
@@ -301,194 +278,12 @@ class MisbehavingHandler final : public content_analysis::sdk::AgentEventHandler
// bit, indicating there should be a byte after this
SendBytesOverPipe(bytes, event);
} else {
- std::cout << "(misbehaving) Handler::AnalyzeContent() about to call "
- "event->Send(), mode is "
- << sModeToString[mode_] << std::endl;
- // Send the response back to Google Chrome.
- auto rc = event->Send();
- if (rc != content_analysis::sdk::ResultCode::OK) {
- std::cout << "[Demo] Error sending response: "
- << content_analysis::sdk::ResultCodeToString(rc) << std::endl;
- std::cout << event->DebugString() << std::endl;
- }
- }
- }
-
- private:
- void OnBrowserConnected(
- const content_analysis::sdk::BrowserInfo& info) override {
- std::cout << std::endl << "==========" << std::endl;
- std::cout << "Browser connected pid=" << info.pid << std::endl;
- }
-
- void OnBrowserDisconnected(
- const content_analysis::sdk::BrowserInfo& info) override {
- std::cout << std::endl
- << "Browser disconnected pid=" << info.pid << std::endl;
- std::cout << "==========" << std::endl;
- }
-
- void OnAnalysisRequested(std::unique_ptr<Event> event) override {
- // If the agent is capable of analyzing content in the background, the
- // events may be handled in background threads. Having said that, a
- // event should not be assumed to be thread safe, that is, it should not
- // be accessed by more than one thread concurrently.
- //
- // In this example code, the event is handled synchronously.
- AnalyzeContent(std::move(event));
- }
- void OnResponseAcknowledged(
- const content_analysis::sdk::ContentAnalysisAcknowledgement& ack)
- override {
- const char* final_action = "<Unknown>";
- if (ack.has_final_action()) {
- switch (ack.final_action()) {
- case content_analysis::sdk::ContentAnalysisAcknowledgement::
- ACTION_UNSPECIFIED:
- final_action = "<Unspecified>";
- break;
- case content_analysis::sdk::ContentAnalysisAcknowledgement::ALLOW:
- final_action = "Allow";
- break;
- case content_analysis::sdk::ContentAnalysisAcknowledgement::REPORT_ONLY:
- final_action = "Report only";
- break;
- case content_analysis::sdk::ContentAnalysisAcknowledgement::WARN:
- final_action = "Warn";
- break;
- case content_analysis::sdk::ContentAnalysisAcknowledgement::BLOCK:
- final_action = "Block";
- break;
- }
- }
-
- std::cout << "Ack: " << ack.request_token() << std::endl;
- std::cout << " Final action: " << final_action << std::endl;
- }
- void OnCancelRequests(
- const content_analysis::sdk::ContentAnalysisCancelRequests& cancel)
- override {
- std::cout << "Cancel: " << std::endl;
- std::cout << " User action ID: " << cancel.user_action_id() << std::endl;
- }
-
- void OnInternalError(const char* context,
- content_analysis::sdk::ResultCode error) override {
- std::cout << std::endl
- << "*ERROR*: context=\"" << context << "\" "
- << content_analysis::sdk::ResultCodeToString(error) << std::endl;
- }
-
- void DumpRequest(
- const content_analysis::sdk::ContentAnalysisRequest& request) {
- std::string connector = "<Unknown>";
- if (request.has_analysis_connector()) {
- switch (request.analysis_connector()) {
- case content_analysis::sdk::FILE_DOWNLOADED:
- connector = "download";
- break;
- case content_analysis::sdk::FILE_ATTACHED:
- connector = "attach";
- break;
- case content_analysis::sdk::BULK_DATA_ENTRY:
- connector = "bulk-data-entry";
- break;
- case content_analysis::sdk::PRINT:
- connector = "print";
- break;
- case content_analysis::sdk::FILE_TRANSFER:
- connector = "file-transfer";
- break;
- default:
- break;
- }
+ return false;
}
-
- std::string url =
- request.has_request_data() && request.request_data().has_url()
- ? request.request_data().url()
- : "<No URL>";
-
- std::string tab_title =
- request.has_request_data() && request.request_data().has_tab_title()
- ? request.request_data().tab_title()
- : "<No tab title>";
-
- std::string filename =
- request.has_request_data() && request.request_data().has_filename()
- ? request.request_data().filename()
- : "<No filename>";
-
- std::string digest =
- request.has_request_data() && request.request_data().has_digest()
- ? request.request_data().digest()
- : "<No digest>";
-
- std::string file_path =
- request.has_file_path() ? request.file_path() : "<none>";
-
- std::string text_content =
- request.has_text_content() ? request.text_content() : "<none>";
-
- std::string machine_user =
- request.has_client_metadata() &&
- request.client_metadata().has_browser() &&
- request.client_metadata().browser().has_machine_user()
- ? request.client_metadata().browser().machine_user()
- : "<No machine user>";
-
- std::string email =
- request.has_request_data() && request.request_data().has_email()
- ? request.request_data().email()
- : "<No email>";
-
- time_t t = request.expires_at();
-
- std::string user_action_id = request.has_user_action_id()
- ? request.user_action_id()
- : "<No user action id>";
-
- std::cout << "Request: " << request.request_token() << std::endl;
- std::cout << " User action ID: " << user_action_id << std::endl;
- std::cout << " Expires at: " << ctime(&t); // Returned string includes \n.
- std::cout << " Connector: " << connector << std::endl;
- std::cout << " URL: " << url << std::endl;
- std::cout << " Tab title: " << tab_title << std::endl;
- std::cout << " Filename: " << filename << std::endl;
- std::cout << " Digest: " << digest << std::endl;
- std::cout << " Filepath: " << file_path << std::endl;
- std::cout << " Text content: '" << text_content << "'" << std::endl;
- std::cout << " Machine user: " << machine_user << std::endl;
- std::cout << " Email: " << email << std::endl;
- }
-
- bool ReadContentFromFile(const std::string& file_path, std::string* content) {
- std::ifstream file(file_path,
- std::ios::in | std::ios::binary | std::ios::ate);
- if (!file.is_open()) return false;
-
- // Get file size. This example does not handle files larger than 1MB.
- // Make sure content string can hold the contents of the file.
- int size = file.tellg();
- if (size > 1024 * 1024) return false;
-
- content->resize(size + 1);
-
- // Read file into string.
- file.seekg(0, std::ios::beg);
- file.read(&(*content)[0], size);
- content->at(size) = 0;
return true;
}
- bool ShouldBlockRequest(const std::string& content) {
- // Determines if the request should be blocked. (not needed for the
- // misbehaving agent)
- std::cout << "'" << content << "' was not blocked\n";
- return false;
- }
-
- unsigned long delay_;
+ private:
Mode mode_;
};
diff --git a/third_party/content_analysis_sdk/moz.yaml b/third_party/content_analysis_sdk/moz.yaml
new file mode 100644
index 0000000000..9d12c72924
--- /dev/null
+++ b/third_party/content_analysis_sdk/moz.yaml
@@ -0,0 +1,33 @@
+schema: 1
+
+bugzilla:
+ product: Firefox
+ component: Data Loss Prevention
+
+origin:
+ name: Content Analysis SDK
+ description: SDK that DLP agents may use to interoperate with web browsers
+ url: https://github.com/chromium/content_analysis_sdk
+ release: 3d82f7523b557d0d5c75e1acf28c3deb8081ead1 (2024-04-03T14:44:34Z).
+ revision: 3d82f7523b557d0d5c75e1acf28c3deb8081ead1
+ license: BSD-3-Clause
+
+vendoring:
+ url: https://github.com/chromium/content_analysis_sdk
+ source-hosting: github
+ exclude:
+ - .gitattributes
+ keep:
+ - demo/handler_misbehaving.h
+ - demo/modes.h
+ patches:
+ - agent_improvements.patch
+
+updatebot:
+ maintainer-phab: "#dlp-reviewers"
+ maintainer-bz: davidp99@gmail.com
+ tasks:
+ - type: vendoring
+ enabled: True
+ frequency: every
+ blocking: 1885485
diff --git a/third_party/content_analysis_sdk/prepare_build b/third_party/content_analysis_sdk/prepare_build
index ce68760f0a..b61cdc42a5 100644
--- a/third_party/content_analysis_sdk/prepare_build
+++ b/third_party/content_analysis_sdk/prepare_build
@@ -1,48 +1,48 @@
-#!/bin/bash
-# Copyright 2022 The Chromium Authors.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# This script is meant to be run once to setup the example demo agent.
-# Run it with one command line argument: the path to a directory where the
-# demo agent will be built. This should be a directory outside the SDK
-# directory tree. By default, if no directory is supplied, a directory
-# named `build` in the project root will be used.
-#
-# Once the build is prepared, the demo binary is built using the command
-# `cmake --build <build-dir>`, where <build-dir> is the same argument given
-# to this script.
-
-set -euo pipefail
-
-export ROOT_DIR=$(realpath $(dirname $0))
-export DEMO_DIR=$(realpath $ROOT_DIR/demo)
-export PROTO_DIR=$(realpath $ROOT_DIR/proto)
-# Defaults to $ROOT_DIR/build if no argument is provided.
-export BUILD_DIR=$(realpath ${1:-$ROOT_DIR/build})
-
-echo Root dir: $ROOT_DIR
-echo Build dir: $BUILD_DIR
-echo Demo dir: $DEMO_DIR
-echo Proto dir: $PROTO_DIR
-
-# Prepare build directory
-mkdir -p $BUILD_DIR
-# Prepare protobuf out directory
-mkdir -p $BUILD_DIR/gen
-# Enter build directory
-cd $BUILD_DIR
-
-# Install vcpkg and use it to install Google Protocol Buffers.
-test -d vcpkg || (
- git clone https://github.com/microsoft/vcpkg
- ./vcpkg/bootstrap-vcpkg.sh -disableMetrics
-)
-# Install any packages we want from vcpkg.
-./vcpkg/vcpkg install protobuf
-./vcpkg/vcpkg install gtest
-
-# Generate the build files.
-export CMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake
-cmake $ROOT_DIR
-
+#!/bin/bash
+# Copyright 2022 The Chromium Authors.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# This script is meant to be run once to setup the example demo agent.
+# Run it with one command line argument: the path to a directory where the
+# demo agent will be built. This should be a directory outside the SDK
+# directory tree. By default, if no directory is supplied, a directory
+# named `build` in the project root will be used.
+#
+# Once the build is prepared, the demo binary is built using the command
+# `cmake --build <build-dir>`, where <build-dir> is the same argument given
+# to this script.
+
+set -euo pipefail
+
+export ROOT_DIR=$(realpath $(dirname $0))
+export DEMO_DIR=$(realpath $ROOT_DIR/demo)
+export PROTO_DIR=$(realpath $ROOT_DIR/proto)
+# Defaults to $ROOT_DIR/build if no argument is provided.
+export BUILD_DIR=$(realpath ${1:-$ROOT_DIR/build})
+
+echo Root dir: $ROOT_DIR
+echo Build dir: $BUILD_DIR
+echo Demo dir: $DEMO_DIR
+echo Proto dir: $PROTO_DIR
+
+# Prepare build directory
+mkdir -p $BUILD_DIR
+# Prepare protobuf out directory
+mkdir -p $BUILD_DIR/gen
+# Enter build directory
+cd $BUILD_DIR
+
+# Install vcpkg and use it to install Google Protocol Buffers.
+test -d vcpkg || (
+ git clone https://github.com/microsoft/vcpkg
+ ./vcpkg/bootstrap-vcpkg.sh -disableMetrics
+)
+# Install any packages we want from vcpkg.
+./vcpkg/vcpkg install protobuf
+./vcpkg/vcpkg install gtest
+
+# Generate the build files.
+export CMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake
+cmake $ROOT_DIR
+
diff --git a/third_party/content_analysis_sdk/proto/content_analysis/sdk/analysis.proto b/third_party/content_analysis_sdk/proto/content_analysis/sdk/analysis.proto
index 0bbd3d4368..614b793f9b 100644
--- a/third_party/content_analysis_sdk/proto/content_analysis/sdk/analysis.proto
+++ b/third_party/content_analysis_sdk/proto/content_analysis/sdk/analysis.proto
@@ -156,8 +156,30 @@ message ContentAnalysisRequest {
// Count of analysis requests that belong to the same user action.
optional int64 user_action_requests_count = 17;
+ // Indicates the exact reason the request was created, ie which user action
+ // led to a data transfer.
+ enum Reason {
+ UNKNOWN = 0;
+
+ // Only possible for the `FILE_ATTACHED` and `BULK_DATA_ENTRY` actions.
+ CLIPBOARD_PASTE = 1;
+ DRAG_AND_DROP = 2;
+
+ // Only possible for the `FILE_ATTACHED` action.
+ FILE_PICKER_DIALOG = 3;
+
+ // Only possible for the `PRINT` analysis connector.
+ PRINT_PREVIEW_PRINT = 4;
+ SYSTEM_DIALOG_PRINT = 5;
+
+ // Only possible for the `FILE_DOWNLOADED` analysis connector.
+ NORMAL_DOWNLOAD = 6;
+ SAVE_AS_DOWNLOAD = 7;
+ }
+ optional Reason reason = 19;
+
// Reserved to make sure there is no overlap with DeepScanningClientRequest.
- reserved 1 to 4, 6 to 8;
+ reserved 1 to 4, 6 to 8, 20;
}
// Verdict response sent from agent to Google Chrome.
diff --git a/third_party/dav1d/NEWS b/third_party/dav1d/NEWS
index 3645474a04..88b1eea00e 100644
--- a/third_party/dav1d/NEWS
+++ b/third_party/dav1d/NEWS
@@ -1,3 +1,15 @@
+Changes for 1.4.1 'Road Runner':
+--------------------------------
+
+1.4.1 is a small release of dav1d, improving notably ARM and RISC-V speed
+
+- Optimizations for 6tap filters for NEON (ARM)
+- More RISC-V optimizations for itx (4x8, 8x4, 4x16, 16x4, 8x16, 16x8)
+- Reduction of binary size on ARM64, ARM32 and RISC-V
+- Fix out-of-bounds read in 8bpc SSE2/SSSE3 wiener_filter
+- Msac optimizations
+
+
Changes for 1.4.0 'Road Runner':
--------------------------------
@@ -26,7 +38,7 @@ Changes for 1.3.0 'Tundra Peregrine Falcon (Calidus)':
Changes for 1.2.1 'Arctic Peregrine Falcon':
--------------------------------------------
+--------------------------------------------
1.2.1 is a small release of dav1d, adding more SIMD and fixes
@@ -42,7 +54,7 @@ Changes for 1.2.1 'Arctic Peregrine Falcon':
Changes for 1.2.0 'Arctic Peregrine Falcon':
--------------------------------------------
+--------------------------------------------
1.2.0 is a small release of dav1d, adding more SIMD and fixes
@@ -55,7 +67,7 @@ Changes for 1.2.0 'Arctic Peregrine Falcon':
Changes for 1.1.0 'Arctic Peregrine Falcon':
--------------------------------------------
+--------------------------------------------
1.1.0 is an important release of dav1d, fixing numerous bugs, and adding SIMD
diff --git a/third_party/dav1d/THANKS.md b/third_party/dav1d/THANKS.md
index 4fc8d27f14..b7aa200d0e 100644
--- a/third_party/dav1d/THANKS.md
+++ b/third_party/dav1d/THANKS.md
@@ -16,19 +16,20 @@ The Alliance for Open Media (AOM) for partially funding this project.
And all the dav1d Authors (git shortlog -sn), including:
-Martin Storsjö, Henrik Gramner, Ronald S. Bultje, Janne Grunau, James Almer,
-Victorien Le Couviour--Tuffet, Matthias Dressel, Marvin Scholz,
-Jean-Baptiste Kempf, Luc Trudeau, Hugo Beauzée-Luyssen, Konstantin Pavlov,
-Niklas Haas, David Michael Barr, Steve Lhomme, Nathan E. Egge, Wan-Teh Chang,
-Kyle Siefring, B Krishnan Iyer, Francois Cartegnie, Liwei Wang, Luca Barbato,
-David Conrad, Derek Buitenhuis, Jan Beich, Michael Bradshaw, Raphaël Zumer,
-Xuefeng Jiang, Christophe Gisquet, Justin Bull, Boyuan Xiao, Dale Curtis,
-Emmanuel Gil Peyrot, Raphael Zumer, Rupert Swarbrick, Thierry Foucu,
-Thomas Daede, Colin Lee, Jonathan Wright, Lynne, Michail Alvanos, Nico Weber,
-Salome Thirot, SmilingWolf, Tristan Laurent, Vittorio Giovara, Yannis Guyon,
-André Kempe, Anisse Astier, Anton Mitrofanov, Charlie Hayden, Dmitriy Sychov,
-Ewout ter Hoeven, Fred Barbier, Jean-Yves Avenard, Joe Drago, Mark Shuttleworth,
-Matthieu Bouron, Mehdi Sabwat, Nicolas Frattaroli, Pablo Stebler, Rostislav
-Pehlivanov, Sebastian Dröge, Shiz, Steinar Midtskogen, Sylvain BERTRAND,
-Sylvestre Ledru, Timo Gurr, Tristan Matthews, Vibhoothi, Xavier Claessens,
-Xu Guangxin, kossh1 and skal.
+Henrik Gramner, Martin Storsjö, Ronald S. Bultje, Janne Grunau, James Almer,
+Victorien Le Couviour--Tuffet, Matthias Dressel, Nathan E. Egge,
+Jean-Baptiste Kempf, Marvin Scholz, Luc Trudeau, Niklas Haas,
+Hugo Beauzée-Luyssen, Konstantin Pavlov, David Michael Barr, Steve Lhomme,
+yuanhecai, Luca Barbato, Wan-Teh Chang, Kyle Siefring, B Krishnan Iyer,
+Francois Cartegnie, Liwei Wang, David Conrad, Derek Buitenhuis, Jan Beich,
+Michael Bradshaw, Raphaël Zumer, Xuefeng Jiang, Arpad Panyik, Christophe Gisquet,
+Justin Bull, Boyuan Xiao, Dale Curtis, Emmanuel Gil Peyrot, Raphael Zumer,
+Rupert Swarbrick, Thierry Foucu, Thomas Daede, jinbo, André Kempe, Colin Lee,
+Jonathan Wright, Lynne, Michail Alvanos, Nico Weber, Salome Thirot, SmilingWolf,
+Tristan Laurent, Tristan Matthews, Vittorio Giovara, Yannis Guyon,
+Andrey Semashev, Anisse Astier, Anton Mitrofanov, Charlie Hayden, Dmitriy Sychov,
+Ewout ter Hoeven, Fred Barbier, Hao Chen, Jean-Yves Avenard, Joe Drago,
+Mark Shuttleworth, Matthieu Bouron, Mehdi Sabwat, Nicolas Frattaroli,
+Pablo Stebler, Rostislav Pehlivanov, Sebastian Dröge, Shiz, Steinar Midtskogen,
+Sylvain BERTRAND, Sylvestre Ledru, Timo Gurr, Vibhoothi,
+Vignesh Venkatasubramanian, Xavier Claessens, Xu Guangxin, kossh1 and skal.
diff --git a/third_party/dav1d/gcovr.cfg b/third_party/dav1d/gcovr.cfg
index d09a0ecab5..e02ae33c33 100644
--- a/third_party/dav1d/gcovr.cfg
+++ b/third_party/dav1d/gcovr.cfg
@@ -1,4 +1,4 @@
exclude = .*/tests/.*
exclude = .*/tools/.*
exclude = .*/include/common/dump.h
-gcov-ignore-parse-errors = yes
+gcov-ignore-parse-errors = negative_hits.warn
diff --git a/third_party/dav1d/meson.build b/third_party/dav1d/meson.build
index 6e49852103..e371415d53 100644
--- a/third_party/dav1d/meson.build
+++ b/third_party/dav1d/meson.build
@@ -23,7 +23,7 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
project('dav1d', ['c'],
- version: '1.4.0',
+ version: '1.4.1',
default_options: ['c_std=c99',
'warning_level=2',
'buildtype=release',
@@ -309,6 +309,10 @@ if (host_machine.system() in ['darwin', 'ios', 'tvos'] and cc.get_id() == 'clang
optional_arguments += '-fno-stack-check'
endif
+if (host_machine.cpu_family() == 'aarch64' or host_machine.cpu_family().startswith('arm'))
+ optional_arguments += '-fno-align-functions'
+endif
+
add_project_arguments(cc.get_supported_arguments(optional_arguments), language : 'c')
add_project_link_arguments(cc.get_supported_link_arguments(optional_link_arguments), language : 'c')
@@ -365,6 +369,66 @@ if (is_asm_enabled and
if cc.compiles(check_pic_code)
cdata.set('PIC', '3')
endif
+
+ if host_machine.cpu_family() == 'aarch64'
+ have_as_arch = cc.compiles('''__asm__ (".arch armv8-a");''')
+ cdata.set10('HAVE_AS_ARCH_DIRECTIVE', have_as_arch)
+ as_arch_str = ''
+ if have_as_arch
+ as_arch_level = 'armv8-a'
+ # Check what .arch levels are supported. In principle, we only
+ # want to detect up to armv8.2-a here (binutils requires that
+ # in order to enable i8mm). However, older Clang versions
+ # (before Clang 17, and Xcode versions up to and including 15.0)
+ # didn't support controlling dotprod/i8mm extensions via
+ # .arch_extension, therefore try to enable a high enough .arch
+ # level as well, to implicitly make them available via that.
+ foreach arch : ['armv8.2-a', 'armv8.4-a', 'armv8.6-a']
+ if cc.compiles('__asm__ (".arch ' + arch + '\\n");')
+ as_arch_level = arch
+ endif
+ endforeach
+ # Clang versions before 17 also had a bug
+ # (https://github.com/llvm/llvm-project/issues/32220)
+ # causing a plain ".arch <level>" to not have any effect unless it
+ # had an extra "+<feature>" included - but it was activated on the
+ # next ".arch_extension" directive instead. Check if we can include
+ # "+crc" as dummy feature to make the .arch directive behave as
+ # expected and take effect right away.
+ if cc.compiles('__asm__ (".arch ' + as_arch_level + '+crc\\n");')
+ as_arch_level = as_arch_level + '+crc'
+ endif
+ cdata.set('AS_ARCH_LEVEL', as_arch_level)
+ as_arch_str = '".arch ' + as_arch_level + '\\n"'
+ endif
+ extensions = {
+ 'dotprod': 'udot v0.4s, v0.16b, v0.16b',
+ 'i8mm': 'usdot v0.4s, v0.16b, v0.16b',
+ 'sve': 'whilelt p0.s, x0, x1',
+ 'sve2': 'sqrdmulh z0.s, z0.s, z0.s',
+ }
+ foreach name, instr : extensions
+ # Test for support for the various extensions. First test if
+ # the assembler supports the .arch_extension directive for
+ # enabling/disabling the extension, then separately check whether
+ # the instructions themselves are supported. Even if .arch_extension
+ # isn't supported, we may be able to assemble the instructions
+ # if the .arch level includes support for them.
+ code = '__asm__ (' + as_arch_str
+ code += '".arch_extension ' + name + '\\n"'
+ code += ');'
+ supports_archext = cc.compiles(code)
+ cdata.set10('HAVE_AS_ARCHEXT_' + name.to_upper() + '_DIRECTIVE', supports_archext)
+ code = '__asm__ (' + as_arch_str
+ if supports_archext
+ code += '".arch_extension ' + name + '\\n"'
+ endif
+ code += '"' + instr + '\\n"'
+ code += ');'
+ supports_instr = cc.compiles(code, name: name.to_upper())
+ cdata.set10('HAVE_' + name.to_upper(), supports_instr)
+ endforeach
+ endif
endif
cdata.set10('ARCH_X86', host_machine.cpu_family().startswith('x86'))
@@ -477,6 +541,17 @@ if (is_asm_enabled and
])
endif
+if is_asm_enabled and host_machine.cpu_family().startswith('riscv')
+ as_option_code = '''__asm__ (
+".option arch, +v\n"
+"vsetivli zero, 0, e8, m1, ta, ma"
+);
+'''
+ if not cc.compiles(as_option_code, name : 'RISC-V Vector')
+ error('Compiler doesn\'t support \'.option arch\' asm directive. Update to binutils>=2.38 or clang>=17 or use \'-Denable_asm=false\'.')
+ endif
+endif
+
# Generate config.h
config_h_target = configure_file(output: 'config.h', configuration: cdata)
diff --git a/third_party/dav1d/src/arm/32/itx.S b/third_party/dav1d/src/arm/32/itx.S
index ceea025e45..9ba1df7a68 100644
--- a/third_party/dav1d/src/arm/32/itx.S
+++ b/third_party/dav1d/src/arm/32/itx.S
@@ -965,6 +965,8 @@ function inv_txfm_\variant\()add_8x8_neon
.ifc \variant, identity_
// The identity shl #1 and downshift srshr #1 cancel out
+
+ b L(itx_8x8_epilog)
.else
blx r4
@@ -976,8 +978,8 @@ function inv_txfm_\variant\()add_8x8_neon
vrshr.s16 q13, q13, #1
vrshr.s16 q14, q14, #1
vrshr.s16 q15, q15, #1
-.endif
+L(itx_8x8_epilog):
transpose_8x8h q8, q9, q10, q11, q12, q13, q14, q15, d17, d19, d21, d23, d24, d26, d28, d30
blx r5
@@ -985,11 +987,12 @@ function inv_txfm_\variant\()add_8x8_neon
load_add_store_8x8 r0, r7
vpop {q4-q7}
pop {r4-r5,r7,pc}
+.endif
endfunc
.endm
-def_fn_8x8_base
def_fn_8x8_base identity_
+def_fn_8x8_base
.macro def_fn_8x8 txfm1, txfm2
function inv_txfm_add_\txfm1\()_\txfm2\()_8x8_8bpc_neon, export=1
@@ -1444,14 +1447,16 @@ function inv_txfm_horz\suffix\()_16x4_neon
.else
identity_4x16_shift1 d0[0]
.endif
+ b L(horz_16x4_epilog)
.else
blx r4
-.endif
-.if \shift > 0
.irp i, q8, q9, q10, q11, q12, q13, q14, q15
vrshr.s16 \i, \i, #\shift
.endr
-.endif
+.if \shift == 1
+ b L(horz_16x4_epilog)
+.else
+L(horz_16x4_epilog):
transpose_4x4h q8, q9, d16, d17, d18, d19
transpose_4x4h q10, q11, d20, d21, d22, d23
transpose_4x4h q12, q13, d24, d25, d26, d27
@@ -1462,13 +1467,15 @@ function inv_txfm_horz\suffix\()_16x4_neon
.endr
pop {pc}
+.endif
+.endif
endfunc
.endm
-def_horz_16 scale=0, identity=0, shift=2
-def_horz_16 scale=1, identity=0, shift=1, suffix=_scale
-def_horz_16 scale=0, identity=1, shift=-2, suffix=_identity
def_horz_16 scale=1, identity=1, shift=-1, suffix=_scale_identity
+def_horz_16 scale=0, identity=1, shift=-2, suffix=_identity
+def_horz_16 scale=1, identity=0, shift=1, suffix=_scale
+def_horz_16 scale=0, identity=0, shift=2
function inv_txfm_add_vert_4x16_neon
push {lr}
@@ -1597,6 +1604,8 @@ function inv_txfm_\variant\()add_16x4_neon
.endr
identity_4x16_shift1 d0[0]
+
+ b L(itx_16x4_epilog)
.else
vmov.i16 q2, #0
vmov.i16 q3, #0
@@ -1615,30 +1624,25 @@ function inv_txfm_\variant\()add_16x4_neon
vswp d19, d22
vswp d18, d20
vswp d19, d21
-.irp i, q8, q9, q10, q11
+ vswp d25, d28
+ vswp d27, d30
+ vswp d26, d28
+ vswp d27, d29
+.irp i, q8, q9, q10, q11, q12, q13, q14, q15
vrshr.s16 \i, \i, #1
.endr
-.endif
+
+L(itx_16x4_epilog):
transpose_4x8h q8, q9, q10, q11
blx r5
mov r6, r0
load_add_store_8x4 r6, r7
-.ifc \variant, identity_
vmov q8, q12
vmov q9, q13
vmov q10, q14
vmov q11, q15
-.else
- vswp d25, d28
- vswp d27, d30
- vswp d26, d28
- vswp d27, d29
- vrshr.s16 q8, q12, #1
- vrshr.s16 q9, q13, #1
- vrshr.s16 q10, q14, #1
- vrshr.s16 q11, q15, #1
-.endif
+
transpose_4x8h q8, q9, q10, q11
blx r5
add r6, r0, #8
@@ -1646,6 +1650,7 @@ function inv_txfm_\variant\()add_16x4_neon
vpop {q4-q7}
pop {r4-r11,pc}
+.endif
endfunc
function inv_txfm_\variant\()add_4x16_neon
@@ -1696,12 +1701,14 @@ function inv_txfm_\variant\()add_4x16_neon
movw r12, #(5793-4096)*8
vdup.16 d0, r12
identity_8x4_shift1 q8, q9, q10, q11, d0[0]
+
+ b L(itx_4x16_epilog)
.else
blx r4
.irp i, q8, q9, q10, q11
vrshr.s16 \i, \i, #1
.endr
-.endif
+L(itx_4x16_epilog):
transpose_4x8h q8, q9, q10, q11
vswp d19, d21
vswp d18, d20
@@ -1714,11 +1721,12 @@ function inv_txfm_\variant\()add_4x16_neon
vpop {q4-q7}
pop {r4-r11,pc}
+.endif
endfunc
.endm
-def_fn_416_base
def_fn_416_base identity_
+def_fn_416_base
.macro def_fn_416 w, h, txfm1, txfm2, eob_half
function inv_txfm_add_\txfm1\()_\txfm2\()_\w\()x\h\()_8bpc_neon, export=1
@@ -1728,11 +1736,15 @@ function inv_txfm_add_\txfm1\()_\txfm2\()_\w\()x\h\()_8bpc_neon, export=1
push {r4-r11,lr}
vpush {q4-q7}
.if \w == 4
+.ifnc \txfm1, identity
movrel_local r4, inv_\txfm1\()_8h_x\w\()_neon
+.endif
movrel_local r5, inv_\txfm2\()_4h_x\h\()_neon
mov r10, #\eob_half
.else
+.ifnc \txfm1, identity
movrel_local r4, inv_\txfm1\()_4h_x\w\()_neon
+.endif
movrel_local r5, inv_\txfm2\()_8h_x\h\()_neon
.endif
.ifc \txfm1, identity
@@ -1765,8 +1777,7 @@ def_fn_416 \w, \h, identity, flipadst, 32
def_fns_416 4, 16
def_fns_416 16, 4
-.macro def_fn_816_base variant
-function inv_txfm_\variant\()add_16x8_neon
+function inv_txfm_add_16x8_neon
sub_sp_align 256
.irp i, 0, 4
@@ -1805,6 +1816,7 @@ function inv_txfm_\variant\()add_16x8_neon
pop {r4-r11,pc}
endfunc
+.macro def_fn_816_base variant
function inv_txfm_\variant\()add_8x16_neon
sub_sp_align 256
@@ -1849,6 +1861,10 @@ function inv_txfm_\variant\()add_8x16_neon
.endr
2:
+.ifc \variant, identity_
+ b L(itx_8x16_epilog)
+.else
+L(itx_8x16_epilog):
.irp i, 0, 4
add r6, r0, #(\i)
add r7, sp, #(\i*2)
@@ -1859,11 +1875,18 @@ function inv_txfm_\variant\()add_8x16_neon
add_sp_align 256
vpop {q4-q7}
pop {r4-r11,pc}
+.endif
endfunc
.endm
-def_fn_816_base
def_fn_816_base identity_
+def_fn_816_base
+
+/* Define symbols used in .if statement */
+.equ dct, 1
+.equ identity, 2
+.equ adst, 3
+.equ flipadst, 4
.macro def_fn_816 w, h, txfm1, txfm2, eob_8x8, eob_4x4
function inv_txfm_add_\txfm1\()_\txfm2\()_\w\()x\h\()_8bpc_neon, export=1
@@ -1873,7 +1896,9 @@ function inv_txfm_add_\txfm1\()_\txfm2\()_\w\()x\h\()_8bpc_neon, export=1
push {r4-r11,lr}
vpush {q4-q7}
.if \w == 8
+.ifnc \txfm1, identity
movrel_local r4, inv_\txfm1\()_8h_x8_neon
+.endif
movrel_local r5, inv_\txfm2\()_4h_x16_neon
.else
.ifc \txfm1, identity
@@ -1889,7 +1914,7 @@ function inv_txfm_add_\txfm1\()_\txfm2\()_\w\()x\h\()_8bpc_neon, export=1
.else
mov r10, #\eob_4x4
.endif
-.ifc \txfm1, identity
+.if \w == 8 && \txfm1 == identity
b inv_txfm_identity_add_\w\()x\h\()_neon
.else
b inv_txfm_add_\w\()x\h\()_neon
diff --git a/third_party/dav1d/src/arm/32/itx16.S b/third_party/dav1d/src/arm/32/itx16.S
index aa6c272e71..7691272517 100644
--- a/third_party/dav1d/src/arm/32/itx16.S
+++ b/third_party/dav1d/src/arm/32/itx16.S
@@ -547,11 +547,11 @@ function inv_txfm_add_wht_wht_4x4_16bpc_neon, export=1
vmov.i16 q15, #0
vld1.32 {q8, q9}, [r2, :128]
vst1.32 {q14, q15}, [r2, :128]!
- vshr.s16 q8, q8, #2
+ vshr.s32 q8, q8, #2
vld1.32 {q10, q11}, [r2, :128]
- vshr.s16 q9, q9, #2
- vshr.s16 q10, q10, #2
- vshr.s16 q11, q11, #2
+ vshr.s32 q9, q9, #2
+ vshr.s32 q10, q10, #2
+ vshr.s32 q11, q11, #2
iwht4
@@ -598,7 +598,9 @@ function inv_txfm_add_4x4_neon
vld1.16 {d3}, [r0, :64], r1
L(itx_4x4_end):
- vmvn.i16 q15, #0xfc00 // 0x3ff
+ // read bitdepth_max from the callers stack
+ ldr r4, [sp, #44]
+ vdup.i16 q15, r4
sub r0, r0, r1, lsl #2
vqadd.s16 q8, q8, q0
vqadd.s16 q9, q9, q1
@@ -1487,6 +1489,10 @@ function inv_txfm_horz\suffix\()_16x2_neon
vqrshrn.s32 d21, q13, #\shift
vqrshrn.s32 d22, q14, #\shift
vqrshrn.s32 d23, q15, #\shift
+.if \scale
+ b L(horz_16x2_epilog)
+.else
+L(horz_16x2_epilog):
vuzp.16 q8, q9
vuzp.16 q10, q11
@@ -1495,11 +1501,12 @@ function inv_txfm_horz\suffix\()_16x2_neon
.endr
pop {pc}
+.endif
endfunc
.endm
-def_horz_16 scale=0, shift=2
def_horz_16 scale=1, shift=1, suffix=_scale
+def_horz_16 scale=0, shift=2
function inv_txfm_add_vert_4x16_neon
push {lr}
diff --git a/third_party/dav1d/src/arm/32/msac.S b/third_party/dav1d/src/arm/32/msac.S
index b06e109dda..b16957fb7e 100644
--- a/third_party/dav1d/src/arm/32/msac.S
+++ b/third_party/dav1d/src/arm/32/msac.S
@@ -279,60 +279,67 @@ L(renorm):
sub r4, r4, r3 // rng = u - v
clz r5, r4 // clz(rng)
eor r5, r5, #16 // d = clz(rng) ^ 16
- mvn r7, r7 // ~dif
- add r7, r7, r3, lsl #16 // ~dif + (v << 16)
+ sub r7, r7, r3, lsl #16 // dif - (v << 16)
L(renorm2):
lsl r4, r4, r5 // rng << d
subs r6, r6, r5 // cnt -= d
- lsl r7, r7, r5 // (~dif + (v << 16)) << d
+ lsl r7, r7, r5 // (dif - (v << 16)) << d
str r4, [r0, #RNG]
- mvn r7, r7 // ~dif
- bhs 9f
+ bhs 4f
// refill
ldr r3, [r0, #BUF_POS] // BUF_POS
ldr r4, [r0, #BUF_END] // BUF_END
add r5, r3, #4
- cmp r5, r4
- bgt 2f
-
- ldr r3, [r3] // next_bits
- add r8, r6, #23 // shift_bits = cnt + 23
- add r6, r6, #16 // cnt += 16
- rev r3, r3 // next_bits = bswap(next_bits)
- sub r5, r5, r8, lsr #3 // buf_pos -= shift_bits >> 3
- and r8, r8, #24 // shift_bits &= 24
- lsr r3, r3, r8 // next_bits >>= shift_bits
- sub r8, r8, r6 // shift_bits -= 16 + cnt
- str r5, [r0, #BUF_POS]
- lsl r3, r3, r8 // next_bits <<= shift_bits
- rsb r6, r8, #16 // cnt = cnt + 32 - shift_bits
- eor r7, r7, r3 // dif ^= next_bits
- b 9f
-
-2: // refill_eob
- rsb r5, r6, #8 // c = 8 - cnt
-3:
- cmp r3, r4
- bge 4f
- ldrb r8, [r3], #1
- lsl r8, r8, r5
- eor r7, r7, r8
- subs r5, r5, #8
- bge 3b
-
-4: // refill_eob_end
+ subs r5, r5, r4
+ bhi 6f
+
+ ldr r8, [r3] // next_bits
+ rsb r5, r6, #16
+ add r4, r6, #16 // shift_bits = cnt + 16
+ mvn r8, r8
+ lsr r5, r5, #3 // num_bytes_read
+ rev r8, r8 // next_bits = bswap(next_bits)
+ lsr r8, r8, r4 // next_bits >>= shift_bits
+
+2: // refill_end
+ add r3, r3, r5
+ add r6, r6, r5, lsl #3 // cnt += num_bits_read
str r3, [r0, #BUF_POS]
- rsb r6, r5, #8 // cnt = 8 - c
-9:
+3: // refill_end2
+ orr r7, r7, r8 // dif |= next_bits
+
+4: // end
str r6, [r0, #CNT]
str r7, [r0, #DIF]
-
mov r0, lr
add sp, sp, #48
-
pop {r4-r10,pc}
+
+5: // pad_with_ones
+ add r8, r6, #-240
+ lsr r8, r8, r8
+ b 3b
+
+6: // refill_eob
+ cmp r3, r4
+ bhs 5b
+
+ ldr r8, [r4, #-4]
+ lsl r5, r5, #3
+ lsr r8, r8, r5
+ add r5, r6, #16
+ mvn r8, r8
+ sub r4, r4, r3 // num_bytes_left
+ rev r8, r8
+ lsr r8, r8, r5
+ rsb r5, r6, #16
+ lsr r5, r5, #3
+ cmp r5, r4
+ it hs
+ movhs r5, r4
+ b 2b
endfunc
function msac_decode_symbol_adapt8_neon, export=1
@@ -414,53 +421,38 @@ function msac_decode_hi_tok_neon, export=1
sub r4, r4, r3 // rng = u - v
clz r5, r4 // clz(rng)
eor r5, r5, #16 // d = clz(rng) ^ 16
- mvn r7, r7 // ~dif
- add r7, r7, r3, lsl #16 // ~dif + (v << 16)
+ sub r7, r7, r3, lsl #16 // dif - (v << 16)
lsl r4, r4, r5 // rng << d
subs r6, r6, r5 // cnt -= d
- lsl r7, r7, r5 // (~dif + (v << 16)) << d
+ lsl r7, r7, r5 // (dif - (v << 16)) << d
str r4, [r0, #RNG]
vdup.16 d1, r4
- mvn r7, r7 // ~dif
- bhs 9f
+ bhs 5f
// refill
ldr r3, [r0, #BUF_POS] // BUF_POS
ldr r4, [r0, #BUF_END] // BUF_END
add r5, r3, #4
- cmp r5, r4
- bgt 2f
-
- ldr r3, [r3] // next_bits
- add r8, r6, #23 // shift_bits = cnt + 23
- add r6, r6, #16 // cnt += 16
- rev r3, r3 // next_bits = bswap(next_bits)
- sub r5, r5, r8, lsr #3 // buf_pos -= shift_bits >> 3
- and r8, r8, #24 // shift_bits &= 24
- lsr r3, r3, r8 // next_bits >>= shift_bits
- sub r8, r8, r6 // shift_bits -= 16 + cnt
- str r5, [r0, #BUF_POS]
- lsl r3, r3, r8 // next_bits <<= shift_bits
- rsb r6, r8, #16 // cnt = cnt + 32 - shift_bits
- eor r7, r7, r3 // dif ^= next_bits
- b 9f
-
-2: // refill_eob
- rsb r5, r6, #8 // c = 40 - cnt
-3:
- cmp r3, r4
- bge 4f
- ldrb r8, [r3], #1
- lsl r8, r8, r5
- eor r7, r7, r8
- subs r5, r5, #8
- bge 3b
-
-4: // refill_eob_end
+ subs r5, r5, r4
+ bhi 7f
+
+ ldr r8, [r3] // next_bits
+ rsb r5, r6, #16
+ add r4, r6, #16 // shift_bits = cnt + 16
+ mvn r8, r8
+ lsr r5, r5, #3 // num_bytes_read
+ rev r8, r8 // next_bits = bswap(next_bits)
+ lsr r8, r8, r4 // next_bits >>= shift_bits
+
+3: // refill_end
+ add r3, r3, r5
+ add r6, r6, r5, lsl #3 // cnt += num_bits_read
str r3, [r0, #BUF_POS]
- rsb r6, r5, #8 // cnt = 40 - c
-9:
+4: // refill_end2
+ orr r7, r7, r8 // dif |= next_bits
+
+5: // end
lsl lr, lr, #1
sub lr, lr, #5
lsr r12, r7, #16
@@ -473,6 +465,30 @@ function msac_decode_hi_tok_neon, export=1
str r7, [r0, #DIF]
lsr r0, r2, #1
pop {r4-r10,pc}
+
+6: // pad_with_ones
+ add r8, r6, #-240
+ lsr r8, r8, r8
+ b 4b
+
+7: // refill_eob
+ cmp r3, r4
+ bhs 6b
+
+ ldr r8, [r4, #-4]
+ lsl r5, r5, #3
+ lsr r8, r8, r5
+ add r5, r6, #16
+ mvn r8, r8
+ sub r4, r4, r3 // num_bytes_left
+ rev r8, r8
+ lsr r8, r8, r5
+ rsb r5, r6, #16
+ lsr r5, r5, #3
+ cmp r5, r4
+ it hs
+ movhs r5, r4
+ b 3b
endfunc
function msac_decode_bool_equi_neon, export=1
@@ -493,7 +509,6 @@ function msac_decode_bool_equi_neon, export=1
movhs r7, r8 // if (ret) dif = dif - vw;
clz r5, r4 // clz(rng)
- mvn r7, r7 // ~dif
eor r5, r5, #16 // d = clz(rng) ^ 16
mov lr, r2
b L(renorm2)
@@ -519,7 +534,6 @@ function msac_decode_bool_neon, export=1
movhs r7, r8 // if (ret) dif = dif - vw;
clz r5, r4 // clz(rng)
- mvn r7, r7 // ~dif
eor r5, r5, #16 // d = clz(rng) ^ 16
mov lr, r2
b L(renorm2)
@@ -549,7 +563,6 @@ function msac_decode_bool_adapt_neon, export=1
cmp r10, #0
clz r5, r4 // clz(rng)
- mvn r7, r7 // ~dif
eor r5, r5, #16 // d = clz(rng) ^ 16
mov lr, r2
diff --git a/third_party/dav1d/src/arm/64/itx.S b/third_party/dav1d/src/arm/64/itx.S
index 53490cd677..7063cbde1d 100644
--- a/third_party/dav1d/src/arm/64/itx.S
+++ b/third_party/dav1d/src/arm/64/itx.S
@@ -879,6 +879,8 @@ function inv_txfm_\variant\()add_8x8_neon
.ifc \variant, identity_
// The identity shl #1 and downshift srshr #1 cancel out
+
+ b L(itx_8x8_epilog)
.else
blr x4
@@ -890,19 +892,20 @@ function inv_txfm_\variant\()add_8x8_neon
srshr v21.8h, v21.8h, #1
srshr v22.8h, v22.8h, #1
srshr v23.8h, v23.8h, #1
-.endif
+L(itx_8x8_epilog):
transpose_8x8h v16, v17, v18, v19, v20, v21, v22, v23, v24, v25
blr x5
load_add_store_8x8 x0, x7
ret x15
+.endif
endfunc
.endm
-def_fn_8x8_base
def_fn_8x8_base identity_
+def_fn_8x8_base
.macro def_fn_8x8 txfm1, txfm2
function inv_txfm_add_\txfm1\()_\txfm2\()_8x8_8bpc_neon, export=1
@@ -1390,14 +1393,16 @@ function inv_txfm_horz\suffix\()_16x8_neon
.endif
.if \identity
identity_8x16_shift2 v0.h[0]
+ b L(horz_16x8_epilog)
.else
blr x4
-.endif
-.if \shift > 0
.irp i, v16.8h, v17.8h, v18.8h, v19.8h, v20.8h, v21.8h, v22.8h, v23.8h, v24.8h, v25.8h, v26.8h, v27.8h, v28.8h, v29.8h, v30.8h, v31.8h
srshr \i, \i, #\shift
.endr
-.endif
+.if \shift == 1
+ b L(horz_16x8_epilog)
+.else
+L(horz_16x8_epilog):
transpose_8x8h v16, v17, v18, v19, v20, v21, v22, v23, v4, v5
transpose_8x8h v24, v25, v26, v27, v28, v29, v30, v31, v4, v5
@@ -1406,12 +1411,14 @@ function inv_txfm_horz\suffix\()_16x8_neon
.endr
ret x14
+.endif
+.endif
endfunc
.endm
-def_horz_16 scale=0, identity=0, shift=2
def_horz_16 scale=1, identity=0, shift=1, suffix=_scale
def_horz_16 scale=0, identity=1, shift=0, suffix=_identity
+def_horz_16 scale=0, identity=0, shift=2
function inv_txfm_add_vert_8x16_neon
mov x14, x30
@@ -1512,6 +1519,8 @@ function inv_txfm_\variant\()add_16x4_neon
.endr
identity_8x16_shift1 v0.h[0]
+
+ b L(itx_16x4_epilog)
.else
.irp i, v16.4h, v17.4h, v18.4h, v19.4h, v20.4h, v21.4h, v22.4h, v23.4h, v24.4h, v25.4h, v26.4h, v27.4h, v28.4h, v29.4h, v30.4h, v31.4h
ld1 {\i}, [x2]
@@ -1527,33 +1536,29 @@ function inv_txfm_\variant\()add_16x4_neon
.irp i, v16.8h, v17.8h, v18.8h, v19.8h
srshr \i, \i, #1
.endr
-.endif
- transpose_4x8h v16, v17, v18, v19, v2, v3, v4, v5
- blr x5
- mov x6, x0
- load_add_store_8x4 x6, x7
-.ifc \variant, identity_
- mov v16.16b, v20.16b
- mov v17.16b, v21.16b
- mov v18.16b, v22.16b
- mov v19.16b, v23.16b
-.else
ins v24.d[1], v28.d[0]
ins v25.d[1], v29.d[0]
ins v26.d[1], v30.d[0]
ins v27.d[1], v31.d[0]
- srshr v16.8h, v24.8h, #1
- srshr v17.8h, v25.8h, #1
- srshr v18.8h, v26.8h, #1
- srshr v19.8h, v27.8h, #1
-.endif
+ srshr v20.8h, v24.8h, #1
+ srshr v21.8h, v25.8h, #1
+ srshr v22.8h, v26.8h, #1
+ srshr v23.8h, v27.8h, #1
+
+L(itx_16x4_epilog):
transpose_4x8h v16, v17, v18, v19, v2, v3, v4, v5
blr x5
+ mov x6, x0
+ load_add_store_8x4 x6, x7
+
+ transpose_4x8h_mov v20, v21, v22, v23, v2, v3, v4, v5, v16, v17, v18, v19
+ blr x5
add x6, x0, #8
load_add_store_8x4 x6, x7
ret x15
+.endif
endfunc
function inv_txfm_\variant\()add_4x16_neon
@@ -1605,12 +1610,14 @@ function inv_txfm_\variant\()add_4x16_neon
mov w16, #(5793-4096)*8
dup v0.4h, w16
identity_8x4_shift1 v16, v17, v18, v19, v0.h[0]
+
+ b L(itx_4x16_epilog)
.else
blr x4
.irp i, v16.8h, v17.8h, v18.8h, v19.8h
srshr \i, \i, #1
.endr
-.endif
+L(itx_4x16_epilog):
transpose_4x8h v16, v17, v18, v19, v4, v5, v6, v7
ins v20.d[0], v16.d[1]
ins v21.d[0], v17.d[1]
@@ -1622,11 +1629,12 @@ function inv_txfm_\variant\()add_4x16_neon
load_add_store_4x16 x0, x6
ret x15
+.endif
endfunc
.endm
-def_fn_416_base
def_fn_416_base identity_
+def_fn_416_base
.macro def_fn_416 w, h, txfm1, txfm2, eob_half
function inv_txfm_add_\txfm1\()_\txfm2\()_\w\()x\h\()_8bpc_neon, export=1
@@ -1634,11 +1642,15 @@ function inv_txfm_add_\txfm1\()_\txfm2\()_\w\()x\h\()_8bpc_neon, export=1
idct_dc \w, \h, 1
.endif
.if \w == 4
+.ifnc \txfm1, identity
adr x4, inv_\txfm1\()_8h_x\w\()_neon
+.endif
adr x5, inv_\txfm2\()_4h_x\h\()_neon
mov w13, #\eob_half
.else
+.ifnc \txfm1, identity
adr x4, inv_\txfm1\()_4h_x\w\()_neon
+.endif
adr x5, inv_\txfm2\()_8h_x\h\()_neon
.endif
.ifc \txfm1, identity
@@ -1690,13 +1702,16 @@ function inv_txfm_\variant\()add_16x8_neon
mov w16, #2*(5793-4096)*8
dup v0.4h, w16
identity_8x16_shift1 v0.h[0]
+
+ b L(itx_16x8_epilog)
.else
blr x4
-.irp i, v16.8h, v17.8h, v18.8h, v19.8h, v20.8h, v21.8h, v22.8h, v23.8h
+.irp i, v16.8h, v17.8h, v18.8h, v19.8h, v20.8h, v21.8h, v22.8h, v23.8h, v24.8h, v25.8h, v26.8h, v27.8h, v28.8h, v29.8h, v30.8h, v31.8h
srshr \i, \i, #1
.endr
-.endif
+
+L(itx_16x8_epilog):
transpose_8x8h v16, v17, v18, v19, v20, v21, v22, v23, v2, v3
blr x5
@@ -1704,27 +1719,7 @@ function inv_txfm_\variant\()add_16x8_neon
mov x6, x0
load_add_store_8x8 x6, x7
-.ifc \variant, identity_
- mov v16.16b, v24.16b
- mov v17.16b, v25.16b
- mov v18.16b, v26.16b
- mov v19.16b, v27.16b
- mov v20.16b, v28.16b
- mov v21.16b, v29.16b
- mov v22.16b, v30.16b
- mov v23.16b, v31.16b
-.else
- srshr v16.8h, v24.8h, #1
- srshr v17.8h, v25.8h, #1
- srshr v18.8h, v26.8h, #1
- srshr v19.8h, v27.8h, #1
- srshr v20.8h, v28.8h, #1
- srshr v21.8h, v29.8h, #1
- srshr v22.8h, v30.8h, #1
- srshr v23.8h, v31.8h, #1
-.endif
-
- transpose_8x8h v16, v17, v18, v19, v20, v21, v22, v23, v2, v3
+ transpose_8x8h_mov v24, v25, v26, v27, v28, v29, v30, v31, v2, v3, v16, v17, v18, v19, v20, v21, v22, v23
blr x5
@@ -1732,6 +1727,7 @@ function inv_txfm_\variant\()add_16x8_neon
load_add_store_8x8 x0, x7
ret x15
+.endif
endfunc
function inv_txfm_\variant\()add_8x16_neon
@@ -1790,14 +1786,16 @@ function inv_txfm_\variant\()add_8x16_neon
scale_input .8h, v0.h[0], v16, v17, v18, v19, v20, v21, v22, v23
.ifc \variant, identity_
// The identity shl #1 and downshift srshr #1 cancel out
+
+ b L(itx_8x16_epilog)
.else
blr x4
.irp i, v16.8h, v17.8h, v18.8h, v19.8h, v20.8h, v21.8h, v22.8h, v23.8h
srshr \i, \i, #1
.endr
-.endif
+L(itx_8x16_epilog):
transpose_8x8h v16, v17, v18, v19, v20, v21, v22, v23, v2, v3
blr x5
@@ -1805,18 +1803,21 @@ function inv_txfm_\variant\()add_8x16_neon
load_add_store_8x16 x0, x6
ret x15
+.endif
endfunc
.endm
-def_fn_816_base
def_fn_816_base identity_
+def_fn_816_base
.macro def_fn_816 w, h, txfm1, txfm2, eob_half
function inv_txfm_add_\txfm1\()_\txfm2\()_\w\()x\h\()_8bpc_neon, export=1
.ifc \txfm1\()_\txfm2, dct_dct
idct_dc \w, \h, 1
.endif
+.ifnc \txfm1, identity
adr x4, inv_\txfm1\()_8h_x\w\()_neon
+.endif
adr x5, inv_\txfm2\()_8h_x\h\()_neon
.if \w == 8
mov x13, #\eob_half
diff --git a/third_party/dav1d/src/arm/64/itx16.S b/third_party/dav1d/src/arm/64/itx16.S
index eee3a9636d..31ee9be1b4 100644
--- a/third_party/dav1d/src/arm/64/itx16.S
+++ b/third_party/dav1d/src/arm/64/itx16.S
@@ -514,13 +514,17 @@ function inv_txfm_add_wht_wht_4x4_16bpc_neon, export=1
b L(itx_4x4_end)
endfunc
+// HBD inv_txfm_add_4x4_neon deviates from the common pattern with registers
+// x0-x4 external parameters
+// x5 function pointer to first transform
+// x6 function pointer to second transform
function inv_txfm_add_4x4_neon
movi v30.4s, #0
movi v31.4s, #0
ld1 {v16.4s,v17.4s,v18.4s,v19.4s}, [x2]
st1 {v30.4s, v31.4s}, [x2], #32
- blr x4
+ blr x5
st1 {v30.4s, v31.4s}, [x2], #32
sqxtn v16.4h, v16.4s
@@ -529,7 +533,7 @@ function inv_txfm_add_4x4_neon
sqxtn v19.4h, v19.4s
transpose_4x4h v16, v17, v18, v19, v20, v21, v22, v23
- blr x5
+ blr x6
ld1 {v0.d}[0], [x0], x1
ld1 {v0.d}[1], [x0], x1
@@ -541,7 +545,7 @@ function inv_txfm_add_4x4_neon
srshr v18.8h, v18.8h, #4
L(itx_4x4_end):
- mvni v31.8h, #0xfc, lsl #8 // 0x3ff
+ dup v31.8h, w4
sub x0, x0, x1, lsl #2
usqadd v0.8h, v16.8h
usqadd v1.8h, v18.8h
@@ -579,8 +583,8 @@ function inv_txfm_add_\txfm1\()_\txfm2\()_4x4_16bpc_neon, export=1
b L(itx_4x4_end)
1:
.endif
- adr x4, inv_\txfm1\()_4s_x4_neon
- movrel x5, X(inv_\txfm2\()_4h_x4_neon)
+ adr x5, inv_\txfm1\()_4s_x4_neon
+ movrel x6, X(inv_\txfm2\()_4h_x4_neon)
b inv_txfm_add_4x4_neon
endfunc
.endm
@@ -1381,6 +1385,10 @@ function inv_txfm_horz\suffix\()_16x4_neon
sqrshrn2 v21.8h, v29.4s, #\shift
sqrshrn2 v22.8h, v30.4s, #\shift
sqrshrn2 v23.8h, v31.4s, #\shift
+.if \scale
+ b L(horz_16x4_epilog)
+.else
+L(horz_16x4_epilog):
transpose_4x8h v16, v17, v18, v19, v4, v5, v6, v7
transpose_4x8h v20, v21, v22, v23, v4, v5, v6, v7
@@ -1389,11 +1397,12 @@ function inv_txfm_horz\suffix\()_16x4_neon
.endr
ret x14
+.endif
endfunc
.endm
-def_horz_16 scale=0, shift=2
def_horz_16 scale=1, shift=1, suffix=_scale
+def_horz_16 scale=0, shift=2
function inv_txfm_add_vert_8x16_neon
mov x14, x30
diff --git a/third_party/dav1d/src/arm/64/mc.S b/third_party/dav1d/src/arm/64/mc.S
index 9f7b4e7a89..3df0393c3a 100644
--- a/third_party/dav1d/src/arm/64/mc.S
+++ b/third_party/dav1d/src/arm/64/mc.S
@@ -1154,7 +1154,7 @@ endfunc
uxtl \r6\().8h, \r6\().8b
.endif
.endm
-.macro mul_mla_4 d, s0, s1, s2, s3, wd
+.macro mul_mla_4tap d, s0, s1, s2, s3, wd
mul \d\wd, \s0\wd, v0.h[0]
mla \d\wd, \s1\wd, v0.h[1]
mla \d\wd, \s2\wd, v0.h[2]
@@ -1163,7 +1163,51 @@ endfunc
// Interleaving the mul/mla chains actually hurts performance
// significantly on Cortex A53, thus keeping mul/mla tightly
// chained like this.
-.macro mul_mla_8_0_4h d0, s0, s1, s2, s3, s4, s5, s6, s7
+.macro mul_mla_6tap_0_4h d0, s0, s1, s2, s3, s4, s5, s6, s7
+ mul \d0\().4h, \s1\().4h, v0.h[1]
+ mla \d0\().4h, \s2\().4h, v0.h[2]
+ mla \d0\().4h, \s3\().4h, v0.h[3]
+ mla \d0\().4h, \s4\().4h, v0.h[4]
+ mla \d0\().4h, \s5\().4h, v0.h[5]
+ mla \d0\().4h, \s6\().4h, v0.h[6]
+.endm
+.macro mul_mla_6tap_0 d0, s0, s1, s2, s3, s4, s5, s6, s7
+ mul \d0\().8h, \s1\().8h, v0.h[1]
+ mla \d0\().8h, \s2\().8h, v0.h[2]
+ mla \d0\().8h, \s3\().8h, v0.h[3]
+ mla \d0\().8h, \s4\().8h, v0.h[4]
+ mla \d0\().8h, \s5\().8h, v0.h[5]
+ mla \d0\().8h, \s6\().8h, v0.h[6]
+.endm
+.macro mul_mla_6tap_1 d0, d1, s0, s1, s2, s3, s4, s5, s6, s7, s8
+ mul \d0\().8h, \s1\().8h, v0.h[1]
+ mla \d0\().8h, \s2\().8h, v0.h[2]
+ mla \d0\().8h, \s3\().8h, v0.h[3]
+ mla \d0\().8h, \s4\().8h, v0.h[4]
+ mla \d0\().8h, \s5\().8h, v0.h[5]
+ mla \d0\().8h, \s6\().8h, v0.h[6]
+ mul \d1\().8h, \s2\().8h, v0.h[1]
+ mla \d1\().8h, \s3\().8h, v0.h[2]
+ mla \d1\().8h, \s4\().8h, v0.h[3]
+ mla \d1\().8h, \s5\().8h, v0.h[4]
+ mla \d1\().8h, \s6\().8h, v0.h[5]
+ mla \d1\().8h, \s7\().8h, v0.h[6]
+.endm
+.macro mul_mla_6tap_2 d0, d1, s0, s1, s2, s3, s4, s5, s6, s7, s8, s9
+ mul \d0\().8h, \s1\().8h, v0.h[1]
+ mla \d0\().8h, \s2\().8h, v0.h[2]
+ mla \d0\().8h, \s3\().8h, v0.h[3]
+ mla \d0\().8h, \s4\().8h, v0.h[4]
+ mla \d0\().8h, \s5\().8h, v0.h[5]
+ mla \d0\().8h, \s6\().8h, v0.h[6]
+ mul \d1\().8h, \s3\().8h, v0.h[1]
+ mla \d1\().8h, \s4\().8h, v0.h[2]
+ mla \d1\().8h, \s5\().8h, v0.h[3]
+ mla \d1\().8h, \s6\().8h, v0.h[4]
+ mla \d1\().8h, \s7\().8h, v0.h[5]
+ mla \d1\().8h, \s8\().8h, v0.h[6]
+.endm
+.macro mul_mla_8tap_0_4h d0, s0, s1, s2, s3, s4, s5, s6, s7
mul \d0\().4h, \s0\().4h, v0.h[0]
mla \d0\().4h, \s1\().4h, v0.h[1]
mla \d0\().4h, \s2\().4h, v0.h[2]
@@ -1173,7 +1217,7 @@ endfunc
mla \d0\().4h, \s6\().4h, v0.h[6]
mla \d0\().4h, \s7\().4h, v0.h[7]
.endm
-.macro mul_mla_8_0 d0, s0, s1, s2, s3, s4, s5, s6, s7
+.macro mul_mla_8tap_0 d0, s0, s1, s2, s3, s4, s5, s6, s7
mul \d0\().8h, \s0\().8h, v0.h[0]
mla \d0\().8h, \s1\().8h, v0.h[1]
mla \d0\().8h, \s2\().8h, v0.h[2]
@@ -1183,7 +1227,7 @@ endfunc
mla \d0\().8h, \s6\().8h, v0.h[6]
mla \d0\().8h, \s7\().8h, v0.h[7]
.endm
-.macro mul_mla_8_1 d0, d1, s0, s1, s2, s3, s4, s5, s6, s7, s8
+.macro mul_mla_8tap_1 d0, d1, s0, s1, s2, s3, s4, s5, s6, s7, s8
mul \d0\().8h, \s0\().8h, v0.h[0]
mla \d0\().8h, \s1\().8h, v0.h[1]
mla \d0\().8h, \s2\().8h, v0.h[2]
@@ -1201,7 +1245,7 @@ endfunc
mla \d1\().8h, \s7\().8h, v0.h[6]
mla \d1\().8h, \s8\().8h, v0.h[7]
.endm
-.macro mul_mla_8_2 d0, d1, s0, s1, s2, s3, s4, s5, s6, s7, s8, s9
+.macro mul_mla_8tap_2 d0, d1, s0, s1, s2, s3, s4, s5, s6, s7, s8, s9
mul \d0\().8h, \s0\().8h, v0.h[0]
mla \d0\().8h, \s1\().8h, v0.h[1]
mla \d0\().8h, \s2\().8h, v0.h[2]
@@ -1315,11 +1359,11 @@ endfunc
.endif
.endm
-.macro make_8tap_fn op, type, type_h, type_v
+.macro make_8tap_fn op, type, type_h, type_v, taps
function \op\()_8tap_\type\()_8bpc_neon, export=1
mov x8, \type_h
mov x9, \type_v
- b \op\()_8tap_neon
+ b \op\()_\taps\()_neon
endfunc
.endm
@@ -1328,18 +1372,8 @@ endfunc
#define SMOOTH ((1*15<<7)|4*15)
#define SHARP ((2*15<<7)|3*15)
-.macro filter_fn type, dst, d_strd, src, s_strd, w, h, mx, xmx, my, xmy, ds2, sr2, shift_hv
-make_8tap_fn \type, regular, REGULAR, REGULAR
-make_8tap_fn \type, regular_smooth, REGULAR, SMOOTH
-make_8tap_fn \type, regular_sharp, REGULAR, SHARP
-make_8tap_fn \type, smooth, SMOOTH, SMOOTH
-make_8tap_fn \type, smooth_regular, SMOOTH, REGULAR
-make_8tap_fn \type, smooth_sharp, SMOOTH, SHARP
-make_8tap_fn \type, sharp, SHARP, SHARP
-make_8tap_fn \type, sharp_regular, SHARP, REGULAR
-make_8tap_fn \type, sharp_smooth, SHARP, SMOOTH
-
-function \type\()_8tap_neon
+.macro filter_fn type, dst, d_strd, src, s_strd, w, h, mx, xmx, my, xmy, ds2, sr2, shift_hv, taps
+function \type\()_\taps\()_neon
mov w10, #0x4081 // (1 << 14) | (1 << 7) | (1 << 0)
mul \mx, \mx, w10
mul \my, \my, w10
@@ -1354,12 +1388,12 @@ function \type\()_8tap_neon
tst \mx, #(0x7f << 14)
sub w8, w8, #24
movrel x10, X(mc_subpel_filters), -8
- b.ne L(\type\()_8tap_h)
+ b.ne L(\type\()_\taps\()_h)
tst \my, #(0x7f << 14)
- b.ne L(\type\()_8tap_v)
+ b.ne L(\type\()_\taps\()_v)
b \type\()_neon
-L(\type\()_8tap_h):
+L(\type\()_\taps\()_h):
cmp \w, #4
ubfx w9, \mx, #7, #7
and \mx, \mx, #0x7f
@@ -1368,9 +1402,9 @@ L(\type\()_8tap_h):
4:
tst \my, #(0x7f << 14)
add \xmx, x10, \mx, uxtw #3
- b.ne L(\type\()_8tap_hv)
+ b.ne L(\type\()_\taps\()_hv)
- adr x9, L(\type\()_8tap_h_tbl)
+ adr x9, L(\type\()_\taps\()_h_tbl)
ldrh w8, [x9, x8, lsl #1]
sub x9, x9, w8, uxtw
br x9
@@ -1471,6 +1505,18 @@ L(\type\()_8tap_h):
uxtl v20.8h, v20.8b
uxtl v21.8h, v21.8b
+.ifc \taps, 6tap
+ ext v19.16b, v16.16b, v17.16b, #2
+ ext v23.16b, v20.16b, v21.16b, #2
+ mul v18.8h, v19.8h, v0.h[1]
+ mul v22.8h, v23.8h, v0.h[1]
+.irpc i, 23456
+ ext v19.16b, v16.16b, v17.16b, #(2*\i)
+ ext v23.16b, v20.16b, v21.16b, #(2*\i)
+ mla v18.8h, v19.8h, v0.h[\i]
+ mla v22.8h, v23.8h, v0.h[\i]
+.endr
+.else // 8tap
mul v18.8h, v16.8h, v0.h[0]
mul v22.8h, v20.8h, v0.h[0]
.irpc i, 1234567
@@ -1479,6 +1525,7 @@ L(\type\()_8tap_h):
mla v18.8h, v19.8h, v0.h[\i]
mla v22.8h, v23.8h, v0.h[\i]
.endr
+.endif
subs \h, \h, #2
srshr v18.8h, v18.8h, #2
srshr v22.8h, v22.8h, #2
@@ -1523,6 +1570,26 @@ L(\type\()_8tap_h):
uxtl v22.8h, v22.8b
16:
+.ifc \taps, 6tap
+ ext v28.16b, v16.16b, v17.16b, #2
+ ext v29.16b, v17.16b, v18.16b, #2
+ ext v30.16b, v20.16b, v21.16b, #2
+ ext v31.16b, v21.16b, v22.16b, #2
+ mul v24.8h, v28.8h, v0.h[1]
+ mul v25.8h, v29.8h, v0.h[1]
+ mul v26.8h, v30.8h, v0.h[1]
+ mul v27.8h, v31.8h, v0.h[1]
+.irpc i, 23456
+ ext v28.16b, v16.16b, v17.16b, #(2*\i)
+ ext v29.16b, v17.16b, v18.16b, #(2*\i)
+ ext v30.16b, v20.16b, v21.16b, #(2*\i)
+ ext v31.16b, v21.16b, v22.16b, #(2*\i)
+ mla v24.8h, v28.8h, v0.h[\i]
+ mla v25.8h, v29.8h, v0.h[\i]
+ mla v26.8h, v30.8h, v0.h[\i]
+ mla v27.8h, v31.8h, v0.h[\i]
+.endr
+.else // 8tap
mul v24.8h, v16.8h, v0.h[0]
mul v25.8h, v17.8h, v0.h[0]
mul v26.8h, v20.8h, v0.h[0]
@@ -1537,6 +1604,7 @@ L(\type\()_8tap_h):
mla v26.8h, v30.8h, v0.h[\i]
mla v27.8h, v31.8h, v0.h[\i]
.endr
+.endif
srshr v24.8h, v24.8h, #2
srshr v25.8h, v25.8h, #2
srshr v26.8h, v26.8h, #2
@@ -1575,18 +1643,18 @@ L(\type\()_8tap_h):
b.gt 161b
ret
-L(\type\()_8tap_h_tbl):
- .hword L(\type\()_8tap_h_tbl) - 1280b
- .hword L(\type\()_8tap_h_tbl) - 640b
- .hword L(\type\()_8tap_h_tbl) - 320b
- .hword L(\type\()_8tap_h_tbl) - 160b
- .hword L(\type\()_8tap_h_tbl) - 80b
- .hword L(\type\()_8tap_h_tbl) - 40b
- .hword L(\type\()_8tap_h_tbl) - 20b
+L(\type\()_\taps\()_h_tbl):
+ .hword L(\type\()_\taps\()_h_tbl) - 1280b
+ .hword L(\type\()_\taps\()_h_tbl) - 640b
+ .hword L(\type\()_\taps\()_h_tbl) - 320b
+ .hword L(\type\()_\taps\()_h_tbl) - 160b
+ .hword L(\type\()_\taps\()_h_tbl) - 80b
+ .hword L(\type\()_\taps\()_h_tbl) - 40b
+ .hword L(\type\()_\taps\()_h_tbl) - 20b
.hword 0
-L(\type\()_8tap_v):
+L(\type\()_\taps\()_v):
cmp \h, #4
ubfx w9, \my, #7, #7
and \my, \my, #0x7f
@@ -1595,7 +1663,7 @@ L(\type\()_8tap_v):
4:
add \xmy, x10, \my, uxtw #3
- adr x9, L(\type\()_8tap_v_tbl)
+ adr x9, L(\type\()_\taps\()_v_tbl)
ldrh w8, [x9, x8, lsl #1]
sub x9, x9, w8, uxtw
br x9
@@ -1620,7 +1688,7 @@ L(\type\()_8tap_v):
interleave_1_h v1, v2, v3, v4, v5
b.gt 24f
uxtl_b v1, v2, v3, v4
- mul_mla_4 v6, v1, v2, v3, v4, .4h
+ mul_mla_4tap v6, v1, v2, v3, v4, .4h
sqrshrun_b 6, v6
st_h \d_strd, v6, 2
ret
@@ -1630,7 +1698,7 @@ L(\type\()_8tap_v):
interleave_1_h v5, v6, v7
interleave_2_s v1, v2, v3, v4, v5, v6
uxtl_b v1, v2, v3, v4
- mul_mla_4 v6, v1, v2, v3, v4, .8h
+ mul_mla_4tap v6, v1, v2, v3, v4, .8h
sqrshrun_b 6, v6
st_h \d_strd, v6, 4
ret
@@ -1655,7 +1723,7 @@ L(\type\()_8tap_v):
interleave_1_h v7, v16, v17, v18, v19
interleave_2_s v5, v6, v7, v16, v17, v18
uxtl_b v5, v6, v7, v16
- mul_mla_8_0 v30, v1, v2, v3, v4, v5, v6, v7, v16
+ mul_mla_\taps\()_0 v30, v1, v2, v3, v4, v5, v6, v7, v16
sqrshrun_b 6, v30
st_h \d_strd, v30, 4
b.le 0f
@@ -1673,7 +1741,7 @@ L(\type\()_8tap_v):
load_h \sr2, \src, \s_strd, v16, v17
interleave_1_h v7, v16, v17
uxtl_b v5, v6, v7, v16
- mul_mla_8_0_4h v30, v1, v2, v3, v4, v5, v6, v7, v16
+ mul_mla_\taps\()_0_4h v30, v1, v2, v3, v4, v5, v6, v7, v16
sqrshrun_b 6, v30
st_h \d_strd, v30, 2
0:
@@ -1698,13 +1766,13 @@ L(\type\()_8tap_v):
load_s \src, \sr2, \s_strd, v1, v2, v3, v4, v5
interleave_1_s v1, v2, v3, v4, v5
uxtl_b v1, v2, v3, v4
- mul_mla_4 v6, v1, v2, v3, v4, .8h
+ mul_mla_4tap v6, v1, v2, v3, v4, .8h
shift_store_4 \type, \d_strd, v6
b.le 0f
load_s \sr2, \src, \s_strd, v6, v7
interleave_1_s v5, v6, v7
uxtl_b v5, v6
- mul_mla_4 v7, v3, v4, v5, v6, .8h
+ mul_mla_4tap v7, v3, v4, v5, v6, .8h
shift_store_4 \type, \d_strd, v7
0:
ret
@@ -1729,28 +1797,28 @@ L(\type\()_8tap_v):
load_s \sr2, \src, \s_strd, v23, v24, v25, v26
interleave_1_s v22, v23, v24, v25, v26
uxtl_b v22, v23, v24, v25
- mul_mla_8_2 v1, v2, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25
+ mul_mla_\taps\()_2 v1, v2, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25
shift_store_4 \type, \d_strd, v1, v2
b.le 0f
load_s \sr2, \src, \s_strd, v27, v16
subs \h, \h, #2
interleave_1_s v26, v27, v16
uxtl_b v26, v27
- mul_mla_8_0 v1, v20, v21, v22, v23, v24, v25, v26, v27
+ mul_mla_\taps\()_0 v1, v20, v21, v22, v23, v24, v25, v26, v27
shift_store_4 \type, \d_strd, v1
b.le 0f
load_s \sr2, \src, \s_strd, v17, v18
subs \h, \h, #2
interleave_1_s v16, v17, v18
uxtl_b v16, v17
- mul_mla_8_0 v2, v22, v23, v24, v25, v26, v27, v16, v17
+ mul_mla_\taps\()_0 v2, v22, v23, v24, v25, v26, v27, v16, v17
shift_store_4 \type, \d_strd, v2
b.le 0f
subs \h, \h, #4
load_s \sr2, \src, \s_strd, v19, v20, v21, v22
interleave_1_s v18, v19, v20, v21, v22
uxtl_b v18, v19, v20, v21
- mul_mla_8_2 v1, v2, v24, v25, v26, v27, v16, v17, v18, v19, v20, v21
+ mul_mla_\taps\()_2 v1, v2, v24, v25, v26, v27, v16, v17, v18, v19, v20, v21
shift_store_4 \type, \d_strd, v1, v2
b.gt 48b
0:
@@ -1773,14 +1841,14 @@ L(\type\()_8tap_v):
load_8b \src, \sr2, \s_strd, v1, v2, v3, v4, v5
uxtl_b v1, v2, v3, v4, v5
- mul_mla_4 v6, v1, v2, v3, v4, .8h
- mul_mla_4 v7, v2, v3, v4, v5, .8h
+ mul_mla_4tap v6, v1, v2, v3, v4, .8h
+ mul_mla_4tap v7, v2, v3, v4, v5, .8h
shift_store_8 \type, \d_strd, v6, v7
b.le 0f
load_8b \sr2, \src, \s_strd, v6, v7
uxtl_b v6, v7
- mul_mla_4 v1, v3, v4, v5, v6, .8h
- mul_mla_4 v2, v4, v5, v6, v7, .8h
+ mul_mla_4tap v1, v3, v4, v5, v6, .8h
+ mul_mla_4tap v2, v4, v5, v6, v7, .8h
shift_store_8 \type, \d_strd, v1, v2
0:
ret
@@ -1809,32 +1877,32 @@ L(\type\()_8tap_v):
subs \h, \h, #2
load_8b \sr2, \src, \s_strd, v23, v24
uxtl_b v23, v24
- mul_mla_8_1 v1, v2, v16, v17, v18, v19, v20, v21, v22, v23, v24
+ mul_mla_\taps\()_1 v1, v2, v16, v17, v18, v19, v20, v21, v22, v23, v24
shift_store_8 \type, \d_strd, v1, v2
b.le 9f
subs \h, \h, #2
load_8b \sr2, \src, \s_strd, v25, v26
uxtl_b v25, v26
- mul_mla_8_1 v3, v4, v18, v19, v20, v21, v22, v23, v24, v25, v26
+ mul_mla_\taps\()_1 v3, v4, v18, v19, v20, v21, v22, v23, v24, v25, v26
shift_store_8 \type, \d_strd, v3, v4
b.le 9f
subs \h, \h, #2
load_8b \sr2, \src, \s_strd, v27, v16
uxtl_b v27, v16
- mul_mla_8_1 v1, v2, v20, v21, v22, v23, v24, v25, v26, v27, v16
+ mul_mla_\taps\()_1 v1, v2, v20, v21, v22, v23, v24, v25, v26, v27, v16
shift_store_8 \type, \d_strd, v1, v2
b.le 9f
subs \h, \h, #2
load_8b \sr2, \src, \s_strd, v17, v18
uxtl_b v17, v18
- mul_mla_8_1 v3, v4, v22, v23, v24, v25, v26, v27, v16, v17, v18
+ mul_mla_\taps\()_1 v3, v4, v22, v23, v24, v25, v26, v27, v16, v17, v18
shift_store_8 \type, \d_strd, v3, v4
b.le 9f
subs \h, \h, #4
load_8b \sr2, \src, \s_strd, v19, v20, v21, v22
uxtl_b v19, v20, v21, v22
- mul_mla_8_1 v1, v2, v24, v25, v26, v27, v16, v17, v18, v19, v20
- mul_mla_8_1 v3, v4, v26, v27, v16, v17, v18, v19, v20, v21, v22
+ mul_mla_\taps\()_1 v1, v2, v24, v25, v26, v27, v16, v17, v18, v19, v20
+ mul_mla_\taps\()_1 v3, v4, v26, v27, v16, v17, v18, v19, v20, v21, v22
shift_store_8 \type, \d_strd, v1, v2, v3, v4
b.gt 88b
9:
@@ -1882,10 +1950,10 @@ L(\type\()_8tap_v):
uxtl2 v25.8h, v3.16b
uxtl2 v26.8h, v4.16b
uxtl2 v27.8h, v5.16b
- mul_mla_4 v1, v16, v17, v18, v19, .8h
- mul_mla_4 v16, v17, v18, v19, v20, .8h
- mul_mla_4 v2, v23, v24, v25, v26, .8h
- mul_mla_4 v17, v24, v25, v26, v27, .8h
+ mul_mla_4tap v1, v16, v17, v18, v19, .8h
+ mul_mla_4tap v16, v17, v18, v19, v20, .8h
+ mul_mla_4tap v2, v23, v24, v25, v26, .8h
+ mul_mla_4tap v17, v24, v25, v26, v27, .8h
shift_store_16 \type, \d_strd, v1, v2, v16, v17
b.le 0f
load_16b \sr2, \src, \s_strd, v6, v7
@@ -1893,25 +1961,25 @@ L(\type\()_8tap_v):
uxtl v22.8h, v7.8b
uxtl2 v28.8h, v6.16b
uxtl2 v29.8h, v7.16b
- mul_mla_4 v1, v18, v19, v20, v21, .8h
- mul_mla_4 v3, v19, v20, v21, v22, .8h
- mul_mla_4 v2, v25, v26, v27, v28, .8h
- mul_mla_4 v4, v26, v27, v28, v29, .8h
+ mul_mla_4tap v1, v18, v19, v20, v21, .8h
+ mul_mla_4tap v3, v19, v20, v21, v22, .8h
+ mul_mla_4tap v2, v25, v26, v27, v28, .8h
+ mul_mla_4tap v4, v26, v27, v28, v29, .8h
shift_store_16 \type, \d_strd, v1, v2, v3, v4
0:
ret
-L(\type\()_8tap_v_tbl):
- .hword L(\type\()_8tap_v_tbl) - 1280b
- .hword L(\type\()_8tap_v_tbl) - 640b
- .hword L(\type\()_8tap_v_tbl) - 320b
- .hword L(\type\()_8tap_v_tbl) - 160b
- .hword L(\type\()_8tap_v_tbl) - 80b
- .hword L(\type\()_8tap_v_tbl) - 40b
- .hword L(\type\()_8tap_v_tbl) - 20b
+L(\type\()_\taps\()_v_tbl):
+ .hword L(\type\()_\taps\()_v_tbl) - 1280b
+ .hword L(\type\()_\taps\()_v_tbl) - 640b
+ .hword L(\type\()_\taps\()_v_tbl) - 320b
+ .hword L(\type\()_\taps\()_v_tbl) - 160b
+ .hword L(\type\()_\taps\()_v_tbl) - 80b
+ .hword L(\type\()_\taps\()_v_tbl) - 40b
+ .hword L(\type\()_\taps\()_v_tbl) - 20b
.hword 0
-L(\type\()_8tap_hv):
+L(\type\()_\taps\()_hv):
cmp \h, #4
ubfx w9, \my, #7, #7
and \my, \my, #0x7f
@@ -1920,7 +1988,7 @@ L(\type\()_8tap_hv):
4:
add \xmy, x10, \my, uxtw #3
- adr x9, L(\type\()_8tap_hv_tbl)
+ adr x9, L(\type\()_\taps\()_hv_tbl)
ldrh w8, [x9, x8, lsl #1]
sub x9, x9, w8, uxtw
br x9
@@ -1952,13 +2020,13 @@ L(\type\()_8tap_hv):
addp v28.4h, v28.4h, v29.4h
addp v16.4h, v28.4h, v28.4h
srshr v16.4h, v16.4h, #2
- bl L(\type\()_8tap_filter_2)
+ bl L(\type\()_\taps\()_filter_2)
trn1 v16.2s, v16.2s, v28.2s
mov v17.8b, v28.8b
2:
- bl L(\type\()_8tap_filter_2)
+ bl L(\type\()_\taps\()_filter_2)
ext v18.8b, v17.8b, v28.8b, #4
smull v2.4s, v16.4h, v1.h[0]
@@ -1997,19 +2065,27 @@ L(\type\()_8tap_hv):
addp v16.4h, v28.4h, v28.4h
srshr v16.4h, v16.4h, #2
- bl L(\type\()_8tap_filter_2)
+ bl L(\type\()_\taps\()_filter_2)
trn1 v16.2s, v16.2s, v28.2s
mov v17.8b, v28.8b
- bl L(\type\()_8tap_filter_2)
+ bl L(\type\()_\taps\()_filter_2)
ext v18.8b, v17.8b, v28.8b, #4
mov v19.8b, v28.8b
- bl L(\type\()_8tap_filter_2)
+ bl L(\type\()_\taps\()_filter_2)
ext v20.8b, v19.8b, v28.8b, #4
mov v21.8b, v28.8b
28:
- bl L(\type\()_8tap_filter_2)
+ bl L(\type\()_\taps\()_filter_2)
ext v22.8b, v21.8b, v28.8b, #4
+.ifc \taps, 6tap
+ smull v2.4s, v17.4h, v1.h[1]
+ smlal v2.4s, v18.4h, v1.h[2]
+ smlal v2.4s, v19.4h, v1.h[3]
+ smlal v2.4s, v20.4h, v1.h[4]
+ smlal v2.4s, v21.4h, v1.h[5]
+ smlal v2.4s, v22.4h, v1.h[6]
+.else // 8tap
smull v2.4s, v16.4h, v1.h[0]
smlal v2.4s, v17.4h, v1.h[1]
smlal v2.4s, v18.4h, v1.h[2]
@@ -2018,6 +2094,7 @@ L(\type\()_8tap_hv):
smlal v2.4s, v21.4h, v1.h[5]
smlal v2.4s, v22.4h, v1.h[6]
smlal v2.4s, v28.4h, v1.h[7]
+.endif
sqrshrn v2.4h, v2.4s, #\shift_hv
sqxtun v2.8b, v2.8h
@@ -2036,7 +2113,7 @@ L(\type\()_8tap_hv):
0:
ret x15
-L(\type\()_8tap_filter_2):
+L(\type\()_\taps\()_filter_2):
ld1 {v28.8b}, [\sr2], \s_strd
ld1 {v30.8b}, [\src], \s_strd
uxtl v28.8h, v28.8b
@@ -2083,12 +2160,12 @@ L(\type\()_8tap_filter_2):
mla v31.4h, v30.4h, v0.h[3]
srshr v16.4h, v31.4h, #2
- bl L(\type\()_8tap_filter_4)
+ bl L(\type\()_\taps\()_filter_4)
mov v17.8b, v28.8b
mov v18.8b, v29.8b
4:
- bl L(\type\()_8tap_filter_4)
+ bl L(\type\()_\taps\()_filter_4)
// Interleaving the mul/mla chains actually hurts performance
// significantly on Cortex A53, thus keeping mul/mla tightly
// chained like this.
@@ -2121,8 +2198,13 @@ L(\type\()_8tap_filter_2):
480: // 4x8, 4x16, 4x32 hv
ld1 {v1.8b}, [\xmy]
sub \src, \src, #1
+.ifc \taps, 6tap
+ sub \sr2, \src, \s_strd
+ sub \src, \src, \s_strd, lsl #1
+.else
sub \sr2, \src, \s_strd, lsl #1
sub \src, \sr2, \s_strd
+.endif
add \ds2, \dst, \d_strd
lsl \s_strd, \s_strd, #1
lsl \d_strd, \d_strd, #1
@@ -2139,20 +2221,38 @@ L(\type\()_8tap_filter_2):
mla v31.4h, v28.4h, v0.h[1]
mla v31.4h, v29.4h, v0.h[2]
mla v31.4h, v30.4h, v0.h[3]
+.ifc \taps, 6tap
+ srshr v18.4h, v31.4h, #2
+.else
srshr v16.4h, v31.4h, #2
- bl L(\type\()_8tap_filter_4)
+ bl L(\type\()_\taps\()_filter_4)
mov v17.8b, v28.8b
mov v18.8b, v29.8b
- bl L(\type\()_8tap_filter_4)
+.endif
+ bl L(\type\()_\taps\()_filter_4)
mov v19.8b, v28.8b
mov v20.8b, v29.8b
- bl L(\type\()_8tap_filter_4)
+ bl L(\type\()_\taps\()_filter_4)
mov v21.8b, v28.8b
mov v22.8b, v29.8b
48:
- bl L(\type\()_8tap_filter_4)
+ bl L(\type\()_\taps\()_filter_4)
+.ifc \taps, 6tap
+ smull v2.4s, v18.4h, v1.h[1]
+ smlal v2.4s, v19.4h, v1.h[2]
+ smlal v2.4s, v20.4h, v1.h[3]
+ smlal v2.4s, v21.4h, v1.h[4]
+ smlal v2.4s, v22.4h, v1.h[5]
+ smlal v2.4s, v28.4h, v1.h[6]
+ smull v3.4s, v19.4h, v1.h[1]
+ smlal v3.4s, v20.4h, v1.h[2]
+ smlal v3.4s, v21.4h, v1.h[3]
+ smlal v3.4s, v22.4h, v1.h[4]
+ smlal v3.4s, v28.4h, v1.h[5]
+ smlal v3.4s, v29.4h, v1.h[6]
+.else // 8tap
smull v2.4s, v16.4h, v1.h[0]
smlal v2.4s, v17.4h, v1.h[1]
smlal v2.4s, v18.4h, v1.h[2]
@@ -2169,6 +2269,7 @@ L(\type\()_8tap_filter_2):
smlal v3.4s, v22.4h, v1.h[5]
smlal v3.4s, v28.4h, v1.h[6]
smlal v3.4s, v29.4h, v1.h[7]
+.endif
sqrshrn v2.4h, v2.4s, #\shift_hv
sqrshrn v3.4h, v3.4s, #\shift_hv
subs \h, \h, #2
@@ -2182,8 +2283,10 @@ L(\type\()_8tap_filter_2):
st1 {v3.4h}, [\ds2], \d_strd
.endif
b.le 0f
+.ifc \taps, 8tap
mov v16.8b, v18.8b
mov v17.8b, v19.8b
+.endif
mov v18.8b, v20.8b
mov v19.8b, v21.8b
mov v20.8b, v22.8b
@@ -2193,7 +2296,7 @@ L(\type\()_8tap_filter_2):
0:
ret x15
-L(\type\()_8tap_filter_4):
+L(\type\()_\taps\()_filter_4):
ld1 {v26.8b}, [\sr2], \s_strd
ld1 {v27.8b}, [\src], \s_strd
uxtl v26.8h, v26.8b
@@ -2237,15 +2340,15 @@ L(\type\()_8tap_filter_4):
lsl \d_strd, \d_strd, #1
lsl \s_strd, \s_strd, #1
- bl L(\type\()_8tap_filter_8_first)
- bl L(\type\()_8tap_filter_8)
+ bl L(\type\()_\taps\()_filter_8_first)
+ bl L(\type\()_\taps\()_filter_8)
mov v17.16b, v24.16b
mov v18.16b, v25.16b
8:
smull v2.4s, v16.4h, v1.h[0]
smull2 v3.4s, v16.8h, v1.h[0]
- bl L(\type\()_8tap_filter_8)
+ bl L(\type\()_\taps\()_filter_8)
smull v4.4s, v17.4h, v1.h[0]
smull2 v5.4s, v17.8h, v1.h[0]
smlal v2.4s, v17.4h, v1.h[1]
@@ -2303,7 +2406,9 @@ L(\type\()_8tap_filter_4):
ld1 {v0.8b}, [\xmx]
ld1 {v1.8b}, [\xmy]
sub \src, \src, #3
+.ifc \taps, 8tap
sub \src, \src, \s_strd
+.endif
sub \src, \src, \s_strd, lsl #1
sxtl v0.8h, v0.8b
sxtl v1.8h, v1.8b
@@ -2316,21 +2421,52 @@ L(\type\()_8tap_filter_4):
lsl \d_strd, \d_strd, #1
lsl \s_strd, \s_strd, #1
- bl L(\type\()_8tap_filter_8_first)
- bl L(\type\()_8tap_filter_8)
+ bl L(\type\()_\taps\()_filter_8_first)
+.ifc \taps, 6tap
+ mov v18.16b, v16.16b
+.else
+ bl L(\type\()_\taps\()_filter_8)
mov v17.16b, v24.16b
mov v18.16b, v25.16b
- bl L(\type\()_8tap_filter_8)
+.endif
+ bl L(\type\()_\taps\()_filter_8)
mov v19.16b, v24.16b
mov v20.16b, v25.16b
- bl L(\type\()_8tap_filter_8)
+ bl L(\type\()_\taps\()_filter_8)
mov v21.16b, v24.16b
mov v22.16b, v25.16b
88:
+.ifc \taps, 6tap
+ smull v2.4s, v18.4h, v1.h[1]
+ smull2 v3.4s, v18.8h, v1.h[1]
+ bl L(\type\()_\taps\()_filter_8)
+ smull v4.4s, v19.4h, v1.h[1]
+ smull2 v5.4s, v19.8h, v1.h[1]
+ smlal v2.4s, v19.4h, v1.h[2]
+ smlal2 v3.4s, v19.8h, v1.h[2]
+ smlal v4.4s, v20.4h, v1.h[2]
+ smlal2 v5.4s, v20.8h, v1.h[2]
+ smlal v2.4s, v20.4h, v1.h[3]
+ smlal2 v3.4s, v20.8h, v1.h[3]
+ smlal v4.4s, v21.4h, v1.h[3]
+ smlal2 v5.4s, v21.8h, v1.h[3]
+ smlal v2.4s, v21.4h, v1.h[4]
+ smlal2 v3.4s, v21.8h, v1.h[4]
+ smlal v4.4s, v22.4h, v1.h[4]
+ smlal2 v5.4s, v22.8h, v1.h[4]
+ smlal v2.4s, v22.4h, v1.h[5]
+ smlal2 v3.4s, v22.8h, v1.h[5]
+ smlal v4.4s, v24.4h, v1.h[5]
+ smlal2 v5.4s, v24.8h, v1.h[5]
+ smlal v2.4s, v24.4h, v1.h[6]
+ smlal2 v3.4s, v24.8h, v1.h[6]
+ smlal v4.4s, v25.4h, v1.h[6]
+ smlal2 v5.4s, v25.8h, v1.h[6]
+.else // 8tap
smull v2.4s, v16.4h, v1.h[0]
smull2 v3.4s, v16.8h, v1.h[0]
- bl L(\type\()_8tap_filter_8)
+ bl L(\type\()_\taps\()_filter_8)
smull v4.4s, v17.4h, v1.h[0]
smull2 v5.4s, v17.8h, v1.h[0]
smlal v2.4s, v17.4h, v1.h[1]
@@ -2361,6 +2497,7 @@ L(\type\()_8tap_filter_4):
smlal2 v3.4s, v24.8h, v1.h[7]
smlal v4.4s, v25.4h, v1.h[7]
smlal2 v5.4s, v25.8h, v1.h[7]
+.endif
sqrshrn v2.4h, v2.4s, #\shift_hv
sqrshrn2 v2.8h, v3.4s, #\shift_hv
sqrshrn v4.4h, v4.4s, #\shift_hv
@@ -2376,8 +2513,10 @@ L(\type\()_8tap_filter_4):
st1 {v4.8h}, [\ds2], \d_strd
.endif
b.le 9f
+.ifc \taps, 8tap
mov v16.16b, v18.16b
mov v17.16b, v19.16b
+.endif
mov v18.16b, v20.16b
mov v19.16b, v21.16b
mov v20.16b, v22.16b
@@ -2399,14 +2538,32 @@ L(\type\()_8tap_filter_4):
.else
add \dst, \dst, #16
.endif
+.ifc \taps, 6tap
+ add \src, \src, \s_strd, lsl #1
+.endif
b 168b
0:
ret x15
-L(\type\()_8tap_filter_8_first):
+L(\type\()_\taps\()_filter_8_first):
ld1 {v28.8b, v29.8b}, [\src], \s_strd
uxtl v28.8h, v28.8b
uxtl v29.8h, v29.8b
+.ifc \taps, 6tap
+ ext v24.16b, v28.16b, v29.16b, #(2*1)
+ ext v25.16b, v28.16b, v29.16b, #(2*2)
+ ext v26.16b, v28.16b, v29.16b, #(2*3)
+ ext v27.16b, v28.16b, v29.16b, #(2*4)
+ mul v16.8h, v24.8h, v0.h[1]
+ mla v16.8h, v25.8h, v0.h[2]
+ mla v16.8h, v26.8h, v0.h[3]
+ mla v16.8h, v27.8h, v0.h[4]
+ ext v24.16b, v28.16b, v29.16b, #(2*5)
+ ext v25.16b, v28.16b, v29.16b, #(2*6)
+ ext v26.16b, v28.16b, v29.16b, #(2*7)
+ mla v16.8h, v24.8h, v0.h[5]
+ mla v16.8h, v25.8h, v0.h[6]
+.else // 8tap
mul v16.8h, v28.8h, v0.h[0]
ext v24.16b, v28.16b, v29.16b, #(2*1)
ext v25.16b, v28.16b, v29.16b, #(2*2)
@@ -2422,16 +2579,29 @@ L(\type\()_8tap_filter_8_first):
mla v16.8h, v24.8h, v0.h[5]
mla v16.8h, v25.8h, v0.h[6]
mla v16.8h, v26.8h, v0.h[7]
+.endif
srshr v16.8h, v16.8h, #2
ret
-L(\type\()_8tap_filter_8):
+L(\type\()_\taps\()_filter_8):
ld1 {v28.8b, v29.8b}, [\sr2], \s_strd
ld1 {v30.8b, v31.8b}, [\src], \s_strd
uxtl v28.8h, v28.8b
uxtl v29.8h, v29.8b
uxtl v30.8h, v30.8b
uxtl v31.8h, v31.8b
+.ifc \taps, 6tap
+ ext v26.16b, v28.16b, v29.16b, #2
+ ext v27.16b, v30.16b, v31.16b, #2
+ mul v24.8h, v26.8h, v0.h[1]
+ mul v25.8h, v27.8h, v0.h[1]
+.irpc i, 23456
+ ext v26.16b, v28.16b, v29.16b, #(2*\i)
+ ext v27.16b, v30.16b, v31.16b, #(2*\i)
+ mla v24.8h, v26.8h, v0.h[\i]
+ mla v25.8h, v27.8h, v0.h[\i]
+.endr
+.else // 8tap
mul v24.8h, v28.8h, v0.h[0]
mul v25.8h, v30.8h, v0.h[0]
.irpc i, 1234567
@@ -2440,22 +2610,25 @@ L(\type\()_8tap_filter_8):
mla v24.8h, v26.8h, v0.h[\i]
mla v25.8h, v27.8h, v0.h[\i]
.endr
+.endif
srshr v24.8h, v24.8h, #2
srshr v25.8h, v25.8h, #2
ret
-L(\type\()_8tap_hv_tbl):
- .hword L(\type\()_8tap_hv_tbl) - 1280b
- .hword L(\type\()_8tap_hv_tbl) - 640b
- .hword L(\type\()_8tap_hv_tbl) - 320b
- .hword L(\type\()_8tap_hv_tbl) - 160b
- .hword L(\type\()_8tap_hv_tbl) - 80b
- .hword L(\type\()_8tap_hv_tbl) - 40b
- .hword L(\type\()_8tap_hv_tbl) - 20b
+L(\type\()_\taps\()_hv_tbl):
+ .hword L(\type\()_\taps\()_hv_tbl) - 1280b
+ .hword L(\type\()_\taps\()_hv_tbl) - 640b
+ .hword L(\type\()_\taps\()_hv_tbl) - 320b
+ .hword L(\type\()_\taps\()_hv_tbl) - 160b
+ .hword L(\type\()_\taps\()_hv_tbl) - 80b
+ .hword L(\type\()_\taps\()_hv_tbl) - 40b
+ .hword L(\type\()_\taps\()_hv_tbl) - 20b
.hword 0
endfunc
+.endm
+.macro filter_bilin_fn type, dst, d_strd, src, s_strd, w, h, mx, xmx, my, xmy, ds2, sr2, shift_hv
function \type\()_bilin_8bpc_neon, export=1
dup v1.16b, \mx
dup v3.16b, \my
@@ -2987,8 +3160,34 @@ L(\type\()_bilin_hv_tbl):
endfunc
.endm
-filter_fn put, x0, x1, x2, x3, w4, w5, w6, x6, w7, x7, x8, x9, 10
-filter_fn prep, x0, x7, x1, x2, w3, w4, w5, x5, w6, x6, x8, x9, 6
+make_8tap_fn put, regular_sharp, REGULAR, SHARP, 8tap
+make_8tap_fn put, smooth_sharp, SMOOTH, SHARP, 8tap
+make_8tap_fn put, sharp, SHARP, SHARP, 8tap
+make_8tap_fn put, sharp_regular, SHARP, REGULAR, 8tap
+make_8tap_fn put, sharp_smooth, SHARP, SMOOTH, 8tap
+filter_fn put, x0, x1, x2, x3, w4, w5, w6, x6, w7, x7, x8, x9, 10, 8tap
+
+make_8tap_fn put, regular, REGULAR, REGULAR, 6tap
+make_8tap_fn put, regular_smooth, REGULAR, SMOOTH, 6tap
+make_8tap_fn put, smooth, SMOOTH, SMOOTH, 6tap
+make_8tap_fn put, smooth_regular, SMOOTH, REGULAR, 6tap
+filter_fn put, x0, x1, x2, x3, w4, w5, w6, x6, w7, x7, x8, x9, 10, 6tap
+filter_bilin_fn put, x0, x1, x2, x3, w4, w5, w6, x6, w7, x7, x8, x9, 10
+
+make_8tap_fn prep, regular_sharp, REGULAR, SHARP, 8tap
+make_8tap_fn prep, smooth_sharp, SMOOTH, SHARP, 8tap
+make_8tap_fn prep, sharp, SHARP, SHARP, 8tap
+make_8tap_fn prep, sharp_regular, SHARP, REGULAR, 8tap
+make_8tap_fn prep, sharp_smooth, SHARP, SMOOTH, 8tap
+filter_fn prep, x0, x7, x1, x2, w3, w4, w5, x5, w6, x6, x8, x9, 6, 8tap
+
+make_8tap_fn prep, regular, REGULAR, REGULAR, 6tap
+make_8tap_fn prep, regular_smooth, REGULAR, SMOOTH, 6tap
+make_8tap_fn prep, smooth, SMOOTH, SMOOTH, 6tap
+make_8tap_fn prep, smooth_regular, SMOOTH, REGULAR, 6tap
+filter_fn prep, x0, x7, x1, x2, w3, w4, w5, x5, w6, x6, x8, x9, 6, 6tap
+filter_bilin_fn prep, x0, x7, x1, x2, w3, w4, w5, x5, w6, x6, x8, x9, 6
+
.macro load_filter_row dst, src, inc
asr w13, \src, #10
diff --git a/third_party/dav1d/src/arm/64/mc16.S b/third_party/dav1d/src/arm/64/mc16.S
index 1bfb12ebb3..576fab158a 100644
--- a/third_party/dav1d/src/arm/64/mc16.S
+++ b/third_party/dav1d/src/arm/64/mc16.S
@@ -1374,19 +1374,35 @@ endfunc
sub \r3\wd, \r3\wd, \c\wd
.endif
.endm
-.macro smull_smlal_4 d, s0, s1, s2, s3
+.macro smull_smlal_4tap d, s0, s1, s2, s3
smull \d\().4s, \s0\().4h, v0.h[0]
smlal \d\().4s, \s1\().4h, v0.h[1]
smlal \d\().4s, \s2\().4h, v0.h[2]
smlal \d\().4s, \s3\().4h, v0.h[3]
.endm
-.macro smull2_smlal2_4 d, s0, s1, s2, s3
+.macro smull2_smlal2_4tap d, s0, s1, s2, s3
smull2 \d\().4s, \s0\().8h, v0.h[0]
smlal2 \d\().4s, \s1\().8h, v0.h[1]
smlal2 \d\().4s, \s2\().8h, v0.h[2]
smlal2 \d\().4s, \s3\().8h, v0.h[3]
.endm
-.macro smull_smlal_8 d, s0, s1, s2, s3, s4, s5, s6, s7
+.macro smull_smlal_6tap d, s0, s1, s2, s3, s4, s5, s6, s7
+ smull \d\().4s, \s1\().4h, v0.h[1]
+ smlal \d\().4s, \s2\().4h, v0.h[2]
+ smlal \d\().4s, \s3\().4h, v0.h[3]
+ smlal \d\().4s, \s4\().4h, v0.h[4]
+ smlal \d\().4s, \s5\().4h, v0.h[5]
+ smlal \d\().4s, \s6\().4h, v0.h[6]
+.endm
+.macro smull2_smlal2_6tap d, s0, s1, s2, s3, s4, s5, s6, s7
+ smull2 \d\().4s, \s1\().8h, v0.h[1]
+ smlal2 \d\().4s, \s2\().8h, v0.h[2]
+ smlal2 \d\().4s, \s3\().8h, v0.h[3]
+ smlal2 \d\().4s, \s4\().8h, v0.h[4]
+ smlal2 \d\().4s, \s5\().8h, v0.h[5]
+ smlal2 \d\().4s, \s6\().8h, v0.h[6]
+.endm
+.macro smull_smlal_8tap d, s0, s1, s2, s3, s4, s5, s6, s7
smull \d\().4s, \s0\().4h, v0.h[0]
smlal \d\().4s, \s1\().4h, v0.h[1]
smlal \d\().4s, \s2\().4h, v0.h[2]
@@ -1396,7 +1412,7 @@ endfunc
smlal \d\().4s, \s6\().4h, v0.h[6]
smlal \d\().4s, \s7\().4h, v0.h[7]
.endm
-.macro smull2_smlal2_8 d, s0, s1, s2, s3, s4, s5, s6, s7
+.macro smull2_smlal2_8tap d, s0, s1, s2, s3, s4, s5, s6, s7
smull2 \d\().4s, \s0\().8h, v0.h[0]
smlal2 \d\().4s, \s1\().8h, v0.h[1]
smlal2 \d\().4s, \s2\().8h, v0.h[2]
@@ -1499,11 +1515,11 @@ endfunc
st1 {\r0\().8h, \r1\().8h}, [\dst], \strd
.endm
-.macro make_8tap_fn op, type, type_h, type_v
+.macro make_8tap_fn op, type, type_h, type_v, taps
function \op\()_8tap_\type\()_16bpc_neon, export=1
mov w9, \type_h
mov w10, \type_v
- b \op\()_8tap_neon
+ b \op\()_\taps\()_neon
endfunc
.endm
@@ -1512,18 +1528,8 @@ endfunc
#define SMOOTH ((1*15<<7)|4*15)
#define SHARP ((2*15<<7)|3*15)
-.macro filter_fn type, dst, d_strd, src, s_strd, w, h, mx, xmx, my, xmy, bdmax, ds2, sr2
-make_8tap_fn \type, regular, REGULAR, REGULAR
-make_8tap_fn \type, regular_smooth, REGULAR, SMOOTH
-make_8tap_fn \type, regular_sharp, REGULAR, SHARP
-make_8tap_fn \type, smooth, SMOOTH, SMOOTH
-make_8tap_fn \type, smooth_regular, SMOOTH, REGULAR
-make_8tap_fn \type, smooth_sharp, SMOOTH, SHARP
-make_8tap_fn \type, sharp, SHARP, SHARP
-make_8tap_fn \type, sharp_regular, SHARP, REGULAR
-make_8tap_fn \type, sharp_smooth, SHARP, SMOOTH
-
-function \type\()_8tap_neon
+.macro filter_fn type, dst, d_strd, src, s_strd, w, h, mx, xmx, my, xmy, bdmax, ds2, sr2, taps
+function \type\()_\taps\()_neon
.ifc \bdmax, w8
ldr w8, [sp]
.endif
@@ -1547,12 +1553,12 @@ function \type\()_8tap_neon
add w13, w12, \bdmax // 6 + intermediate_bits
sub w12, w12, \bdmax // 6 - intermediate_bits
movrel x11, X(mc_subpel_filters), -8
- b.ne L(\type\()_8tap_h)
+ b.ne L(\type\()_\taps\()_h)
tst \my, #(0x7f << 14)
- b.ne L(\type\()_8tap_v)
+ b.ne L(\type\()_\taps\()_v)
b \type\()_neon
-L(\type\()_8tap_h):
+L(\type\()_\taps\()_h):
cmp \w, #4
ubfx w10, \mx, #7, #7
and \mx, \mx, #0x7f
@@ -1561,9 +1567,9 @@ L(\type\()_8tap_h):
4:
tst \my, #(0x7f << 14)
add \xmx, x11, \mx, uxtw #3
- b.ne L(\type\()_8tap_hv)
+ b.ne L(\type\()_\taps\()_hv)
- adr x10, L(\type\()_8tap_h_tbl)
+ adr x10, L(\type\()_\taps\()_h_tbl)
dup v30.4s, w12 // 6 - intermediate_bits
ldrh w9, [x10, x9, lsl #1]
neg v30.4s, v30.4s // -(6-intermediate_bits)
@@ -1682,6 +1688,22 @@ L(\type\()_8tap_h):
mov \mx, \w
8:
+.ifc \taps, 6tap
+ ext v24.16b, v16.16b, v17.16b, #2
+ ext v25.16b, v20.16b, v21.16b, #2
+ smull v18.4s, v24.4h, v0.h[1]
+ smull2 v19.4s, v24.8h, v0.h[1]
+ smull v22.4s, v25.4h, v0.h[1]
+ smull2 v23.4s, v25.8h, v0.h[1]
+.irpc i, 23456
+ ext v24.16b, v16.16b, v17.16b, #(2*\i)
+ ext v25.16b, v20.16b, v21.16b, #(2*\i)
+ smlal v18.4s, v24.4h, v0.h[\i]
+ smlal2 v19.4s, v24.8h, v0.h[\i]
+ smlal v22.4s, v25.4h, v0.h[\i]
+ smlal2 v23.4s, v25.8h, v0.h[\i]
+.endr
+.else // 8tap
smull v18.4s, v16.4h, v0.h[0]
smull2 v19.4s, v16.8h, v0.h[0]
smull v22.4s, v20.4h, v0.h[0]
@@ -1694,6 +1716,7 @@ L(\type\()_8tap_h):
smlal v22.4s, v25.4h, v0.h[\i]
smlal2 v23.4s, v25.8h, v0.h[\i]
.endr
+.endif
subs \mx, \mx, #8
srshl v18.4s, v18.4s, v30.4s // -(6-intermediate_bits)
srshl v19.4s, v19.4s, v30.4s // -(6-intermediate_bits)
@@ -1734,18 +1757,18 @@ L(\type\()_8tap_h):
b.gt 81b
ret
-L(\type\()_8tap_h_tbl):
- .hword L(\type\()_8tap_h_tbl) - 1280b
- .hword L(\type\()_8tap_h_tbl) - 640b
- .hword L(\type\()_8tap_h_tbl) - 320b
- .hword L(\type\()_8tap_h_tbl) - 160b
- .hword L(\type\()_8tap_h_tbl) - 80b
- .hword L(\type\()_8tap_h_tbl) - 40b
- .hword L(\type\()_8tap_h_tbl) - 20b
+L(\type\()_\taps\()_h_tbl):
+ .hword L(\type\()_\taps\()_h_tbl) - 1280b
+ .hword L(\type\()_\taps\()_h_tbl) - 640b
+ .hword L(\type\()_\taps\()_h_tbl) - 320b
+ .hword L(\type\()_\taps\()_h_tbl) - 160b
+ .hword L(\type\()_\taps\()_h_tbl) - 80b
+ .hword L(\type\()_\taps\()_h_tbl) - 40b
+ .hword L(\type\()_\taps\()_h_tbl) - 20b
.hword 0
-L(\type\()_8tap_v):
+L(\type\()_\taps\()_v):
cmp \h, #4
ubfx w10, \my, #7, #7
and \my, \my, #0x7f
@@ -1758,7 +1781,7 @@ L(\type\()_8tap_v):
dup v30.4s, w12 // 6 - intermediate_bits
movi v29.8h, #(PREP_BIAS >> 8), lsl #8
.endif
- adr x10, L(\type\()_8tap_v_tbl)
+ adr x10, L(\type\()_\taps\()_v_tbl)
ldrh w9, [x10, x9, lsl #1]
.ifc \type, prep
neg v30.4s, v30.4s // -(6-intermediate_bits)
@@ -1785,7 +1808,7 @@ L(\type\()_8tap_v):
load_s \src, \sr2, \s_strd, v1, v2, v3, v4, v5
interleave_1_s v1, v2, v3, v4, v5
b.gt 24f
- smull_smlal_4 v6, v1, v2, v3, v4
+ smull_smlal_4tap v6, v1, v2, v3, v4
sqrshrun_h 6, v6
umin_h v31, .8h, v6
st_s \d_strd, v6, 2
@@ -1794,8 +1817,8 @@ L(\type\()_8tap_v):
24: // 2x4 v
load_s \sr2, \src, \s_strd, v6, v7
interleave_1_s v5, v6, v7
- smull_smlal_4 v16, v1, v2, v3, v4
- smull_smlal_4 v17, v3, v4, v5, v6
+ smull_smlal_4tap v16, v1, v2, v3, v4
+ smull_smlal_4tap v17, v3, v4, v5, v6
sqrshrun_h 6, v16, v17
umin_h v31, .8h, v16
st_s \d_strd, v16, 4
@@ -1817,8 +1840,8 @@ L(\type\()_8tap_v):
subs \h, \h, #4
load_s \sr2, \src, \s_strd, v16, v17, v18, v19
interleave_1_s v7, v16, v17, v18, v19
- smull_smlal_8 v24, v1, v2, v3, v4, v5, v6, v7, v16
- smull_smlal_8 v25, v3, v4, v5, v6, v7, v16, v17, v18
+ smull_smlal_\taps v24, v1, v2, v3, v4, v5, v6, v7, v16
+ smull_smlal_\taps v25, v3, v4, v5, v6, v7, v16, v17, v18
sqrshrun_h 6, v24, v25
umin_h v31, .8h, v24
st_s \d_strd, v24, 4
@@ -1836,7 +1859,7 @@ L(\type\()_8tap_v):
26:
load_s \sr2, \src, \s_strd, v16, v17
interleave_1_s v7, v16, v17
- smull_smlal_8 v24, v1, v2, v3, v4, v5, v6, v7, v16
+ smull_smlal_\taps v24, v1, v2, v3, v4, v5, v6, v7, v16
sqrshrun_h 6, v24
umin_h v31, .4h, v24
st_s \d_strd, v24, 2
@@ -1860,13 +1883,13 @@ L(\type\()_8tap_v):
sxtl v0.8h, v0.8b
load_4h \src, \sr2, \s_strd, v1, v2, v3, v4, v5
- smull_smlal_4 v6, v1, v2, v3, v4
- smull_smlal_4 v7, v2, v3, v4, v5
+ smull_smlal_4tap v6, v1, v2, v3, v4
+ smull_smlal_4tap v7, v2, v3, v4, v5
shift_store_4 \type, \d_strd, v6, v7
b.le 0f
load_4h \sr2, \src, \s_strd, v6, v7
- smull_smlal_4 v1, v3, v4, v5, v6
- smull_smlal_4 v2, v4, v5, v6, v7
+ smull_smlal_4tap v1, v3, v4, v5, v6
+ smull_smlal_4tap v2, v4, v5, v6, v7
shift_store_4 \type, \d_strd, v1, v2
0:
ret
@@ -1885,10 +1908,10 @@ L(\type\()_8tap_v):
48:
subs \h, \h, #4
load_4h \sr2, \src, \s_strd, v23, v24, v25, v26
- smull_smlal_8 v1, v16, v17, v18, v19, v20, v21, v22, v23
- smull_smlal_8 v2, v17, v18, v19, v20, v21, v22, v23, v24
- smull_smlal_8 v3, v18, v19, v20, v21, v22, v23, v24, v25
- smull_smlal_8 v4, v19, v20, v21, v22, v23, v24, v25, v26
+ smull_smlal_\taps v1, v16, v17, v18, v19, v20, v21, v22, v23
+ smull_smlal_\taps v2, v17, v18, v19, v20, v21, v22, v23, v24
+ smull_smlal_\taps v3, v18, v19, v20, v21, v22, v23, v24, v25
+ smull_smlal_\taps v4, v19, v20, v21, v22, v23, v24, v25, v26
shift_store_4 \type, \d_strd, v1, v2, v3, v4
b.le 0f
cmp \h, #2
@@ -1903,8 +1926,8 @@ L(\type\()_8tap_v):
b 48b
46:
load_4h \sr2, \src, \s_strd, v23, v24
- smull_smlal_8 v1, v16, v17, v18, v19, v20, v21, v22, v23
- smull_smlal_8 v2, v17, v18, v19, v20, v21, v22, v23, v24
+ smull_smlal_\taps v1, v16, v17, v18, v19, v20, v21, v22, v23
+ smull_smlal_\taps v2, v17, v18, v19, v20, v21, v22, v23, v24
shift_store_4 \type, \d_strd, v1, v2
0:
ret
@@ -1925,17 +1948,17 @@ L(\type\()_8tap_v):
sxtl v0.8h, v0.8b
load_8h \src, \sr2, \s_strd, v1, v2, v3, v4, v5
- smull_smlal_4 v16, v1, v2, v3, v4
- smull2_smlal2_4 v17, v1, v2, v3, v4
- smull_smlal_4 v18, v2, v3, v4, v5
- smull2_smlal2_4 v19, v2, v3, v4, v5
+ smull_smlal_4tap v16, v1, v2, v3, v4
+ smull2_smlal2_4tap v17, v1, v2, v3, v4
+ smull_smlal_4tap v18, v2, v3, v4, v5
+ smull2_smlal2_4tap v19, v2, v3, v4, v5
shift_store_8 \type, \d_strd, v16, v17, v18, v19
b.le 0f
load_8h \sr2, \src, \s_strd, v6, v7
- smull_smlal_4 v16, v3, v4, v5, v6
- smull2_smlal2_4 v17, v3, v4, v5, v6
- smull_smlal_4 v18, v4, v5, v6, v7
- smull2_smlal2_4 v19, v4, v5, v6, v7
+ smull_smlal_4tap v16, v3, v4, v5, v6
+ smull2_smlal2_4tap v17, v3, v4, v5, v6
+ smull_smlal_4tap v18, v4, v5, v6, v7
+ smull2_smlal2_4tap v19, v4, v5, v6, v7
shift_store_8 \type, \d_strd, v16, v17, v18, v19
0:
ret
@@ -1962,18 +1985,18 @@ L(\type\()_8tap_v):
88:
subs \h, \h, #2
load_8h \sr2, \src, \s_strd, v23, v24
- smull_smlal_8 v1, v16, v17, v18, v19, v20, v21, v22, v23
- smull2_smlal2_8 v2, v16, v17, v18, v19, v20, v21, v22, v23
- smull_smlal_8 v3, v17, v18, v19, v20, v21, v22, v23, v24
- smull2_smlal2_8 v4, v17, v18, v19, v20, v21, v22, v23, v24
+ smull_smlal_\taps v1, v16, v17, v18, v19, v20, v21, v22, v23
+ smull2_smlal2_\taps v2, v16, v17, v18, v19, v20, v21, v22, v23
+ smull_smlal_\taps v3, v17, v18, v19, v20, v21, v22, v23, v24
+ smull2_smlal2_\taps v4, v17, v18, v19, v20, v21, v22, v23, v24
shift_store_8 \type, \d_strd, v1, v2, v3, v4
b.le 9f
subs \h, \h, #2
load_8h \sr2, \src, \s_strd, v25, v26
- smull_smlal_8 v1, v18, v19, v20, v21, v22, v23, v24, v25
- smull2_smlal2_8 v2, v18, v19, v20, v21, v22, v23, v24, v25
- smull_smlal_8 v3, v19, v20, v21, v22, v23, v24, v25, v26
- smull2_smlal2_8 v4, v19, v20, v21, v22, v23, v24, v25, v26
+ smull_smlal_\taps v1, v18, v19, v20, v21, v22, v23, v24, v25
+ smull2_smlal2_\taps v2, v18, v19, v20, v21, v22, v23, v24, v25
+ smull_smlal_\taps v3, v19, v20, v21, v22, v23, v24, v25, v26
+ smull2_smlal2_\taps v4, v19, v20, v21, v22, v23, v24, v25, v26
shift_store_8 \type, \d_strd, v1, v2, v3, v4
b.le 9f
mov v16.16b, v20.16b
@@ -2013,10 +2036,10 @@ L(\type\()_8tap_v):
16:
load_16h \src, \src, \s_strd, v22, v23
subs \h, \h, #1
- smull_smlal_4 v1, v16, v18, v20, v22
- smull2_smlal2_4 v2, v16, v18, v20, v22
- smull_smlal_4 v3, v17, v19, v21, v23
- smull2_smlal2_4 v4, v17, v19, v21, v23
+ smull_smlal_4tap v1, v16, v18, v20, v22
+ smull2_smlal2_4tap v2, v16, v18, v20, v22
+ smull_smlal_4tap v3, v17, v19, v21, v23
+ smull2_smlal2_4tap v4, v17, v19, v21, v23
shift_store_16 \type, \d_strd, x0, v1, v2, v3, v4
b.le 0f
mov v16.16b, v18.16b
@@ -2029,17 +2052,17 @@ L(\type\()_8tap_v):
0:
ret
-L(\type\()_8tap_v_tbl):
- .hword L(\type\()_8tap_v_tbl) - 1280b
- .hword L(\type\()_8tap_v_tbl) - 640b
- .hword L(\type\()_8tap_v_tbl) - 320b
- .hword L(\type\()_8tap_v_tbl) - 160b
- .hword L(\type\()_8tap_v_tbl) - 80b
- .hword L(\type\()_8tap_v_tbl) - 40b
- .hword L(\type\()_8tap_v_tbl) - 20b
+L(\type\()_\taps\()_v_tbl):
+ .hword L(\type\()_\taps\()_v_tbl) - 1280b
+ .hword L(\type\()_\taps\()_v_tbl) - 640b
+ .hword L(\type\()_\taps\()_v_tbl) - 320b
+ .hword L(\type\()_\taps\()_v_tbl) - 160b
+ .hword L(\type\()_\taps\()_v_tbl) - 80b
+ .hword L(\type\()_\taps\()_v_tbl) - 40b
+ .hword L(\type\()_\taps\()_v_tbl) - 20b
.hword 0
-L(\type\()_8tap_hv):
+L(\type\()_\taps\()_hv):
cmp \h, #4
ubfx w10, \my, #7, #7
and \my, \my, #0x7f
@@ -2048,7 +2071,7 @@ L(\type\()_8tap_hv):
4:
add \xmy, x11, \my, uxtw #3
- adr x10, L(\type\()_8tap_hv_tbl)
+ adr x10, L(\type\()_\taps\()_hv_tbl)
dup v30.4s, w12 // 6 - intermediate_bits
ldrh w9, [x10, x9, lsl #1]
neg v30.4s, v30.4s // -(6-intermediate_bits)
@@ -2089,7 +2112,7 @@ L(\type\()_8tap_hv):
addp v27.4s, v27.4s, v28.4s
addp v16.4s, v27.4s, v27.4s
srshl v16.2s, v16.2s, v30.2s // -(6-intermediate_bits)
- bl L(\type\()_8tap_filter_2)
+ bl L(\type\()_\taps\()_filter_2)
// The intermediates from the horizontal pass fit in 16 bit without
// any bias; we could just as well keep them as .4s, but narrowing
// them to .4h gives a significant speedup on out of order cores
@@ -2100,7 +2123,7 @@ L(\type\()_8tap_hv):
mov v17.8b, v24.8b
2:
- bl L(\type\()_8tap_filter_2)
+ bl L(\type\()_\taps\()_filter_2)
ext v18.8b, v17.8b, v24.8b, #4
smull v2.4s, v16.4h, v1.h[0]
@@ -2143,20 +2166,28 @@ L(\type\()_8tap_hv):
// them to .4h gives a significant speedup on out of order cores
// (at the cost of a smaller slowdown on in-order cores such as A53).
- bl L(\type\()_8tap_filter_2)
+ bl L(\type\()_\taps\()_filter_2)
xtn v16.4h, v16.4s
trn1 v16.2s, v16.2s, v24.2s
mov v17.8b, v24.8b
- bl L(\type\()_8tap_filter_2)
+ bl L(\type\()_\taps\()_filter_2)
ext v18.8b, v17.8b, v24.8b, #4
mov v19.8b, v24.8b
- bl L(\type\()_8tap_filter_2)
+ bl L(\type\()_\taps\()_filter_2)
ext v20.8b, v19.8b, v24.8b, #4
mov v21.8b, v24.8b
28:
- bl L(\type\()_8tap_filter_2)
+ bl L(\type\()_\taps\()_filter_2)
ext v22.8b, v21.8b, v24.8b, #4
+.ifc \taps, 6tap
+ smull v3.4s, v17.4h, v1.h[1]
+ smlal v3.4s, v18.4h, v1.h[2]
+ smlal v3.4s, v19.4h, v1.h[3]
+ smlal v3.4s, v20.4h, v1.h[4]
+ smlal v3.4s, v21.4h, v1.h[5]
+ smlal v3.4s, v22.4h, v1.h[6]
+.else // 8tap
smull v3.4s, v16.4h, v1.h[0]
smlal v3.4s, v17.4h, v1.h[1]
smlal v3.4s, v18.4h, v1.h[2]
@@ -2165,6 +2196,7 @@ L(\type\()_8tap_hv):
smlal v3.4s, v21.4h, v1.h[5]
smlal v3.4s, v22.4h, v1.h[6]
smlal v3.4s, v24.4h, v1.h[7]
+.endif
srshl v3.4s, v3.4s, v29.4s // -(6+intermediate_bits)
sqxtun v3.4h, v3.4s
@@ -2184,7 +2216,7 @@ L(\type\()_8tap_hv):
0:
ret x15
-L(\type\()_8tap_filter_2):
+L(\type\()_\taps\()_filter_2):
ld1 {v25.8h}, [\sr2], \s_strd
ld1 {v27.8h}, [\src], \s_strd
ext v26.16b, v25.16b, v25.16b, #2
@@ -2234,12 +2266,12 @@ L(\type\()_8tap_filter_2):
// (at the cost of a smaller slowdown on in-order cores such as A53).
xtn v16.4h, v16.4s
- bl L(\type\()_8tap_filter_4)
+ bl L(\type\()_\taps\()_filter_4)
mov v17.8b, v24.8b
mov v18.8b, v25.8b
4:
- bl L(\type\()_8tap_filter_4)
+ bl L(\type\()_\taps\()_filter_4)
smull v2.4s, v16.4h, v1.h[0]
smlal v2.4s, v17.4h, v1.h[1]
smlal v2.4s, v18.4h, v1.h[2]
@@ -2272,8 +2304,13 @@ L(\type\()_8tap_filter_2):
480: // 4x8, 4x16, 4x32 hv
ld1 {v1.8b}, [\xmy]
sub \src, \src, #2
+.ifc \taps, 6tap
+ sub \sr2, \src, \s_strd
+ sub \src, \src, \s_strd, lsl #1
+.else
sub \sr2, \src, \s_strd, lsl #1
sub \src, \sr2, \s_strd
+.endif
add \ds2, \dst, \d_strd
lsl \s_strd, \s_strd, #1
lsl \d_strd, \d_strd, #1
@@ -2294,20 +2331,38 @@ L(\type\()_8tap_filter_2):
// any bias; we could just as well keep them as .4s, but narrowing
// them to .4h gives a significant speedup on out of order cores
// (at the cost of a smaller slowdown on in-order cores such as A53).
+.ifc \taps, 6tap
+ xtn v18.4h, v16.4s
+.else
xtn v16.4h, v16.4s
- bl L(\type\()_8tap_filter_4)
+ bl L(\type\()_\taps\()_filter_4)
mov v17.8b, v24.8b
mov v18.8b, v25.8b
- bl L(\type\()_8tap_filter_4)
+.endif
+ bl L(\type\()_\taps\()_filter_4)
mov v19.8b, v24.8b
mov v20.8b, v25.8b
- bl L(\type\()_8tap_filter_4)
+ bl L(\type\()_\taps\()_filter_4)
mov v21.8b, v24.8b
mov v22.8b, v25.8b
48:
- bl L(\type\()_8tap_filter_4)
+ bl L(\type\()_\taps\()_filter_4)
+.ifc \taps, 6tap
+ smull v3.4s, v18.4h, v1.h[1]
+ smlal v3.4s, v19.4h, v1.h[2]
+ smlal v3.4s, v20.4h, v1.h[3]
+ smlal v3.4s, v21.4h, v1.h[4]
+ smlal v3.4s, v22.4h, v1.h[5]
+ smlal v3.4s, v24.4h, v1.h[6]
+ smull v4.4s, v19.4h, v1.h[1]
+ smlal v4.4s, v20.4h, v1.h[2]
+ smlal v4.4s, v21.4h, v1.h[3]
+ smlal v4.4s, v22.4h, v1.h[4]
+ smlal v4.4s, v24.4h, v1.h[5]
+ smlal v4.4s, v25.4h, v1.h[6]
+.else // 8tap
smull v3.4s, v16.4h, v1.h[0]
smlal v3.4s, v17.4h, v1.h[1]
smlal v3.4s, v18.4h, v1.h[2]
@@ -2324,6 +2379,7 @@ L(\type\()_8tap_filter_2):
smlal v4.4s, v22.4h, v1.h[5]
smlal v4.4s, v24.4h, v1.h[6]
smlal v4.4s, v25.4h, v1.h[7]
+.endif
.ifc \type, put
srshl v3.4s, v3.4s, v29.4s // -(6+intermediate_bits)
srshl v4.4s, v4.4s, v29.4s // -(6+intermediate_bits)
@@ -2339,8 +2395,10 @@ L(\type\()_8tap_filter_2):
st1 {v3.d}[0], [\dst], \d_strd
st1 {v3.d}[1], [\ds2], \d_strd
b.le 0f
+.ifc \taps, 8tap
mov v16.8b, v18.8b
mov v17.8b, v19.8b
+.endif
mov v18.8b, v20.8b
mov v19.8b, v21.8b
mov v20.8b, v22.8b
@@ -2350,7 +2408,7 @@ L(\type\()_8tap_filter_2):
0:
ret x15
-L(\type\()_8tap_filter_4):
+L(\type\()_\taps\()_filter_4):
ld1 {v24.8h}, [\sr2], \s_strd
ld1 {v25.8h}, [\src], \s_strd
ext v26.16b, v24.16b, v24.16b, #2
@@ -2411,14 +2469,14 @@ L(\type\()_8tap_filter_4):
// and conserves register space (no need to clobber v8-v15).
uzp1 v16.8h, v24.8h, v25.8h // Same as xtn, xtn2
- bl L(\type\()_8tap_filter_8)
+ bl L(\type\()_\taps\()_filter_8)
mov v17.16b, v23.16b
mov v18.16b, v24.16b
8:
smull v2.4s, v16.4h, v1.h[0]
smull2 v3.4s, v16.8h, v1.h[0]
- bl L(\type\()_8tap_filter_8)
+ bl L(\type\()_\taps\()_filter_8)
smull v4.4s, v17.4h, v1.h[0]
smull2 v5.4s, v17.8h, v1.h[0]
smlal v2.4s, v17.4h, v1.h[1]
@@ -2480,7 +2538,9 @@ L(\type\()_8tap_filter_4):
ld1 {v0.8b}, [\xmx]
ld1 {v1.8b}, [\xmy]
sub \src, \src, #6
+.ifc \taps, 8tap
sub \src, \src, \s_strd
+.endif
sub \src, \src, \s_strd, lsl #1
sxtl v0.8h, v0.8b
sxtl v1.8h, v1.8b
@@ -2494,6 +2554,16 @@ L(\type\()_8tap_filter_4):
lsl \s_strd, \s_strd, #1
ld1 {v27.8h, v28.8h}, [\src], \s_strd
+.ifc \taps, 6tap
+ ext v26.16b, v27.16b, v28.16b, #2
+ smull v24.4s, v26.4h, v0.h[1]
+ smull2 v25.4s, v26.8h, v0.h[1]
+.irpc i, 23456
+ ext v26.16b, v27.16b, v28.16b, #(2*\i)
+ smlal v24.4s, v26.4h, v0.h[\i]
+ smlal2 v25.4s, v26.8h, v0.h[\i]
+.endr
+.else // 8tap
smull v24.4s, v27.4h, v0.h[0]
smull2 v25.4s, v27.8h, v0.h[0]
.irpc i, 1234567
@@ -2501,6 +2571,7 @@ L(\type\()_8tap_filter_4):
smlal v24.4s, v26.4h, v0.h[\i]
smlal2 v25.4s, v26.8h, v0.h[\i]
.endr
+.endif
srshl v24.4s, v24.4s, v30.4s // -(6-intermediate_bits)
srshl v25.4s, v25.4s, v30.4s // -(6-intermediate_bits)
// The intermediates from the horizontal pass fit in 16 bit without
@@ -2508,22 +2579,53 @@ L(\type\()_8tap_filter_4):
// them to .4h gives a significant speedup on out of order cores
// (at the cost of a smaller slowdown on in-order cores such as A53),
// and conserves register space (no need to clobber v8-v15).
+.ifc \taps, 6tap
+ uzp1 v18.8h, v24.8h, v25.8h // Same as xtn, xtn2
+.else
uzp1 v16.8h, v24.8h, v25.8h // Same as xtn, xtn2
- bl L(\type\()_8tap_filter_8)
+ bl L(\type\()_\taps\()_filter_8)
mov v17.16b, v23.16b
mov v18.16b, v24.16b
- bl L(\type\()_8tap_filter_8)
+.endif
+ bl L(\type\()_\taps\()_filter_8)
mov v19.16b, v23.16b
mov v20.16b, v24.16b
- bl L(\type\()_8tap_filter_8)
+ bl L(\type\()_\taps\()_filter_8)
mov v21.16b, v23.16b
mov v22.16b, v24.16b
88:
+.ifc \taps, 6tap
+ smull v2.4s, v18.4h, v1.h[1]
+ smull2 v3.4s, v18.8h, v1.h[1]
+ bl L(\type\()_\taps\()_filter_8)
+ smull v4.4s, v19.4h, v1.h[1]
+ smull2 v5.4s, v19.8h, v1.h[1]
+ smlal v2.4s, v19.4h, v1.h[2]
+ smlal2 v3.4s, v19.8h, v1.h[2]
+ smlal v4.4s, v20.4h, v1.h[2]
+ smlal2 v5.4s, v20.8h, v1.h[2]
+ smlal v2.4s, v20.4h, v1.h[3]
+ smlal2 v3.4s, v20.8h, v1.h[3]
+ smlal v4.4s, v21.4h, v1.h[3]
+ smlal2 v5.4s, v21.8h, v1.h[3]
+ smlal v2.4s, v21.4h, v1.h[4]
+ smlal2 v3.4s, v21.8h, v1.h[4]
+ smlal v4.4s, v22.4h, v1.h[4]
+ smlal2 v5.4s, v22.8h, v1.h[4]
+ smlal v2.4s, v22.4h, v1.h[5]
+ smlal2 v3.4s, v22.8h, v1.h[5]
+ smlal v4.4s, v23.4h, v1.h[5]
+ smlal2 v5.4s, v23.8h, v1.h[5]
+ smlal v2.4s, v23.4h, v1.h[6]
+ smlal2 v3.4s, v23.8h, v1.h[6]
+ smlal v4.4s, v24.4h, v1.h[6]
+ smlal2 v5.4s, v24.8h, v1.h[6]
+.else // 8tap
smull v2.4s, v16.4h, v1.h[0]
smull2 v3.4s, v16.8h, v1.h[0]
- bl L(\type\()_8tap_filter_8)
+ bl L(\type\()_\taps\()_filter_8)
smull v4.4s, v17.4h, v1.h[0]
smull2 v5.4s, v17.8h, v1.h[0]
smlal v2.4s, v17.4h, v1.h[1]
@@ -2554,6 +2656,7 @@ L(\type\()_8tap_filter_4):
smlal2 v3.4s, v23.8h, v1.h[7]
smlal v4.4s, v24.4h, v1.h[7]
smlal2 v5.4s, v24.8h, v1.h[7]
+.endif
.ifc \type, put
srshl v2.4s, v2.4s, v29.4s // -(6+intermediate_bits)
srshl v3.4s, v3.4s, v29.4s // -(6+intermediate_bits)
@@ -2577,8 +2680,10 @@ L(\type\()_8tap_filter_4):
st1 {v2.8h}, [\dst], \d_strd
st1 {v3.8h}, [\ds2], \d_strd
b.le 9f
+.ifc \taps, 8tap
mov v16.16b, v18.16b
mov v17.16b, v19.16b
+.endif
mov v18.16b, v20.16b
mov v19.16b, v21.16b
mov v20.16b, v22.16b
@@ -2596,13 +2701,32 @@ L(\type\()_8tap_filter_4):
mov \h, \my
add \src, \src, #16
add \dst, \dst, #16
+.ifc \taps, 6tap
+ add \src, \src, \s_strd, lsl #1
+.endif
b 168b
0:
ret x15
-L(\type\()_8tap_filter_8):
+L(\type\()_\taps\()_filter_8):
ld1 {v4.8h, v5.8h}, [\sr2], \s_strd
ld1 {v6.8h, v7.8h}, [\src], \s_strd
+.ifc \taps, 6tap
+ ext v23.16b, v4.16b, v5.16b, #2
+ ext v24.16b, v6.16b, v7.16b, #2
+ smull v25.4s, v23.4h, v0.h[1]
+ smull2 v26.4s, v23.8h, v0.h[1]
+ smull v27.4s, v24.4h, v0.h[1]
+ smull2 v28.4s, v24.8h, v0.h[1]
+.irpc i, 23456
+ ext v23.16b, v4.16b, v5.16b, #(2*\i)
+ ext v24.16b, v6.16b, v7.16b, #(2*\i)
+ smlal v25.4s, v23.4h, v0.h[\i]
+ smlal2 v26.4s, v23.8h, v0.h[\i]
+ smlal v27.4s, v24.4h, v0.h[\i]
+ smlal2 v28.4s, v24.8h, v0.h[\i]
+.endr
+.else // 8tap
smull v25.4s, v4.4h, v0.h[0]
smull2 v26.4s, v4.8h, v0.h[0]
smull v27.4s, v6.4h, v0.h[0]
@@ -2615,6 +2739,7 @@ L(\type\()_8tap_filter_8):
smlal v27.4s, v24.4h, v0.h[\i]
smlal2 v28.4s, v24.8h, v0.h[\i]
.endr
+.endif
srshl v25.4s, v25.4s, v30.4s // -(6-intermediate_bits)
srshl v26.4s, v26.4s, v30.4s // -(6-intermediate_bits)
srshl v27.4s, v27.4s, v30.4s // -(6-intermediate_bits)
@@ -2623,18 +2748,20 @@ L(\type\()_8tap_filter_8):
uzp1 v24.8h, v27.8h, v28.8h // Ditto
ret
-L(\type\()_8tap_hv_tbl):
- .hword L(\type\()_8tap_hv_tbl) - 1280b
- .hword L(\type\()_8tap_hv_tbl) - 640b
- .hword L(\type\()_8tap_hv_tbl) - 320b
- .hword L(\type\()_8tap_hv_tbl) - 160b
- .hword L(\type\()_8tap_hv_tbl) - 80b
- .hword L(\type\()_8tap_hv_tbl) - 40b
- .hword L(\type\()_8tap_hv_tbl) - 20b
+L(\type\()_\taps\()_hv_tbl):
+ .hword L(\type\()_\taps\()_hv_tbl) - 1280b
+ .hword L(\type\()_\taps\()_hv_tbl) - 640b
+ .hword L(\type\()_\taps\()_hv_tbl) - 320b
+ .hword L(\type\()_\taps\()_hv_tbl) - 160b
+ .hword L(\type\()_\taps\()_hv_tbl) - 80b
+ .hword L(\type\()_\taps\()_hv_tbl) - 40b
+ .hword L(\type\()_\taps\()_hv_tbl) - 20b
.hword 0
endfunc
+.endm
+.macro filter_bilin_fn type, dst, d_strd, src, s_strd, w, h, mx, xmx, my, xmy, bdmax, ds2, sr2
function \type\()_bilin_16bpc_neon, export=1
.ifc \bdmax, w8
ldr w8, [sp]
@@ -3236,8 +3363,34 @@ L(\type\()_bilin_hv_tbl):
endfunc
.endm
-filter_fn put, x0, x1, x2, x3, w4, w5, w6, x6, w7, x7, w8, x9, x10
-filter_fn prep, x0, x8, x1, x2, w3, w4, w5, x5, w6, x6, w7, x9, x10
+make_8tap_fn put, regular_sharp, REGULAR, SHARP, 8tap
+make_8tap_fn put, smooth_sharp, SMOOTH, SHARP, 8tap
+make_8tap_fn put, sharp, SHARP, SHARP, 8tap
+make_8tap_fn put, sharp_regular, SHARP, REGULAR, 8tap
+make_8tap_fn put, sharp_smooth, SHARP, SMOOTH, 8tap
+filter_fn put, x0, x1, x2, x3, w4, w5, w6, x6, w7, x7, w8, x9, x10, 8tap
+
+make_8tap_fn put, regular, REGULAR, REGULAR, 6tap
+make_8tap_fn put, regular_smooth, REGULAR, SMOOTH, 6tap
+make_8tap_fn put, smooth, SMOOTH, SMOOTH, 6tap
+make_8tap_fn put, smooth_regular, SMOOTH, REGULAR, 6tap
+filter_fn put, x0, x1, x2, x3, w4, w5, w6, x6, w7, x7, w8, x9, x10, 6tap
+filter_bilin_fn put, x0, x1, x2, x3, w4, w5, w6, x6, w7, x7, w8, x9, x10
+
+make_8tap_fn prep, regular_sharp, REGULAR, SHARP, 8tap
+make_8tap_fn prep, smooth_sharp, SMOOTH, SHARP, 8tap
+make_8tap_fn prep, sharp, SHARP, SHARP, 8tap
+make_8tap_fn prep, sharp_regular, SHARP, REGULAR, 8tap
+make_8tap_fn prep, sharp_smooth, SHARP, SMOOTH, 8tap
+filter_fn prep, x0, x8, x1, x2, w3, w4, w5, x5, w6, x6, w7, x9, x10, 8tap
+
+make_8tap_fn prep, regular, REGULAR, REGULAR, 6tap
+make_8tap_fn prep, regular_smooth, REGULAR, SMOOTH, 6tap
+make_8tap_fn prep, smooth, SMOOTH, SMOOTH, 6tap
+make_8tap_fn prep, smooth_regular, SMOOTH, REGULAR, 6tap
+filter_fn prep, x0, x8, x1, x2, w3, w4, w5, x5, w6, x6, w7, x9, x10, 6tap
+filter_bilin_fn prep, x0, x8, x1, x2, w3, w4, w5, x5, w6, x6, w7, x9, x10
+
.macro load_filter_row dst, src, inc
asr w13, \src, #10
diff --git a/third_party/dav1d/src/arm/64/msac.S b/third_party/dav1d/src/arm/64/msac.S
index 3a6cf900a9..7bef9243fb 100644
--- a/third_party/dav1d/src/arm/64/msac.S
+++ b/third_party/dav1d/src/arm/64/msac.S
@@ -208,60 +208,66 @@ L(renorm):
sub w4, w4, w3 // rng = u - v
clz w5, w4 // clz(rng)
eor w5, w5, #16 // d = clz(rng) ^ 16
- mvn x7, x7 // ~dif
- add x7, x7, x3, lsl #48 // ~dif + (v << 48)
+ sub x7, x7, x3, lsl #48 // dif - (v << 48)
L(renorm2):
lsl w4, w4, w5 // rng << d
subs w6, w6, w5 // cnt -= d
- lsl x7, x7, x5 // (~dif + (v << 48)) << d
+ lsl x7, x7, x5 // (dif - (v << 48)) << d
str w4, [x0, #RNG]
- mvn x7, x7 // ~dif
- b.hs 9f
+ b.hs 4f
// refill
ldp x3, x4, [x0] // BUF_POS, BUF_END
add x5, x3, #8
- cmp x5, x4
- b.gt 2f
-
- ldr x3, [x3] // next_bits
- add w8, w6, #23 // shift_bits = cnt + 23
- add w6, w6, #16 // cnt += 16
- rev x3, x3 // next_bits = bswap(next_bits)
- sub x5, x5, x8, lsr #3 // buf_pos -= shift_bits >> 3
- and w8, w8, #24 // shift_bits &= 24
- lsr x3, x3, x8 // next_bits >>= shift_bits
- sub w8, w8, w6 // shift_bits -= 16 + cnt
- str x5, [x0, #BUF_POS]
- lsl x3, x3, x8 // next_bits <<= shift_bits
- mov w4, #48
- sub w6, w4, w8 // cnt = cnt + 64 - shift_bits
- eor x7, x7, x3 // dif ^= next_bits
- b 9f
-
-2: // refill_eob
- mov w14, #40
- sub w5, w14, w6 // c = 40 - cnt
-3:
- cmp x3, x4
- b.ge 4f
- ldrb w8, [x3], #1
- lsl x8, x8, x5
- eor x7, x7, x8
- subs w5, w5, #8
- b.ge 3b
-
-4: // refill_eob_end
+ subs x5, x5, x4
+ b.hi 6f
+
+ ldr x8, [x3] // next_bits
+ add w4, w6, #-48 // shift_bits = cnt + 16 (- 64)
+ mvn x8, x8
+ neg w5, w4
+ rev x8, x8 // next_bits = bswap(next_bits)
+ lsr w5, w5, #3 // num_bytes_read
+ lsr x8, x8, x4 // next_bits >>= (shift_bits & 63)
+
+2: // refill_end
+ add x3, x3, x5
+ add w6, w6, w5, lsl #3 // cnt += num_bits_read
str x3, [x0, #BUF_POS]
- sub w6, w14, w5 // cnt = 40 - c
-9:
+3: // refill_end2
+ orr x7, x7, x8 // dif |= next_bits
+
+4: // end
str w6, [x0, #CNT]
str x7, [x0, #DIF]
mov w0, w15
add sp, sp, #48
ret
+
+5: // pad_with_ones
+ add w8, w6, #-16
+ ror x8, x8, x8
+ b 3b
+
+6: // refill_eob
+ cmp x3, x4
+ b.hs 5b
+
+ ldr x8, [x4, #-8]
+ lsl w5, w5, #3
+ lsr x8, x8, x5
+ add w5, w6, #-48
+ mvn x8, x8
+ sub w4, w4, w3 // num_bytes_left
+ rev x8, x8
+ lsr x8, x8, x5
+ neg w5, w5
+ lsr w5, w5, #3
+ cmp w5, w4
+ csel w5, w5, w4, lo // num_bytes_read
+ b 2b
endfunc
function msac_decode_symbol_adapt8_neon, export=1
@@ -334,54 +340,37 @@ function msac_decode_hi_tok_neon, export=1
sub w4, w4, w3 // rng = u - v
clz w5, w4 // clz(rng)
eor w5, w5, #16 // d = clz(rng) ^ 16
- mvn x7, x7 // ~dif
- add x7, x7, x3, lsl #48 // ~dif + (v << 48)
+ sub x7, x7, x3, lsl #48 // dif - (v << 48)
lsl w4, w4, w5 // rng << d
subs w6, w6, w5 // cnt -= d
- lsl x7, x7, x5 // (~dif + (v << 48)) << d
+ lsl x7, x7, x5 // (dif - (v << 48)) << d
str w4, [x0, #RNG]
dup v3.4h, w4
- mvn x7, x7 // ~dif
- b.hs 9f
+ b.hs 5f
// refill
ldp x3, x4, [x0] // BUF_POS, BUF_END
add x5, x3, #8
- cmp x5, x4
- b.gt 2f
-
- ldr x3, [x3] // next_bits
- add w8, w6, #23 // shift_bits = cnt + 23
- add w6, w6, #16 // cnt += 16
- rev x3, x3 // next_bits = bswap(next_bits)
- sub x5, x5, x8, lsr #3 // buf_pos -= shift_bits >> 3
- and w8, w8, #24 // shift_bits &= 24
- lsr x3, x3, x8 // next_bits >>= shift_bits
- sub w8, w8, w6 // shift_bits -= 16 + cnt
- str x5, [x0, #BUF_POS]
- lsl x3, x3, x8 // next_bits <<= shift_bits
- mov w4, #48
- sub w6, w4, w8 // cnt = cnt + 64 - shift_bits
- eor x7, x7, x3 // dif ^= next_bits
- b 9f
-
-2: // refill_eob
- mov w14, #40
- sub w5, w14, w6 // c = 40 - cnt
-3:
- cmp x3, x4
- b.ge 4f
- ldrb w8, [x3], #1
- lsl x8, x8, x5
- eor x7, x7, x8
- subs w5, w5, #8
- b.ge 3b
-
-4: // refill_eob_end
+ subs x5, x5, x4
+ b.hi 7f
+
+ ldr x8, [x3] // next_bits
+ add w4, w6, #-48 // shift_bits = cnt + 16 (- 64)
+ mvn x8, x8
+ neg w5, w4
+ rev x8, x8 // next_bits = bswap(next_bits)
+ lsr w5, w5, #3 // num_bytes_read
+ lsr x8, x8, x4 // next_bits >>= (shift_bits & 63)
+
+3: // refill_end
+ add x3, x3, x5
+ add w6, w6, w5, lsl #3 // cnt += num_bits_read
str x3, [x0, #BUF_POS]
- sub w6, w14, w5 // cnt = 40 - c
-9:
+4: // refill_end2
+ orr x7, x7, x8 // dif |= next_bits
+
+5: // end
lsl w15, w15, #1
sub w15, w15, #5
lsr x12, x7, #48
@@ -394,6 +383,29 @@ function msac_decode_hi_tok_neon, export=1
str x7, [x0, #DIF]
lsr w0, w13, #1
ret
+
+6: // pad_with_ones
+ add w8, w6, #-16
+ ror x8, x8, x8
+ b 4b
+
+7: // refill_eob
+ cmp x3, x4
+ b.hs 6b
+
+ ldr x8, [x4, #-8]
+ lsl w5, w5, #3
+ lsr x8, x8, x5
+ add w5, w6, #-48
+ mvn x8, x8
+ sub w4, w4, w3 // num_bytes_left
+ rev x8, x8
+ lsr x8, x8, x5
+ neg w5, w5
+ lsr w5, w5, #3
+ cmp w5, w4
+ csel w5, w5, w4, lo // num_bytes_read
+ b 3b
endfunc
function msac_decode_bool_equi_neon, export=1
@@ -410,7 +422,6 @@ function msac_decode_bool_equi_neon, export=1
csel x7, x8, x7, hs // if (ret) dif = dif - vw;
clz w5, w4 // clz(rng)
- mvn x7, x7 // ~dif
eor w5, w5, #16 // d = clz(rng) ^ 16
b L(renorm2)
endfunc
@@ -431,7 +442,6 @@ function msac_decode_bool_neon, export=1
csel x7, x8, x7, hs // if (ret) dif = dif - vw;
clz w5, w4 // clz(rng)
- mvn x7, x7 // ~dif
eor w5, w5, #16 // d = clz(rng) ^ 16
b L(renorm2)
endfunc
@@ -455,7 +465,6 @@ function msac_decode_bool_adapt_neon, export=1
ldr w10, [x0, #ALLOW_UPDATE_CDF]
clz w5, w4 // clz(rng)
- mvn x7, x7 // ~dif
eor w5, w5, #16 // d = clz(rng) ^ 16
cbz w10, L(renorm2)
diff --git a/third_party/dav1d/src/arm/64/util.S b/third_party/dav1d/src/arm/64/util.S
index 9013fd4b1e..1b3f319ce5 100644
--- a/third_party/dav1d/src/arm/64/util.S
+++ b/third_party/dav1d/src/arm/64/util.S
@@ -32,6 +32,10 @@
#include "config.h"
#include "src/arm/asm.S"
+#ifndef __has_feature
+#define __has_feature(x) 0
+#endif
+
.macro movrel rd, val, offset=0
#if defined(__APPLE__)
.if \offset < 0
@@ -51,6 +55,10 @@
adrp \rd, \val+(\offset)
add \rd, \rd, :lo12:\val+(\offset)
.endif
+#elif __has_feature(hwaddress_sanitizer)
+ adrp \rd, :pg_hi21_nc:\val+(\offset)
+ movk \rd, #:prel_g3:\val+0x100000000
+ add \rd, \rd, :lo12:\val+(\offset)
#elif defined(PIC)
adrp \rd, \val+(\offset)
add \rd, \rd, :lo12:\val+(\offset)
@@ -149,6 +157,35 @@
trn2 \r7\().2d, \t9\().2d, \r7\().2d
.endm
+.macro transpose_8x8h_mov r0, r1, r2, r3, r4, r5, r6, r7, t8, t9, o0, o1, o2, o3, o4, o5, o6, o7
+ trn1 \t8\().8h, \r0\().8h, \r1\().8h
+ trn2 \t9\().8h, \r0\().8h, \r1\().8h
+ trn1 \r1\().8h, \r2\().8h, \r3\().8h
+ trn2 \r3\().8h, \r2\().8h, \r3\().8h
+ trn1 \r0\().8h, \r4\().8h, \r5\().8h
+ trn2 \r5\().8h, \r4\().8h, \r5\().8h
+ trn1 \r2\().8h, \r6\().8h, \r7\().8h
+ trn2 \r7\().8h, \r6\().8h, \r7\().8h
+
+ trn1 \r4\().4s, \r0\().4s, \r2\().4s
+ trn2 \r2\().4s, \r0\().4s, \r2\().4s
+ trn1 \r6\().4s, \r5\().4s, \r7\().4s
+ trn2 \r7\().4s, \r5\().4s, \r7\().4s
+ trn1 \r5\().4s, \t9\().4s, \r3\().4s
+ trn2 \t9\().4s, \t9\().4s, \r3\().4s
+ trn1 \r3\().4s, \t8\().4s, \r1\().4s
+ trn2 \t8\().4s, \t8\().4s, \r1\().4s
+
+ trn1 \o0\().2d, \r3\().2d, \r4\().2d
+ trn2 \o4\().2d, \r3\().2d, \r4\().2d
+ trn1 \o1\().2d, \r5\().2d, \r6\().2d
+ trn2 \o5\().2d, \r5\().2d, \r6\().2d
+ trn2 \o6\().2d, \t8\().2d, \r2\().2d
+ trn1 \o2\().2d, \t8\().2d, \r2\().2d
+ trn1 \o3\().2d, \t9\().2d, \r7\().2d
+ trn2 \o7\().2d, \t9\().2d, \r7\().2d
+.endm
+
.macro transpose_8x16b r0, r1, r2, r3, r4, r5, r6, r7, t8, t9
trn1 \t8\().16b, \r0\().16b, \r1\().16b
trn2 \t9\().16b, \r0\().16b, \r1\().16b
@@ -226,4 +263,16 @@
trn2 \r3\().4s, \t5\().4s, \t7\().4s
.endm
+.macro transpose_4x8h_mov r0, r1, r2, r3, t4, t5, t6, t7, o0, o1, o2, o3
+ trn1 \t4\().8h, \r0\().8h, \r1\().8h
+ trn2 \t5\().8h, \r0\().8h, \r1\().8h
+ trn1 \t6\().8h, \r2\().8h, \r3\().8h
+ trn2 \t7\().8h, \r2\().8h, \r3\().8h
+
+ trn1 \o0\().4s, \t4\().4s, \t6\().4s
+ trn2 \o2\().4s, \t4\().4s, \t6\().4s
+ trn1 \o1\().4s, \t5\().4s, \t7\().4s
+ trn2 \o3\().4s, \t5\().4s, \t7\().4s
+.endm
+
#endif /* DAV1D_SRC_ARM_64_UTIL_S */
diff --git a/third_party/dav1d/src/arm/asm.S b/third_party/dav1d/src/arm/asm.S
index dc50415f1f..fed73b3048 100644
--- a/third_party/dav1d/src/arm/asm.S
+++ b/third_party/dav1d/src/arm/asm.S
@@ -34,6 +34,50 @@
#define x18 do_not_use_x18
#define w18 do_not_use_w18
+#if HAVE_AS_ARCH_DIRECTIVE
+ .arch AS_ARCH_LEVEL
+#endif
+
+#if HAVE_AS_ARCHEXT_DOTPROD_DIRECTIVE
+#define ENABLE_DOTPROD .arch_extension dotprod
+#define DISABLE_DOTPROD .arch_extension nodotprod
+#else
+#define ENABLE_DOTPROD
+#define DISABLE_DOTPROD
+#endif
+#if HAVE_AS_ARCHEXT_I8MM_DIRECTIVE
+#define ENABLE_I8MM .arch_extension i8mm
+#define DISABLE_I8MM .arch_extension noi8mm
+#else
+#define ENABLE_I8MM
+#define DISABLE_I8MM
+#endif
+#if HAVE_AS_ARCHEXT_SVE_DIRECTIVE
+#define ENABLE_SVE .arch_extension sve
+#define DISABLE_SVE .arch_extension nosve
+#else
+#define ENABLE_SVE
+#define DISABLE_SVE
+#endif
+#if HAVE_AS_ARCHEXT_SVE2_DIRECTIVE
+#define ENABLE_SVE2 .arch_extension sve2
+#define DISABLE_SVE2 .arch_extension nosve2
+#else
+#define ENABLE_SVE2
+#define DISABLE_SVE2
+#endif
+
+/* If we do support the .arch_extension directives, disable support for all
+ * the extensions that we may use, in case they were implicitly enabled by
+ * the .arch level. This makes it clear if we try to assemble an instruction
+ * from an unintended extension set; we only allow assmbling such instructions
+ * within regions where we explicitly enable those extensions. */
+DISABLE_DOTPROD
+DISABLE_I8MM
+DISABLE_SVE
+DISABLE_SVE2
+
+
/* Support macros for
* - Armv8.3-A Pointer Authentication and
* - Armv8.5-A Branch Target Identification
diff --git a/third_party/dav1d/src/arm/cpu.c b/third_party/dav1d/src/arm/cpu.c
index b7a0d3adbc..d9b1751a6a 100644
--- a/third_party/dav1d/src/arm/cpu.c
+++ b/third_party/dav1d/src/arm/cpu.c
@@ -31,22 +31,95 @@
#include "src/arm/cpu.h"
-#if defined(__ARM_NEON) || defined(__APPLE__) || defined(_WIN32) || ARCH_AARCH64
-// NEON is always available; runtime tests are not needed.
-#elif defined(HAVE_GETAUXVAL) && ARCH_ARM
+#if defined(HAVE_GETAUXVAL) || defined(HAVE_ELF_AUX_INFO)
#include <sys/auxv.h>
+#if ARCH_AARCH64
+
+#define HWCAP_AARCH64_ASIMDDP (1 << 20)
+#define HWCAP_AARCH64_SVE (1 << 22)
+#define HWCAP2_AARCH64_SVE2 (1 << 1)
+#define HWCAP2_AARCH64_I8MM (1 << 13)
+
+COLD unsigned dav1d_get_cpu_flags_arm(void) {
+#ifdef HAVE_GETAUXVAL
+ unsigned long hw_cap = getauxval(AT_HWCAP);
+ unsigned long hw_cap2 = getauxval(AT_HWCAP2);
+#else
+ unsigned long hw_cap = 0;
+ unsigned long hw_cap2 = 0;
+ elf_aux_info(AT_HWCAP, &hw_cap, sizeof(hw_cap));
+ elf_aux_info(AT_HWCAP2, &hw_cap2, sizeof(hw_cap2));
+#endif
+
+ unsigned flags = DAV1D_ARM_CPU_FLAG_NEON;
+ flags |= (hw_cap & HWCAP_AARCH64_ASIMDDP) ? DAV1D_ARM_CPU_FLAG_DOTPROD : 0;
+ flags |= (hw_cap2 & HWCAP2_AARCH64_I8MM) ? DAV1D_ARM_CPU_FLAG_I8MM : 0;
+ flags |= (hw_cap & HWCAP_AARCH64_SVE) ? DAV1D_ARM_CPU_FLAG_SVE : 0;
+ flags |= (hw_cap2 & HWCAP2_AARCH64_SVE2) ? DAV1D_ARM_CPU_FLAG_SVE2 : 0;
+ return flags;
+}
+#else /* !ARCH_AARCH64 */
+
#ifndef HWCAP_ARM_NEON
-#define HWCAP_ARM_NEON (1 << 12)
+#define HWCAP_ARM_NEON (1 << 12)
#endif
-#define NEON_HWCAP HWCAP_ARM_NEON
+#define HWCAP_ARM_ASIMDDP (1 << 24)
+#define HWCAP_ARM_I8MM (1 << 27)
-#elif defined(HAVE_ELF_AUX_INFO) && ARCH_ARM
-#include <sys/auxv.h>
+COLD unsigned dav1d_get_cpu_flags_arm(void) {
+#ifdef HAVE_GETAUXVAL
+ unsigned long hw_cap = getauxval(AT_HWCAP);
+#else
+ unsigned long hw_cap = 0;
+ elf_aux_info(AT_HWCAP, &hw_cap, sizeof(hw_cap));
+#endif
+
+ unsigned flags = (hw_cap & HWCAP_ARM_NEON) ? DAV1D_ARM_CPU_FLAG_NEON : 0;
+ flags |= (hw_cap & HWCAP_ARM_ASIMDDP) ? DAV1D_ARM_CPU_FLAG_DOTPROD : 0;
+ flags |= (hw_cap & HWCAP_ARM_I8MM) ? DAV1D_ARM_CPU_FLAG_I8MM : 0;
+ return flags;
+}
+#endif /* ARCH_AARCH64 */
+
+#elif defined(__APPLE__)
+#include <sys/sysctl.h>
+
+static int have_feature(const char *feature) {
+ int supported = 0;
+ size_t size = sizeof(supported);
+ if (sysctlbyname(feature, &supported, &size, NULL, 0) != 0) {
+ return 0;
+ }
+ return supported;
+}
+
+COLD unsigned dav1d_get_cpu_flags_arm(void) {
+ unsigned flags = DAV1D_ARM_CPU_FLAG_NEON;
+ if (have_feature("hw.optional.arm.FEAT_DotProd"))
+ flags |= DAV1D_ARM_CPU_FLAG_DOTPROD;
+ if (have_feature("hw.optional.arm.FEAT_I8MM"))
+ flags |= DAV1D_ARM_CPU_FLAG_I8MM;
+ /* No SVE and SVE2 feature detection available on Apple platforms. */
+ return flags;
+}
+
+#elif defined(_WIN32)
+#include <windows.h>
-#define NEON_HWCAP HWCAP_NEON
+COLD unsigned dav1d_get_cpu_flags_arm(void) {
+ unsigned flags = DAV1D_ARM_CPU_FLAG_NEON;
+#ifdef PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE
+ if (IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE))
+ flags |= DAV1D_ARM_CPU_FLAG_DOTPROD;
+#endif
+ /* No I8MM or SVE feature detection available on Windows at the time of
+ * writing. */
+ return flags;
+}
#elif defined(__ANDROID__)
+#include <ctype.h>
#include <stdio.h>
#include <string.h>
@@ -58,18 +131,25 @@ static unsigned parse_proc_cpuinfo(const char *flag) {
char line_buffer[120];
const char *line;
+ size_t flaglen = strlen(flag);
while ((line = fgets(line_buffer, sizeof(line_buffer), file))) {
- if (strstr(line, flag)) {
- fclose(file);
- return 1;
+ // check all occurances as whole words
+ const char *found = line;
+ while ((found = strstr(found, flag))) {
+ if ((found == line_buffer || !isgraph(found[-1])) &&
+ (isspace(found[flaglen]) || feof(file))) {
+ fclose(file);
+ return 1;
+ }
+ found += flaglen;
}
// if line is incomplete seek back to avoid splitting the search
// string into two buffers
- if (!strchr(line, '\n') && strlen(line) > strlen(flag)) {
+ if (!strchr(line, '\n') && strlen(line) > flaglen) {
// use fseek since the 64 bit fseeko is only available since
// Android API level 24 and meson defines _FILE_OFFSET_BITS
// by default 64
- if (fseek(file, -strlen(flag), SEEK_CUR))
+ if (fseek(file, -flaglen, SEEK_CUR))
break;
}
}
@@ -78,22 +158,23 @@ static unsigned parse_proc_cpuinfo(const char *flag) {
return 0;
}
-#endif
COLD unsigned dav1d_get_cpu_flags_arm(void) {
- unsigned flags = 0;
-#if defined(__ARM_NEON) || defined(__APPLE__) || defined(_WIN32) || ARCH_AARCH64
- flags |= DAV1D_ARM_CPU_FLAG_NEON;
-#elif defined(HAVE_GETAUXVAL) && ARCH_ARM
- unsigned long hw_cap = getauxval(AT_HWCAP);
- flags |= (hw_cap & NEON_HWCAP) ? DAV1D_ARM_CPU_FLAG_NEON : 0;
-#elif defined(HAVE_ELF_AUX_INFO) && ARCH_ARM
- unsigned long hw_cap = 0;
- elf_aux_info(AT_HWCAP, &hw_cap, sizeof(hw_cap));
- flags |= (hw_cap & NEON_HWCAP) ? DAV1D_ARM_CPU_FLAG_NEON : 0;
-#elif defined(__ANDROID__)
- flags |= parse_proc_cpuinfo("neon") ? DAV1D_ARM_CPU_FLAG_NEON : 0;
-#endif
-
+ unsigned flags = parse_proc_cpuinfo("neon") ? DAV1D_ARM_CPU_FLAG_NEON : 0;
+ flags |= parse_proc_cpuinfo("asimd") ? DAV1D_ARM_CPU_FLAG_NEON : 0;
+ flags |= parse_proc_cpuinfo("asimddp") ? DAV1D_ARM_CPU_FLAG_DOTPROD : 0;
+ flags |= parse_proc_cpuinfo("i8mm") ? DAV1D_ARM_CPU_FLAG_I8MM : 0;
+#if ARCH_AARCH64
+ flags |= parse_proc_cpuinfo("sve") ? DAV1D_ARM_CPU_FLAG_SVE : 0;
+ flags |= parse_proc_cpuinfo("sve2") ? DAV1D_ARM_CPU_FLAG_SVE2 : 0;
+#endif /* ARCH_AARCH64 */
return flags;
}
+
+#else /* Unsupported OS */
+
+COLD unsigned dav1d_get_cpu_flags_arm(void) {
+ return 0;
+}
+
+#endif
diff --git a/third_party/dav1d/src/arm/cpu.h b/third_party/dav1d/src/arm/cpu.h
index 8c10a1b6b0..de9bde6ccf 100644
--- a/third_party/dav1d/src/arm/cpu.h
+++ b/third_party/dav1d/src/arm/cpu.h
@@ -30,6 +30,10 @@
enum CpuFlags {
DAV1D_ARM_CPU_FLAG_NEON = 1 << 0,
+ DAV1D_ARM_CPU_FLAG_DOTPROD = 1 << 1,
+ DAV1D_ARM_CPU_FLAG_I8MM = 1 << 2,
+ DAV1D_ARM_CPU_FLAG_SVE = 1 << 3,
+ DAV1D_ARM_CPU_FLAG_SVE2 = 1 << 4,
};
unsigned dav1d_get_cpu_flags_arm(void);
diff --git a/third_party/dav1d/src/arm/itx.h b/third_party/dav1d/src/arm/itx.h
index 2ecd086b3b..17234e027a 100644
--- a/third_party/dav1d/src/arm/itx.h
+++ b/third_party/dav1d/src/arm/itx.h
@@ -117,9 +117,11 @@ static ALWAYS_INLINE void itx_dsp_init_arm(Dav1dInvTxfmDSPContext *const c, int
if (!(flags & DAV1D_ARM_CPU_FLAG_NEON)) return;
+ assign_itx_fn( , 4, 4, wht_wht, WHT_WHT, neon);
+
if (BITDEPTH == 16 && bpc != 10) return;
- assign_itx17_fn( , 4, 4, neon);
+ assign_itx16_fn( , 4, 4, neon);
assign_itx16_fn(R, 4, 8, neon);
assign_itx16_fn(R, 4, 16, neon);
assign_itx16_fn(R, 8, 4, neon);
diff --git a/third_party/dav1d/src/arm/msac.h b/third_party/dav1d/src/arm/msac.h
index 9db0bf86ae..6eee0da424 100644
--- a/third_party/dav1d/src/arm/msac.h
+++ b/third_party/dav1d/src/arm/msac.h
@@ -39,7 +39,7 @@ unsigned dav1d_msac_decode_bool_adapt_neon(MsacContext *s, uint16_t *cdf);
unsigned dav1d_msac_decode_bool_equi_neon(MsacContext *s);
unsigned dav1d_msac_decode_bool_neon(MsacContext *s, unsigned f);
-#if ARCH_AARCH64 || defined(__ARM_NEON)
+#if defined(__ARM_NEON) || defined(__APPLE__) || defined(_WIN32) || ARCH_AARCH64
#define dav1d_msac_decode_symbol_adapt4 dav1d_msac_decode_symbol_adapt4_neon
#define dav1d_msac_decode_symbol_adapt8 dav1d_msac_decode_symbol_adapt8_neon
#define dav1d_msac_decode_symbol_adapt16 dav1d_msac_decode_symbol_adapt16_neon
diff --git a/third_party/dav1d/src/cpu.h b/third_party/dav1d/src/cpu.h
index c9009c7778..d20c5f0168 100644
--- a/third_party/dav1d/src/cpu.h
+++ b/third_party/dav1d/src/cpu.h
@@ -64,6 +64,20 @@ static ALWAYS_INLINE unsigned dav1d_get_cpu_flags(void) {
#if defined(__ARM_NEON) || defined(__APPLE__) || defined(_WIN32) || ARCH_AARCH64
flags |= DAV1D_ARM_CPU_FLAG_NEON;
#endif
+#ifdef __ARM_FEATURE_DOTPROD
+ flags |= DAV1D_ARM_CPU_FLAG_DOTPROD;
+#endif
+#ifdef __ARM_FEATURE_MATMUL_INT8
+ flags |= DAV1D_ARM_CPU_FLAG_I8MM;
+#endif
+#if ARCH_AARCH64
+#ifdef __ARM_FEATURE_SVE
+ flags |= DAV1D_ARM_CPU_FLAG_SVE;
+#endif
+#ifdef __ARM_FEATURE_SVE2
+ flags |= DAV1D_ARM_CPU_FLAG_SVE2;
+#endif
+#endif /* ARCH_AARCH64 */
#elif ARCH_PPC64LE
#if defined(__VSX__)
flags |= DAV1D_PPC_CPU_FLAG_VSX;
diff --git a/third_party/dav1d/src/ext/x86/x86inc.asm b/third_party/dav1d/src/ext/x86/x86inc.asm
index 68b1f74f4b..d2bd758e67 100644
--- a/third_party/dav1d/src/ext/x86/x86inc.asm
+++ b/third_party/dav1d/src/ext/x86/x86inc.asm
@@ -1,7 +1,7 @@
;*****************************************************************************
;* x86inc.asm: x86 abstraction layer
;*****************************************************************************
-;* Copyright (C) 2005-2022 x264 project
+;* Copyright (C) 2005-2024 x264 project
;*
;* Authors: Loren Merritt <lorenm@u.washington.edu>
;* Henrik Gramner <henrik@gramner.com>
@@ -104,7 +104,7 @@
%endif
%define HAVE_PRIVATE_EXTERN 1
-%ifdef __NASM_VER__
+%ifdef __NASM_VERSION_ID__
%use smartalign
%if __NASM_VERSION_ID__ < 0x020e0000 ; 2.14
%define HAVE_PRIVATE_EXTERN 0
@@ -386,7 +386,24 @@ DECLARE_REG_TMP_SIZE 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
%endif
%endmacro
-%macro ALLOC_STACK 0-2 0, 0 ; stack_size, n_xmm_regs (for win64 only)
+%macro RESET_STACK_STATE 0
+ %ifidn rstk, rsp
+ %assign stack_offset stack_offset - stack_size_padded
+ %else
+ %xdefine rstk rsp
+ %endif
+ %assign stack_size 0
+ %assign stack_size_padded 0
+ %assign xmm_regs_used 0
+%endmacro
+
+%macro ALLOC_STACK 0-2 0, 0 ; stack_size, n_xmm_regs
+ RESET_STACK_STATE
+ %ifnum %2
+ %if mmsize != 8
+ %assign xmm_regs_used %2
+ %endif
+ %endif
%ifnum %1
%if %1 != 0
%assign %%pad 0
@@ -396,11 +413,8 @@ DECLARE_REG_TMP_SIZE 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14
%endif
%if WIN64
%assign %%pad %%pad + 32 ; shadow space
- %if mmsize != 8
- %assign xmm_regs_used %2
- %if xmm_regs_used > 8
- %assign %%pad %%pad + (xmm_regs_used-8)*16 ; callee-saved xmm registers
- %endif
+ %if xmm_regs_used > 8
+ %assign %%pad %%pad + (xmm_regs_used-8)*16 ; callee-saved xmm registers
%endif
%endif
%if required_stack_alignment <= STACK_ALIGNMENT
@@ -496,35 +510,62 @@ DECLARE_REG 14, R13, 120
%endif
%endmacro
-%macro WIN64_PUSH_XMM 0
- ; Use the shadow space to store XMM6 and XMM7, the rest needs stack space allocated.
- %if xmm_regs_used > 6 + high_mm_regs
- movaps [rstk + stack_offset + 8], xmm6
- %endif
- %if xmm_regs_used > 7 + high_mm_regs
- movaps [rstk + stack_offset + 24], xmm7
- %endif
- %assign %%xmm_regs_on_stack xmm_regs_used - high_mm_regs - 8
- %if %%xmm_regs_on_stack > 0
- %assign %%i 8
- %rep %%xmm_regs_on_stack
- movaps [rsp + (%%i-8)*16 + stack_size + 32], xmm %+ %%i
- %assign %%i %%i+1
- %endrep
+; Push XMM registers to the stack. If no argument is specified all used register
+; will be pushed, otherwise only push previously unpushed registers.
+%macro WIN64_PUSH_XMM 0-2 ; new_xmm_regs_used, xmm_regs_pushed
+ %if mmsize != 8
+ %if %0 == 2
+ %assign %%pushed %2
+ %assign xmm_regs_used %1
+ %elif %0 == 1
+ %assign %%pushed xmm_regs_used
+ %assign xmm_regs_used %1
+ %else
+ %assign %%pushed 0
+ %endif
+ ; Use the shadow space to store XMM6 and XMM7, the rest needs stack space allocated.
+ %if %%pushed <= 6 + high_mm_regs && xmm_regs_used > 6 + high_mm_regs
+ movaps [rstk + stack_offset + 8], xmm6
+ %endif
+ %if %%pushed <= 7 + high_mm_regs && xmm_regs_used > 7 + high_mm_regs
+ movaps [rstk + stack_offset + 24], xmm7
+ %endif
+ %assign %%pushed %%pushed - high_mm_regs - 8
+ %if %%pushed < 0
+ %assign %%pushed 0
+ %endif
+ %assign %%regs_to_push xmm_regs_used - %%pushed - high_mm_regs - 8
+ %if %%regs_to_push > 0
+ ASSERT (%%regs_to_push + %%pushed) * 16 <= stack_size_padded - stack_size - 32
+ %assign %%i %%pushed + 8
+ %rep %%regs_to_push
+ movaps [rsp + (%%i-8)*16 + stack_size + 32], xmm %+ %%i
+ %assign %%i %%i+1
+ %endrep
+ %endif
%endif
%endmacro
-%macro WIN64_SPILL_XMM 1
- %assign xmm_regs_used %1
- ASSERT xmm_regs_used <= 16 + high_mm_regs
- %assign %%xmm_regs_on_stack xmm_regs_used - high_mm_regs - 8
- %if %%xmm_regs_on_stack > 0
- ; Allocate stack space for callee-saved xmm registers plus shadow space and align the stack.
- %assign %%pad %%xmm_regs_on_stack*16 + 32
- %assign stack_size_padded %%pad + ((-%%pad-stack_offset-gprsize) & (STACK_ALIGNMENT-1))
- SUB rsp, stack_size_padded
+; Allocated stack space for XMM registers and push all, or a subset, of those
+%macro WIN64_SPILL_XMM 1-2 ; xmm_regs_used, xmm_regs_reserved
+ RESET_STACK_STATE
+ %if mmsize != 8
+ %assign xmm_regs_used %1
+ ASSERT xmm_regs_used <= 16 + high_mm_regs
+ %if %0 == 2
+ ASSERT %2 >= %1
+ %assign %%xmm_regs_on_stack %2 - high_mm_regs - 8
+ %else
+ %assign %%xmm_regs_on_stack %1 - high_mm_regs - 8
+ %endif
+ %if %%xmm_regs_on_stack > 0
+ ; Allocate stack space for callee-saved xmm registers plus shadow space and align the stack.
+ %assign %%pad %%xmm_regs_on_stack*16 + 32
+ %assign stack_size_padded %%pad + ((-%%pad-stack_offset-gprsize) & (STACK_ALIGNMENT-1))
+ SUB rsp, stack_size_padded
+ %endif
+ WIN64_PUSH_XMM
%endif
- WIN64_PUSH_XMM
%endmacro
%macro WIN64_RESTORE_XMM_INTERNAL 0
@@ -555,9 +596,7 @@ DECLARE_REG 14, R13, 120
%macro WIN64_RESTORE_XMM 0
WIN64_RESTORE_XMM_INTERNAL
- %assign stack_offset (stack_offset-stack_size_padded)
- %assign stack_size_padded 0
- %assign xmm_regs_used 0
+ RESET_STACK_STATE
%endmacro
%define has_epilogue regs_used > 7 || stack_size > 0 || vzeroupper_required || xmm_regs_used > 6+high_mm_regs
@@ -592,12 +631,11 @@ DECLARE_REG 14, R13, 72
%macro PROLOGUE 2-5+ 0, 0 ; #args, #regs, #xmm_regs, [stack_size,] arg_names...
%assign num_args %1
%assign regs_used %2
- %assign xmm_regs_used %3
ASSERT regs_used >= num_args
SETUP_STACK_POINTER %4
ASSERT regs_used <= 15
PUSH_IF_USED 9, 10, 11, 12, 13, 14
- ALLOC_STACK %4
+ ALLOC_STACK %4, %3
LOAD_IF_USED 6, 7, 8, 9, 10, 11, 12, 13, 14
%if %0 > 4
%ifnum %4
@@ -661,7 +699,7 @@ DECLARE_ARG 7, 8, 9, 10, 11, 12, 13, 14
SETUP_STACK_POINTER %4
ASSERT regs_used <= 7
PUSH_IF_USED 3, 4, 5, 6
- ALLOC_STACK %4
+ ALLOC_STACK %4, %3
LOAD_IF_USED 0, 1, 2, 3, 4, 5, 6
%if %0 > 4
%ifnum %4
@@ -694,13 +732,19 @@ DECLARE_ARG 7, 8, 9, 10, 11, 12, 13, 14
%endif ;======================================================================
%if WIN64 == 0
- %macro WIN64_SPILL_XMM 1
- %assign xmm_regs_used %1
+ %macro WIN64_SPILL_XMM 1-2
+ RESET_STACK_STATE
+ %if mmsize != 8
+ %assign xmm_regs_used %1
+ %endif
%endmacro
%macro WIN64_RESTORE_XMM 0
- %assign xmm_regs_used 0
+ RESET_STACK_STATE
%endmacro
- %macro WIN64_PUSH_XMM 0
+ %macro WIN64_PUSH_XMM 0-2
+ %if mmsize != 8 && %0 >= 1
+ %assign xmm_regs_used %1
+ %endif
%endmacro
%endif
@@ -845,9 +889,26 @@ BRANCH_INSTR jz, je, jnz, jne, jl, jle, jnl, jnle, jg, jge, jng, jnge, ja, jae,
%1: %2
%endmacro
-; This is needed for ELF, otherwise the GNU linker assumes the stack is executable by default.
%if FORMAT_ELF
+ ; The GNU linker assumes the stack is executable by default.
[SECTION .note.GNU-stack noalloc noexec nowrite progbits]
+
+ %ifdef __NASM_VERSION_ID__
+ %if __NASM_VERSION_ID__ >= 0x020e0300 ; 2.14.03
+ %if ARCH_X86_64
+ ; Control-flow Enforcement Technology (CET) properties.
+ [SECTION .note.gnu.property alloc noexec nowrite note align=gprsize]
+ dd 0x00000004 ; n_namesz
+ dd gprsize + 8 ; n_descsz
+ dd 0x00000005 ; n_type = NT_GNU_PROPERTY_TYPE_0
+ db "GNU",0 ; n_name
+ dd 0xc0000002 ; pr_type = GNU_PROPERTY_X86_FEATURE_1_AND
+ dd 0x00000004 ; pr_datasz
+ dd 0x00000002 ; pr_data = GNU_PROPERTY_X86_FEATURE_1_SHSTK
+ dd 0x00000000 ; pr_padding
+ %endif
+ %endif
+ %endif
%endif
; Tell debuggers how large the function was.
@@ -883,21 +944,22 @@ BRANCH_INSTR jz, je, jnz, jne, jl, jle, jnl, jnle, jg, jge, jng, jnge, ja, jae,
%assign cpuflags_sse4 (1<<10) | cpuflags_ssse3
%assign cpuflags_sse42 (1<<11) | cpuflags_sse4
%assign cpuflags_aesni (1<<12) | cpuflags_sse42
-%assign cpuflags_gfni (1<<13) | cpuflags_sse42
-%assign cpuflags_avx (1<<14) | cpuflags_sse42
-%assign cpuflags_xop (1<<15) | cpuflags_avx
-%assign cpuflags_fma4 (1<<16) | cpuflags_avx
-%assign cpuflags_fma3 (1<<17) | cpuflags_avx
-%assign cpuflags_bmi1 (1<<18) | cpuflags_avx|cpuflags_lzcnt
-%assign cpuflags_bmi2 (1<<19) | cpuflags_bmi1
-%assign cpuflags_avx2 (1<<20) | cpuflags_fma3|cpuflags_bmi2
-%assign cpuflags_avx512 (1<<21) | cpuflags_avx2 ; F, CD, BW, DQ, VL
-%assign cpuflags_avx512icl (1<<22) | cpuflags_avx512|cpuflags_gfni ; VNNI, IFMA, VBMI, VBMI2, VPOPCNTDQ, BITALG, VAES, VPCLMULQDQ
-
-%assign cpuflags_cache32 (1<<23)
-%assign cpuflags_cache64 (1<<24)
-%assign cpuflags_aligned (1<<25) ; not a cpu feature, but a function variant
-%assign cpuflags_atom (1<<26)
+%assign cpuflags_clmul (1<<13) | cpuflags_sse42
+%assign cpuflags_gfni (1<<14) | cpuflags_aesni|cpuflags_clmul
+%assign cpuflags_avx (1<<15) | cpuflags_sse42
+%assign cpuflags_xop (1<<16) | cpuflags_avx
+%assign cpuflags_fma4 (1<<17) | cpuflags_avx
+%assign cpuflags_fma3 (1<<18) | cpuflags_avx
+%assign cpuflags_bmi1 (1<<19) | cpuflags_avx|cpuflags_lzcnt
+%assign cpuflags_bmi2 (1<<20) | cpuflags_bmi1
+%assign cpuflags_avx2 (1<<21) | cpuflags_fma3|cpuflags_bmi2
+%assign cpuflags_avx512 (1<<22) | cpuflags_avx2 ; F, CD, BW, DQ, VL
+%assign cpuflags_avx512icl (1<<23) | cpuflags_avx512|cpuflags_gfni ; VNNI, IFMA, VBMI, VBMI2, VPOPCNTDQ, BITALG, VAES, VPCLMULQDQ
+
+%assign cpuflags_cache32 (1<<24)
+%assign cpuflags_cache64 (1<<25)
+%assign cpuflags_aligned (1<<26) ; not a cpu feature, but a function variant
+%assign cpuflags_atom (1<<27)
; Returns a boolean value expressing whether or not the specified cpuflag is enabled.
%define cpuflag(x) (((((cpuflags & (cpuflags_ %+ x)) ^ (cpuflags_ %+ x)) - 1) >> 31) & 1)
@@ -939,13 +1001,13 @@ BRANCH_INSTR jz, je, jnz, jne, jl, jle, jnl, jnle, jg, jge, jng, jnge, ja, jae,
%endif
%if ARCH_X86_64 || cpuflag(sse2)
- %ifdef __NASM_VER__
+ %ifdef __NASM_VERSION_ID__
ALIGNMODE p6
%else
CPU amdnop
%endif
%else
- %ifdef __NASM_VER__
+ %ifdef __NASM_VERSION_ID__
ALIGNMODE nop
%else
CPU basicnop
@@ -1035,6 +1097,7 @@ BRANCH_INSTR jz, je, jnz, jne, jl, jle, jnl, jnle, jg, jge, jng, jnge, ja, jae,
%if WIN64
AVX512_MM_PERMUTATION 6 ; Swap callee-saved registers with volatile registers
%endif
+ %xdefine bcstw 1to8
%xdefine bcstd 1to4
%xdefine bcstq 1to2
%endmacro
@@ -1050,6 +1113,7 @@ BRANCH_INSTR jz, je, jnz, jne, jl, jle, jnl, jnle, jg, jge, jng, jnge, ja, jae,
INIT_CPUFLAGS %1
DEFINE_MMREGS ymm
AVX512_MM_PERMUTATION
+ %xdefine bcstw 1to16
%xdefine bcstd 1to8
%xdefine bcstq 1to4
%endmacro
@@ -1065,6 +1129,7 @@ BRANCH_INSTR jz, je, jnz, jne, jl, jle, jnl, jnle, jg, jge, jng, jnge, ja, jae,
INIT_CPUFLAGS %1
DEFINE_MMREGS zmm
AVX512_MM_PERMUTATION
+ %xdefine bcstw 1to32
%xdefine bcstd 1to16
%xdefine bcstq 1to8
%endmacro
@@ -1607,11 +1672,11 @@ AVX_INSTR pavgb, mmx2, 0, 0, 1
AVX_INSTR pavgw, mmx2, 0, 0, 1
AVX_INSTR pblendvb, sse4, 0, 1, 0 ; last operand must be xmm0 with legacy encoding
AVX_INSTR pblendw, sse4, 0, 1, 0
-AVX_INSTR pclmulhqhqdq, fnord, 0, 0, 0
-AVX_INSTR pclmulhqlqdq, fnord, 0, 0, 0
-AVX_INSTR pclmullqhqdq, fnord, 0, 0, 0
-AVX_INSTR pclmullqlqdq, fnord, 0, 0, 0
-AVX_INSTR pclmulqdq, fnord, 0, 1, 0
+AVX_INSTR pclmulhqhqdq, clmul, 0, 0, 0
+AVX_INSTR pclmulhqlqdq, clmul, 0, 0, 0
+AVX_INSTR pclmullqhqdq, clmul, 0, 0, 0
+AVX_INSTR pclmullqlqdq, clmul, 0, 0, 0
+AVX_INSTR pclmulqdq, clmul, 0, 1, 0
AVX_INSTR pcmpeqb, mmx, 0, 0, 1
AVX_INSTR pcmpeqd, mmx, 0, 0, 1
AVX_INSTR pcmpeqq, sse4, 0, 0, 1
@@ -1766,6 +1831,7 @@ GPR_INSTR blsi, bmi1
GPR_INSTR blsmsk, bmi1
GPR_INSTR blsr, bmi1
GPR_INSTR bzhi, bmi2
+GPR_INSTR crc32, sse42
GPR_INSTR mulx, bmi2
GPR_INSTR pdep, bmi2
GPR_INSTR pext, bmi2
diff --git a/third_party/dav1d/src/itx_1d.c b/third_party/dav1d/src/itx_1d.c
index ca14fc8c41..8f75c653af 100644
--- a/third_party/dav1d/src/itx_1d.c
+++ b/third_party/dav1d/src/itx_1d.c
@@ -1016,6 +1016,10 @@ void dav1d_inv_identity32_1d_c(int32_t *const c, const ptrdiff_t stride,
c[stride * i] *= 4;
}
+#if !(HAVE_ASM && TRIM_DSP_FUNCTIONS && ( \
+ ARCH_AARCH64 || \
+ (ARCH_ARM && (defined(__ARM_NEON) || defined(__APPLE__) || defined(_WIN32))) \
+))
void dav1d_inv_wht4_1d_c(int32_t *const c, const ptrdiff_t stride) {
assert(stride > 0);
const int in0 = c[0 * stride], in1 = c[1 * stride];
@@ -1032,3 +1036,4 @@ void dav1d_inv_wht4_1d_c(int32_t *const c, const ptrdiff_t stride) {
c[2 * stride] = t1;
c[3 * stride] = t2 + t1;
}
+#endif
diff --git a/third_party/dav1d/src/itx_tmpl.c b/third_party/dav1d/src/itx_tmpl.c
index 8ff245a0de..a226223c96 100644
--- a/third_party/dav1d/src/itx_tmpl.c
+++ b/third_party/dav1d/src/itx_tmpl.c
@@ -159,6 +159,10 @@ inv_txfm_fn64(64, 16, 2)
inv_txfm_fn64(64, 32, 1)
inv_txfm_fn64(64, 64, 2)
+#if !(HAVE_ASM && TRIM_DSP_FUNCTIONS && ( \
+ ARCH_AARCH64 || \
+ (ARCH_ARM && (defined(__ARM_NEON) || defined(__APPLE__) || defined(_WIN32))) \
+))
static void inv_txfm_add_wht_wht_4x4_c(pixel *dst, const ptrdiff_t stride,
coef *const coeff, const int eob
HIGHBD_DECL_SUFFIX)
@@ -179,6 +183,7 @@ static void inv_txfm_add_wht_wht_4x4_c(pixel *dst, const ptrdiff_t stride,
for (int x = 0; x < 4; x++)
dst[x] = iclip_pixel(dst[x] + *c++);
}
+#endif
#if HAVE_ASM
#if ARCH_AARCH64 || ARCH_ARM
@@ -236,7 +241,12 @@ COLD void bitfn(dav1d_itx_dsp_init)(Dav1dInvTxfmDSPContext *const c, int bpc) {
c->itxfm_add[pfx##TX_##w##X##h][V_ADST] = \
inv_txfm_add_identity_adst_##w##x##h##_c; \
+#if !(HAVE_ASM && TRIM_DSP_FUNCTIONS && ( \
+ ARCH_AARCH64 || \
+ (ARCH_ARM && (defined(__ARM_NEON) || defined(__APPLE__) || defined(_WIN32))) \
+))
c->itxfm_add[TX_4X4][WHT_WHT] = inv_txfm_add_wht_wht_4x4_c;
+#endif
assign_itx_all_fn84( 4, 4, );
assign_itx_all_fn84( 4, 8, R);
assign_itx_all_fn84( 4, 16, R);
diff --git a/third_party/dav1d/src/loongarch/msac.S b/third_party/dav1d/src/loongarch/msac.S
index c371eba4de..5bf18250a5 100644
--- a/third_party/dav1d/src/loongarch/msac.S
+++ b/third_party/dav1d/src/loongarch/msac.S
@@ -133,55 +133,58 @@ endconst
slli.d t4, t4, 48
vpickve2gr.d t6, vr2, 0
sub.d t6, t6, t4 // dif
- addi.d t6, t6, 1
clz.w t4, t5 // d
xori t4, t4, 16 // d
sll.d t6, t6, t4
- addi.d t6, t6, -1 // dif
addi.d a5, a0, 28 // cnt
- ld.w t7, a5, 0
- sub.w t7, t7, t4 // cnt-d
+ ld.w t0, a5, 0
sll.w t5, t5, t4
+ sub.w t7, t0, t4 // cnt-d
st.w t5, a4, 0 // store rng
- bge t7, zero, 9f
+ bgeu t0, t4, 9f
// refill
ld.d t0, a0, 0 // buf_pos
- addi.d t1, a0, 8
- ld.d t1, t1, 0 // buf_end
+ ld.d t1, a0, 8 // buf_end
addi.d t2, t0, 8
- blt t1, t2, 1f
+ bltu t1, t2, 2f
- ld.d t0, t0, 0 // next_bits
- addi.w t3, t7, 23 // shift_bits = cnt + 23
- addi.w t7, t7, 16 // cnt += 16
- revb.d t0, t0 // next_bits = bswap(next_bits)
- srli.w t4, t3, 3
- sub.d t2, t2, t4 // buf_pos -= shift_bits >> 3
- st.d t2, a0, 0
- andi t3, t3, 24 // shift_bits &= 24
- srl.d t0, t0, t3 // next_bits >>= shift_bits
- sub.w t3, t3, t7 // shift_bits -= 16 + cnt
- sll.d t0, t0, t3 // next_bits <<= shift_bits
- li.w t5, 48
- sub.w t7, t5, t3 // cnt = cnt + 64 - shift_bits
- xor t6, t6, t0 // dif ^= next_bits
- b 9f
+ ld.d t3, t0, 0 // next_bits
+ addi.w t1, t7, -48 // shift_bits = cnt + 16 (- 64)
+ nor t3, t3, t3
+ sub.w t2, zero, t1
+ revb.d t3, t3 // next_bits = bswap(next_bits)
+ srli.w t2, t2, 3 // num_bytes_read
+ srl.d t3, t3, t1 // next_bits >>= (shift_bits & 63)
+ b 3f
1:
- li.w t4, 40
- sub.w t5, t4, t7 // c = 40 - cnt
+ addi.w t3, t7, -48
+ srl.d t3, t3, t3 // pad with ones
+ b 4f
2:
- bge t0, t1, 3f
- ld.bu t2, t0, 0
- addi.d t0, t0, 1
- sll.d t2, t2, t5
- xor t6, t6, t2
- addi.w t5, t5, -8
- bge t5, zero, 2b
- // refill_eob_end
+ bgeu t0, t1, 1b
+ ld.d t3, t1, -8 // next_bits
+ sub.w t2, t2, t1
+ sub.w t1, t1, t0 // num_bytes_left
+ slli.w t2, t2, 3
+ srl.d t3, t3, t2
+ addi.w t2, t7, -48
+ nor t3, t3, t3
+ sub.w t4, zero, t2
+ revb.d t3, t3
+ srli.w t4, t4, 3
+ srl.d t3, t3, t2
+ sltu t2, t1, t4
+ maskeqz t1, t1, t2
+ masknez t2, t4, t2
+ or t2, t2, t1 // num_bytes_read
3:
- st.d t0, a0, 0 // s->buf_pos = buf_pos
- sub.w t7, t4, t5 // cnt = 40 - c
+ slli.w t1, t2, 3
+ add.d t0, t0, t2
+ add.w t7, t7, t1 // cnt += num_bits_read
+ st.d t0, a0, 0
+4:
+ or t6, t6, t3 // dif |= next_bits
9:
st.w t7, a5, 0 // store cnt
st.d t6, a6, 0 // store dif
@@ -208,7 +211,6 @@ function msac_decode_bool_lsx
srli.w t2, t0, 8 // r >> 8
mul.w t2, t2, a1
ld.w a5, a0, 28 // cnt
- addi.d t1, t1, 1 // dif + 1
srli.w t2, t2, 1
addi.w t2, t2, 4 // v
slli.d t3, t2, 48 // vw
@@ -226,49 +228,53 @@ function msac_decode_bool_lsx
clz.w t4, t5 // d
xori t4, t4, 16 // d
sll.d t6, t6, t4
- addi.d t6, t6, -1 // dif
- sub.w t7, a5, t4 // cnt-d
sll.w t5, t5, t4
+ sub.w t7, a5, t4 // cnt-d
st.w t5, a0, 24 // store rng
- bge t7, zero, 9f
+ bgeu a5, t4, 9f
// refill
ld.d t0, a0, 0 // buf_pos
- addi.d t1, a0, 8
- ld.d t1, t1, 0 // buf_end
+ ld.d t1, a0, 8 // buf_end
addi.d t2, t0, 8
- blt t1, t2, 1f
+ bltu t1, t2, 2f
- ld.d t0, t0, 0 // next_bits
- addi.w t3, t7, 23 // shift_bits = cnt + 23
- addi.w t7, t7, 16 // cnt += 16
- revb.d t0, t0 // next_bits = bswap(next_bits)
- srli.w t4, t3, 3
- sub.d t2, t2, t4 // buf_pos -= shift_bits >> 3
- st.d t2, a0, 0
- andi t3, t3, 24 // shift_bits &= 24
- srl.d t0, t0, t3 // next_bits >>= shift_bits
- sub.w t3, t3, t7 // shift_bits -= 16 + cnt
- sll.d t0, t0, t3 // next_bits <<= shift_bits
- li.w t5, 48
- sub.w t7, t5, t3 // cnt = cnt + 64 - shift_bits
- xor t6, t6, t0 // dif ^= next_bits
- b 9f
+ ld.d t3, t0, 0 // next_bits
+ addi.w t1, t7, -48 // shift_bits = cnt + 16 (- 64)
+ nor t3, t3, t3
+ sub.w t2, zero, t1
+ revb.d t3, t3 // next_bits = bswap(next_bits)
+ srli.w t2, t2, 3 // num_bytes_read
+ srl.d t3, t3, t1 // next_bits >>= (shift_bits & 63)
+ b 3f
1:
- li.w t4, 40
- sub.w t5, t4, t7 // c = 40 - cnt
+ addi.w t3, t7, -48
+ srl.d t3, t3, t3 // pad with ones
+ b 4f
2:
- bge t0, t1, 3f
- ld.bu t2, t0, 0
- addi.d t0, t0, 1
- sll.d t2, t2, t5
- xor t6, t6, t2
- addi.w t5, t5, -8
- bge t5, zero, 2b
- // refill_eob_end
+ bgeu t0, t1, 1b
+ ld.d t3, t1, -8 // next_bits
+ sub.w t2, t2, t1
+ sub.w t1, t1, t0 // num_bytes_left
+ slli.w t2, t2, 3
+ srl.d t3, t3, t2
+ addi.w t2, t7, -48
+ nor t3, t3, t3
+ sub.w t4, zero, t2
+ revb.d t3, t3
+ srli.w t4, t4, 3
+ srl.d t3, t3, t2
+ sltu t2, t1, t4
+ maskeqz t1, t1, t2
+ masknez t2, t4, t2
+ or t2, t2, t1 // num_bytes_read
3:
- st.d t0, a0, 0 // s->buf_pos = buf_pos
- sub.w t7, t4, t5 // cnt = 40 - c
+ slli.w t1, t2, 3
+ add.d t0, t0, t2
+ add.w t7, t7, t1 // cnt += num_bits_read
+ st.d t0, a0, 0
+4:
+ or t6, t6, t3 // dif |= next_bits
9:
st.w t7, a0, 28 // store cnt
st.d t6, a0, 16 // store dif
@@ -313,54 +319,56 @@ function msac_decode_bool_adapt_lsx
st.h t0, a1, 2
.renorm:
- // renorm
- addi.d t6, t6, 1
clz.w t4, t5 // d
xori t4, t4, 16 // d
sll.d t6, t6, t4
- addi.d t6, t6, -1 // dif
- sub.w t7, a5, t4 // cnt-d
sll.w t5, t5, t4
+ sub.w t7, a5, t4 // cnt-d
st.w t5, a0, 24 // store rng
- bge t7, zero, 9f
+ bgeu a5, t4, 9f
// refill
ld.d t0, a0, 0 // buf_pos
- addi.d t1, a0, 8
- ld.d t1, t1, 0 // buf_end
+ ld.d t1, a0, 8 // buf_end
addi.d t2, t0, 8
- blt t1, t2, 1f
+ bltu t1, t2, 2f
- ld.d t0, t0, 0 // next_bits
- addi.w t3, t7, 23 // shift_bits = cnt + 23
- addi.w t7, t7, 16 // cnt += 16
- revb.d t0, t0 // next_bits = bswap(next_bits)
- srli.w t4, t3, 3
- sub.d t2, t2, t4 // buf_pos -= shift_bits >> 3
- st.d t2, a0, 0
- andi t3, t3, 24 // shift_bits &= 24
- srl.d t0, t0, t3 // next_bits >>= shift_bits
- sub.w t3, t3, t7 // shift_bits -= 16 + cnt
- sll.d t0, t0, t3 // next_bits <<= shift_bits
- li.w t5, 48
- sub.w t7, t5, t3 // cnt = cnt + 64 - shift_bits
- xor t6, t6, t0 // dif ^= next_bits
- b 9f
+ ld.d t3, t0, 0 // next_bits
+ addi.w t1, t7, -48 // shift_bits = cnt + 16 (- 64)
+ nor t3, t3, t3
+ sub.w t2, zero, t1
+ revb.d t3, t3 // next_bits = bswap(next_bits)
+ srli.w t2, t2, 3 // num_bytes_read
+ srl.d t3, t3, t1 // next_bits >>= (shift_bits & 63)
+ b 3f
1:
- li.w t4, 40
- sub.w t5, t4, t7 // c = 40 - cnt
+ addi.w t3, t7, -48
+ srl.d t3, t3, t3 // pad with ones
+ b 4f
2:
- bge t0, t1, 3f
- ld.bu t2, t0, 0
- addi.d t0, t0, 1
- sll.d t2, t2, t5
- xor t6, t6, t2
- addi.w t5, t5, -8
- bge t5, zero, 2b
- // refill_eob_end
+ bgeu t0, t1, 1b
+ ld.d t3, t1, -8 // next_bits
+ sub.w t2, t2, t1
+ sub.w t1, t1, t0 // num_bytes_left
+ slli.w t2, t2, 3
+ srl.d t3, t3, t2
+ addi.w t2, t7, -48
+ nor t3, t3, t3
+ sub.w t4, zero, t2
+ revb.d t3, t3
+ srli.w t4, t4, 3
+ srl.d t3, t3, t2
+ sltu t2, t1, t4
+ maskeqz t1, t1, t2
+ masknez t2, t4, t2
+ or t2, t2, t1 // num_bytes_read
3:
- st.d t0, a0, 0 // s->buf_pos = buf_pos
- sub.w t7, t4, t5 // cnt = 40 - c
+ slli.w t1, t2, 3
+ add.d t0, t0, t2
+ add.w t7, t7, t1 // cnt += num_bits_read
+ st.d t0, a0, 0
+4:
+ or t6, t6, t3 // dif |= next_bits
9:
st.w t7, a0, 28 // store cnt
st.d t6, a0, 16 // store dif
diff --git a/third_party/dav1d/src/msac.c b/third_party/dav1d/src/msac.c
index 43d8ae5d07..971ba85e29 100644
--- a/third_party/dav1d/src/msac.c
+++ b/third_party/dav1d/src/msac.c
@@ -43,15 +43,40 @@ static inline void ctx_refill(MsacContext *const s) {
const uint8_t *buf_end = s->buf_end;
int c = EC_WIN_SIZE - s->cnt - 24;
ec_win dif = s->dif;
- while (c >= 0 && buf_pos < buf_end) {
- dif ^= ((ec_win)*buf_pos++) << c;
+ do {
+ if (buf_pos >= buf_end) {
+ // set remaining bits to 1;
+ dif |= ~(~(ec_win)0xff << c);
+ break;
+ }
+ dif |= (ec_win)(*buf_pos++ ^ 0xff) << c;
c -= 8;
- }
+ } while (c >= 0);
s->dif = dif;
s->cnt = EC_WIN_SIZE - c - 24;
s->buf_pos = buf_pos;
}
+int dav1d_msac_decode_subexp(MsacContext *const s, const int ref,
+ const int n, unsigned k)
+{
+ assert(n >> k == 8);
+
+ unsigned a = 0;
+ if (dav1d_msac_decode_bool_equi(s)) {
+ if (dav1d_msac_decode_bool_equi(s))
+ k += dav1d_msac_decode_bool_equi(s) + 1;
+ a = 1 << k;
+ }
+ const unsigned v = dav1d_msac_decode_bools(s, k) + a;
+ return ref * 2 <= n ? inv_recenter(ref, v) :
+ n - 1 - inv_recenter(n - 1 - ref, v);
+}
+
+#if !(HAVE_ASM && TRIM_DSP_FUNCTIONS && ( \
+ ARCH_AARCH64 || \
+ (ARCH_ARM && (defined(__ARM_NEON) || defined(__APPLE__) || defined(_WIN32))) \
+))
/* Takes updated dif and range values, renormalizes them so that
* 32768 <= rng < 65536 (reading more bytes from the stream into dif if
* necessary), and stores them back in the decoder context.
@@ -61,11 +86,13 @@ static inline void ctx_norm(MsacContext *const s, const ec_win dif,
const unsigned rng)
{
const int d = 15 ^ (31 ^ clz(rng));
+ const int cnt = s->cnt;
assert(rng <= 65535U);
- s->cnt -= d;
- s->dif = ((dif + 1) << d) - 1; /* Shift in 1s in the LSBs */
+ s->dif = dif << d;
s->rng = rng << d;
- if (s->cnt < 0)
+ s->cnt = cnt - d;
+ // unsigned compare avoids redundant refills at eob
+ if ((unsigned)cnt < (unsigned)d)
ctx_refill(s);
}
@@ -100,22 +127,6 @@ unsigned dav1d_msac_decode_bool_c(MsacContext *const s, const unsigned f) {
return !ret;
}
-int dav1d_msac_decode_subexp(MsacContext *const s, const int ref,
- const int n, unsigned k)
-{
- assert(n >> k == 8);
-
- unsigned a = 0;
- if (dav1d_msac_decode_bool_equi(s)) {
- if (dav1d_msac_decode_bool_equi(s))
- k += dav1d_msac_decode_bool_equi(s) + 1;
- a = 1 << k;
- }
- const unsigned v = dav1d_msac_decode_bools(s, k) + a;
- return ref * 2 <= n ? inv_recenter(ref, v) :
- n - 1 - inv_recenter(n - 1 - ref, v);
-}
-
/* Decodes a symbol given an inverse cumulative distribution function (CDF)
* table in Q15. */
unsigned dav1d_msac_decode_symbol_adapt_c(MsacContext *const s,
@@ -188,13 +199,14 @@ unsigned dav1d_msac_decode_hi_tok_c(MsacContext *const s, uint16_t *const cdf) {
}
return tok;
}
+#endif
void dav1d_msac_init(MsacContext *const s, const uint8_t *const data,
const size_t sz, const int disable_cdf_update_flag)
{
s->buf_pos = data;
s->buf_end = data + sz;
- s->dif = ((ec_win)1 << (EC_WIN_SIZE - 1)) - 1;
+ s->dif = 0;
s->rng = 0x8000;
s->cnt = -15;
s->allow_update_cdf = !disable_cdf_update_flag;
diff --git a/third_party/dav1d/src/ppc/cdef_tmpl.c b/third_party/dav1d/src/ppc/cdef_tmpl.c
index e2e759810f..6ef87ad448 100644
--- a/third_party/dav1d/src/ppc/cdef_tmpl.c
+++ b/third_party/dav1d/src/ppc/cdef_tmpl.c
@@ -29,11 +29,10 @@
#if BITDEPTH == 8
static inline i16x8 vconstrain(const i16x8 diff, const int16_t threshold,
- const int damping)
+ const uint16_t shift)
{
const i16x8 zero = vec_splat_s16(0);
if (!threshold) return zero;
- const uint16_t shift = imax(0, damping - ulog2(threshold));
const i16x8 abs_diff = vec_abs(diff);
const b16x8 mask = vec_cmplt(diff, zero);
const i16x8 thr = vec_splats(threshold);
@@ -44,7 +43,7 @@ static inline i16x8 vconstrain(const i16x8 diff, const int16_t threshold,
return vec_sel(min, neg, mask);
}
-static inline void copy4xN(uint16_t *tmp, const ptrdiff_t tmp_stride,
+static inline void copy4xN(uint16_t *tmp,
const uint8_t *src, const ptrdiff_t src_stride,
const uint8_t (*left)[2], const uint8_t *const top,
const uint8_t *const bottom, const int w, const int h,
@@ -114,7 +113,7 @@ static inline void copy4xN(uint16_t *tmp, const ptrdiff_t tmp_stride,
}
}
-static inline void copy8xN(uint16_t *tmp, const ptrdiff_t tmp_stride,
+static inline void copy8xN(uint16_t *tmp,
const uint8_t *src, const ptrdiff_t src_stride,
const uint8_t (*left)[2], const uint8_t *const top,
const uint8_t *const bottom, const int w, const int h,
@@ -218,16 +217,12 @@ static inline i16x8 max_mask(i16x8 a, i16x8 b) {
#define LOAD_PIX(addr) \
const i16x8 px = (i16x8)vec_vsx_ld(0, addr); \
- i16x8 max = px; \
- i16x8 min = px; \
i16x8 sum = vec_splat_s16(0);
#define LOAD_PIX4(addr) \
const i16x8 a = (i16x8)vec_vsx_ld(0, addr); \
- const i16x8 b = (i16x8)vec_vsx_ld(0, addr + tmp_stride); \
+ const i16x8 b = (i16x8)vec_vsx_ld(0, addr + 8); \
const i16x8 px = vec_xxpermdi(a, b, 0); \
- i16x8 max = px; \
- i16x8 min = px; \
i16x8 sum = vec_splat_s16(0);
#define LOAD_DIR(p, addr, o0, o1) \
@@ -238,22 +233,26 @@ static inline i16x8 max_mask(i16x8 a, i16x8 b) {
#define LOAD_DIR4(p, addr, o0, o1) \
LOAD_DIR(p ## a, addr, o0, o1) \
- LOAD_DIR(p ## b, addr + tmp_stride, o0, o1) \
+ LOAD_DIR(p ## b, addr + 8, o0, o1) \
const i16x8 p ## 0 = vec_xxpermdi(p ## a ## 0, p ## b ## 0, 0); \
const i16x8 p ## 1 = vec_xxpermdi(p ## a ## 1, p ## b ## 1, 0); \
const i16x8 p ## 2 = vec_xxpermdi(p ## a ## 2, p ## b ## 2, 0); \
const i16x8 p ## 3 = vec_xxpermdi(p ## a ## 3, p ## b ## 3, 0);
-#define CONSTRAIN(p, strength) \
+#define CONSTRAIN(p, strength, shift) \
const i16x8 p ## _d0 = vec_sub(p ## 0, px); \
const i16x8 p ## _d1 = vec_sub(p ## 1, px); \
const i16x8 p ## _d2 = vec_sub(p ## 2, px); \
const i16x8 p ## _d3 = vec_sub(p ## 3, px); \
\
- i16x8 p ## _c0 = vconstrain(p ## _d0, strength, damping); \
- i16x8 p ## _c1 = vconstrain(p ## _d1, strength, damping); \
- i16x8 p ## _c2 = vconstrain(p ## _d2, strength, damping); \
- i16x8 p ## _c3 = vconstrain(p ## _d3, strength, damping);
+ i16x8 p ## _c0 = vconstrain(p ## _d0, strength, shift); \
+ i16x8 p ## _c1 = vconstrain(p ## _d1, strength, shift); \
+ i16x8 p ## _c2 = vconstrain(p ## _d2, strength, shift); \
+ i16x8 p ## _c3 = vconstrain(p ## _d3, strength, shift);
+
+#define SETUP_MINMAX \
+ i16x8 max = px; \
+ i16x8 min = px; \
#define MIN_MAX(p) \
max = max_mask(p ## 0, max); \
@@ -265,19 +264,16 @@ static inline i16x8 max_mask(i16x8 a, i16x8 b) {
max = max_mask(p ## 3, max); \
min = vec_min(p ## 3, min);
-#define PRI_0(p) \
- p ## _c0 = vec_add(vec_sl(p ## _c0, vec_splat_u16(1)), vec_sl(p ## _c0, vec_splats(tap_even))); \
- p ## _c1 = vec_add(vec_sl(p ## _c1, vec_splat_u16(1)), vec_sl(p ## _c1, vec_splats(tap_even)));
+#define MAKE_TAPS \
+ const int16_t tap_odd = (pri_strength >> bitdepth_min_8) & 1; \
+ const i16x8 tap0 = vec_splats((int16_t)(4 - tap_odd)); \
+ const i16x8 tap1 = vec_splats((int16_t)(2 + tap_odd));
-#define PRI_1(p) \
- p ## _c2 = vec_sub(vec_sl(p ## _c2, vec_splat_u16(2)), vec_sl(p ## _c2, vec_splats(tap_even))); \
- p ## _c3 = vec_sub(vec_sl(p ## _c3, vec_splat_u16(2)), vec_sl(p ## _c3, vec_splats(tap_even)));
-
-#define SEC_0(p) \
- p ## _c0 = vec_sl(p ## _c0, vec_splat_u16(1)); \
- p ## _c1 = vec_sl(p ## _c1, vec_splat_u16(1)); \
- p ## _c2 = vec_sl(p ## _c2, vec_splat_u16(1)); \
- p ## _c3 = vec_sl(p ## _c3, vec_splat_u16(1));
+#define PRI_0_UPDATE_SUM(p) \
+ sum = vec_madd(tap0, p ## _c0, sum); \
+ sum = vec_madd(tap0, p ## _c1, sum); \
+ sum = vec_madd(tap1, p ## _c2, sum); \
+ sum = vec_madd(tap1, p ## _c3, sum);
#define UPDATE_SUM(p) \
const i16x8 p ## sum0 = vec_add(p ## _c0, p ## _c1); \
@@ -285,92 +281,198 @@ static inline i16x8 max_mask(i16x8 a, i16x8 b) {
sum = vec_add(sum, p ## sum0); \
sum = vec_add(sum, p ## sum1);
+#define SEC_0_UPDATE_SUM(p) \
+ sum = vec_madd(vec_splat_s16(2), p ## _c0, sum); \
+ sum = vec_madd(vec_splat_s16(2), p ## _c1, sum); \
+ sum = vec_madd(vec_splat_s16(2), p ## _c2, sum); \
+ sum = vec_madd(vec_splat_s16(2), p ## _c3, sum);
+
+#define BIAS \
+ i16x8 bias = vec_and((i16x8)vec_cmplt(sum, vec_splat_s16(0)), vec_splat_s16(1)); \
+ bias = vec_sub(vec_splat_s16(8), bias); \
+
+#define STORE4 \
+ dst[0] = vdst[0]; \
+ dst[1] = vdst[1]; \
+ dst[2] = vdst[2]; \
+ dst[3] = vdst[3]; \
+\
+ tmp += 8; \
+ dst += PXSTRIDE(dst_stride); \
+ dst[0] = vdst[4]; \
+ dst[1] = vdst[5]; \
+ dst[2] = vdst[6]; \
+ dst[3] = vdst[7]; \
+\
+ tmp += 8; \
+ dst += PXSTRIDE(dst_stride);
+
+#define STORE4_CLAMPED \
+ BIAS \
+ i16x8 unclamped = vec_add(px, vec_sra(vec_add(sum, bias), vec_splat_u16(4))); \
+ i16x8 vdst = vec_max(vec_min(unclamped, max), min); \
+ STORE4
+
+#define STORE4_UNCLAMPED \
+ BIAS \
+ i16x8 vdst = vec_add(px, vec_sra(vec_add(sum, bias), vec_splat_u16(4))); \
+ STORE4
+
+#define STORE8 \
+ dst[0] = vdst[0]; \
+ dst[1] = vdst[1]; \
+ dst[2] = vdst[2]; \
+ dst[3] = vdst[3]; \
+ dst[4] = vdst[4]; \
+ dst[5] = vdst[5]; \
+ dst[6] = vdst[6]; \
+ dst[7] = vdst[7]; \
+\
+ tmp += 16; \
+ dst += PXSTRIDE(dst_stride);
+
+#define STORE8_CLAMPED \
+ BIAS \
+ i16x8 unclamped = vec_add(px, vec_sra(vec_add(sum, bias), vec_splat_u16(4))); \
+ i16x8 vdst = vec_max(vec_min(unclamped, max), min); \
+ STORE8
+
+#define STORE8_UNCLAMPED \
+ BIAS \
+ i16x8 vdst = vec_add(px, vec_sra(vec_add(sum, bias), vec_splat_u16(4))); \
+ STORE8
+
+#define DIRECTIONS(w, tmp_stride) \
+ static const int8_t cdef_directions##w[8 /* dir */][2 /* pass */] = { \
+ { -1 * tmp_stride + 1, -2 * tmp_stride + 2 }, \
+ { 0 * tmp_stride + 1, -1 * tmp_stride + 2 }, \
+ { 0 * tmp_stride + 1, 0 * tmp_stride + 2 }, \
+ { 0 * tmp_stride + 1, 1 * tmp_stride + 2 }, \
+ { 1 * tmp_stride + 1, 2 * tmp_stride + 2 }, \
+ { 1 * tmp_stride + 0, 2 * tmp_stride + 1 }, \
+ { 1 * tmp_stride + 0, 2 * tmp_stride + 0 }, \
+ { 1 * tmp_stride + 0, 2 * tmp_stride - 1 } \
+ };
+
+DIRECTIONS(4, 8)
+DIRECTIONS(8, 16)
+
static inline void
filter_4xN(pixel *dst, const ptrdiff_t dst_stride,
const pixel (*left)[2], const pixel *const top,
const pixel *const bottom, const int w, const int h,
const int pri_strength, const int sec_strength, const int dir,
- const int damping, const enum CdefEdgeFlags edges,
- const ptrdiff_t tmp_stride, uint16_t *tmp)
+ const int pri_shift, const int sec_shift,
+ const enum CdefEdgeFlags edges, uint16_t *tmp)
{
- const int8_t cdef_directions[8 /* dir */][2 /* pass */] = {
- { -1 * tmp_stride + 1, -2 * tmp_stride + 2 },
- { 0 * tmp_stride + 1, -1 * tmp_stride + 2 },
- { 0 * tmp_stride + 1, 0 * tmp_stride + 2 },
- { 0 * tmp_stride + 1, 1 * tmp_stride + 2 },
- { 1 * tmp_stride + 1, 2 * tmp_stride + 2 },
- { 1 * tmp_stride + 0, 2 * tmp_stride + 1 },
- { 1 * tmp_stride + 0, 2 * tmp_stride + 0 },
- { 1 * tmp_stride + 0, 2 * tmp_stride - 1 }
- };
const int bitdepth_min_8 = bitdepth_from_max(bitdepth_max) - 8;
- const uint16_t tap_even = !((pri_strength >> bitdepth_min_8) & 1);
- const int off1 = cdef_directions[dir][0];
- const int off1_1 = cdef_directions[dir][1];
+ const int off1 = cdef_directions4[dir][0];
+ const int off1_1 = cdef_directions4[dir][1];
- const int off2 = cdef_directions[(dir + 2) & 7][0];
- const int off3 = cdef_directions[(dir + 6) & 7][0];
+ const int off2 = cdef_directions4[(dir + 2) & 7][0];
+ const int off3 = cdef_directions4[(dir + 6) & 7][0];
- const int off2_1 = cdef_directions[(dir + 2) & 7][1];
- const int off3_1 = cdef_directions[(dir + 6) & 7][1];
+ const int off2_1 = cdef_directions4[(dir + 2) & 7][1];
+ const int off3_1 = cdef_directions4[(dir + 6) & 7][1];
- copy4xN(tmp - 2, tmp_stride, dst, dst_stride, left, top, bottom, w, h, edges);
+ MAKE_TAPS
for (int y = 0; y < h / 2; y++) {
LOAD_PIX4(tmp)
+ SETUP_MINMAX
+
// Primary pass
LOAD_DIR4(p, tmp, off1, off1_1)
- CONSTRAIN(p, pri_strength)
+ CONSTRAIN(p, pri_strength, pri_shift)
MIN_MAX(p)
- PRI_0(p)
- PRI_1(p)
-
- UPDATE_SUM(p)
+ PRI_0_UPDATE_SUM(p)
// Secondary pass 1
LOAD_DIR4(s, tmp, off2, off3)
- CONSTRAIN(s, sec_strength)
+ CONSTRAIN(s, sec_strength, sec_shift)
MIN_MAX(s)
- SEC_0(s)
-
- UPDATE_SUM(s)
+ SEC_0_UPDATE_SUM(s)
// Secondary pass 2
LOAD_DIR4(s2, tmp, off2_1, off3_1)
- CONSTRAIN(s2, sec_strength)
+ CONSTRAIN(s2, sec_strength, sec_shift)
MIN_MAX(s2)
UPDATE_SUM(s2)
// Store
- i16x8 bias = vec_and((i16x8)vec_cmplt(sum, vec_splat_s16(0)), vec_splat_s16(1));
- bias = vec_sub(vec_splat_s16(8), bias);
- i16x8 unclamped = vec_add(px, vec_sra(vec_add(sum, bias), vec_splat_u16(4)));
- i16x8 vdst = vec_max(vec_min(unclamped, max), min);
-
- dst[0] = vdst[0];
- dst[1] = vdst[1];
- dst[2] = vdst[2];
- dst[3] = vdst[3];
-
- tmp += tmp_stride;
- dst += PXSTRIDE(dst_stride);
- dst[0] = vdst[4];
- dst[1] = vdst[5];
- dst[2] = vdst[6];
- dst[3] = vdst[7];
-
- tmp += tmp_stride;
- dst += PXSTRIDE(dst_stride);
+ STORE4_CLAMPED
+ }
+}
+
+static inline void
+filter_4xN_pri(pixel *dst, const ptrdiff_t dst_stride,
+ const pixel (*left)[2], const pixel *const top,
+ const pixel *const bottom, const int w, const int h,
+ const int pri_strength, const int dir,
+ const int pri_shift, const enum CdefEdgeFlags edges,
+ uint16_t *tmp)
+{
+ const int bitdepth_min_8 = bitdepth_from_max(bitdepth_max) - 8;
+ const int off1 = cdef_directions4[dir][0];
+ const int off1_1 = cdef_directions4[dir][1];
+
+ MAKE_TAPS
+
+ for (int y = 0; y < h / 2; y++) {
+ LOAD_PIX4(tmp)
+
+ // Primary pass
+ LOAD_DIR4(p, tmp, off1, off1_1)
+
+ CONSTRAIN(p, pri_strength, pri_shift)
+
+ PRI_0_UPDATE_SUM(p)
+
+ STORE4_UNCLAMPED
+ }
+}
+
+static inline void
+filter_4xN_sec(pixel *dst, const ptrdiff_t dst_stride,
+ const pixel (*left)[2], const pixel *const top,
+ const pixel *const bottom, const int w, const int h,
+ const int sec_strength, const int dir,
+ const int sec_shift, const enum CdefEdgeFlags edges,
+ uint16_t *tmp)
+{
+ const int off2 = cdef_directions4[(dir + 2) & 7][0];
+ const int off3 = cdef_directions4[(dir + 6) & 7][0];
+
+ const int off2_1 = cdef_directions4[(dir + 2) & 7][1];
+ const int off3_1 = cdef_directions4[(dir + 6) & 7][1];
+
+ for (int y = 0; y < h / 2; y++) {
+ LOAD_PIX4(tmp)
+ // Secondary pass 1
+ LOAD_DIR4(s, tmp, off2, off3)
+
+ CONSTRAIN(s, sec_strength, sec_shift)
+
+ SEC_0_UPDATE_SUM(s)
+
+ // Secondary pass 2
+ LOAD_DIR4(s2, tmp, off2_1, off3_1)
+
+ CONSTRAIN(s2, sec_strength, sec_shift)
+
+ UPDATE_SUM(s2)
+
+ STORE4_UNCLAMPED
}
}
@@ -379,88 +481,121 @@ filter_8xN(pixel *dst, const ptrdiff_t dst_stride,
const pixel (*left)[2], const pixel *const top,
const pixel *const bottom, const int w, const int h,
const int pri_strength, const int sec_strength, const int dir,
- const int damping, const enum CdefEdgeFlags edges,
- const ptrdiff_t tmp_stride, uint16_t *tmp)
+ const int pri_shift, const int sec_shift, const enum CdefEdgeFlags edges,
+ uint16_t *tmp)
{
- const int8_t cdef_directions[8 /* dir */][2 /* pass */] = {
- { -1 * tmp_stride + 1, -2 * tmp_stride + 2 },
- { 0 * tmp_stride + 1, -1 * tmp_stride + 2 },
- { 0 * tmp_stride + 1, 0 * tmp_stride + 2 },
- { 0 * tmp_stride + 1, 1 * tmp_stride + 2 },
- { 1 * tmp_stride + 1, 2 * tmp_stride + 2 },
- { 1 * tmp_stride + 0, 2 * tmp_stride + 1 },
- { 1 * tmp_stride + 0, 2 * tmp_stride + 0 },
- { 1 * tmp_stride + 0, 2 * tmp_stride - 1 }
- };
const int bitdepth_min_8 = bitdepth_from_max(bitdepth_max) - 8;
+ const int off1 = cdef_directions8[dir][0];
+ const int off1_1 = cdef_directions8[dir][1];
- const uint16_t tap_even = !((pri_strength >> bitdepth_min_8) & 1);
- const int off1 = cdef_directions[dir][0];
- const int off1_1 = cdef_directions[dir][1];
+ const int off2 = cdef_directions8[(dir + 2) & 7][0];
+ const int off3 = cdef_directions8[(dir + 6) & 7][0];
- const int off2 = cdef_directions[(dir + 2) & 7][0];
- const int off3 = cdef_directions[(dir + 6) & 7][0];
+ const int off2_1 = cdef_directions8[(dir + 2) & 7][1];
+ const int off3_1 = cdef_directions8[(dir + 6) & 7][1];
- const int off2_1 = cdef_directions[(dir + 2) & 7][1];
- const int off3_1 = cdef_directions[(dir + 6) & 7][1];
-
- copy8xN(tmp - 2, tmp_stride, dst, dst_stride, left, top, bottom, w, h, edges);
+ MAKE_TAPS
for (int y = 0; y < h; y++) {
LOAD_PIX(tmp)
+ SETUP_MINMAX
+
// Primary pass
LOAD_DIR(p, tmp, off1, off1_1)
- CONSTRAIN(p, pri_strength)
+ CONSTRAIN(p, pri_strength, pri_shift)
MIN_MAX(p)
- PRI_0(p)
- PRI_1(p)
-
- UPDATE_SUM(p)
+ PRI_0_UPDATE_SUM(p)
// Secondary pass 1
LOAD_DIR(s, tmp, off2, off3)
- CONSTRAIN(s, sec_strength)
+ CONSTRAIN(s, sec_strength, sec_shift)
MIN_MAX(s)
- SEC_0(s)
-
- UPDATE_SUM(s)
+ SEC_0_UPDATE_SUM(s)
// Secondary pass 2
LOAD_DIR(s2, tmp, off2_1, off3_1)
- CONSTRAIN(s2, sec_strength)
+ CONSTRAIN(s2, sec_strength, sec_shift)
MIN_MAX(s2)
UPDATE_SUM(s2)
// Store
- i16x8 bias = vec_and((i16x8)vec_cmplt(sum, vec_splat_s16(0)), vec_splat_s16(1));
- bias = vec_sub(vec_splat_s16(8), bias);
- i16x8 unclamped = vec_add(px, vec_sra(vec_add(sum, bias), vec_splat_u16(4)));
- i16x8 vdst = vec_max(vec_min(unclamped, max), min);
-
- dst[0] = vdst[0];
- dst[1] = vdst[1];
- dst[2] = vdst[2];
- dst[3] = vdst[3];
- dst[4] = vdst[4];
- dst[5] = vdst[5];
- dst[6] = vdst[6];
- dst[7] = vdst[7];
-
- tmp += tmp_stride;
- dst += PXSTRIDE(dst_stride);
+ STORE8_CLAMPED
+ }
+
+}
+
+static inline void
+filter_8xN_pri(pixel *dst, const ptrdiff_t dst_stride,
+ const pixel (*left)[2], const pixel *const top,
+ const pixel *const bottom, const int w, const int h,
+ const int pri_strength, const int dir,
+ const int pri_shift, const enum CdefEdgeFlags edges,
+ uint16_t *tmp)
+{
+ const int bitdepth_min_8 = bitdepth_from_max(bitdepth_max) - 8;
+ const int off1 = cdef_directions8[dir][0];
+ const int off1_1 = cdef_directions8[dir][1];
+
+ MAKE_TAPS
+
+ for (int y = 0; y < h; y++) {
+ LOAD_PIX(tmp)
+
+ // Primary pass
+ LOAD_DIR(p, tmp, off1, off1_1)
+
+ CONSTRAIN(p, pri_strength, pri_shift)
+
+ PRI_0_UPDATE_SUM(p)
+
+ STORE8_UNCLAMPED
}
+}
+
+static inline void
+filter_8xN_sec(pixel *dst, const ptrdiff_t dst_stride,
+ const pixel (*left)[2], const pixel *const top,
+ const pixel *const bottom, const int w, const int h,
+ const int sec_strength, const int dir,
+ const int sec_shift, const enum CdefEdgeFlags edges,
+ uint16_t *tmp)
+{
+ const int off2 = cdef_directions8[(dir + 2) & 7][0];
+ const int off3 = cdef_directions8[(dir + 6) & 7][0];
+
+ const int off2_1 = cdef_directions8[(dir + 2) & 7][1];
+ const int off3_1 = cdef_directions8[(dir + 6) & 7][1];
+
+ for (int y = 0; y < h; y++) {
+ LOAD_PIX(tmp)
+
+ // Secondary pass 1
+ LOAD_DIR(s, tmp, off2, off3)
+ CONSTRAIN(s, sec_strength, sec_shift)
+
+ SEC_0_UPDATE_SUM(s)
+
+ // Secondary pass 2
+ LOAD_DIR(s2, tmp, off2_1, off3_1)
+
+ CONSTRAIN(s2, sec_strength, sec_shift)
+
+ UPDATE_SUM(s2)
+
+ STORE8_UNCLAMPED
+ }
}
#define cdef_fn(w, h, tmp_stride) \
@@ -477,8 +612,22 @@ void dav1d_cdef_filter_##w##x##h##_vsx(pixel *const dst, \
{ \
ALIGN_STK_16(uint16_t, tmp_buf, 12 * tmp_stride + 8,); \
uint16_t *tmp = tmp_buf + 2 * tmp_stride + 2; \
- filter_##w##xN(dst, dst_stride, left, top, bottom, w, h, pri_strength, \
- sec_strength, dir, damping, edges, tmp_stride, tmp); \
+ copy##w##xN(tmp - 2, dst, dst_stride, left, top, bottom, w, h, edges); \
+ if (pri_strength) { \
+ const int pri_shift = imax(0, damping - ulog2(pri_strength)); \
+ if (sec_strength) { \
+ const int sec_shift = damping - ulog2(sec_strength); \
+ filter_##w##xN(dst, dst_stride, left, top, bottom, w, h, pri_strength, \
+ sec_strength, dir, pri_shift, sec_shift, edges, tmp); \
+ } else { \
+ filter_##w##xN_pri(dst, dst_stride, left, top, bottom, w, h, pri_strength, \
+ dir, pri_shift, edges, tmp); \
+ } \
+ } else { \
+ const int sec_shift = damping - ulog2(sec_strength); \
+ filter_##w##xN_sec(dst, dst_stride, left, top, bottom, w, h, sec_strength, \
+ dir, sec_shift, edges, tmp); \
+ } \
}
cdef_fn(4, 4, 8);
diff --git a/third_party/dav1d/src/riscv/64/itx.S b/third_party/dav1d/src/riscv/64/itx.S
index 60d045150d..dfec548e40 100644
--- a/third_party/dav1d/src/riscv/64/itx.S
+++ b/third_party/dav1d/src/riscv/64/itx.S
@@ -163,48 +163,48 @@ endfunc
vssub.vv \o3, v16, v20
.endm
-.macro iadst_4 o0, o1, o2, o3
+.macro iadst_4 o0, o1, o2, o3, lm2, lm
li t1, 1321
li t2, 3803
li t3, 2482
- vwmul.vx v4, v0, t1
- vwmul.vx v5, v0, t3
+ vwmul.vx v16, v0, t1
+ vwmul.vx v18, v0, t3
neg t1, t1
- vwmacc.vx v4, t2, v2
- vwmacc.vx v5, t1, v2
+ vwmacc.vx v16, t2, v2
+ vwmacc.vx v18, t1, v2
neg t2, t2
- vwmacc.vx v4, t3, v3
- vwmacc.vx v5, t2, v3
+ vwmacc.vx v16, t3, v3
+ vwmacc.vx v18, t2, v3
- vwsub.vv v6, v0, v2
- vwadd.wv v6, v6, v3
+ vwsub.vv v20, v0, v2
+ vwadd.wv v20, v20, v3
li t1, 3344
- vwmul.vx v7, v1, t1
+ vwmul.vx v22, v1, t1
- vsetvli zero, zero, e32, m1, ta, ma
+ vsetvli zero, zero, e32, \lm2, ta, ma
- vmul.vx v6, v6, t1
+ vmul.vx v20, v20, t1
- vadd.vv v8, v4, v5
- vadd.vv v4, v4, v7
- vadd.vv v5, v5, v7
- vsub.vv v7, v8, v7
+ vadd.vv v24, v16, v18
+ vadd.vv v16, v16, v22
+ vadd.vv v18, v18, v22
+ vsub.vv v22, v24, v22
li t1, 2048
- vadd.vx v4, v4, t1
- vadd.vx v5, v5, t1
- vadd.vx v6, v6, t1
- vadd.vx v7, v7, t1
+ vadd.vx v16, v16, t1
+ vadd.vx v18, v18, t1
+ vadd.vx v20, v20, t1
+ vadd.vx v22, v22, t1
- vsetvli zero, zero, e16, mf2, ta, ma
+ vsetvli zero, zero, e16, \lm, ta, ma
- vnsra.wi \o0, v4, 12
- vnsra.wi \o1, v5, 12
- vnsra.wi \o2, v6, 12
- vnsra.wi \o3, v7, 12
+ vnsra.wi \o0, v16, 12
+ vnsra.wi \o1, v18, 12
+ vnsra.wi \o2, v20, 12
+ vnsra.wi \o3, v22, 12
.endm
function inv_dct_e16_x4_rvv, export=1, ext=v
@@ -213,12 +213,22 @@ function inv_dct_e16_x4_rvv, export=1, ext=v
endfunc
function inv_adst_e16_x4_rvv, export=1, ext=v
- iadst_4 v0, v1, v2, v3
+ iadst_4 v0, v1, v2, v3, m1, mf2
jr t0
endfunc
function inv_flipadst_e16_x4_rvv, export=1, ext=v
- iadst_4 v3, v2, v1, v0
+ iadst_4 v3, v2, v1, v0, m1, mf2
+ jr t0
+endfunc
+
+function inv_adst_e16_x4w_rvv, export=1, ext=v
+ iadst_4 v0, v1, v2, v3, m2, m1
+ jr t0
+endfunc
+
+function inv_flipadst_e16_x4w_rvv, export=1, ext=v
+ iadst_4 v3, v2, v1, v0, m2, m1
jr t0
endfunc
@@ -328,6 +338,8 @@ function inv_txfm_\variant\()add_8x8_rvv, export=1, ext=v
.ifc \variant, identity_
// The identity vsadd.vv and downshift vssra.vi 1 cancel out
+
+ j L(itx_8x8_epilog)
.else
jalr t0, a4
@@ -339,8 +351,8 @@ function inv_txfm_\variant\()add_8x8_rvv, export=1, ext=v
vssra.vi v5, v5, 1
vssra.vi v6, v6, 1
vssra.vi v7, v7, 1
-.endif
+L(itx_8x8_epilog):
vsseg8e16.v v0, (a2)
vle16.v v0, (a2)
addi t0, a2, 16
@@ -374,9 +386,7 @@ function inv_txfm_\variant\()add_8x8_rvv, export=1, ext=v
vmv.v.x v8, zero
vse16.v v8, (a2)
-.ifc \variant, identity_
itx_8x8_end:
-.endif
vsetivli zero, 8, e8, mf2, ta, ma
vle8.v v8, (a0)
add t0, a0, a1
@@ -441,11 +451,12 @@ itx_8x8_end:
vse8.v v15, (a0)
ret
+.endif
endfunc
.endm
-def_fn_8x8_base
def_fn_8x8_base identity_
+def_fn_8x8_base
function inv_identity_e16_x8_rvv, export=1, ext=v
vsadd.vv v0, v0, v0
@@ -530,23 +541,23 @@ endfunc
li t5, 2598
li t6, 3166
- vwmul.vx v8, v7, t1
+ vwmul.vx v16, v7, t1
neg t1, t1
- vwmul.vx v10, v7, t2
- vwmacc.vx v8, t2, v0
- vwmacc.vx v10, t1, v0
+ vwmul.vx v18, v7, t2
+ vwmacc.vx v16, t2, v0
+ vwmacc.vx v18, t1, v0
- vwmul.vx v12, v5, t3
+ vwmul.vx v20, v5, t3
neg t3, t3
- vwmul.vx v14, v5, t4
- vwmacc.vx v12, t4, v2
- vwmacc.vx v14, t3, v2
+ vwmul.vx v22, v5, t4
+ vwmacc.vx v20, t4, v2
+ vwmacc.vx v22, t3, v2
- vwmul.vx v16, v3, t5
+ vwmul.vx v24, v3, t5
neg t5, t5
- vwmul.vx v18, v3, t6
- vwmacc.vx v16, t6, v4
- vwmacc.vx v18, t5, v4
+ vwmul.vx v26, v3, t6
+ vwmacc.vx v24, t6, v4
+ vwmacc.vx v26, t5, v4
li t1, 2048
li t2, 1189
@@ -555,95 +566,95 @@ endfunc
li t5, 3784
li t6, 2896
- vwmul.vx v20, v1, t2
+ vwmul.vx v28, v1, t2
neg t2, t2
- vwmul.vx v22, v1, t3
- vwmacc.vx v20, t3, v6
- vwmacc.vx v22, t2, v6
-
- vwadd.wx v8, v8, t1
- vwadd.wx v10, v10, t1
- vwadd.wx v12, v12, t1
- vwadd.wx v14, v14, t1
+ vwmul.vx v30, v1, t3
+ vwmacc.vx v28, t3, v6
+ vwmacc.vx v30, t2, v6
+
vwadd.wx v16, v16, t1
vwadd.wx v18, v18, t1
vwadd.wx v20, v20, t1
vwadd.wx v22, v22, t1
+ vwadd.wx v24, v24, t1
+ vwadd.wx v26, v26, t1
+ vwadd.wx v28, v28, t1
+ vwadd.wx v30, v30, t1
- vnsra.wi v8, v8, 12
- vnsra.wi v10, v10, 12
- vnsra.wi v12, v12, 12
- vnsra.wi v14, v14, 12
vnsra.wi v16, v16, 12
vnsra.wi v18, v18, 12
vnsra.wi v20, v20, 12
vnsra.wi v22, v22, 12
+ vnsra.wi v24, v24, 12
+ vnsra.wi v26, v26, 12
+ vnsra.wi v28, v28, 12
+ vnsra.wi v30, v30, 12
- vssub.vv v4, v8, v16
- vsadd.vv v8, v8, v16
- vsadd.vv v1, v10, v18
- vsadd.vv v2, v12, v20
- vsadd.vv v3, v14, v22
- vssub.vv v5, v10, v18
- vssub.vv v6, v12, v20
- vssub.vv v22, v14, v22
-
- vsadd.vv \o0, v8, v2
- vsadd.vv \o7, v1, v3
- vssub.vv v2, v8, v2
- vssub.vv v3, v1, v3
-
- vwmul.vx v8, v4, t5
- vwmul.vx v10, v4, t4
- vwmul.vx v12, v22, t5
- vwmul.vx v14, v22, t4
- vwmacc.vx v8, t4, v5
+ vssub.vv v4, v16, v24
+ vsadd.vv v16, v16, v24
+ vsadd.vv v1, v18, v26
+ vsadd.vv v2, v20, v28
+ vsadd.vv v3, v22, v30
+ vssub.vv v5, v18, v26
+ vssub.vv v6, v20, v28
+ vssub.vv v30, v22, v30
+
+ vsadd.vv \o0, v16, v2
+ vsadd.vv \o7, v1, v3
+ vssub.vv v2, v16, v2
+ vssub.vv v3, v1, v3
+
+ vwmul.vx v16, v4, t5
+ vwmul.vx v18, v4, t4
+ vwmul.vx v20, v30, t5
+ vwmul.vx v22, v30, t4
+ vwmacc.vx v16, t4, v5
neg t4, t4
- vwmacc.vx v14, t5, v6
+ vwmacc.vx v22, t5, v6
neg t5, t5
- vwmacc.vx v12, t4, v6
- vwmacc.vx v10, t5, v5
-
- vwadd.wx v8, v8, t1
- vwadd.wx v10, v10, t1
- vwadd.wx v12, v12, t1
- vwadd.wx v14, v14, t1
-
- vnsra.wi v8, v8, 12
- vnsra.wi v10, v10, 12
- vnsra.wi v12, v12, 12
- vnsra.wi v14, v14, 12
-
- vsadd.vv \o1, v8, v12
- vsadd.vv \o6, v10, v14
- vssub.vv v8, v8, v12
- vssub.vv v9, v10, v14
-
- vwmul.vx v10, v2, t6
- vwmul.vx v12, v2, t6
- vwmul.vx v14, v8, t6
- vwmul.vx v16, v8, t6
- vwmacc.vx v10, t6, v3
- vwmacc.vx v14, t6, v9
- neg t6, t6
- vwmacc.vx v12, t6, v3
- vwmacc.vx v16, t6, v9
+ vwmacc.vx v20, t4, v6
+ vwmacc.vx v18, t5, v5
- vwadd.wx v10, v10, t1
- vwadd.wx v12, v12, t1
- vwadd.wx v14, v14, t1
vwadd.wx v16, v16, t1
+ vwadd.wx v18, v18, t1
+ vwadd.wx v20, v20, t1
+ vwadd.wx v22, v22, t1
+
+ vnsra.wi v16, v16, 12
+ vnsra.wi v18, v18, 12
+ vnsra.wi v20, v20, 12
+ vnsra.wi v22, v22, 12
- vnsra.wi \o3, v10, 12
- vnsra.wi \o4, v12, 12
- vnsra.wi \o2, v14, 12
- vnsra.wi \o5, v16, 12
+ vsadd.vv \o1, v16, v20
+ vsadd.vv \o6, v18, v22
+ vssub.vv v16, v16, v20
+ vssub.vv v17, v18, v22
+
+ vwmul.vx v18, v2, t6
+ vwmul.vx v20, v2, t6
+ vwmul.vx v22, v16, t6
+ vwmul.vx v24, v16, t6
+ vwmacc.vx v18, t6, v3
+ vwmacc.vx v22, t6, v17
+ neg t6, t6
+ vwmacc.vx v20, t6, v3
+ vwmacc.vx v24, t6, v17
- vmv.v.x v8, zero
- vssub.vv \o1, v8, \o1
- vssub.vv \o3, v8, \o3
- vssub.vv \o5, v8, \o5
- vssub.vv \o7, v8, \o7
+ vwadd.wx v18, v18, t1
+ vwadd.wx v20, v20, t1
+ vwadd.wx v22, v22, t1
+ vwadd.wx v24, v24, t1
+
+ vnsra.wi \o3, v18, 12
+ vnsra.wi \o4, v20, 12
+ vnsra.wi \o2, v22, 12
+ vnsra.wi \o5, v24, 12
+
+ vmv.v.x v16, zero
+ vssub.vv \o1, v16, \o1
+ vssub.vv \o3, v16, \o3
+ vssub.vv \o5, v16, \o5
+ vssub.vv \o7, v16, \o7
.endm
function inv_dct_e16_x8_rvv, export=1, ext=v
@@ -714,6 +725,206 @@ def_fn_8x8 flipadst, identity
def_fn_8x8 identity, adst
def_fn_8x8 identity, flipadst
+function inv_txfm_add_4x8_rvv, export=1, ext=v
+ csrw vxrm, zero
+
+ vsetivli zero, 8, e16, m1, ta, ma
+ vle16.v v0, (a2)
+ addi t0, a2, 16
+ vle16.v v1, (t0)
+ addi t0, t0, 16
+ vle16.v v2, (t0)
+ addi t0, t0, 16
+ vle16.v v3, (t0)
+
+ li t1, 2896*8
+.irp i, 0, 1, 2, 3
+ vsmul.vx v\i, v\i, t1
+.endr
+
+ jalr t0, a4
+
+ vsseg4e16.v v0, (a2)
+
+ vsetivli zero, 4, e16, mf2, ta, ma
+ vmv.v.x v8, zero
+ vle16.v v0, (a2)
+ vse16.v v8, (a2)
+.irp i, 1, 2, 3, 4, 5, 6, 7
+ addi a2, a2, 8
+ vle16.v v\i, (a2)
+ vse16.v v8, (a2)
+.endr
+
+ jalr t0, a5
+
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7
+ vssra.vi v\i, v\i, 4
+.endr
+
+ vsetvli zero, zero, e8, mf4, ta, ma
+ vle8.v v8, (a0)
+ add t0, a0, a1
+ vle8.v v9, (t0)
+.irp i, 10, 11, 12, 13, 14, 15
+ add t0, t0, a1
+ vle8.v v\i, (t0)
+.endr
+
+ vwaddu.wv v0, v0, v8
+ vwaddu.wv v1, v1, v9
+ vwaddu.wv v2, v2, v10
+ vwaddu.wv v3, v3, v11
+ vwaddu.wv v4, v4, v12
+ vwaddu.wv v5, v5, v13
+ vwaddu.wv v6, v6, v14
+ vwaddu.wv v7, v7, v15
+
+ vsetvli zero, zero, e16, mf2, ta, ma
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7
+ vmax.vx v\i, v\i, zero
+.endr
+
+ vsetvli zero, zero, e8, mf4, ta, ma
+
+ vnclipu.wi v8, v0, 0
+ vnclipu.wi v9, v1, 0
+ vnclipu.wi v10, v2, 0
+ vnclipu.wi v11, v3, 0
+ vnclipu.wi v12, v4, 0
+ vnclipu.wi v13, v5, 0
+ vnclipu.wi v14, v6, 0
+ vnclipu.wi v15, v7, 0
+
+ vse8.v v8, (a0)
+.irp i, 9, 10, 11, 12, 13, 14, 15
+ add a0, a0, a1
+ vse8.v v\i, (a0)
+.endr
+
+ ret
+endfunc
+
+function inv_txfm_add_8x4_rvv, export=1, ext=v
+ csrw vxrm, zero
+
+ vsetivli zero, 4, e16, mf2, ta, ma
+ vle16.v v0, (a2)
+ addi t0, a2, 8
+ vle16.v v1, (t0)
+.irp i, 2, 3, 4, 5, 6, 7
+ addi t0, t0, 8
+ vle16.v v\i, (t0)
+.endr
+
+ li t1, 2896*8
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7
+ vsmul.vx v\i, v\i, t1
+.endr
+
+ jalr t0, a4
+
+ vsseg8e16.v v0, (a2)
+
+ vsetivli zero, 8, e16, m1, ta, ma
+ vmv.v.x v4, zero
+ vle16.v v0, (a2)
+ vse16.v v4, (a2)
+.irp i, 1, 2, 3
+ addi a2, a2, 16
+ vle16.v v\i, (a2)
+ vse16.v v4, (a2)
+.endr
+
+ jalr t0, a5
+
+ vssra.vi v0, v0, 4
+ vssra.vi v1, v1, 4
+ vssra.vi v2, v2, 4
+ vssra.vi v3, v3, 4
+
+ vsetvli zero, zero, e8, mf2, ta, ma
+ vle8.v v4, (a0)
+ add t0, a0, a1
+ vle8.v v5, (t0)
+ add t0, t0, a1
+ vle8.v v6, (t0)
+ add t0, t0, a1
+ vle8.v v7, (t0)
+
+ vwaddu.wv v0, v0, v4
+ vwaddu.wv v1, v1, v5
+ vwaddu.wv v2, v2, v6
+ vwaddu.wv v3, v3, v7
+
+ vsetvli zero, zero, e16, m1, ta, ma
+ vmax.vx v0, v0, zero
+ vmax.vx v1, v1, zero
+ vmax.vx v2, v2, zero
+ vmax.vx v3, v3, zero
+
+ vsetvli zero, zero, e8, mf2, ta, ma
+
+ vnclipu.wi v4, v0, 0
+ vnclipu.wi v5, v1, 0
+ vnclipu.wi v6, v2, 0
+ vnclipu.wi v7, v3, 0
+
+ vse8.v v4, (a0)
+ add a0, a0, a1
+ vse8.v v5, (a0)
+ add a0, a0, a1
+ vse8.v v6, (a0)
+ add a0, a0, a1
+ vse8.v v7, (a0)
+
+ ret
+endfunc
+
+/* Define symbols added in .if statement */
+.equ dct, 1
+.equ identity, 2
+.equ adst, 3
+.equ flipadst, 4
+
+.macro def_fn_48 w, h, txfm1, txfm2
+function inv_txfm_add_\txfm1\()_\txfm2\()_\w\()x\h\()_8bpc_rvv, export=1
+.if \w == 4 && (\txfm1 == adst || \txfm1 == flipadst)
+ la a4, inv_\txfm1\()_e16_x\w\()w_rvv
+.else
+ la a4, inv_\txfm1\()_e16_x\w\()_rvv
+.endif
+.if \h == 4 && (\txfm2 == adst || \txfm2 == flipadst)
+ la a5, inv_\txfm2\()_e16_x\h\()w_rvv
+.else
+ la a5, inv_\txfm2\()_e16_x\h\()_rvv
+.endif
+ j inv_txfm_add_\w\()x\h\()_rvv
+endfunc
+.endm
+
+.macro def_fns_48 w, h
+def_fn_48 \w, \h, dct, dct
+def_fn_48 \w, \h, identity, identity
+def_fn_48 \w, \h, dct, adst
+def_fn_48 \w, \h, dct, flipadst
+def_fn_48 \w, \h, dct, identity
+def_fn_48 \w, \h, adst, dct
+def_fn_48 \w, \h, adst, adst
+def_fn_48 \w, \h, adst, flipadst
+def_fn_48 \w, \h, flipadst, dct
+def_fn_48 \w, \h, flipadst, adst
+def_fn_48 \w, \h, flipadst, flipadst
+def_fn_48 \w, \h, identity, dct
+def_fn_48 \w, \h, adst, identity
+def_fn_48 \w, \h, flipadst, identity
+def_fn_48 \w, \h, identity, adst
+def_fn_48 \w, \h, identity, flipadst
+.endm
+
+def_fns_48 4, 8
+def_fns_48 8, 4
+
function inv_identity_e16_x16_rvv, export=1, ext=v
li t1, 2*(5793-4096)*8
.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
@@ -1196,10 +1407,12 @@ endfunc
.macro def_horz_16 variant
function inv_txfm_horz\variant\()_16x8_rvv, export=1, ext=v
vmv.v.x v16, zero
-.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
- vle16.v v\i, (t4)
+ vle16.v v0, (t4)
vse16.v v16, (t4)
+.irp i, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
add t4, t4, t6
+ vle16.v v\i, (t4)
+ vse16.v v16, (t4)
.endr
.ifc \variant, _identity
li t1, 2*(5793-4096)*8
@@ -1208,29 +1421,35 @@ function inv_txfm_horz\variant\()_16x8_rvv, export=1, ext=v
vsra.vi v16, v16, 1
vaadd.vv v\i, v\i, v16
.endr
+ j L(horz_16x8_epilog)
.else
jalr t0, a4
.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
vssra.vi v\i, v\i, 2
.endr
-.endif
-.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
- vsse16.v v\i, (t5), t6
+L(horz_16x8_epilog):
+ vsse16.v v0, (t5), t6
+.irp i, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
addi t5, t5, 2
+ vsse16.v v\i, (t5), t6
.endr
jr a7
+.endif
endfunc
.endm
-def_horz_16
def_horz_16 _identity
+def_horz_16
function inv_txfm_add_vert_8x16_rvv, export=1, ext=v
vsetivli zero, 8, e16, m1, ta, ma
-.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
- vle16.v v\i, (t4)
+
+ vle16.v v0, (t4)
+.irp i, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
add t4, t4, t6
+ vle16.v v\i, (t4)
.endr
+
jalr t0, a5
.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
@@ -1238,10 +1457,13 @@ function inv_txfm_add_vert_8x16_rvv, export=1, ext=v
.endr
vsetivli zero, 8, e8, mf2, ta, ma
- mv t0, t5
-.irp i, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
- vle8.v v\i, (t0)
+
+ vle8.v v16, (t5)
+ add t0, t5, a1
+ vle8.v v17, (t0)
+.irp i, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
add t0, t0, a1
+ vle8.v v\i, (t0)
.endr
vwaddu.wv v0, v0, v16
@@ -1284,9 +1506,10 @@ function inv_txfm_add_vert_8x16_rvv, export=1, ext=v
vnclipu.wi v30, v14, 0
vnclipu.wi v31, v15, 0
-.irp i, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
- vse8.v v\i, (t5)
+ vse8.v v16, (t5)
+.irp i, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
add t5, t5, a1
+ vse8.v v\i, (t5)
.endr
jr a7
@@ -1296,11 +1519,26 @@ function inv_txfm_add_16x16_rvv, export=1, ext=v
csrw vxrm, zero
vsetivli zero, 8, e16, m1, ta, ma
addi sp, sp, -16*32
-.irp i, 0, 8
+.irp i, 8, 0
addi t4, a2, \i*2
addi t5, sp, \i*16*2
+.if \i == 8
+ blt a3, a7, 1f
+.endif
li t6, 16*2
jalr a7, a6
+.if \i == 8
+ j 2f
+1:
+ li t1, 64
+ vsetvli zero, t1, e16, m8, ta, ma
+ vmv.v.x v0, zero
+ vse16.v v0, (t5)
+ addi t5, t5, 128
+ vse16.v v0, (t5)
+ vsetivli zero, 8, e16, m1, ta, ma
+2:
+.endif
.endr
.irp i, 0, 8
addi t4, sp, \i*2
@@ -1312,7 +1550,7 @@ function inv_txfm_add_16x16_rvv, export=1, ext=v
ret
endfunc
-.macro def_fn_16x16 txfm1, txfm2
+.macro def_fn_16x16 txfm1, txfm2, eob_half
function inv_txfm_add_\txfm1\()_\txfm2\()_16x16_8bpc_rvv, export=1, ext=v
.ifc \txfm1, identity
la a6, inv_txfm_horz_identity_16x8_rvv
@@ -1321,19 +1559,558 @@ function inv_txfm_add_\txfm1\()_\txfm2\()_16x16_8bpc_rvv, export=1, ext=v
la a4, inv_\txfm1\()_e16_x16_rvv
.endif
la a5, inv_\txfm2\()_e16_x16_rvv
+ li a7, \eob_half
j inv_txfm_add_16x16_rvv
endfunc
.endm
-def_fn_16x16 dct, dct
-def_fn_16x16 identity, identity
-def_fn_16x16 dct, adst
-def_fn_16x16 dct, flipadst
-def_fn_16x16 dct, identity
-def_fn_16x16 adst, dct
-def_fn_16x16 adst, adst
-def_fn_16x16 adst, flipadst
-def_fn_16x16 flipadst, dct
-def_fn_16x16 flipadst, adst
-def_fn_16x16 flipadst, flipadst
-def_fn_16x16 identity, dct
+def_fn_16x16 dct, dct, 36
+def_fn_16x16 identity, identity, 36
+def_fn_16x16 dct, adst, 36
+def_fn_16x16 dct, flipadst, 36
+def_fn_16x16 dct, identity, 8
+def_fn_16x16 adst, dct, 36
+def_fn_16x16 adst, adst, 36
+def_fn_16x16 adst, flipadst, 36
+def_fn_16x16 flipadst, dct, 36
+def_fn_16x16 flipadst, adst, 36
+def_fn_16x16 flipadst, flipadst, 36
+def_fn_16x16 identity, dct, 8
+
+.macro def_fn_416_base variant
+function inv_txfm_\variant\()add_4x16_rvv, export=1, ext=v
+ csrw vxrm, zero
+
+ vsetivli zero, 8, e16, m1, ta, ma
+
+ blt a3, a6, 1f
+
+ addi t0, a2, 16
+ vle16.v v0, (t0)
+ addi t0, t0, 32
+ vle16.v v1, (t0)
+ addi t0, t0, 32
+ vle16.v v2, (t0)
+ addi t0, t0, 32
+ vle16.v v3, (t0)
+
+.ifc \variant, identity_
+ li t1, (5793-4096)*8
+ vsmul.vx v8, v0, t1
+ vaadd.vv v4, v0, v8
+ vsmul.vx v8, v1, t1
+ vaadd.vv v5, v1, v8
+ vsmul.vx v8, v2, t1
+ vaadd.vv v6, v2, v8
+ vsmul.vx v8, v3, t1
+ vaadd.vv v7, v3, v8
+.else
+ jalr t0, a4
+
+ vssra.vi v4, v0, 1
+ vssra.vi v5, v1, 1
+ vssra.vi v6, v2, 1
+ vssra.vi v7, v3, 1
+.endif
+
+ j 2f
+
+1:
+.irp i, 4, 5, 6, 7
+ vmv.v.x v\i, zero
+.endr
+
+2:
+ vle16.v v0, (a2)
+ addi t0, a2, 32
+ vle16.v v1, (t0)
+ addi t0, t0, 32
+ vle16.v v2, (t0)
+ addi t0, t0, 32
+ vle16.v v3, (t0)
+
+.ifc \variant, identity_
+ li t1, (5793-4096)*8
+.irp i, 0, 1, 2, 3
+ vsmul.vx v8, v\i, t1
+ vaadd.vv v\i, v\i, v8
+.endr
+
+ j L(itx_4x16_epilog)
+.else
+ jalr t0, a4
+
+ vssra.vi v0, v0, 1
+ vssra.vi v1, v1, 1
+ vssra.vi v2, v2, 1
+ vssra.vi v3, v3, 1
+
+L(itx_4x16_epilog):
+ vsseg4e16.v v0, (a2)
+ addi t0, a2, 64
+ vsseg4e16.v v4, (t0)
+
+ vsetivli zero, 4, e16, mf2, ta, ma
+
+ vmv.v.x v16, zero
+ vle16.v v0, (a2)
+ vse16.v v16, (a2)
+ addi t0, a2, 8
+ vle16.v v1, (t0)
+ vse16.v v16, (t0)
+.irp i, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ addi t0, t0, 8
+ vle16.v v\i, (t0)
+ vse16.v v16, (t0)
+.endr
+
+ jalr t0, a5
+
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ vssra.vi v\i, v\i, 4
+.endr
+
+ vsetvli zero, zero, e8, mf4, ta, ma
+
+ vle8.v v16, (a0)
+ add t0, a0, a1
+ vle8.v v17, (t0)
+.irp i, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
+ add t0, t0, a1
+ vle8.v v\i, (t0)
+.endr
+
+ vwaddu.wv v0, v0, v16
+ vwaddu.wv v1, v1, v17
+ vwaddu.wv v2, v2, v18
+ vwaddu.wv v3, v3, v19
+ vwaddu.wv v4, v4, v20
+ vwaddu.wv v5, v5, v21
+ vwaddu.wv v6, v6, v22
+ vwaddu.wv v7, v7, v23
+ vwaddu.wv v8, v8, v24
+ vwaddu.wv v9, v9, v25
+ vwaddu.wv v10, v10, v26
+ vwaddu.wv v11, v11, v27
+ vwaddu.wv v12, v12, v28
+ vwaddu.wv v13, v13, v29
+ vwaddu.wv v14, v14, v30
+ vwaddu.wv v15, v15, v31
+
+ vsetvli zero, zero, e16, mf2, ta, ma
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ vmax.vx v\i, v\i, zero
+.endr
+
+ vsetvli zero, zero, e8, mf4, ta, ma
+
+ vnclipu.wi v16, v0, 0
+ vnclipu.wi v17, v1, 0
+ vnclipu.wi v18, v2, 0
+ vnclipu.wi v19, v3, 0
+ vnclipu.wi v20, v4, 0
+ vnclipu.wi v21, v5, 0
+ vnclipu.wi v22, v6, 0
+ vnclipu.wi v23, v7, 0
+ vnclipu.wi v24, v8, 0
+ vnclipu.wi v25, v9, 0
+ vnclipu.wi v26, v10, 0
+ vnclipu.wi v27, v11, 0
+ vnclipu.wi v28, v12, 0
+ vnclipu.wi v29, v13, 0
+ vnclipu.wi v30, v14, 0
+ vnclipu.wi v31, v15, 0
+
+ vse8.v v16, (a0)
+.irp i, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
+ add a0, a0, a1
+ vse8.v v\i, (a0)
+.endr
+
+ ret
+.endif
+endfunc
+
+function inv_txfm_\variant\()add_16x4_rvv, export=1, ext=v
+ csrw vxrm, zero
+
+ vsetivli zero, 4, e16, mf2, ta, ma
+ vle16.v v0, (a2)
+ addi t0, a2, 8
+ vle16.v v1, (t0)
+.irp i, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ addi t0, t0, 8
+ vle16.v v\i, (t0)
+.endr
+
+.ifc \variant, identity_
+ li t1, 2*(5793-4096)*8
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ vsmul.vx v16, v\i, t1
+ vssra.vi v16, v16, 1
+ vsadd.vv v\i, v\i, v16
+.endr
+
+ j L(itx_16x4_epilog)
+.else
+ jalr t0, a4
+
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ vssra.vi v\i, v\i, 1
+.endr
+
+L(itx_16x4_epilog):
+ li t0, 32
+ vssseg8e16.v v0, (a2), t0
+ addi t1, a2, 16
+ vssseg8e16.v v8, (t1), t0
+
+.irp j, 0, 8
+ vsetivli zero, 8, e16, m1, ta, ma
+
+ vmv.v.x v4, zero
+ addi t0, a2, \j*2
+ vle16.v v0, (t0)
+ vse16.v v4, (t0)
+.irp i, 1, 2, 3
+ addi t0, t0, 32
+ vle16.v v\i, (t0)
+ vse16.v v4, (t0)
+.endr
+
+ jalr t0, a5
+
+ vssra.vi v0, v0, 4
+ vssra.vi v1, v1, 4
+ vssra.vi v2, v2, 4
+ vssra.vi v3, v3, 4
+
+ vsetvli zero, zero, e8, mf2, ta, ma
+ addi t0, a0, \j
+ vle8.v v4, (t0)
+ add t0, t0, a1
+ vle8.v v5, (t0)
+ add t0, t0, a1
+ vle8.v v6, (t0)
+ add t0, t0, a1
+ vle8.v v7, (t0)
+
+ vwaddu.wv v0, v0, v4
+ vwaddu.wv v1, v1, v5
+ vwaddu.wv v2, v2, v6
+ vwaddu.wv v3, v3, v7
+
+ vsetvli zero, zero, e16, m1, ta, ma
+ vmax.vx v0, v0, zero
+ vmax.vx v1, v1, zero
+ vmax.vx v2, v2, zero
+ vmax.vx v3, v3, zero
+
+ vsetvli zero, zero, e8, mf2, ta, ma
+
+ vnclipu.wi v4, v0, 0
+ vnclipu.wi v5, v1, 0
+ vnclipu.wi v6, v2, 0
+ vnclipu.wi v7, v3, 0
+
+ addi t0, a0, \j
+ vse8.v v4, (t0)
+ add t0, t0, a1
+ vse8.v v5, (t0)
+ add t0, t0, a1
+ vse8.v v6, (t0)
+ add t0, t0, a1
+ vse8.v v7, (t0)
+.endr
+
+ ret
+.endif
+endfunc
+.endm
+
+def_fn_416_base identity_
+def_fn_416_base
+
+.macro def_fn_416 w, h, txfm1, txfm2, eob_half
+function inv_txfm_add_\txfm1\()_\txfm2\()_\w\()x\h\()_8bpc_rvv, export=1
+.if \w == 4 && (\txfm1 == adst || \txfm1 == flipadst)
+ la a4, inv_\txfm1\()_e16_x\w\()w_rvv
+.elseif \txfm1 != identity
+ la a4, inv_\txfm1\()_e16_x\w\()_rvv
+.endif
+.if \h == 4 && (\txfm2 == adst || \txfm2 == flipadst)
+ la a5, inv_\txfm2\()_e16_x\h\()w_rvv
+.else
+ la a5, inv_\txfm2\()_e16_x\h\()_rvv
+.endif
+.if \w == 4
+ li a6, \eob_half
+.endif
+.ifc \txfm1, identity
+ j inv_txfm_identity_add_\w\()x\h\()_rvv
+.else
+ j inv_txfm_add_\w\()x\h\()_rvv
+.endif
+endfunc
+.endm
+
+.macro def_fns_416 w, h
+def_fn_416 \w, \h, dct, dct, 29
+def_fn_416 \w, \h, identity, identity, 29
+def_fn_416 \w, \h, dct, adst, 29
+def_fn_416 \w, \h, dct, flipadst, 29
+def_fn_416 \w, \h, dct, identity, 8
+def_fn_416 \w, \h, adst, dct, 29
+def_fn_416 \w, \h, adst, adst, 29
+def_fn_416 \w, \h, adst, flipadst, 29
+def_fn_416 \w, \h, flipadst, dct, 29
+def_fn_416 \w, \h, flipadst, adst, 29
+def_fn_416 \w, \h, flipadst, flipadst, 29
+def_fn_416 \w, \h, identity, dct, 32
+def_fn_416 \w, \h, adst, identity, 8
+def_fn_416 \w, \h, flipadst, identity, 8
+def_fn_416 \w, \h, identity, adst, 32
+def_fn_416 \w, \h, identity, flipadst, 32
+.endm
+
+def_fns_416 4, 16
+def_fns_416 16, 4
+
+.macro def_fn_816_base variant
+function inv_txfm_\variant\()add_8x16_rvv, export=1, ext=v
+ csrw vxrm, zero
+
+ vsetivli zero, 8, e16, m1, ta, ma
+
+ blt a3, a6, 1f
+
+ vmv.v.x v16, zero
+ addi t0, a2, 16
+ vle16.v v0, (t0)
+ vse16.v v16, (t0)
+.irp i, 1, 2, 3, 4, 5, 6, 7
+ addi t0, t0, 32
+ vle16.v v\i, (t0)
+ vse16.v v16, (t0)
+.endr
+
+ li t1, 2896*8
+.ifc \variant, identity_
+ vsmul.vx v8, v0, t1
+ vsmul.vx v9, v1, t1
+ vsmul.vx v10, v2, t1
+ vsmul.vx v11, v3, t1
+ vsmul.vx v12, v4, t1
+ vsmul.vx v13, v5, t1
+ vsmul.vx v14, v6, t1
+ vsmul.vx v15, v7, t1
+.else
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7
+ vsmul.vx v\i, v\i, t1
+.endr
+
+ jalr t0, a4
+
+ vssra.vi v8, v0, 1
+ vssra.vi v9, v1, 1
+ vssra.vi v10, v2, 1
+ vssra.vi v11, v3, 1
+ vssra.vi v12, v4, 1
+ vssra.vi v13, v5, 1
+ vssra.vi v14, v6, 1
+ vssra.vi v15, v7, 1
+.endif
+
+ j 2f
+
+1:
+.irp i, 8, 9, 10, 11, 12, 13, 14, 15
+ vmv.v.x v\i, zero
+.endr
+
+2:
+ vmv.v.x v16, zero
+ vle16.v v0, (a2)
+ vse16.v v16, (a2)
+ addi t0, a2, 32
+ vle16.v v1, (t0)
+ vse16.v v16, (t0)
+.irp i, 2, 3, 4, 5, 6, 7
+ addi t0, t0, 32
+ vle16.v v\i, (t0)
+ vse16.v v16, (t0)
+.endr
+
+ li t1, 2896*8
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7
+ vsmul.vx v\i, v\i, t1
+.endr
+
+.ifc \variant, identity_
+ j L(itx_8x16_epilog)
+.else
+ jalr t0, a4
+
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7
+ vssra.vi v\i, v\i, 1
+.endr
+
+L(itx_8x16_epilog):
+ addi t4, sp, -8*32
+ vsseg8e16.v v0, (t4)
+ addi t0, t4, 8*16
+ vsseg8e16.v v8, (t0)
+
+ mv t5, a0
+ li t6, 16
+ jal a7, inv_txfm_add_vert_8x16_rvv
+
+ ret
+.endif
+endfunc
+
+function inv_txfm_\variant\()add_16x8_rvv, export=1, ext=v
+ csrw vxrm, zero
+
+ vsetivli zero, 8, e16, m1, ta, ma
+ vle16.v v0, (a2)
+ addi t0, a2, 16
+ vle16.v v1, (t0)
+.irp i, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ addi t0, t0, 16
+ vle16.v v\i, (t0)
+.endr
+
+ li t1, 2896*8
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ vsmul.vx v\i, v\i, t1
+.endr
+
+.ifc \variant, identity_
+ li t1, 2*(5793-4096)*8
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ vsmul.vx v16, v\i, t1
+ vssra.vi v16, v16, 1
+ vsadd.vv v\i, v\i, v16
+.endr
+
+ j L(itx_16x8_epilog)
+.else
+ jalr t0, a4
+
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+ vssra.vi v\i, v\i, 1
+.endr
+
+L(itx_16x8_epilog):
+ li t0, 32
+ vssseg8e16.v v0, (a2), t0
+ addi t1, a2, 16
+ vssseg8e16.v v8, (t1), t0
+
+.irp j, 0, 8
+ vsetivli zero, 8, e16, m1, ta, ma
+
+ vmv.v.x v8, zero
+ addi t0, a2, \j*2
+ vle16.v v0, (t0)
+ vse16.v v8, (t0)
+.irp i, 1, 2, 3, 4, 5, 6, 7
+ addi t0, t0, 32
+ vle16.v v\i, (t0)
+ vse16.v v8, (t0)
+.endr
+
+ jalr t0, a5
+
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7
+ vssra.vi v\i, v\i, 4
+.endr
+
+ vsetvli zero, zero, e8, mf2, ta, ma
+ addi t0, a0, \j
+ vle8.v v8, (t0)
+.irp i, 9, 10, 11, 12, 13, 14, 15
+ add t0, t0, a1
+ vle8.v v\i, (t0)
+.endr
+
+ vwaddu.wv v0, v0, v8
+ vwaddu.wv v1, v1, v9
+ vwaddu.wv v2, v2, v10
+ vwaddu.wv v3, v3, v11
+ vwaddu.wv v4, v4, v12
+ vwaddu.wv v5, v5, v13
+ vwaddu.wv v6, v6, v14
+ vwaddu.wv v7, v7, v15
+
+ vsetvli zero, zero, e16, m1, ta, ma
+.irp i, 0, 1, 2, 3, 4, 5, 6, 7
+ vmax.vx v\i, v\i, zero
+.endr
+
+ vsetvli zero, zero, e8, mf2, ta, ma
+
+ vnclipu.wi v8, v0, 0
+ vnclipu.wi v9, v1, 0
+ vnclipu.wi v10, v2, 0
+ vnclipu.wi v11, v3, 0
+ vnclipu.wi v12, v4, 0
+ vnclipu.wi v13, v5, 0
+ vnclipu.wi v14, v6, 0
+ vnclipu.wi v15, v7, 0
+
+ addi t0, a0, \j
+ vse8.v v8, (t0)
+.irp i, 9, 10, 11, 12, 13, 14, 15
+ add t0, t0, a1
+ vse8.v v\i, (t0)
+.endr
+.endr
+
+ ret
+.endif
+endfunc
+.endm
+
+def_fn_816_base identity_
+def_fn_816_base
+
+.macro def_fn_816 w, h, txfm1, txfm2, eob_half
+function inv_txfm_add_\txfm1\()_\txfm2\()_\w\()x\h\()_8bpc_rvv, export=1
+.ifnc \txfm1, identity
+ la a4, inv_\txfm1\()_e16_x\w\()_rvv
+.endif
+ la a5, inv_\txfm2\()_e16_x\h\()_rvv
+.if \w == 8
+ li a6, \eob_half
+.endif
+.ifc \txfm1, identity
+ j inv_txfm_identity_add_\w\()x\h\()_rvv
+.else
+ j inv_txfm_add_\w\()x\h\()_rvv
+.endif
+endfunc
+.endm
+
+.macro def_fns_816 w, h
+def_fn_816 \w, \h, dct, dct, 43
+def_fn_816 \w, \h, identity, identity, 43
+def_fn_816 \w, \h, dct, adst, 43
+def_fn_816 \w, \h, dct, flipadst, 43
+def_fn_816 \w, \h, dct, identity, 8
+def_fn_816 \w, \h, adst, dct, 43
+def_fn_816 \w, \h, adst, adst, 43
+def_fn_816 \w, \h, adst, flipadst, 43
+def_fn_816 \w, \h, flipadst, dct, 43
+def_fn_816 \w, \h, flipadst, adst, 43
+def_fn_816 \w, \h, flipadst, flipadst, 43
+def_fn_816 \w, \h, identity, dct, 64
+def_fn_816 \w, \h, adst, identity, 8
+def_fn_816 \w, \h, flipadst, identity, 8
+def_fn_816 \w, \h, identity, adst, 64
+def_fn_816 \w, \h, identity, flipadst, 64
+.endm
+
+def_fns_816 8, 16
+def_fns_816 16, 8
diff --git a/third_party/dav1d/src/riscv/asm.S b/third_party/dav1d/src/riscv/asm.S
index 2435170acb..eed4d67bf5 100644
--- a/third_party/dav1d/src/riscv/asm.S
+++ b/third_party/dav1d/src/riscv/asm.S
@@ -123,4 +123,6 @@ EXTERN\name:
end_thread_local
.endm
+#define L(x) .L ## x
+
#endif /* DAV1D_SRC_RISCV_ASM_S */
diff --git a/third_party/dav1d/src/riscv/itx.h b/third_party/dav1d/src/riscv/itx.h
index 28c5e54d42..d3f9a03a03 100644
--- a/third_party/dav1d/src/riscv/itx.h
+++ b/third_party/dav1d/src/riscv/itx.h
@@ -58,7 +58,13 @@ decl_itx_fn(BF(dav1d_inv_txfm_add_wht_wht_##w##x##h, opt))
#define decl_itx_fns(ext) \
decl_itx17_fns( 4, 4, ext); \
+decl_itx16_fns( 4, 8, ext); \
+decl_itx16_fns( 4, 16, ext); \
+decl_itx16_fns( 8, 4, ext); \
decl_itx16_fns( 8, 8, ext); \
+decl_itx16_fns( 8, 16, ext); \
+decl_itx16_fns(16, 4, ext); \
+decl_itx16_fns(16, 8, ext); \
decl_itx16_fns(16, 16, ext)
decl_itx_fns(rvv);
@@ -105,7 +111,13 @@ static ALWAYS_INLINE void itx_dsp_init_riscv(Dav1dInvTxfmDSPContext *const c, in
#if BITDEPTH == 8
assign_itx17_fn( , 4, 4, rvv);
+ assign_itx16_fn(R, 4, 8, rvv);
+ assign_itx16_fn(R, 4, 16, rvv);
+ assign_itx16_fn(R, 8, 4, rvv);
assign_itx16_fn( , 8, 8, rvv);
+ assign_itx16_fn(R, 8, 16, rvv);
+ assign_itx16_fn(R, 16, 4, rvv);
+ assign_itx16_fn(R, 16, 8, rvv);
assign_itx12_fn( , 16, 16, rvv);
#endif
}
diff --git a/third_party/dav1d/src/x86/cdef_avx2.asm b/third_party/dav1d/src/x86/cdef_avx2.asm
index 1f30f8a3b7..95d35fc1c8 100644
--- a/third_party/dav1d/src/x86/cdef_avx2.asm
+++ b/third_party/dav1d/src/x86/cdef_avx2.asm
@@ -398,7 +398,6 @@ SECTION .text
INIT_YMM avx2
cglobal cdef_filter_%1x%2_8bpc, 5, 11, 0, dst, stride, left, top, bot, \
pri, sec, dir, damping, edge
-%assign stack_offset_entry stack_offset
mov edged, edgem
cmp edged, 0xf
jne .border_block
@@ -1195,9 +1194,9 @@ cglobal cdef_filter_%1x%2_8bpc, 5, 11, 0, dst, stride, left, top, bot, \
.border_block:
DEFINE_ARGS dst, stride, left, top, bot, pri, sec, stride3, dst4, edge
-%define rstk rsp
-%assign stack_offset stack_offset_entry
-%assign regs_used 11
+ RESET_STACK_STATE
+ %assign stack_offset stack_offset - (regs_used - 11) * gprsize
+ %assign regs_used 11
ALLOC_STACK 2*16+(%2+4)*32, 16
%define px rsp+2*16+2*32
diff --git a/third_party/dav1d/src/x86/filmgrain16_avx2.asm b/third_party/dav1d/src/x86/filmgrain16_avx2.asm
index a1d4c41f27..eda6035923 100644
--- a/third_party/dav1d/src/x86/filmgrain16_avx2.asm
+++ b/third_party/dav1d/src/x86/filmgrain16_avx2.asm
@@ -646,18 +646,9 @@ INIT_XMM avx2
INIT_YMM avx2
.ar2:
%if WIN64
- ; xmm6 and xmm7 already saved
- %assign xmm_regs_used 13 + %2
%assign stack_size_padded 136
SUB rsp, stack_size_padded
- movaps [rsp+16*2], xmm8
- movaps [rsp+16*3], xmm9
- movaps [rsp+16*4], xmm10
- movaps [rsp+16*5], xmm11
- movaps [rsp+16*6], xmm12
-%if %2
- movaps [rsp+16*7], xmm13
-%endif
+ WIN64_PUSH_XMM 13 + %2, 8
%endif
DEFINE_ARGS buf, bufy, fg_data, uv, bdmax, shift
mov shiftd, [fg_dataq+FGData.ar_coeff_shift]
@@ -747,20 +738,10 @@ INIT_YMM avx2
.ar3:
%if WIN64
- ; xmm6 and xmm7 already saved
%assign stack_offset 32
- %assign xmm_regs_used 14 + %2
%assign stack_size_padded 152
SUB rsp, stack_size_padded
- movaps [rsp+16*2], xmm8
- movaps [rsp+16*3], xmm9
- movaps [rsp+16*4], xmm10
- movaps [rsp+16*5], xmm11
- movaps [rsp+16*6], xmm12
- movaps [rsp+16*7], xmm13
-%if %2
- movaps [rsp+16*8], xmm14
-%endif
+ WIN64_PUSH_XMM 14 + %2, 8
%endif
DEFINE_ARGS buf, bufy, fg_data, uv, bdmax, shift
mov shiftd, [fg_dataq+FGData.ar_coeff_shift]
diff --git a/third_party/dav1d/src/x86/filmgrain16_sse.asm b/third_party/dav1d/src/x86/filmgrain16_sse.asm
index 6b0daaac0b..25d01caa19 100644
--- a/third_party/dav1d/src/x86/filmgrain16_sse.asm
+++ b/third_party/dav1d/src/x86/filmgrain16_sse.asm
@@ -275,7 +275,6 @@ cglobal generate_grain_y_16bpc, 3, 6, 8, buf, fg_data, bdmax
.ar2:
%if ARCH_X86_32
-%assign stack_offset_old stack_offset
ALLOC_STACK -16*8
%endif
DEFINE_ARGS buf, fg_data, bdmax, shift
@@ -428,7 +427,6 @@ cglobal generate_grain_y_16bpc, 3, 6, 8, buf, fg_data, bdmax
%elif ARCH_X86_64
%define tmp rsp+stack_offset-72
%else
-%assign stack_offset stack_offset_old
ALLOC_STACK -16*12
%define tmp rsp
mov bdmaxd, bdmaxm
@@ -715,7 +713,6 @@ cglobal generate_grain_uv_%1_16bpc, 1, 7, 8, buf, x, pic_reg, fg_data, h
DEFINE_ARGS buf, bufy, fg_data, uv, bdmax, shift
%else
DEFINE_ARGS buf, bufy, pic_reg, fg_data, uv, shift
-%assign stack_offset_old stack_offset
ALLOC_STACK -16*2
mov bufyq, r1m
mov uvd, r3m
@@ -831,9 +828,7 @@ cglobal generate_grain_uv_%1_16bpc, 1, 7, 8, buf, x, pic_reg, fg_data, h
%if ARCH_X86_64
DEFINE_ARGS buf, bufy, fg_data, uv, max, cf3, min, val3, x
%else
-%assign stack_offset stack_offset_old
-%xdefine rstk rsp
-%assign stack_size_padded 0
+ RESET_STACK_STATE
DEFINE_ARGS buf, shift, pic_reg, fg_data, uv, bufy, cf3
mov bufyq, r1m
mov uvd, r3m
@@ -1159,7 +1154,6 @@ cglobal generate_grain_uv_%1_16bpc, 1, 7, 8, buf, x, pic_reg, fg_data, h
%endif
%else
DEFINE_ARGS buf, bufy, pic_reg, fg_data, uv, shift
-%assign stack_offset stack_offset_old
ALLOC_STACK -16*14
mov bufyq, r1m
mov uvd, r3m
diff --git a/third_party/dav1d/src/x86/filmgrain_avx2.asm b/third_party/dav1d/src/x86/filmgrain_avx2.asm
index 55445cf593..91d8ca5c14 100644
--- a/third_party/dav1d/src/x86/filmgrain_avx2.asm
+++ b/third_party/dav1d/src/x86/filmgrain_avx2.asm
@@ -204,18 +204,9 @@ cglobal generate_grain_y_8bpc, 2, 9, 8, buf, fg_data
.ar2:
%if WIN64
- ; xmm6 and xmm7 already saved
- %assign xmm_regs_used 16
%assign stack_size_padded 168
SUB rsp, stack_size_padded
- movaps [rsp+16*2], xmm8
- movaps [rsp+16*3], xmm9
- movaps [rsp+16*4], xmm10
- movaps [rsp+16*5], xmm11
- movaps [rsp+16*6], xmm12
- movaps [rsp+16*7], xmm13
- movaps [rsp+16*8], xmm14
- movaps [rsp+16*9], xmm15
+ WIN64_PUSH_XMM 16, 8
%endif
DEFINE_ARGS buf, fg_data, h, x
mov r6d, [fg_dataq+FGData.ar_coeff_shift]
@@ -287,15 +278,9 @@ cglobal generate_grain_y_8bpc, 2, 9, 8, buf, fg_data
INIT_YMM avx2
.ar3:
%if WIN64
- ; xmm6 and xmm7 already saved
- %assign stack_offset 16
ALLOC_STACK 16*14
%assign stack_size stack_size - 16*4
- %assign xmm_regs_used 12
- movaps [rsp+16*12], xmm8
- movaps [rsp+16*13], xmm9
- movaps [rsp+16*14], xmm10
- movaps [rsp+16*15], xmm11
+ WIN64_PUSH_XMM 12, 8
%else
ALLOC_STACK 16*12
%endif
diff --git a/third_party/dav1d/src/x86/filmgrain_sse.asm b/third_party/dav1d/src/x86/filmgrain_sse.asm
index 0172f98760..d06e349a8c 100644
--- a/third_party/dav1d/src/x86/filmgrain_sse.asm
+++ b/third_party/dav1d/src/x86/filmgrain_sse.asm
@@ -232,7 +232,6 @@ cglobal generate_grain_y_8bpc, 2, 7 + 2 * ARCH_X86_64, 16, buf, fg_data
.ar2:
%if ARCH_X86_32
-%assign stack_offset_old stack_offset
ALLOC_STACK -16*8
%endif
DEFINE_ARGS buf, fg_data, shift
@@ -333,7 +332,6 @@ cglobal generate_grain_y_8bpc, 2, 7 + 2 * ARCH_X86_64, 16, buf, fg_data
.ar3:
DEFINE_ARGS buf, fg_data, shift
%if ARCH_X86_32
-%assign stack_offset stack_offset_old
ALLOC_STACK -16*14
%elif WIN64
SUB rsp, 16*6
@@ -601,7 +599,6 @@ cglobal generate_grain_uv_%1_8bpc, 1, 7 + 3 * ARCH_X86_64, 16, buf, bufy, fg_dat
DEFINE_ARGS buf, bufy, fg_data, uv, unused, shift
movifnidn bufyq, bufymp
%if ARCH_X86_32
-%assign stack_offset_old stack_offset
ALLOC_STACK -2*16
%endif
imul uvd, 28
@@ -738,9 +735,7 @@ cglobal generate_grain_uv_%1_8bpc, 1, 7 + 3 * ARCH_X86_64, 16, buf, bufy, fg_dat
.ar1:
%if ARCH_X86_32
-%assign stack_offset stack_offset_old
-%assign stack_size_padded 0
-%xdefine rstk rsp
+ RESET_STACK_STATE
%endif
DEFINE_ARGS buf, bufy, fg_data, uv, val3, cf3, min, max, x
imul uvd, 28
@@ -881,9 +876,6 @@ cglobal generate_grain_uv_%1_8bpc, 1, 7 + 3 * ARCH_X86_64, 16, buf, bufy, fg_dat
.ar2:
%if ARCH_X86_32
-%assign stack_offset stack_offset_old
-%assign stack_size_padded 0
-%xdefine rstk rsp
ALLOC_STACK -8*16
%endif
DEFINE_ARGS buf, bufy, fg_data, uv, unused, shift
@@ -1014,9 +1006,7 @@ cglobal generate_grain_uv_%1_8bpc, 1, 7 + 3 * ARCH_X86_64, 16, buf, bufy, fg_dat
.ar3:
%if ARCH_X86_32
-%assign stack_offset stack_offset_old
-%assign stack_size_padded 0
-%xdefine rstk rsp
+ RESET_STACK_STATE
%endif
DEFINE_ARGS buf, bufy, fg_data, uv, unused, shift
movifnidn bufyq, bufymp
diff --git a/third_party/dav1d/src/x86/ipred16_avx2.asm b/third_party/dav1d/src/x86/ipred16_avx2.asm
index f4931e977b..7b52abaa10 100644
--- a/third_party/dav1d/src/x86/ipred16_avx2.asm
+++ b/third_party/dav1d/src/x86/ipred16_avx2.asm
@@ -946,7 +946,6 @@ cglobal ipred_smooth_16bpc, 3, 7, 6, dst, stride, tl, w, h, v_weights
jg .w4_loop
RET
.w8:
-%assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 12
vpbroadcastw m0, [tlq] ; bottom
vbroadcasti128 m7, [tlq+hq*2+2]
@@ -974,7 +973,6 @@ cglobal ipred_smooth_16bpc, 3, 7, 6, dst, stride, tl, w, h, v_weights
jg .w8_loop
RET
.w16:
-%assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 11
vpbroadcastw m0, [tlq] ; bottom
movu m7, [tlq+hq*2+2]
@@ -1005,7 +1003,6 @@ cglobal ipred_smooth_16bpc, 3, 7, 6, dst, stride, tl, w, h, v_weights
jg .w16_loop
RET
.w32:
-%assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 15
vpbroadcastw m0, [tlq] ; bottom
movu m7, [tlq+hq*2+ 2]
@@ -1047,7 +1044,6 @@ cglobal ipred_smooth_16bpc, 3, 7, 6, dst, stride, tl, w, h, v_weights
jg .w32_loop
RET
.w64:
-%assign stack_offset stack_offset - stack_size_padded
PROLOGUE 0, 11, 16, dst, stride, tl, tl_base, h, v_weights, dummy, v_weights_base, x, y, dst_base
mov dst_baseq, dstq
mov tl_baseq, tlq
@@ -1104,7 +1100,6 @@ cglobal ipred_smooth_16bpc, 3, 7, 6, dst, stride, tl, w, h, v_weights
RET
cglobal ipred_z1_16bpc, 3, 8, 0, dst, stride, tl, w, h, angle, dx, maxbase
- %assign org_stack_offset stack_offset
lea r6, [ipred_z1_16bpc_avx2_table]
tzcnt wd, wm
movifnidn angled, anglem
@@ -1312,7 +1307,6 @@ ALIGN function_align
.w4_end:
RET
.w8:
- %assign stack_offset org_stack_offset
ALLOC_STACK -64, 7
lea r3d, [angleq+216]
mov r3b, hb
@@ -1476,7 +1470,6 @@ ALIGN function_align
or maxbased, 16 ; imin(h+15, 31)
jmp .w16_main
.w16:
- %assign stack_offset org_stack_offset
ALLOC_STACK -96, 7
lea maxbased, [hq+15]
test angled, 0x400
@@ -1622,7 +1615,6 @@ ALIGN function_align
.w16_end:
RET
.w32:
- %assign stack_offset org_stack_offset
ALLOC_STACK -160, 8
lea maxbased, [hq+31]
mov r3d, 63
@@ -1737,7 +1729,6 @@ ALIGN function_align
.w32_end:
RET
.w64:
- %assign stack_offset org_stack_offset
ALLOC_STACK -256, 10
lea maxbased, [hq+63]
test angled, 0x400
@@ -2691,7 +2682,6 @@ ALIGN function_align
jmp .w32_filter_above
cglobal ipred_z3_16bpc, 4, 9, 0, dst, stride, tl, w, h, angle, dy, org_w, maxbase
- %assign org_stack_offset stack_offset
lea r6, [ipred_z3_16bpc_avx2_table]
tzcnt hd, hm
movifnidn angled, anglem
@@ -2907,7 +2897,6 @@ ALIGN function_align
RET
.h8:
lea r4d, [angleq+216]
- %assign stack_offset org_stack_offset
ALLOC_STACK -64, 8
mov r4b, wb
lea r7, [strideq*3]
@@ -3155,7 +3144,6 @@ ALIGN function_align
jmp .h16_main
ALIGN function_align
.h16:
- %assign stack_offset org_stack_offset
ALLOC_STACK -96, 10
lea maxbased, [wq+15]
lea r7, [strideq*3]
@@ -3372,7 +3360,6 @@ ALIGN function_align
.h16_end:
RET
.h32:
- %assign stack_offset org_stack_offset
ALLOC_STACK -160, 9
lea maxbased, [wq+31]
and maxbased, 31
@@ -3557,7 +3544,6 @@ ALIGN function_align
.h32_end:
RET
.h64:
- %assign stack_offset org_stack_offset
ALLOC_STACK -256, 10
lea maxbased, [wq+63]
test angled, 0x400
@@ -3804,7 +3790,6 @@ ALIGN function_align
; 5 8 8 i
cglobal ipred_filter_16bpc, 3, 9, 0, dst, stride, tl, w, h, filter
-%assign org_stack_offset stack_offset
%define base r6-ipred_filter_16bpc_avx2_table
lea r6, [filter_intra_taps]
tzcnt wd, wm
@@ -3846,7 +3831,6 @@ cglobal ipred_filter_16bpc, 3, 9, 0, dst, stride, tl, w, h, filter
RET
ALIGN function_align
.w8:
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 16
vbroadcasti128 m14, [base+filter_shuf3]
vpbroadcastw m15, r8m ; bitdepth_max
@@ -3883,7 +3867,6 @@ ALIGN function_align
RET
ALIGN function_align
.w16:
- %assign stack_offset stack_offset - stack_size_padded
ALLOC_STACK 32, 16
vpbroadcastw m15, r8m ; bitdepth_max
sub hd, 2
@@ -3977,7 +3960,6 @@ ALIGN function_align
ret
ALIGN function_align
.w32:
- %assign stack_offset org_stack_offset
ALLOC_STACK 64, 16
vpbroadcastw m15, r8m ; bitdepth_max
sub hd, 2
diff --git a/third_party/dav1d/src/x86/ipred_avx2.asm b/third_party/dav1d/src/x86/ipred_avx2.asm
index 58e40935ac..35738e7c0b 100644
--- a/third_party/dav1d/src/x86/ipred_avx2.asm
+++ b/third_party/dav1d/src/x86/ipred_avx2.asm
@@ -772,7 +772,6 @@ ALIGN function_align
RET
ALIGN function_align
.w32:
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 6
movu m3, [tlq+1]
punpcklbw m2, m3, m5
@@ -823,29 +822,17 @@ ALIGN function_align
jl .w64_loop
RET
-%macro SETUP_STACK_FRAME 3 ; stack_size, regs_used, xmm_regs_used
- %assign stack_offset 0
- %assign stack_size_padded 0
- %assign regs_used %2
- %xdefine rstk rsp
- SETUP_STACK_POINTER %1
- %if regs_used != %2 && WIN64
- PUSH r%2
- %endif
- ALLOC_STACK %1, %3
-%endmacro
-
cglobal ipred_smooth_h_8bpc, 3, 7, 0, dst, stride, tl, w, h
-%define base r6-ipred_smooth_h_avx2_table
- lea r6, [ipred_smooth_h_avx2_table]
+%define base r5-ipred_smooth_h_avx2_table
+ lea r5, [ipred_smooth_h_avx2_table]
mov wd, wm
vpbroadcastb m3, [tlq+wq] ; right
tzcnt wd, wd
mov hd, hm
- movsxd wq, [r6+wq*4]
+ movsxd wq, [r5+wq*4]
vpbroadcastd m4, [base+pb_127_m127]
vpbroadcastd m5, [base+pw_128]
- add wq, r6
+ add wq, r5
jmp wq
.w4:
WIN64_SPILL_XMM 8
@@ -891,7 +878,6 @@ cglobal ipred_smooth_h_8bpc, 3, 7, 0, dst, stride, tl, w, h
RET
ALIGN function_align
.w8:
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 8
vbroadcasti128 m6, [base+smooth_weights+8*2]
mova m7, [base+ipred_h_shuf]
@@ -927,7 +913,7 @@ ALIGN function_align
RET
ALIGN function_align
.w16:
- SETUP_STACK_FRAME 32*4, 7, 8
+ ALLOC_STACK 32*4, 8
lea r3, [rsp+64*2-4]
call .prep ; only worthwhile for for w16 and above
sub tlq, 2
@@ -951,7 +937,7 @@ ALIGN function_align
RET
ALIGN function_align
.w32:
- SETUP_STACK_FRAME 32*4, 7, 6
+ ALLOC_STACK 32*4
lea r3, [rsp+64*2-2]
call .prep
dec tlq
@@ -971,19 +957,19 @@ ALIGN function_align
RET
ALIGN function_align
.w64:
- SETUP_STACK_FRAME 32*4, 7, 9
+ ALLOC_STACK 32*4, 9
lea r3, [rsp+64*2-2]
call .prep
- add r6, smooth_weights+16*15-ipred_smooth_h_avx2_table
+ add r5, smooth_weights+16*15-ipred_smooth_h_avx2_table
dec tlq
- mova xm5, [r6-16*7]
- vinserti128 m5, [r6-16*5], 1
- mova xm6, [r6-16*6]
- vinserti128 m6, [r6-16*4], 1
- mova xm7, [r6-16*3]
- vinserti128 m7, [r6-16*1], 1
- mova xm8, [r6-16*2]
- vinserti128 m8, [r6-16*0], 1
+ mova xm5, [r5-16*7]
+ vinserti128 m5, [r5-16*5], 1
+ mova xm6, [r5-16*6]
+ vinserti128 m6, [r5-16*4], 1
+ mova xm7, [r5-16*3]
+ vinserti128 m7, [r5-16*1], 1
+ mova xm8, [r5-16*2]
+ vinserti128 m8, [r5-16*0], 1
.w64_loop:
vpbroadcastb m2, [tlq+hq]
punpcklbw m2, m3
@@ -1113,7 +1099,6 @@ cglobal ipred_smooth_8bpc, 3, 7, 0, dst, stride, tl, w, h, v_weights
RET
ALIGN function_align
.w8:
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 12
mova m10, [base+ipred_h_shuf]
vbroadcasti128 m11, [base+smooth_weights+8*2]
@@ -1157,7 +1142,9 @@ ALIGN function_align
RET
ALIGN function_align
.w16:
- SETUP_STACK_FRAME 32*4, 7, 14
+ %assign regs_used 4
+ ALLOC_STACK -32*4, 14
+ %assign regs_used 7
vbroadcasti128 m11, [tlq+1]
lea r3, [rsp+64*2-4]
punpcklbw m10, m11, m0 ; top, bottom
@@ -1197,7 +1184,9 @@ ALIGN function_align
RET
ALIGN function_align
.w32:
- SETUP_STACK_FRAME 32*4, 7, 11
+ %assign regs_used 4
+ ALLOC_STACK -32*4, 11
+ %assign regs_used 7
movu m8, [tlq+1]
lea r3, [rsp+64*2-2]
punpcklbw m7, m8, m0
@@ -1232,7 +1221,9 @@ ALIGN function_align
RET
ALIGN function_align
.w64:
- SETUP_STACK_FRAME 32*8, 7, 16
+ %assign regs_used 4
+ ALLOC_STACK -32*8, 16
+ %assign regs_used 7
movu m13, [tlq+1 ]
movu m15, [tlq+33]
add r6, smooth_weights+16*15-ipred_smooth_avx2_table
@@ -1316,7 +1307,6 @@ ALIGN function_align
ret
cglobal ipred_z1_8bpc, 3, 8, 0, dst, stride, tl, w, h, angle, dx, maxbase
- %assign org_stack_offset stack_offset
lea r6, [ipred_z1_avx2_table]
tzcnt wd, wm
movifnidn angled, anglem
@@ -1415,7 +1405,6 @@ ALIGN function_align
pmovmskb r5d, m1
ret
.w4_no_upsample:
- %assign stack_offset org_stack_offset
ALLOC_STACK -16, 11
mov maxbased, 7
test angled, 0x400 ; !enable_intra_edge_filter
@@ -1522,7 +1511,6 @@ ALIGN function_align
mov r3b, hb
cmp r3d, 8
ja .w8_no_upsample ; !enable_intra_edge_filter || is_sm || d >= 40 || h > 8
- %assign stack_offset org_stack_offset
ALLOC_STACK -32, 8
movu xm2, [z_filter_s+6]
mova xm0, [tlq-1]
@@ -1592,7 +1580,6 @@ ALIGN function_align
or maxbased, 8 ; imin(h+7, 15)
jmp .w8_main
.w8_no_upsample:
- %assign stack_offset org_stack_offset
ALLOC_STACK -32, 10
lea maxbased, [hq+7]
test angled, 0x400
@@ -1696,7 +1683,6 @@ ALIGN function_align
jmp .w16_main
ALIGN function_align
.w16:
- %assign stack_offset org_stack_offset
ALLOC_STACK -64, 12
lea maxbased, [hq+15]
test angled, 0x400
@@ -1816,7 +1802,6 @@ ALIGN function_align
RET
ALIGN function_align
.w32:
- %assign stack_offset org_stack_offset
ALLOC_STACK -96, 15
lea r3d, [hq+31]
mov maxbased, 63
@@ -1960,7 +1945,6 @@ ALIGN function_align
RET
ALIGN function_align
.w64:
- %assign stack_offset org_stack_offset
ALLOC_STACK -128, 16
lea maxbased, [hq+63]
test angled, 0x400 ; !enable_intra_edge_filter
@@ -3001,7 +2985,6 @@ ALIGN function_align
jmp .w32_filter_above
cglobal ipred_z3_8bpc, 4, 9, 0, dst, stride, tl, w, h, angle, dy, org_w, maxbase
- %assign org_stack_offset stack_offset
lea r6, [ipred_z3_avx2_table]
tzcnt hd, hm
movifnidn angled, anglem
@@ -3102,7 +3085,6 @@ ALIGN function_align
pmovmskb r5d, m1
ret
.h4_no_upsample:
- %assign stack_offset org_stack_offset
ALLOC_STACK -16, 12
mov maxbased, 7
test angled, 0x400 ; !enable_intra_edge_filter
@@ -3215,7 +3197,6 @@ ALIGN function_align
mov r4b, wb
cmp r4d, 8
ja .h8_no_upsample ; !enable_intra_edge_filter || is_sm || d >= 40 || w > 8
- %assign stack_offset org_stack_offset
ALLOC_STACK -32, 8
and r4d, 4
mova xm0, [tlq-15]
@@ -3297,7 +3278,6 @@ ALIGN function_align
or maxbased, 8 ; imin(w+7, 15)
jmp .h8_main
.h8_no_upsample:
- %assign stack_offset org_stack_offset
ALLOC_STACK -32, 10
lea maxbased, [wq+7]
test angled, 0x400
@@ -3455,7 +3435,6 @@ ALIGN function_align
jmp .h16_main
ALIGN function_align
.h16:
- %assign stack_offset org_stack_offset
ALLOC_STACK -64, 12
lea maxbased, [wq+15]
test angled, 0x400
@@ -3661,7 +3640,6 @@ ALIGN function_align
RET
ALIGN function_align
.h32:
- %assign stack_offset org_stack_offset
ALLOC_STACK -96, 15
lea maxbased, [wq+31]
and maxbased, 31
@@ -3890,7 +3868,6 @@ ALIGN function_align
RET
ALIGN function_align
.h64:
- %assign stack_offset org_stack_offset
ALLOC_STACK -128, 16
lea maxbased, [wq+63]
test angled, 0x400 ; !enable_intra_edge_filter
@@ -4221,6 +4198,7 @@ cglobal ipred_filter_8bpc, 3, 7, 0, dst, stride, tl, w, h, filter
movzx filterd, byte filterm
%endif
shl filterd, 6
+ WIN64_SPILL_XMM 9, 15
add filterq, r6
lea r6, [ipred_filter_avx2_table]
movq xm0, [tlq-3] ; _ 6 5 0 1 2 3 4
@@ -4234,7 +4212,6 @@ cglobal ipred_filter_8bpc, 3, 7, 0, dst, stride, tl, w, h, filter
mov hd, hm
jmp wq
.w4:
- WIN64_SPILL_XMM 9
mova xm8, [base+filter_shuf2]
sub tlq, 3
sub tlq, hq
@@ -4251,8 +4228,7 @@ cglobal ipred_filter_8bpc, 3, 7, 0, dst, stride, tl, w, h, filter
RET
ALIGN function_align
.w8:
- %assign stack_offset stack_offset - stack_size_padded
- WIN64_SPILL_XMM 10
+ WIN64_PUSH_XMM 10
mova m8, [base+filter_shuf1]
FILTER_XMM 7, 0, 6, [base+filter_shuf2]
vpbroadcastd m0, [tlq+4]
@@ -4278,26 +4254,18 @@ ALIGN function_align
RET
ALIGN function_align
.w16:
-%if WIN64
- %assign stack_offset stack_offset - stack_size_padded
- %assign xmm_regs_used 15
- %assign stack_size_padded 0x98
- SUB rsp, stack_size_padded
-%endif
sub hd, 2
- TAIL_CALL .w16_main, 0
-.w16_main:
+ call .w16_main
%if WIN64
- movaps [rsp+0xa8], xmm6
- movaps [rsp+0xb8], xmm7
- movaps [rsp+0x28], xmm8
- movaps [rsp+0x38], xmm9
- movaps [rsp+0x48], xmm10
- movaps [rsp+0x58], xmm11
- movaps [rsp+0x68], xmm12
- movaps [rsp+0x78], xmm13
- movaps [rsp+0x88], xmm14
+ jmp .end
+%else
+ RET
%endif
+.w16_main:
+ ; The spills are into the callers stack frame
+ %assign stack_size stack_size + gprsize
+ WIN64_PUSH_XMM 15, 9
+ %assign stack_size stack_size - gprsize
FILTER_XMM 12, 0, 7, [base+filter_shuf2]
vpbroadcastd m0, [tlq+5]
vpblendd m0, [tlq-12], 0x14
@@ -4350,7 +4318,6 @@ ALIGN function_align
ret
ALIGN function_align
.w32:
- sub rsp, stack_size_padded
sub hd, 2
lea r3, [dstq+16]
lea r5d, [hq-2]
@@ -4415,6 +4382,7 @@ ALIGN function_align
shufps xm6, xm12, xm6, q3131 ; d0 d1 d2 d3
mova [dstq+strideq*0], xm0
mova [dstq+strideq*1], xm6
+.end:
RET
ALIGN function_align
.main:
diff --git a/third_party/dav1d/src/x86/ipred_sse.asm b/third_party/dav1d/src/x86/ipred_sse.asm
index 976f33a24b..f6b0cad001 100644
--- a/third_party/dav1d/src/x86/ipred_sse.asm
+++ b/third_party/dav1d/src/x86/ipred_sse.asm
@@ -670,10 +670,7 @@ ALIGN function_align
RET
ALIGN function_align
.w32:
-%if WIN64
- movaps [rsp+24], xmm7
- %define xmm_regs_used 8
-%endif
+ WIN64_PUSH_XMM 8, 7
mova m7, m5
.w32_loop_init:
mov r3d, 2
@@ -705,10 +702,7 @@ ALIGN function_align
RET
ALIGN function_align
.w64:
-%if WIN64
- movaps [rsp+24], xmm7
- %define xmm_regs_used 8
-%endif
+ WIN64_PUSH_XMM 8, 7
mova m7, m5
.w64_loop_init:
mov r3d, 4
diff --git a/third_party/dav1d/src/x86/looprestoration_sse.asm b/third_party/dav1d/src/x86/looprestoration_sse.asm
index 01eb6fa348..b5c73a51d4 100644
--- a/third_party/dav1d/src/x86/looprestoration_sse.asm
+++ b/third_party/dav1d/src/x86/looprestoration_sse.asm
@@ -42,7 +42,6 @@ pb_0to15: db 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
pb_right_ext_mask: times 24 db 0xff
times 8 db 0
pb_1: times 16 db 1
-pb_3: times 16 db 3
pw_256: times 8 dw 256
pw_2056: times 8 dw 2056
pw_m16380: times 8 dw -16380
@@ -290,7 +289,7 @@ cglobal wiener_filter7_8bpc, 0, 7, 8, -384*12-stk_off, _, x, left, lpf, tmpstrid
call mangle(private_prefix %+ _wiener_filter7_8bpc_ssse3).v
jmp .v1
.extend_right:
- movd m2, [lpfq-4]
+ movd m2, [lpfq-1]
%if ARCH_X86_64
push r0
lea r0, [pb_right_ext_mask+21]
@@ -302,10 +301,11 @@ cglobal wiener_filter7_8bpc, 0, 7, 8, -384*12-stk_off, _, x, left, lpf, tmpstrid
movu m1, [r6+xq+8]
%endif
%if cpuflag(ssse3)
- pshufb m2, [base+pb_3]
+ pxor m3, m3
+ pshufb m2, m3
%else
punpcklbw m2, m2
- pshuflw m2, m2, q3333
+ pshuflw m2, m2, q0000
punpcklqdq m2, m2
%endif
pand m4, m0
diff --git a/third_party/dav1d/src/x86/mc16_avx2.asm b/third_party/dav1d/src/x86/mc16_avx2.asm
index 61eeaa1007..42e2a5525e 100644
--- a/third_party/dav1d/src/x86/mc16_avx2.asm
+++ b/third_party/dav1d/src/x86/mc16_avx2.asm
@@ -1337,7 +1337,6 @@ cglobal put_8tap_16bpc, 4, 9, 0, dst, ds, src, ss, w, h, mx, my
cmp wd, 4
je .h_w4
jl .h_w2
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 13
shr mxd, 16
sub srcq, 6
@@ -1415,7 +1414,6 @@ cglobal put_8tap_16bpc, 4, 9, 0, dst, ds, src, ss, w, h, mx, my
cmp hd, 4
cmovle myd, mxd
vpbroadcastq m0, [base+subpel_filters+myq*8]
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 15
vpbroadcastd m6, [pd_32]
vpbroadcastw m7, r8m
@@ -1590,7 +1588,6 @@ cglobal put_8tap_16bpc, 4, 9, 0, dst, ds, src, ss, w, h, mx, my
jg .v_w8_loop0
RET
.hv:
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 16
vpbroadcastw m15, r8m
cmp wd, 4
@@ -2046,7 +2043,6 @@ cglobal prep_8tap_16bpc, 4, 8, 0, tmp, src, stride, w, h, mx, my
shr mxd, 16
sub srcq, 6
vpbroadcastq m0, [base+subpel_filters+mxq*8]
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 12
vbroadcasti128 m6, [subpel_h_shufA]
vbroadcasti128 m7, [subpel_h_shufB]
@@ -2125,7 +2121,6 @@ cglobal prep_8tap_16bpc, 4, 8, 0, tmp, src, stride, w, h, mx, my
cmp hd, 4
cmovle myd, mxd
vpbroadcastq m0, [base+subpel_filters+myq*8]
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 15
vpbroadcastd m7, [prep_8tap_1d_rnd]
lea r6, [strideq*3]
@@ -2264,7 +2259,6 @@ cglobal prep_8tap_16bpc, 4, 8, 0, tmp, src, stride, w, h, mx, my
%endif
RET
.hv:
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 16
vpbroadcastd m15, [prep_8tap_2d_rnd]
cmp wd, 4
diff --git a/third_party/dav1d/src/x86/mc16_avx512.asm b/third_party/dav1d/src/x86/mc16_avx512.asm
index 585ba53e08..e5de7ecd96 100644
--- a/third_party/dav1d/src/x86/mc16_avx512.asm
+++ b/third_party/dav1d/src/x86/mc16_avx512.asm
@@ -2377,7 +2377,6 @@ cglobal put_8tap_16bpc, 4, 9, 16, dst, ds, src, ss, w, h, mx, my
jg .hv_w16_loop
RET
.hv_w32:
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 32
vbroadcasti32x4 m20, [spel_h_shufA]
vbroadcasti32x4 m21, [spel_h_shufB]
@@ -3175,7 +3174,6 @@ cglobal prep_8tap_16bpc, 3, 8, 16, tmp, src, stride, w, h, mx, my, stride3
jg .hv_w8_loop
RET
.hv_w16:
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 27
vbroadcasti32x8 m5, [srcq+strideq*0+ 8]
vinserti32x8 m4, m5, [srcq+strideq*0+ 0], 0
@@ -3313,7 +3311,6 @@ cglobal prep_8tap_16bpc, 3, 8, 16, tmp, src, stride, w, h, mx, my, stride3
RET
.hv_w32:
%if WIN64
- %assign stack_offset stack_offset - stack_size_padded
PUSH r8
%assign regs_used regs_used + 1
WIN64_SPILL_XMM 32
diff --git a/third_party/dav1d/src/x86/mc16_sse.asm b/third_party/dav1d/src/x86/mc16_sse.asm
index fde8e372a3..b0c42597f7 100644
--- a/third_party/dav1d/src/x86/mc16_sse.asm
+++ b/third_party/dav1d/src/x86/mc16_sse.asm
@@ -1302,10 +1302,7 @@ cglobal put_8tap_16bpc, 4, 9, 0, dst, ds, src, ss, w, h, mx, my
jg .h_w4_loop
RET
.h_w8:
-%if WIN64
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 12
-%endif
shr mxd, 16
movq m3, [base+subpel_filters+mxq*8]
movifnidn dstq, dstmp
@@ -1383,14 +1380,7 @@ cglobal put_8tap_16bpc, 4, 9, 0, dst, ds, src, ss, w, h, mx, my
cmp hd, 6
cmovb myd, mxd
movq m3, [base+subpel_filters+myq*8]
-%if STACK_ALIGNMENT < 16
- %xdefine rstk rsp
-%else
- %assign stack_offset stack_offset - stack_size_padded
-%endif
-%if WIN64
WIN64_SPILL_XMM 15
-%endif
movd m7, r8m
movifnidn dstq, dstmp
movifnidn dsq, dsmp
@@ -1604,11 +1594,7 @@ cglobal put_8tap_16bpc, 4, 9, 0, dst, ds, src, ss, w, h, mx, my
jg .v_w4_loop0
RET
.hv:
-%if STACK_ALIGNMENT < 16
- %xdefine rstk rsp
-%else
- %assign stack_offset stack_offset - stack_size_padded
-%endif
+ RESET_STACK_STATE
%if ARCH_X86_32
movd m4, r8m
mova m6, [base+pd_512]
@@ -1750,11 +1736,7 @@ cglobal put_8tap_16bpc, 4, 9, 0, dst, ds, src, ss, w, h, mx, my
cmovb myd, mxd
movq m3, [base+subpel_filters+myq*8]
%if ARCH_X86_32
-%if STACK_ALIGNMENT < 16
- %xdefine rstk rsp
-%else
- %assign stack_offset stack_offset - stack_size_padded
-%endif
+ RESET_STACK_STATE
mov dstq, dstmp
mov dsq, dsmp
mova m0, [base+spel_h_shufA]
@@ -2182,11 +2164,6 @@ cglobal prep_8tap_16bpc, 4, 8, 0, tmp, src, ss, w, h, mx, my
cmp hd, 4
cmove myd, mxd
movq m3, [base+subpel_filters+myq*8]
-%if STACK_ALIGNMENT < 16
- %xdefine rstk rsp
-%else
- %assign stack_offset stack_offset - stack_size_padded
-%endif
WIN64_SPILL_XMM 15
movddup m7, [base+prep_8tap_1d_rnd]
movifnidn ssq, r2mp
@@ -2339,11 +2316,7 @@ cglobal prep_8tap_16bpc, 4, 8, 0, tmp, src, ss, w, h, mx, my
jg .v_loop0
RET
.hv:
-%if STACK_ALIGNMENT < 16
- %xdefine rstk rsp
-%else
- %assign stack_offset stack_offset - stack_size_padded
-%endif
+ RESET_STACK_STATE
movzx t3d, mxb
shr mxd, 16
cmp wd, 4
diff --git a/third_party/dav1d/src/x86/mc_avx2.asm b/third_party/dav1d/src/x86/mc_avx2.asm
index 3b208033bd..58e3cb5af1 100644
--- a/third_party/dav1d/src/x86/mc_avx2.asm
+++ b/third_party/dav1d/src/x86/mc_avx2.asm
@@ -1259,7 +1259,6 @@ cglobal prep_bilin_8bpc, 3, 7, 0, tmp, src, stride, w, h, mxy, stride3
.hv:
; (16 * src[x] + (my * (src[x + src_stride] - src[x])) + 8) >> 4
; = src[x] + (((my * (src[x + src_stride] - src[x])) + 8) >> 4)
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 7
movzx wd, word [r6+wq*2+table_offset(prep, _bilin_hv)]
shl mxyd, 11
@@ -1620,7 +1619,6 @@ cglobal put_8tap_8bpc, 4, 9, 0, dst, ds, src, ss, w, h, mx, my, ss3
jg .h_loop
RET
.v:
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 16
movzx mxd, myb
shr myd, 16
@@ -1834,7 +1832,6 @@ cglobal put_8tap_8bpc, 4, 9, 0, dst, ds, src, ss, w, h, mx, my, ss3
jg .v_w16_loop0
RET
.hv:
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 16
cmp wd, 4
jg .hv_w8
@@ -2247,7 +2244,6 @@ cglobal prep_8tap_8bpc, 3, 8, 0, tmp, src, stride, w, h, mx, my, stride3
jg .h_loop
RET
.v:
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 16
movzx mxd, myb ; Select 4-tap/8-tap filter multipliers.
shr myd, 16 ; Note that the code is 8-tap only, having
@@ -2430,8 +2426,6 @@ cglobal prep_8tap_8bpc, 3, 8, 0, tmp, src, stride, w, h, mx, my, stride3
jg .v_w16_loop0
RET
.hv:
- %assign stack_offset stack_offset - stack_size_padded
- %assign stack_size_padded 0
WIN64_SPILL_XMM 16
cmp wd, 4
je .hv_w4
@@ -4108,10 +4102,9 @@ cglobal warp_affine_8x8t_8bpc, 0, 14, 0, tmp, ts
cglobal warp_affine_8x8_8bpc, 0, 14, 0, dst, ds, src, ss, abcd, mx, tmp2, alpha, \
beta, filter, tmp1, delta, my, gamma
%if WIN64
- sub rsp, 0xa0
%assign xmm_regs_used 16
%assign stack_size_padded 0xa0
- %assign stack_offset stack_offset+stack_size_padded
+ SUB rsp, stack_size_padded
%endif
call .main
jmp .start
@@ -4134,21 +4127,13 @@ cglobal warp_affine_8x8_8bpc, 0, 14, 0, dst, ds, src, ss, abcd, mx, tmp2, alpha,
RET
ALIGN function_align
.main:
- ; Stack args offset by one (r4m -> r5m etc.) due to call
-%if WIN64
- mov abcdq, r5m
- mov mxd, r6m
- movaps [rsp+stack_offset+0x10], xmm6
- movaps [rsp+stack_offset+0x20], xmm7
- movaps [rsp+0x28], xmm8
- movaps [rsp+0x38], xmm9
- movaps [rsp+0x48], xmm10
- movaps [rsp+0x58], xmm11
- movaps [rsp+0x68], xmm12
- movaps [rsp+0x78], xmm13
- movaps [rsp+0x88], xmm14
- movaps [rsp+0x98], xmm15
-%endif
+ ; Stack is offset due to call
+ %assign stack_offset stack_offset + gprsize
+ %assign stack_size stack_size + gprsize
+ %assign stack_size_padded stack_size_padded + gprsize
+ movifnidn abcdq, abcdmp
+ movifnidn mxd, mxm
+ WIN64_PUSH_XMM
movsx alphad, word [abcdq+2*0]
movsx betad, word [abcdq+2*1]
mova m12, [warp_8x8_shufA]
@@ -4162,7 +4147,7 @@ ALIGN function_align
lea tmp2d, [alphaq*3]
sub srcq, tmp1q ; src -= src_stride*3 + 3
sub betad, tmp2d ; beta -= alpha*3
- mov myd, r7m
+ mov myd, r6m
call .h
psrld m1, m0, 16
call .h
diff --git a/third_party/dav1d/src/x86/mc_avx512.asm b/third_party/dav1d/src/x86/mc_avx512.asm
index 7897f1decc..f9043f1ad3 100644
--- a/third_party/dav1d/src/x86/mc_avx512.asm
+++ b/third_party/dav1d/src/x86/mc_avx512.asm
@@ -1276,7 +1276,6 @@ cglobal prep_bilin_8bpc, 3, 7, 0, tmp, src, stride, w, h, mxy, stride3
.hv:
; (16 * src[x] + (my * (src[x + src_stride] - src[x])) + 8) >> 4
; = src[x] + (((my * (src[x + src_stride] - src[x])) + 8) >> 4)
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 7
movzx wd, word [t2+wq*2+table_offset(prep, _bilin_hv)]
shl mxyd, 11
@@ -2853,8 +2852,6 @@ cglobal prep_8tap_8bpc, 3, 8, 0, tmp, src, stride, w, h, mx, my, stride3
jg .v_loop0
RET
.hv:
- %assign stack_offset stack_offset - stack_size_padded
- %assign stack_size_padded 0
WIN64_SPILL_XMM 16
cmp wd, 4
je .hv_w4
diff --git a/third_party/dav1d/src/x86/mc_sse.asm b/third_party/dav1d/src/x86/mc_sse.asm
index 54939c647a..a447a80161 100644
--- a/third_party/dav1d/src/x86/mc_sse.asm
+++ b/third_party/dav1d/src/x86/mc_sse.asm
@@ -1199,7 +1199,6 @@ cglobal prep_bilin_8bpc, 3, 7, 0, tmp, src, stride, w, h, mxy, stride3
RET
.v:
%if notcpuflag(ssse3)
- %assign stack_offset stack_offset - stack_size_padded
WIN64_SPILL_XMM 8
%endif
movzx wd, word [r6+wq*2+table_offset(prep, _bilin_v)]
@@ -1375,7 +1374,6 @@ cglobal prep_bilin_8bpc, 3, 7, 0, tmp, src, stride, w, h, mxy, stride3
; (16 * src[x] + (my * (src[x + src_stride] - src[x])) + 8) >> 4
; = src[x] + (((my * (src[x + src_stride] - src[x])) + 8) >> 4)
movzx wd, word [r6+wq*2+table_offset(prep, _bilin_hv)]
-%assign stack_offset stack_offset - stack_size_padded
%if cpuflag(ssse3)
imul mxyd, 0x08000800
WIN64_SPILL_XMM 8
@@ -1592,7 +1590,6 @@ FN put_8tap, regular, REGULAR, REGULAR
%endif
cglobal put_8tap_8bpc, 1, 9, 0, dst, ds, src, ss, w, h, mx, my, ss3
-%assign org_stack_offset stack_offset
imul mxd, mxm, 0x010101
add mxd, t0d ; 8tap_h, mx, 4tap_h
%if ARCH_X86_64
@@ -1618,7 +1615,6 @@ cglobal put_8tap_8bpc, 1, 9, 0, dst, ds, src, ss, w, h, mx, my, ss3
movzx wd, word [base_reg+wq*2+table_offset(put,)]
add wq, base_reg
; put_bilin mangling jump
-%assign stack_offset org_stack_offset
movifnidn dsq, dsmp
movifnidn ssq, ssmp
%if WIN64
@@ -1792,7 +1788,6 @@ cglobal put_8tap_8bpc, 1, 9, 0, dst, ds, src, ss, w, h, mx, my, ss3
cmovs ssd, mxd
movq m0, [base_reg+ssq*8+subpel_filters-put_ssse3]
%else
- %assign stack_offset org_stack_offset
WIN64_SPILL_XMM 16
movzx mxd, myb
shr myd, 16
@@ -2048,7 +2043,7 @@ cglobal put_8tap_8bpc, 1, 9, 0, dst, ds, src, ss, w, h, mx, my, ss3
%undef subpel2
%undef subpel3
.hv:
- %assign stack_offset org_stack_offset
+ RESET_STACK_STATE
cmp wd, 4
jg .hv_w8
%if ARCH_X86_32
@@ -2369,7 +2364,7 @@ cglobal put_8tap_8bpc, 1, 9, 0, dst, ds, src, ss, w, h, mx, my, ss3
%undef subpelv2
%undef subpelv3
.hv_w8:
- %assign stack_offset org_stack_offset
+ RESET_STACK_STATE
%define hv8_line_1 0
%define hv8_line_2 1
%define hv8_line_3 2
@@ -2843,7 +2838,6 @@ FN prep_8tap, regular, REGULAR, REGULAR
%define base 0
%endif
cglobal prep_8tap_8bpc, 1, 9, 0, tmp, src, stride, w, h, mx, my, stride3
-%assign org_stack_offset stack_offset
imul mxd, mxm, 0x010101
add mxd, t0d ; 8tap_h, mx, 4tap_h
imul myd, mym, 0x010101
@@ -2862,7 +2856,6 @@ cglobal prep_8tap_8bpc, 1, 9, 0, tmp, src, stride, w, h, mx, my, stride3
add wq, base_reg
movifnidn strided, stridem
lea r6, [strideq*3]
- %assign stack_offset org_stack_offset
%if WIN64
pop r8
pop r7
@@ -3095,7 +3088,6 @@ cglobal prep_8tap_8bpc, 1, 9, 0, tmp, src, stride, w, h, mx, my, stride3
mov mxd, myd
and mxd, 0x7f
%else
- %assign stack_offset org_stack_offset
WIN64_SPILL_XMM 16
movzx mxd, myb
%endif
@@ -3359,7 +3351,7 @@ cglobal prep_8tap_8bpc, 1, 9, 0, tmp, src, stride, w, h, mx, my, stride3
%undef subpel2
%undef subpel3
.hv:
- %assign stack_offset org_stack_offset
+ RESET_STACK_STATE
cmp wd, 4
jg .hv_w8
and mxd, 0x7f
@@ -3659,7 +3651,7 @@ cglobal prep_8tap_8bpc, 1, 9, 0, tmp, src, stride, w, h, mx, my, stride3
%undef subpelv2
%undef subpelv3
.hv_w8:
- %assign stack_offset org_stack_offset
+ RESET_STACK_STATE
%define hv8_line_1 0
%define hv8_line_2 1
%define hv8_line_3 2
diff --git a/third_party/dav1d/src/x86/msac.asm b/third_party/dav1d/src/x86/msac.asm
index 9f05c921a6..4156efe914 100644
--- a/third_party/dav1d/src/x86/msac.asm
+++ b/third_party/dav1d/src/x86/msac.asm
@@ -143,10 +143,9 @@ cglobal msac_decode_symbol_adapt4, 0, 6, 6
mov esp, [esp]
%endif
%endif
- not t4
sub t2d, t1d ; rng
shl t1, gprsize*8-16
- add t4, t1 ; ~dif
+ sub t4, t1 ; dif - v
.renorm3:
mov t1d, [t0+msac.cnt]
movifnidn t7, t0
@@ -157,33 +156,31 @@ cglobal msac_decode_symbol_adapt4, 0, 6, 6
shl t2d, cl
shl t4, cl
mov [t7+msac.rng], t2d
- not t4
sub t1d, ecx
jae .end ; no refill required
; refill:
- mov t2, [t7+msac.buf]
- mov rcx, [t7+msac.end]
%if ARCH_X86_64 == 0
push t5
%endif
- lea t5, [t2+gprsize]
- cmp t5, rcx
+ mov t2, [t7+msac.buf]
+ mov t5, [t7+msac.end]
+ lea rcx, [t2+gprsize]
+ sub rcx, t5
ja .refill_eob
- mov t2, [t2]
- lea ecx, [t1+23]
- add t1d, 16
- shr ecx, 3 ; shift_bytes
- bswap t2
- sub t5, rcx
- shl ecx, 3 ; shift_bits
- shr t2, cl
- sub ecx, t1d ; shift_bits - 16 - cnt
- mov t1d, gprsize*8-16
- shl t2, cl
- mov [t7+msac.buf], t5
- sub t1d, ecx ; cnt + gprsize*8 - shift_bits
- xor t4, t2
+ mov t5, [t2]
+ lea ecx, [t1+16-gprsize*8]
+ not t5
+ bswap t5
+ shr t5, cl
+ neg ecx
+ shr ecx, 3 ; num_bytes_read
+ or t4, t5
+.refill_end:
+ add t2, rcx
+ lea t1d, [t1+rcx*8] ; cnt += num_bits_read
+ mov [t7+msac.buf], t2
+.refill_end2:
%if ARCH_X86_64 == 0
pop t5
%endif
@@ -191,29 +188,35 @@ cglobal msac_decode_symbol_adapt4, 0, 6, 6
mov [t7+msac.cnt], t1d
mov [t7+msac.dif], t4
RET
+.pad_with_ones:
+ lea ecx, [t1-16]
+%if ARCH_X86_64
+ ror rcx, cl
+%else
+ shr ecx, cl
+%endif
+ or t4, rcx
+ jmp .refill_end2
.refill_eob: ; avoid overreading the input buffer
- mov t5, rcx
- mov ecx, gprsize*8-24
- sub ecx, t1d ; c
-.refill_eob_loop:
cmp t2, t5
- jae .refill_eob_end ; eob reached
- movzx t1d, byte [t2]
- inc t2
- shl t1, cl
- xor t4, t1
- sub ecx, 8
- jge .refill_eob_loop
-.refill_eob_end:
- mov t1d, gprsize*8-24
-%if ARCH_X86_64 == 0
- pop t5
-%endif
- sub t1d, ecx
- mov [t7+msac.buf], t2
- mov [t7+msac.dif], t4
- mov [t7+msac.cnt], t1d
- RET
+ jae .pad_with_ones ; eob reached
+ ; We can safely do a register-sized load of the last bytes of the buffer
+ ; as this code is only reached if the msac buffer size is >= gprsize.
+ mov t5, [t5-gprsize]
+ shl ecx, 3
+ shr t5, cl
+ lea ecx, [t1+16-gprsize*8]
+ not t5
+ bswap t5
+ shr t5, cl
+ neg ecx
+ or t4, t5
+ mov t5d, [t7+msac.end]
+ shr ecx, 3
+ sub t5d, t2d ; num_bytes_left
+ cmp ecx, t5d
+ cmovae ecx, t5d ; num_bytes_read
+ jmp .refill_end
cglobal msac_decode_symbol_adapt8, 0, 6, 6
DECODE_SYMBOL_ADAPT_INIT
@@ -366,7 +369,6 @@ cglobal msac_decode_bool_adapt, 0, 6, 0
%if ARCH_X86_64 == 0
movzx eax, al
%endif
- not t4
test t3d, t3d
jz m(msac_decode_symbol_adapt4, SUFFIX).renorm3
%if UNIX64 == 0
@@ -420,7 +422,6 @@ cglobal msac_decode_bool_equi, 0, 6, 0
mov ecx, 0xbfff
setb al ; the upper 32 bits contains garbage but that's OK
sub ecx, t2d
- not t4
; In this case of this function, (d =) 16 - clz(v) = 2 - (v >> 14)
; i.e. (0 <= d <= 2) and v < (3 << 14)
shr ecx, 14 ; d
@@ -447,7 +448,6 @@ cglobal msac_decode_bool, 0, 6, 0
cmovb t2d, t1d
cmovb t4, t3
setb al
- not t4
%if ARCH_X86_64 == 0
movzx eax, al
%endif
@@ -497,48 +497,45 @@ cglobal msac_decode_bool, 0, 6, 0
tzcnt eax, eax
movzx ecx, word [buf+rax+16]
movzx t2d, word [buf+rax+14]
- not t4
%if ARCH_X86_64
add t6d, 5
%endif
sub eax, 5 ; setup for merging the tok_br and tok branches
sub t2d, ecx
shl rcx, gprsize*8-16
- add t4, rcx
+ sub t4, rcx
bsr ecx, t2d
xor ecx, 15
shl t2d, cl
shl t4, cl
movd m2, t2d
mov [t7+msac.rng], t2d
- not t4
sub t5d, ecx
jae %%end
- mov t2, [t7+msac.buf]
- mov rcx, [t7+msac.end]
%if UNIX64 == 0
push t8
%endif
- lea t8, [t2+gprsize]
- cmp t8, rcx
+ mov t2, [t7+msac.buf]
+ mov t8, [t7+msac.end]
+ lea rcx, [t2+gprsize]
+ sub rcx, t8
ja %%refill_eob
- mov t2, [t2]
- lea ecx, [t5+23]
- add t5d, 16
+ mov t8, [t2]
+ lea ecx, [t5+16-gprsize*8]
+ not t8
+ bswap t8
+ shr t8, cl
+ neg ecx
shr ecx, 3
- bswap t2
- sub t8, rcx
- shl ecx, 3
- shr t2, cl
- sub ecx, t5d
- mov t5d, gprsize*8-16
- shl t2, cl
- mov [t7+msac.buf], t8
+ or t4, t8
+%%refill_end:
+ add t2, rcx
+ lea t5d, [t5+rcx*8]
+ mov [t7+msac.buf], t2
+%%refill_end2:
%if UNIX64 == 0
pop t8
%endif
- sub t5d, ecx
- xor t4, t2
%%end:
movp m3, t4
%if ARCH_X86_64
@@ -559,27 +556,34 @@ cglobal msac_decode_bool, 0, 6, 0
shr eax, 1
mov [t7+msac.cnt], t5d
RET
+%%pad_with_ones:
+ ; ensure that dif is padded with at least 15 bits of ones at the end
+ lea ecx, [t5-16]
+%if ARCH_X86_64
+ ror rcx, cl
+%else
+ shr ecx, cl
+%endif
+ or t4, rcx
+ jmp %%refill_end2
%%refill_eob:
- mov t8, rcx
- mov ecx, gprsize*8-24
- sub ecx, t5d
-%%refill_eob_loop:
cmp t2, t8
- jae %%refill_eob_end
- movzx t5d, byte [t2]
- inc t2
- shl t5, cl
- xor t4, t5
- sub ecx, 8
- jge %%refill_eob_loop
-%%refill_eob_end:
-%if UNIX64 == 0
- pop t8
-%endif
- mov t5d, gprsize*8-24
- mov [t7+msac.buf], t2
- sub t5d, ecx
- jmp %%end
+ jae %%pad_with_ones
+ mov t8, [t8-gprsize]
+ shl ecx, 3
+ shr t8, cl
+ lea ecx, [t5+16-gprsize*8]
+ not t8
+ bswap t8
+ shr t8, cl
+ neg ecx
+ or t4, t8
+ mov t8d, [t7+msac.end]
+ shr ecx, 3
+ sub t8d, t2d
+ cmp ecx, t8d
+ cmovae ecx, t8d
+ jmp %%refill_end
%endmacro
cglobal msac_decode_hi_tok, 0, 7 + ARCH_X86_64, 6
diff --git a/third_party/jpeg-xl/CHANGELOG.md b/third_party/jpeg-xl/CHANGELOG.md
index 3c62a1f2df..42f0f2e5c1 100644
--- a/third_party/jpeg-xl/CHANGELOG.md
+++ b/third_party/jpeg-xl/CHANGELOG.md
@@ -16,6 +16,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
+## [0.10.2] - 2024-03-08
+
+### Fixed
+ - bugs in (lossless) encoding (#3367, #3359 and #3386)
+ - re-enable installation of MIME file (#3375)
+ - bugs in streaming mode (#3379 and #3380)
+
+## [0.10.1] - 2024-02-28
+
+### Fixed
+ - reduce allocations (#3336 and #3339),
+ fixing a significant speed regression present since 0.9.0
+ - bug in streaming encoding (#3331)
+
## [0.10.0] - 2024-02-21
### Added
diff --git a/third_party/jpeg-xl/WORKSPACE b/third_party/jpeg-xl/WORKSPACE
index 1383b44e0d..db9fcddb6e 100644
--- a/third_party/jpeg-xl/WORKSPACE
+++ b/third_party/jpeg-xl/WORKSPACE
@@ -1,5 +1,5 @@
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository", "new_git_repository")
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
workspace(name = "libjxl")
diff --git a/third_party/jpeg-xl/debian/changelog b/third_party/jpeg-xl/debian/changelog
index 83f256f642..0722b1bd4a 100644
--- a/third_party/jpeg-xl/debian/changelog
+++ b/third_party/jpeg-xl/debian/changelog
@@ -1,8 +1,20 @@
-jpeg-xl (0.10.0) UNRELEASED; urgency=medium
+jpeg-xl (0.10.2) unstable; urgency=medium
+
+ * Bump JPEG XL version to 0.10.2.
+
+ -- JPEG XL Maintainers <jpegxl@google.com> Thu, 07 Mar 2024 13:50:05 +0100
+
+jpeg-xl (0.10.1) unstable; urgency=medium
+
+ * Bump JPEG XL version to 0.10.1.
+
+ -- JPEG XL Maintainers <jpegxl@google.com> Wed, 28 Feb 2024 12:52:20 +0100
+
+jpeg-xl (0.10.0) unstable; urgency=medium
* Bump JPEG XL version to 0.10.0.
- -- JPEG XL Maintainers <jpegxl@google.com> Tue, 21 Nov 2023 10:32:59 +0100
+ -- JPEG XL Maintainers <jpegxl@google.com> Wed, 21 Feb 2024 08:37:00 +0100
jpeg-xl (0.9.0) unstable; urgency=medium
diff --git a/third_party/jpeg-xl/examples/decode_exif_metadata.cc b/third_party/jpeg-xl/examples/decode_exif_metadata.cc
index d5f11705bd..65608621d9 100644
--- a/third_party/jpeg-xl/examples/decode_exif_metadata.cc
+++ b/third_party/jpeg-xl/examples/decode_exif_metadata.cc
@@ -97,7 +97,7 @@ bool LoadFile(const char* filename, std::vector<uint8_t>* out) {
return false;
}
- long size = ftell(file);
+ long size = ftell(file); // NOLINT
// Avoid invalid file or directory.
if (size >= LONG_MAX || size < 0) {
fclose(file);
diff --git a/third_party/jpeg-xl/examples/decode_oneshot.cc b/third_party/jpeg-xl/examples/decode_oneshot.cc
index a24bd73bfb..b7e17c21a4 100644
--- a/third_party/jpeg-xl/examples/decode_oneshot.cc
+++ b/third_party/jpeg-xl/examples/decode_oneshot.cc
@@ -169,7 +169,7 @@ bool LoadFile(const char* filename, std::vector<uint8_t>* out) {
return false;
}
- long size = ftell(file);
+ long size = ftell(file); // NOLINT
// Avoid invalid file or directory.
if (size >= LONG_MAX || size < 0) {
fclose(file);
diff --git a/third_party/jpeg-xl/examples/decode_progressive.cc b/third_party/jpeg-xl/examples/decode_progressive.cc
index fa7f3df663..2cdc175e8a 100644
--- a/third_party/jpeg-xl/examples/decode_progressive.cc
+++ b/third_party/jpeg-xl/examples/decode_progressive.cc
@@ -6,6 +6,10 @@
// This C++ example decodes a JPEG XL image progressively (input bytes are
// passed in chunks). The example outputs the intermediate steps to PAM files.
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS
+#endif
+
#include <inttypes.h>
#include <jxl/decode.h>
#include <jxl/decode_cxx.h>
@@ -174,7 +178,7 @@ bool LoadFile(const char* filename, std::vector<uint8_t>* out) {
return false;
}
- long size = ftell(file);
+ long size = ftell(file); // NOLINT
// Avoid invalid file or directory.
if (size >= LONG_MAX || size < 0) {
fclose(file);
@@ -220,7 +224,7 @@ int main(int argc, char* argv[]) {
}
size_t chunksize = jxl.size();
if (argc > 3) {
- long cs = atol(argv[3]);
+ long cs = atol(argv[3]); // NOLINT
if (cs < 100) {
fprintf(stderr, "Chunk size is too low, try at least 100 bytes\n");
return 1;
diff --git a/third_party/jpeg-xl/examples/encode_oneshot.cc b/third_party/jpeg-xl/examples/encode_oneshot.cc
index 1582570432..6c45a70411 100644
--- a/third_party/jpeg-xl/examples/encode_oneshot.cc
+++ b/third_party/jpeg-xl/examples/encode_oneshot.cc
@@ -48,7 +48,7 @@ bool ReadPFM(const char* filename, std::vector<float>* pixels, uint32_t* xsize,
return false;
}
- long size = ftell(file);
+ long size = ftell(file); // NOLINT
// Avoid invalid file or directory.
if (size >= LONG_MAX || size < 0) {
fclose(file);
@@ -64,7 +64,7 @@ bool ReadPFM(const char* filename, std::vector<float>* pixels, uint32_t* xsize,
data.resize(size);
size_t readsize = fread(data.data(), 1, size, file);
- if (static_cast<long>(readsize) != size) {
+ if (static_cast<long>(readsize) != size) { // NOLINT
fclose(file);
return false;
}
diff --git a/third_party/jpeg-xl/lib/BUILD b/third_party/jpeg-xl/lib/BUILD
index d93858f22d..3d48919f77 100644
--- a/third_party/jpeg-xl/lib/BUILD
+++ b/third_party/jpeg-xl/lib/BUILD
@@ -3,6 +3,9 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
+load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
+load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
+
# Load sources/headers/tests lists.
load(
"jxl_lists.bzl",
@@ -57,8 +60,6 @@ load(
"libjxl_test_shards",
"libjxl_test_timeouts",
)
-load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
-load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
DEFAULT_VISIBILITY = ["//:__subpackages__"]
diff --git a/third_party/jpeg-xl/lib/CMakeLists.txt b/third_party/jpeg-xl/lib/CMakeLists.txt
index 1e6f4abf74..a178e02f67 100644
--- a/third_party/jpeg-xl/lib/CMakeLists.txt
+++ b/third_party/jpeg-xl/lib/CMakeLists.txt
@@ -5,7 +5,7 @@
set(JPEGXL_MAJOR_VERSION 0)
set(JPEGXL_MINOR_VERSION 10)
-set(JPEGXL_PATCH_VERSION 0)
+set(JPEGXL_PATCH_VERSION 2)
set(JPEGXL_LIBRARY_VERSION
"${JPEGXL_MAJOR_VERSION}.${JPEGXL_MINOR_VERSION}.${JPEGXL_PATCH_VERSION}")
diff --git a/third_party/jpeg-xl/lib/extras/codec_test.cc b/third_party/jpeg-xl/lib/extras/codec_test.cc
index 50d5e37bc9..1357b5a616 100644
--- a/third_party/jpeg-xl/lib/extras/codec_test.cc
+++ b/third_party/jpeg-xl/lib/extras/codec_test.cc
@@ -26,6 +26,7 @@
#include "lib/extras/enc/encode.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/byte_order.h"
+#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/random.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
@@ -333,10 +334,10 @@ TEST(CodecTest, TestRoundTrip) {
params.add_alpha = add_alpha;
params.big_endian = big_endian;
params.add_extra_channels = false;
- TestRoundTrip(params, &pool);
+ TestRoundTrip(params, pool.get());
if (codec == Codec::kPNM && add_alpha) {
params.add_extra_channels = true;
- TestRoundTrip(params, &pool);
+ TestRoundTrip(params, pool.get());
}
}
}
@@ -369,7 +370,7 @@ TEST(CodecTest, LosslessPNMRoundtrip) {
EncodedImage encoded;
auto encoder = Encoder::FromExtension(extension);
ASSERT_TRUE(encoder.get());
- ASSERT_TRUE(encoder->Encode(ppf, &encoded, &pool));
+ ASSERT_TRUE(encoder->Encode(ppf, &encoded, pool.get()));
ASSERT_EQ(encoded.bitstreams.size(), 1);
ASSERT_EQ(orig.size(), encoded.bitstreams[0].size());
EXPECT_EQ(0,
diff --git a/third_party/jpeg-xl/lib/extras/dec/apng.cc b/third_party/jpeg-xl/lib/extras/dec/apng.cc
index 8b0da06eb1..c607a71d08 100644
--- a/third_party/jpeg-xl/lib/extras/dec/apng.cc
+++ b/third_party/jpeg-xl/lib/extras/dec/apng.cc
@@ -381,9 +381,11 @@ class BlobsReaderPNG {
// We need 2*bytes for the hex values plus 1 byte every 36 values,
// plus terminal \n for length.
- const unsigned long needed_bytes =
- bytes_to_decode * 2 + 1 + DivCeil(bytes_to_decode, 36);
- if (needed_bytes != static_cast<size_t>(encoded_end - pos)) {
+ size_t tail = static_cast<size_t>(encoded_end - pos);
+ bool ok = ((tail / 2) >= bytes_to_decode);
+ if (ok) tail -= 2 * static_cast<size_t>(bytes_to_decode);
+ ok = ok && (tail == 1 + DivCeil(bytes_to_decode, 36));
+ if (!ok) {
return JXL_FAILURE("Not enough bytes to parse %d bytes in hex",
bytes_to_decode);
}
@@ -439,7 +441,7 @@ struct APNGFrame {
Status Resize(size_t new_size) {
if (new_size > pixels_size) {
pixels.reset(malloc(new_size));
- if (!pixels.get()) {
+ if (!pixels) {
// TODO(szabadka): use specialized OOM error code
return JXL_FAILURE("Failed to allocate memory for image buffer");
}
@@ -462,7 +464,7 @@ struct Reader {
bool Eof() const { return next == last; }
};
-const unsigned long cMaxPNGSize = 1000000UL;
+const uint32_t cMaxPNGSize = 1000000UL;
const size_t kMaxPNGChunkSize = 1lu << 30; // 1 GB
void info_fn(png_structp png_ptr, png_infop info_ptr) {
@@ -641,17 +643,17 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
bool have_srgb = false;
bool errorstate = true;
if (id == kId_IHDR && chunkIHDR.size() == 25) {
- unsigned int x0 = 0;
- unsigned int y0 = 0;
- unsigned int delay_num = 1;
- unsigned int delay_den = 10;
- unsigned int dop = 0;
- unsigned int bop = 0;
-
- unsigned int w = png_get_uint_32(chunkIHDR.data() + 8);
- unsigned int h = png_get_uint_32(chunkIHDR.data() + 12);
- unsigned int w0 = w;
- unsigned int h0 = h;
+ uint32_t x0 = 0;
+ uint32_t y0 = 0;
+ uint32_t delay_num = 1;
+ uint32_t delay_den = 10;
+ uint32_t dop = 0;
+ uint32_t bop = 0;
+
+ uint32_t w = png_get_uint_32(chunkIHDR.data() + 8);
+ uint32_t h = png_get_uint_32(chunkIHDR.data() + 12);
+ uint32_t w0 = w;
+ uint32_t h0 = h;
if (w > cMaxPNGSize || h > cMaxPNGSize) {
return false;
}
diff --git a/third_party/jpeg-xl/lib/extras/dec/pnm.cc b/third_party/jpeg-xl/lib/extras/dec/pnm.cc
index 040c0bff81..e64d7e95f9 100644
--- a/third_party/jpeg-xl/lib/extras/dec/pnm.cc
+++ b/third_party/jpeg-xl/lib/extras/dec/pnm.cc
@@ -332,7 +332,7 @@ struct PNMChunkedInputFrame {
METHOD_TO_C_CALLBACK(&PNMChunkedInputFrame::ReleaseCurrentData)};
}
- void GetColorChannelsPixelFormat(JxlPixelFormat* pixel_format) {
+ void /* NOLINT */ GetColorChannelsPixelFormat(JxlPixelFormat* pixel_format) {
*pixel_format = format;
}
@@ -349,12 +349,14 @@ struct PNMChunkedInputFrame {
void GetExtraChannelPixelFormat(size_t ec_index,
JxlPixelFormat* pixel_format) {
+ (void)this;
JXL_ABORT("Not implemented");
}
const void* GetExtraChannelDataAt(size_t ec_index, size_t xpos, size_t ypos,
size_t xsize, size_t ysize,
size_t* row_offset) {
+ (void)this;
JXL_ABORT("Not implemented");
}
diff --git a/third_party/jpeg-xl/lib/extras/enc/apng.cc b/third_party/jpeg-xl/lib/extras/enc/apng.cc
index 40aa876e84..a28408a7f2 100644
--- a/third_party/jpeg-xl/lib/extras/enc/apng.cc
+++ b/third_party/jpeg-xl/lib/extras/enc/apng.cc
@@ -73,17 +73,30 @@ class APNGEncoder : public Encoder {
}
Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
ThreadPool* pool) const override {
+ // Encode main image frames
JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
encoded_image->icc.clear();
encoded_image->bitstreams.resize(1);
- return EncodePackedPixelFileToAPNG(ppf, pool,
- &encoded_image->bitstreams.front());
+ JXL_RETURN_IF_ERROR(EncodePackedPixelFileToAPNG(
+ ppf, pool, &encoded_image->bitstreams.front()));
+
+ // Encode extra channels
+ for (size_t i = 0; i < ppf.extra_channels_info.size(); ++i) {
+ encoded_image->extra_channel_bitstreams.emplace_back();
+ auto& ec_bitstreams = encoded_image->extra_channel_bitstreams.back();
+ ec_bitstreams.emplace_back();
+ JXL_RETURN_IF_ERROR(EncodePackedPixelFileToAPNG(
+ ppf, pool, &ec_bitstreams.back(), true, i));
+ }
+ return true;
}
private:
Status EncodePackedPixelFileToAPNG(const PackedPixelFile& ppf,
ThreadPool* pool,
- std::vector<uint8_t>* bytes) const;
+ std::vector<uint8_t>* bytes,
+ bool encode_extra_channels = false,
+ size_t extra_channel_index = 0) const;
};
void PngWrite(png_structp png_ptr, png_bytep data, png_size_t length) {
@@ -266,15 +279,21 @@ void MaybeAddCLLi(const JxlColorEncoding& c_enc, const float intensity_target,
}
Status APNGEncoder::EncodePackedPixelFileToAPNG(
- const PackedPixelFile& ppf, ThreadPool* pool,
- std::vector<uint8_t>* bytes) const {
- size_t xsize = ppf.info.xsize;
- size_t ysize = ppf.info.ysize;
- bool has_alpha = ppf.info.alpha_bits != 0;
- bool is_gray = ppf.info.num_color_channels == 1;
- size_t color_channels = ppf.info.num_color_channels;
+ const PackedPixelFile& ppf, ThreadPool* pool, std::vector<uint8_t>* bytes,
+ bool encode_extra_channels, size_t extra_channel_index) const {
+ JxlExtraChannelInfo ec_info{};
+ if (encode_extra_channels) {
+ if (ppf.extra_channels_info.size() <= extra_channel_index) {
+ return JXL_FAILURE("Invalid index for extra channel");
+ }
+ ec_info = ppf.extra_channels_info[extra_channel_index].ec_info;
+ }
+
+ bool has_alpha = !encode_extra_channels && (ppf.info.alpha_bits != 0);
+ bool is_gray = encode_extra_channels || (ppf.info.num_color_channels == 1);
+ size_t color_channels =
+ encode_extra_channels ? 1 : ppf.info.num_color_channels;
size_t num_channels = color_channels + (has_alpha ? 1 : 0);
- size_t num_samples = num_channels * xsize * ysize;
if (!ppf.info.have_animation && ppf.frames.size() != 1) {
return JXL_FAILURE("Invalid number of frames");
@@ -284,9 +303,24 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG(
size_t anim_chunks = 0;
for (const auto& frame : ppf.frames) {
- JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
-
- const PackedImage& color = frame.color;
+ const PackedImage& color = encode_extra_channels
+ ? frame.extra_channels[extra_channel_index]
+ : frame.color;
+
+ size_t xsize = color.xsize;
+ size_t ysize = color.ysize;
+ size_t num_samples = num_channels * xsize * ysize;
+
+ uint32_t bits_per_sample = encode_extra_channels ? ec_info.bits_per_sample
+ : ppf.info.bits_per_sample;
+ if (!encode_extra_channels) {
+ JXL_RETURN_IF_ERROR(VerifyPackedImage(color, ppf.info));
+ } else {
+ JXL_RETURN_IF_ERROR(VerifyFormat(color.format));
+ JXL_RETURN_IF_ERROR(VerifyBitDepth(color.format.data_type,
+ bits_per_sample,
+ ec_info.exponent_bits_per_sample));
+ }
const JxlPixelFormat format = color.format;
const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels());
size_t data_bits_per_sample = PackedImage::BitsPerChannel(format.data_type);
@@ -297,24 +331,23 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG(
std::vector<uint8_t> out(out_size);
if (format.data_type == JXL_TYPE_UINT8) {
- if (ppf.info.bits_per_sample < 8) {
- float mul = 255.0 / ((1u << ppf.info.bits_per_sample) - 1);
+ if (bits_per_sample < 8) {
+ float mul = 255.0 / ((1u << bits_per_sample) - 1);
for (size_t i = 0; i < num_samples; ++i) {
- out[i] = static_cast<uint8_t>(in[i] * mul + 0.5);
+ out[i] = static_cast<uint8_t>(std::lroundf(in[i] * mul));
}
} else {
memcpy(out.data(), in, out_size);
}
} else if (format.data_type == JXL_TYPE_UINT16) {
- if (ppf.info.bits_per_sample < 16 ||
- format.endianness != JXL_BIG_ENDIAN) {
- float mul = 65535.0 / ((1u << ppf.info.bits_per_sample) - 1);
+ if (bits_per_sample < 16 || format.endianness != JXL_BIG_ENDIAN) {
+ float mul = 65535.0 / ((1u << bits_per_sample) - 1);
const uint8_t* p_in = in;
uint8_t* p_out = out.data();
for (size_t i = 0; i < num_samples; ++i, p_in += 2, p_out += 2) {
uint32_t val = (format.endianness == JXL_BIG_ENDIAN ? LoadBE16(p_in)
: LoadLE16(p_in));
- StoreBE16(static_cast<uint32_t>(val * mul + 0.5), p_out);
+ StoreBE16(static_cast<uint32_t>(std::lroundf(val * mul)), p_out);
}
} else {
memcpy(out.data(), in, out_size);
@@ -344,7 +377,7 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG(
png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
PNG_FILTER_TYPE_BASE);
- if (count == 0) {
+ if (count == 0 && !encode_extra_channels) {
if (!MaybeAddSRGB(ppf.color_encoding, png_ptr, info_ptr)) {
MaybeAddCICP(ppf.color_encoding, png_ptr, info_ptr);
if (!ppf.icc.empty()) {
diff --git a/third_party/jpeg-xl/lib/extras/enc/jpegli.cc b/third_party/jpeg-xl/lib/extras/enc/jpegli.cc
index aa10b584d0..cb473a1290 100644
--- a/third_party/jpeg-xl/lib/extras/enc/jpegli.cc
+++ b/third_party/jpeg-xl/lib/extras/enc/jpegli.cc
@@ -390,7 +390,7 @@ Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
// before the call to setjmp().
std::vector<uint8_t> pixels;
unsigned char* output_buffer = nullptr;
- unsigned long output_size = 0;
+ unsigned long output_size = 0; // NOLINT
std::vector<uint8_t> row_bytes;
size_t rowlen = RoundUpTo(ppf.info.xsize, MaxVectorSize());
hwy::AlignedFreeUniquePtr<float[]> xyb_tmp =
diff --git a/third_party/jpeg-xl/lib/extras/enc/jpg.cc b/third_party/jpeg-xl/lib/extras/enc/jpg.cc
index de0228fc0d..0095ac9294 100644
--- a/third_party/jpeg-xl/lib/extras/enc/jpg.cc
+++ b/third_party/jpeg-xl/lib/extras/enc/jpg.cc
@@ -276,7 +276,7 @@ Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info,
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
unsigned char* buffer = nullptr;
- unsigned long size = 0;
+ unsigned long size = 0; // NOLINT
jpeg_mem_dest(&cinfo, &buffer, &size);
cinfo.image_width = image.xsize;
cinfo.image_height = image.ysize;
diff --git a/third_party/jpeg-xl/lib/extras/enc/pnm.cc b/third_party/jpeg-xl/lib/extras/enc/pnm.cc
index 966611cfca..d2d67ae52a 100644
--- a/third_party/jpeg-xl/lib/extras/enc/pnm.cc
+++ b/third_party/jpeg-xl/lib/extras/enc/pnm.cc
@@ -87,8 +87,8 @@ class PNMEncoder : public BasePNMEncoder {
}
private:
- Status EncodeImage(const PackedImage& image, size_t bits_per_sample,
- std::vector<uint8_t>* bytes) const {
+ static Status EncodeImage(const PackedImage& image, size_t bits_per_sample,
+ std::vector<uint8_t>* bytes) {
uint32_t maxval = (1u << bits_per_sample) - 1;
char type = image.format.num_channels == 1 ? '5' : '6';
char header[kMaxHeaderSize];
@@ -161,8 +161,8 @@ class PFMEncoder : public BasePNMEncoder {
}
private:
- Status EncodeImage(const PackedImage& image,
- std::vector<uint8_t>* bytes) const {
+ static Status EncodeImage(const PackedImage& image,
+ std::vector<uint8_t>* bytes) {
char type = image.format.num_channels == 1 ? 'f' : 'F';
double scale = image.format.endianness == JXL_LITTLE_ENDIAN ? -1.0 : 1.0;
char header[kMaxHeaderSize];
diff --git a/third_party/jpeg-xl/lib/extras/mmap.h b/third_party/jpeg-xl/lib/extras/mmap.h
index 8bc023dec0..60cc215993 100644
--- a/third_party/jpeg-xl/lib/extras/mmap.h
+++ b/third_party/jpeg-xl/lib/extras/mmap.h
@@ -18,10 +18,10 @@ class MemoryMappedFile {
static StatusOr<MemoryMappedFile> Init(const char* path);
const uint8_t* data() const;
size_t size() const;
- MemoryMappedFile();
- ~MemoryMappedFile();
- MemoryMappedFile(MemoryMappedFile&&) noexcept;
- MemoryMappedFile& operator=(MemoryMappedFile&&) noexcept;
+ MemoryMappedFile(); // NOLINT
+ ~MemoryMappedFile(); // NOLINT
+ MemoryMappedFile(MemoryMappedFile&&) noexcept; // NOLINT
+ MemoryMappedFile& operator=(MemoryMappedFile&&) noexcept; // NOLINT
private:
std::unique_ptr<MemoryMappedFileImpl> impl_;
diff --git a/third_party/jpeg-xl/lib/jpegli/color_transform.cc b/third_party/jpeg-xl/lib/jpegli/color_transform.cc
index 020a6fd80c..60a0dc83bb 100644
--- a/third_party/jpeg-xl/lib/jpegli/color_transform.cc
+++ b/third_party/jpeg-xl/lib/jpegli/color_transform.cc
@@ -135,19 +135,25 @@ bool CheckColorSpaceComponents(int num_components, J_COLOR_SPACE colorspace) {
return num_components == 1;
case JCS_RGB:
case JCS_YCbCr:
+#ifdef JCS_EXTENSIONS
case JCS_EXT_RGB:
case JCS_EXT_BGR:
+#endif
return num_components == 3;
case JCS_CMYK:
case JCS_YCCK:
+#ifdef JCS_EXTENSIONS
case JCS_EXT_RGBX:
case JCS_EXT_BGRX:
case JCS_EXT_XBGR:
case JCS_EXT_XRGB:
+#endif
+#ifdef JCS_ALPHA_EXTENSIONS
case JCS_EXT_RGBA:
case JCS_EXT_BGRA:
case JCS_EXT_ABGR:
case JCS_EXT_ARGB:
+#endif
return num_components == 4;
default:
// Unrecognized colorspaces can have any number of channels, since no
diff --git a/third_party/jpeg-xl/lib/jpegli/decode.h b/third_party/jpeg-xl/lib/jpegli/decode.h
index 9800ebf67a..f5b099eda3 100644
--- a/third_party/jpeg-xl/lib/jpegli/decode.h
+++ b/third_party/jpeg-xl/lib/jpegli/decode.h
@@ -36,7 +36,7 @@ void jpegli_CreateDecompress(j_decompress_ptr cinfo, int version,
void jpegli_stdio_src(j_decompress_ptr cinfo, FILE *infile);
void jpegli_mem_src(j_decompress_ptr cinfo, const unsigned char *inbuffer,
- unsigned long insize);
+ unsigned long insize /* NOLINT */);
int jpegli_read_header(j_decompress_ptr cinfo, boolean require_image);
diff --git a/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc b/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc
index 0cc5a194d7..3ecd479951 100644
--- a/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc
+++ b/third_party/jpeg-xl/lib/jpegli/decode_api_test.cc
@@ -3,8 +3,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-#include <cmath>
#include <cstdint>
+#include <cstdio>
#include <vector>
#include "lib/jpegli/decode.h"
@@ -78,7 +78,8 @@ class SourceManager {
return TRUE;
}
- static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+ static void skip_input_data(j_decompress_ptr cinfo,
+ long num_bytes /* NOLINT */) {
auto* src = reinterpret_cast<SourceManager*>(cinfo->src);
if (num_bytes <= 0) {
return;
@@ -447,7 +448,7 @@ std::vector<TestConfig> GenerateBasicConfigs() {
TEST(DecodeAPITest, ReuseCinfoSameMemSource) {
std::vector<TestConfig> all_configs = GenerateBasicConfigs();
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
{
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
@@ -502,7 +503,7 @@ TEST(DecodeAPITest, ReuseCinfoSameStdSource) {
EXPECT_TRUE(try_catch_block());
jpegli_destroy_compress(&cinfo);
}
- rewind(tmpf);
+ fseek(tmpf, 0, SEEK_SET);
std::vector<TestImage> all_outputs(all_configs.size());
{
jpeg_decompress_struct cinfo;
@@ -527,9 +528,9 @@ TEST(DecodeAPITest, ReuseCinfoSameStdSource) {
TEST(DecodeAPITest, AbbreviatedStreams) {
uint8_t* table_stream = nullptr;
- unsigned long table_stream_size = 0;
+ unsigned long table_stream_size = 0; // NOLINT
uint8_t* data_stream = nullptr;
- unsigned long data_stream_size = 0;
+ unsigned long data_stream_size = 0; // NOLINT
{
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
diff --git a/third_party/jpeg-xl/lib/jpegli/destination_manager.cc b/third_party/jpeg-xl/lib/jpegli/destination_manager.cc
index 6548130866..05d35797b8 100644
--- a/third_party/jpeg-xl/lib/jpegli/destination_manager.cc
+++ b/third_party/jpeg-xl/lib/jpegli/destination_manager.cc
@@ -52,7 +52,7 @@ struct MemoryDestinationManager {
jpeg_destination_mgr pub;
// Output buffer supplied by the application
uint8_t** output;
- unsigned long* output_size;
+ unsigned long* output_size; // NOLINT
// Output buffer allocated by us.
uint8_t* temp_buffer;
// Current output buffer (either application supplied or allocated by us).
@@ -113,7 +113,7 @@ void jpegli_stdio_dest(j_compress_ptr cinfo, FILE* outfile) {
}
void jpegli_mem_dest(j_compress_ptr cinfo, unsigned char** outbuffer,
- unsigned long* outsize) {
+ unsigned long* outsize /* NOLINT */) {
if (outbuffer == nullptr || outsize == nullptr) {
JPEGLI_ERROR("jpegli_mem_dest: Invalid destination.");
}
diff --git a/third_party/jpeg-xl/lib/jpegli/encode.h b/third_party/jpeg-xl/lib/jpegli/encode.h
index 320dfaaf8d..ed34838450 100644
--- a/third_party/jpeg-xl/lib/jpegli/encode.h
+++ b/third_party/jpeg-xl/lib/jpegli/encode.h
@@ -35,7 +35,7 @@ void jpegli_CreateCompress(j_compress_ptr cinfo, int version,
void jpegli_stdio_dest(j_compress_ptr cinfo, FILE* outfile);
void jpegli_mem_dest(j_compress_ptr cinfo, unsigned char** outbuffer,
- unsigned long* outsize);
+ unsigned long* outsize /* NOLINT */);
void jpegli_set_defaults(j_compress_ptr cinfo);
diff --git a/third_party/jpeg-xl/lib/jpegli/encode_api_test.cc b/third_party/jpeg-xl/lib/jpegli/encode_api_test.cc
index 1afdcf610d..2978b3f35d 100644
--- a/third_party/jpeg-xl/lib/jpegli/encode_api_test.cc
+++ b/third_party/jpeg-xl/lib/jpegli/encode_api_test.cc
@@ -69,7 +69,7 @@ TEST(EncodeAPITest, ReuseCinfoSameImageTwice) {
CompressParams jparams;
GenerateInput(PIXELS, jparams, &input);
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
std::vector<uint8_t> compressed0;
std::vector<uint8_t> compressed1;
jpeg_compress_struct cinfo;
@@ -117,7 +117,7 @@ std::vector<TestConfig> GenerateBasicConfigs() {
TEST(EncodeAPITest, ReuseCinfoSameMemOutput) {
std::vector<TestConfig> all_configs = GenerateBasicConfigs();
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
{
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
@@ -161,7 +161,7 @@ TEST(EncodeAPITest, ReuseCinfoSameStdOutput) {
jpegli_destroy_compress(&cinfo);
}
size_t total_size = ftell(tmpf);
- rewind(tmpf);
+ fseek(tmpf, 0, SEEK_SET);
std::vector<uint8_t> compressed(total_size);
JXL_CHECK(total_size == fread(compressed.data(), 1, total_size, tmpf));
fclose(tmpf);
@@ -181,7 +181,7 @@ TEST(EncodeAPITest, ReuseCinfoChangeParams) {
CompressParams jparams;
DecompressParams dparams;
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
std::vector<uint8_t> compressed;
jpeg_compress_struct cinfo;
const auto max_rms = [](int q, int hs, int vs) {
@@ -246,9 +246,9 @@ TEST(EncodeAPITest, ReuseCinfoChangeParams) {
TEST(EncodeAPITest, AbbreviatedStreams) {
uint8_t* table_stream = nullptr;
- unsigned long table_stream_size = 0;
+ unsigned long table_stream_size = 0; // NOLINT
uint8_t* data_stream = nullptr;
- unsigned long data_stream_size = 0;
+ unsigned long data_stream_size = 0; // NOLINT
{
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
diff --git a/third_party/jpeg-xl/lib/jpegli/error_handling_test.cc b/third_party/jpeg-xl/lib/jpegli/error_handling_test.cc
index 3eaf6a313b..bcd7355124 100644
--- a/third_party/jpeg-xl/lib/jpegli/error_handling_test.cc
+++ b/third_party/jpeg-xl/lib/jpegli/error_handling_test.cc
@@ -15,7 +15,7 @@ namespace {
TEST(EncoderErrorHandlingTest, MinimalSuccess) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
{
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
@@ -64,7 +64,7 @@ TEST(EncoderErrorHandlingTest, NoDestination) {
TEST(EncoderErrorHandlingTest, NoImageDimensions) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -82,7 +82,7 @@ TEST(EncoderErrorHandlingTest, NoImageDimensions) {
TEST(EncoderErrorHandlingTest, ImageTooBig) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -102,7 +102,7 @@ TEST(EncoderErrorHandlingTest, ImageTooBig) {
TEST(EncoderErrorHandlingTest, NoInputComponents) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -121,7 +121,7 @@ TEST(EncoderErrorHandlingTest, NoInputComponents) {
TEST(EncoderErrorHandlingTest, TooManyInputComponents) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -141,7 +141,7 @@ TEST(EncoderErrorHandlingTest, TooManyInputComponents) {
TEST(EncoderErrorHandlingTest, NoSetDefaults) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -164,7 +164,7 @@ TEST(EncoderErrorHandlingTest, NoSetDefaults) {
TEST(EncoderErrorHandlingTest, NoStartCompress) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -186,7 +186,7 @@ TEST(EncoderErrorHandlingTest, NoStartCompress) {
TEST(EncoderErrorHandlingTest, NoWriteScanlines) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -207,7 +207,7 @@ TEST(EncoderErrorHandlingTest, NoWriteScanlines) {
TEST(EncoderErrorHandlingTest, NoWriteAllScanlines) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -231,7 +231,7 @@ TEST(EncoderErrorHandlingTest, NoWriteAllScanlines) {
TEST(EncoderErrorHandlingTest, InvalidQuantValue) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -260,7 +260,7 @@ TEST(EncoderErrorHandlingTest, InvalidQuantValue) {
TEST(EncoderErrorHandlingTest, InvalidQuantTableIndex) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -285,7 +285,7 @@ TEST(EncoderErrorHandlingTest, InvalidQuantTableIndex) {
TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch1) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -306,7 +306,7 @@ TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch1) {
TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch2) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -327,7 +327,7 @@ TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch2) {
TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch3) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -353,7 +353,7 @@ TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch3) {
TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch4) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -378,7 +378,7 @@ TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch4) {
TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch5) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -403,7 +403,7 @@ TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch5) {
TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch6) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -429,7 +429,7 @@ TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch6) {
TEST(EncoderErrorHandlingTest, InvalidColorTransform) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -455,7 +455,7 @@ TEST(EncoderErrorHandlingTest, InvalidColorTransform) {
TEST(EncoderErrorHandlingTest, DuplicateComponentIds) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -477,7 +477,7 @@ TEST(EncoderErrorHandlingTest, DuplicateComponentIds) {
TEST(EncoderErrorHandlingTest, InvalidComponentIndex) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -498,7 +498,7 @@ TEST(EncoderErrorHandlingTest, InvalidComponentIndex) {
TEST(EncoderErrorHandlingTest, ArithmeticCoding) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -519,7 +519,7 @@ TEST(EncoderErrorHandlingTest, ArithmeticCoding) {
TEST(EncoderErrorHandlingTest, CCIR601Sampling) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -540,7 +540,7 @@ TEST(EncoderErrorHandlingTest, CCIR601Sampling) {
TEST(EncoderErrorHandlingTest, InvalidScanScript1) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -563,7 +563,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript1) {
TEST(EncoderErrorHandlingTest, InvalidScanScript2) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -586,7 +586,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript2) {
TEST(EncoderErrorHandlingTest, InvalidScanScript3) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -609,7 +609,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript3) {
TEST(EncoderErrorHandlingTest, InvalidScanScript4) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -632,7 +632,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript4) {
TEST(EncoderErrorHandlingTest, InvalidScanScript5) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -655,7 +655,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript5) {
TEST(EncoderErrorHandlingTest, InvalidScanScript6) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -678,7 +678,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript6) {
TEST(EncoderErrorHandlingTest, InvalidScanScript7) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -701,7 +701,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript7) {
TEST(EncoderErrorHandlingTest, InvalidScanScript8) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -726,7 +726,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript8) {
TEST(EncoderErrorHandlingTest, InvalidScanScript9) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -751,7 +751,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript9) {
TEST(EncoderErrorHandlingTest, InvalidScanScript10) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -776,7 +776,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript10) {
TEST(EncoderErrorHandlingTest, InvalidScanScript11) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -801,7 +801,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript11) {
TEST(EncoderErrorHandlingTest, InvalidScanScript12) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -826,7 +826,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript12) {
TEST(EncoderErrorHandlingTest, InvalidScanScript13) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -854,7 +854,7 @@ TEST(EncoderErrorHandlingTest, InvalidScanScript13) {
TEST(EncoderErrorHandlingTest, MCUSizeTooBig) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -877,7 +877,7 @@ TEST(EncoderErrorHandlingTest, MCUSizeTooBig) {
TEST(EncoderErrorHandlingTest, RestartIntervalTooBig) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -898,7 +898,7 @@ TEST(EncoderErrorHandlingTest, RestartIntervalTooBig) {
TEST(EncoderErrorHandlingTest, SamplingFactorTooBig) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
@@ -919,7 +919,7 @@ TEST(EncoderErrorHandlingTest, SamplingFactorTooBig) {
TEST(EncoderErrorHandlingTest, NonIntegralSamplingRatio) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
diff --git a/third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc b/third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc
index eb8b7ebc26..6546b7b087 100644
--- a/third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc
+++ b/third_party/jpeg-xl/lib/jpegli/input_suspension_test.cc
@@ -80,7 +80,8 @@ struct SourceManager {
static boolean fill_input_buffer(j_decompress_ptr cinfo) { return FALSE; }
- static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+ static void skip_input_data(j_decompress_ptr cinfo,
+ long num_bytes /* NOLINT*/) {
auto* src = reinterpret_cast<SourceManager*>(cinfo->src);
if (num_bytes <= 0) {
return;
diff --git a/third_party/jpeg-xl/lib/jpegli/libjpeg_wrapper.cc b/third_party/jpeg-xl/lib/jpegli/libjpeg_wrapper.cc
index 471b7c7192..a2b333afef 100644
--- a/third_party/jpeg-xl/lib/jpegli/libjpeg_wrapper.cc
+++ b/third_party/jpeg-xl/lib/jpegli/libjpeg_wrapper.cc
@@ -38,7 +38,7 @@ void jpeg_stdio_src(j_decompress_ptr cinfo, FILE *infile) {
}
void jpeg_mem_src(j_decompress_ptr cinfo, const unsigned char *inbuffer,
- unsigned long insize) {
+ unsigned long insize /* NOLINT */) {
jpegli_mem_src(cinfo, inbuffer, insize);
}
@@ -138,7 +138,7 @@ void jpeg_stdio_dest(j_compress_ptr cinfo, FILE *outfile) {
}
void jpeg_mem_dest(j_compress_ptr cinfo, unsigned char **outbuffer,
- unsigned long *outsize) {
+ unsigned long *outsize /* NOLINT */) {
jpegli_mem_dest(cinfo, outbuffer, outsize);
}
diff --git a/third_party/jpeg-xl/lib/jpegli/source_manager.cc b/third_party/jpeg-xl/lib/jpegli/source_manager.cc
index 58adf803b1..e0f0b4c275 100644
--- a/third_party/jpeg-xl/lib/jpegli/source_manager.cc
+++ b/third_party/jpeg-xl/lib/jpegli/source_manager.cc
@@ -12,9 +12,10 @@ namespace jpegli {
void init_mem_source(j_decompress_ptr cinfo) {}
void init_stdio_source(j_decompress_ptr cinfo) {}
-void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
+void skip_input_data(j_decompress_ptr cinfo, long num_bytes /* NOLINT */) {
if (num_bytes <= 0) return;
- while (num_bytes > static_cast<long>(cinfo->src->bytes_in_buffer)) {
+ while (num_bytes >
+ static_cast<long>(cinfo->src->bytes_in_buffer)) { // NOLINT
num_bytes -= cinfo->src->bytes_in_buffer;
(*cinfo->src->fill_input_buffer)(cinfo);
}
@@ -53,7 +54,7 @@ struct StdioSourceManager {
} // namespace jpegli
void jpegli_mem_src(j_decompress_ptr cinfo, const unsigned char* inbuffer,
- unsigned long insize) {
+ unsigned long insize /* NOLINT */) {
if (cinfo->src && cinfo->src->init_source != jpegli::init_mem_source) {
JPEGLI_ERROR("jpegli_mem_src: a different source manager was already set");
}
diff --git a/third_party/jpeg-xl/lib/jpegli/source_manager_test.cc b/third_party/jpeg-xl/lib/jpegli/source_manager_test.cc
index 59d12b001b..a513b7063b 100644
--- a/third_party/jpeg-xl/lib/jpegli/source_manager_test.cc
+++ b/third_party/jpeg-xl/lib/jpegli/source_manager_test.cc
@@ -43,7 +43,7 @@ FILE* MemOpen(const std::vector<uint8_t>& data) {
FILE* src = tmpfile();
if (!src) return nullptr;
fwrite(data.data(), 1, data.size(), src);
- rewind(src);
+ fseek(src, 0, SEEK_SET);
return src;
}
} // namespace
diff --git a/third_party/jpeg-xl/lib/jpegli/streaming_test.cc b/third_party/jpeg-xl/lib/jpegli/streaming_test.cc
index 2e6f7029b0..1f19dc2045 100644
--- a/third_party/jpeg-xl/lib/jpegli/streaming_test.cc
+++ b/third_party/jpeg-xl/lib/jpegli/streaming_test.cc
@@ -28,7 +28,8 @@ struct SourceManager {
static void init_source(j_decompress_ptr cinfo) {}
static boolean fill_input_buffer(j_decompress_ptr cinfo) { return FALSE; }
- static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) {}
+ static void skip_input_data(j_decompress_ptr cinfo,
+ long num_bytes /* NOLINT */) {}
static void term_source(j_decompress_ptr cinfo) {}
};
diff --git a/third_party/jpeg-xl/lib/jpegli/test_utils.cc b/third_party/jpeg-xl/lib/jpegli/test_utils.cc
index 4e675070cf..db5a30e8dc 100644
--- a/third_party/jpeg-xl/lib/jpegli/test_utils.cc
+++ b/third_party/jpeg-xl/lib/jpegli/test_utils.cc
@@ -673,7 +673,7 @@ void EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
bool EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
std::vector<uint8_t>* compressed) {
uint8_t* buffer = nullptr;
- unsigned long buffer_size = 0;
+ unsigned long buffer_size = 0; // NOLINT
jpeg_compress_struct cinfo;
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
diff --git a/third_party/jpeg-xl/lib/jpegli/transcode_api_test.cc b/third_party/jpeg-xl/lib/jpegli/transcode_api_test.cc
index 1d99ce37fa..13c81a1119 100644
--- a/third_party/jpeg-xl/lib/jpegli/transcode_api_test.cc
+++ b/third_party/jpeg-xl/lib/jpegli/transcode_api_test.cc
@@ -20,7 +20,7 @@ void TranscodeWithJpegli(const std::vector<uint8_t>& jpeg_input,
jpeg_decompress_struct dinfo = {};
jpeg_compress_struct cinfo = {};
uint8_t* transcoded_data = nullptr;
- unsigned long transcoded_size;
+ unsigned long transcoded_size; // NOLINT
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
dinfo.err = cinfo.err;
diff --git a/third_party/jpeg-xl/lib/jxl.cmake b/third_party/jpeg-xl/lib/jxl.cmake
index 86fa37151d..82a2716978 100644
--- a/third_party/jpeg-xl/lib/jxl.cmake
+++ b/third_party/jpeg-xl/lib/jxl.cmake
@@ -80,10 +80,10 @@ foreach(path ${JPEGXL_INTERNAL_PUBLIC_HEADERS})
endforeach()
add_library(jxl_base INTERFACE)
-target_include_directories(jxl_base SYSTEM INTERFACE
+target_include_directories(jxl_base SYSTEM BEFORE INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
)
-target_include_directories(jxl_base INTERFACE
+target_include_directories(jxl_base BEFORE INTERFACE
${PROJECT_SOURCE_DIR}
${JXL_HWY_INCLUDE_DIRS}
)
@@ -104,7 +104,7 @@ add_library(jxl_dec-obj OBJECT ${JPEGXL_INTERNAL_DEC_SOURCES})
target_compile_options(jxl_dec-obj PRIVATE ${JPEGXL_INTERNAL_FLAGS})
target_compile_options(jxl_dec-obj PUBLIC ${JPEGXL_COVERAGE_FLAGS})
set_property(TARGET jxl_dec-obj PROPERTY POSITION_INDEPENDENT_CODE ON)
-target_include_directories(jxl_dec-obj PUBLIC
+target_include_directories(jxl_dec-obj BEFORE PUBLIC
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>"
"${JXL_HWY_INCLUDE_DIRS}"
"$<BUILD_INTERFACE:$<TARGET_PROPERTY:brotlicommon,INTERFACE_INCLUDE_DIRECTORIES>>"
@@ -119,7 +119,7 @@ add_library(jxl_enc-obj OBJECT ${JPEGXL_INTERNAL_ENC_SOURCES})
target_compile_options(jxl_enc-obj PRIVATE ${JPEGXL_INTERNAL_FLAGS})
target_compile_options(jxl_enc-obj PUBLIC ${JPEGXL_COVERAGE_FLAGS})
set_property(TARGET jxl_enc-obj PROPERTY POSITION_INDEPENDENT_CODE ON)
-target_include_directories(jxl_enc-obj PUBLIC
+target_include_directories(jxl_enc-obj BEFORE PUBLIC
${PROJECT_SOURCE_DIR}
${JXL_HWY_INCLUDE_DIRS}
$<TARGET_PROPERTY:brotlicommon,INTERFACE_INCLUDE_DIRECTORIES>
@@ -172,7 +172,7 @@ target_link_libraries(jxl-internal PUBLIC
jxl_cms
jxl_base
)
-target_include_directories(jxl-internal PUBLIC
+target_include_directories(jxl-internal BEFORE PUBLIC
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>")
target_compile_definitions(jxl-internal INTERFACE -DJXL_STATIC_DEFINE)
diff --git a/third_party/jpeg-xl/lib/jxl/alpha_test.cc b/third_party/jpeg-xl/lib/jxl/alpha_test.cc
index a93254f3dd..6e26e77be6 100644
--- a/third_party/jpeg-xl/lib/jxl/alpha_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/alpha_test.cc
@@ -13,6 +13,16 @@
namespace jxl {
namespace {
+AlphaBlendingInputLayer makeAbil(const Color& rgb, const float& a) {
+ const float* data = rgb.data();
+ return {data, data + 1, data + 2, &a};
+}
+
+AlphaBlendingOutput makeAbo(Color& rgb, float& a) {
+ float* data = rgb.data();
+ return {data, data + 1, data + 2, &a};
+}
+
TEST(AlphaTest, BlendingWithNonPremultiplied) {
const Color bg_rgb{100, 110, 120};
const float bg_a = 180.f / 255;
@@ -22,16 +32,16 @@ TEST(AlphaTest, BlendingWithNonPremultiplied) {
Color out_rgb;
float out_a;
PerformAlphaBlending(
- /*bg=*/{&bg_rgb[0], &bg_rgb[1], &bg_rgb[2], &bg_a},
- /*fg=*/{&fg_rgb[0], &fg_rgb[1], &fg_rgb[2], &fg_a},
- /*out=*/{&out_rgb[0], &out_rgb[1], &out_rgb[2], &out_a}, 1,
+ /*bg=*/makeAbil(bg_rgb, bg_a),
+ /*fg=*/makeAbil(fg_rgb, fg_a),
+ /*out=*/makeAbo(out_rgb, out_a), 1,
/*alpha_is_premultiplied=*/false, /*clamp=*/false);
EXPECT_ARRAY_NEAR(out_rgb, (Color{77.2f, 83.0f, 90.6f}), 0.05f);
EXPECT_NEAR(out_a, 3174.f / 4095, 1e-5);
PerformAlphaBlending(
- /*bg=*/{&bg_rgb[0], &bg_rgb[1], &bg_rgb[2], &bg_a},
- /*fg=*/{&fg_rgb[0], &fg_rgb[1], &fg_rgb[2], &fg_a2},
- /*out=*/{&out_rgb[0], &out_rgb[1], &out_rgb[2], &out_a}, 1,
+ /*bg=*/makeAbil(bg_rgb, bg_a),
+ /*fg=*/makeAbil(fg_rgb, fg_a2),
+ /*out=*/makeAbo(out_rgb, out_a), 1,
/*alpha_is_premultiplied=*/false, /*clamp=*/true);
EXPECT_ARRAY_NEAR(out_rgb, fg_rgb, 0.05f);
EXPECT_NEAR(out_a, 1.0f, 1e-5);
@@ -46,16 +56,16 @@ TEST(AlphaTest, BlendingWithPremultiplied) {
Color out_rgb;
float out_a;
PerformAlphaBlending(
- /*bg=*/{&bg_rgb[0], &bg_rgb[1], &bg_rgb[2], &bg_a},
- /*fg=*/{&fg_rgb[0], &fg_rgb[1], &fg_rgb[2], &fg_a},
- /*out=*/{&out_rgb[0], &out_rgb[1], &out_rgb[2], &out_a}, 1,
+ /*bg=*/makeAbil(bg_rgb, bg_a),
+ /*fg=*/makeAbil(fg_rgb, fg_a),
+ /*out=*/makeAbo(out_rgb, out_a), 1,
/*alpha_is_premultiplied=*/true, /*clamp=*/false);
EXPECT_ARRAY_NEAR(out_rgb, (Color{101.5f, 105.1f, 114.8f}), 0.05f);
EXPECT_NEAR(out_a, 3174.f / 4095, 1e-5);
PerformAlphaBlending(
- /*bg=*/{&bg_rgb[0], &bg_rgb[1], &bg_rgb[2], &bg_a},
- /*fg=*/{&fg_rgb[0], &fg_rgb[1], &fg_rgb[2], &fg_a2},
- /*out=*/{&out_rgb[0], &out_rgb[1], &out_rgb[2], &out_a}, 1,
+ /*bg=*/makeAbil(bg_rgb, bg_a),
+ /*fg=*/makeAbil(fg_rgb, fg_a2),
+ /*out=*/makeAbo(out_rgb, out_a), 1,
/*alpha_is_premultiplied=*/true, /*clamp=*/true);
EXPECT_ARRAY_NEAR(out_rgb, fg_rgb, 0.05f);
EXPECT_NEAR(out_a, 1.0f, 1e-5);
diff --git a/third_party/jpeg-xl/lib/jxl/bit_reader_test.cc b/third_party/jpeg-xl/lib/jxl/bit_reader_test.cc
index 22a20649e0..802da855fa 100644
--- a/third_party/jpeg-xl/lib/jxl/bit_reader_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/bit_reader_test.cc
@@ -53,7 +53,7 @@ struct Symbol {
TEST(BitReaderTest, TestRoundTrip) {
test::ThreadPoolForTests pool(8);
EXPECT_TRUE(RunOnPool(
- &pool, 0, 1000, ThreadPool::NoInit,
+ pool.get(), 0, 1000, ThreadPool::NoInit,
[](const uint32_t task, size_t /* thread */) {
constexpr size_t kMaxBits = 8000;
BitWriter writer;
@@ -87,7 +87,7 @@ TEST(BitReaderTest, TestRoundTrip) {
TEST(BitReaderTest, TestSkip) {
test::ThreadPoolForTests pool(8);
EXPECT_TRUE(RunOnPool(
- &pool, 0, 96, ThreadPool::NoInit,
+ pool.get(), 0, 96, ThreadPool::NoInit,
[](const uint32_t task, size_t /* thread */) {
constexpr size_t kSize = 100;
diff --git a/third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli.h b/third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli.h
index e0bfd354e1..487db2bdce 100644
--- a/third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli.h
+++ b/third_party/jpeg-xl/lib/jxl/butteraugli/butteraugli.h
@@ -8,12 +8,10 @@
#ifndef LIB_JXL_BUTTERAUGLI_BUTTERAUGLI_H_
#define LIB_JXL_BUTTERAUGLI_BUTTERAUGLI_H_
-#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <atomic>
-#include <cmath>
#include <cstddef>
#include <memory>
@@ -21,7 +19,10 @@
#include "lib/jxl/base/status.h"
#include "lib/jxl/image.h"
+#if !defined(BUTTERAUGLI_ENABLE_CHECKS)
#define BUTTERAUGLI_ENABLE_CHECKS 0
+#endif
+
#define BUTTERAUGLI_RESTRICT JXL_RESTRICT
// This is the main interface to butteraugli image similarity
diff --git a/third_party/jpeg-xl/lib/jxl/cache_aligned.cc b/third_party/jpeg-xl/lib/jxl/cache_aligned.cc
index 8a95634d68..814b538cf6 100644
--- a/third_party/jpeg-xl/lib/jxl/cache_aligned.cc
+++ b/third_party/jpeg-xl/lib/jxl/cache_aligned.cc
@@ -128,11 +128,12 @@ void* CacheAligned::Allocate(const size_t payload_size, size_t offset) {
const uintptr_t payload = aligned + offset; // still aligned
// Stash `allocated` and payload_size inside header for use by Free().
- AllocationHeader* header = reinterpret_cast<AllocationHeader*>(payload) - 1;
+ AllocationHeader* header =
+ reinterpret_cast<AllocationHeader*>(payload) - 1; // NOLINT
header->allocated = allocated;
header->allocated_size = allocated_size;
- return JXL_ASSUME_ALIGNED(reinterpret_cast<void*>(payload), 64);
+ return JXL_ASSUME_ALIGNED(reinterpret_cast<void*>(payload), 64); // NOLINT
}
void CacheAligned::Free(const void* aligned_pointer) {
@@ -142,7 +143,7 @@ void CacheAligned::Free(const void* aligned_pointer) {
const uintptr_t payload = reinterpret_cast<uintptr_t>(aligned_pointer);
JXL_ASSERT(payload % kAlignment == 0);
const AllocationHeader* header =
- reinterpret_cast<const AllocationHeader*>(payload) - 1;
+ reinterpret_cast<const AllocationHeader*>(payload) - 1; // NOLINT
// Subtract (2's complement negation).
bytes_in_use.fetch_add(~header->allocated_size + 1,
diff --git a/third_party/jpeg-xl/lib/jxl/cms/jxl_cms_internal.h b/third_party/jpeg-xl/lib/jxl/cms/jxl_cms_internal.h
index 7f59e688d0..a787bd11d8 100644
--- a/third_party/jpeg-xl/lib/jxl/cms/jxl_cms_internal.h
+++ b/third_party/jpeg-xl/lib/jxl/cms/jxl_cms_internal.h
@@ -183,12 +183,12 @@ static Status ToneMapPixel(const JxlColorEncoding& c, const float in[3],
const float f_y = lab_f(xyz[1] / kYn);
const float f_z = lab_f(xyz[2] / kZn);
- pcslab_out[0] =
- static_cast<uint8_t>(.5f + 255.f * Clamp1(1.16f * f_y - .16f, 0.f, 1.f));
+ pcslab_out[0] = static_cast<uint8_t>(
+ std::lroundf(255.f * Clamp1(1.16f * f_y - .16f, 0.f, 1.f)));
pcslab_out[1] = static_cast<uint8_t>(
- .5f + 128.f + Clamp1(500 * (f_x - f_y), -128.f, 127.f));
+ std::lroundf(128.f + Clamp1(500 * (f_x - f_y), -128.f, 127.f)));
pcslab_out[2] = static_cast<uint8_t>(
- .5f + 128.f + Clamp1(200 * (f_y - f_z), -128.f, 127.f));
+ std::lroundf(128.f + Clamp1(200 * (f_y - f_z), -128.f, 127.f)));
return true;
}
@@ -581,7 +581,8 @@ static void CreateICCCurvCurvTag(const std::vector<uint16_t>& curve,
}
// Writes 12 + 4*params.size() bytes
-static Status CreateICCCurvParaTag(std::vector<float> params, size_t curve_type,
+static Status CreateICCCurvParaTag(const std::vector<float>& params,
+ size_t curve_type,
std::vector<uint8_t>* tags) {
WriteICCTag("para", tags->size(), tags);
WriteICCUint32(0, tags->size(), tags);
@@ -637,7 +638,7 @@ static Status CreateICCLutAtoBTagForXYB(std::vector<uint8_t>* tags) {
for (size_t ib = 0; ib < 2; ++ib) {
const jxl::cms::ColorCube0D& out_f = cube[ix][iy][ib];
for (int i = 0; i < 3; ++i) {
- int32_t val = static_cast<int32_t>(0.5f + 65535 * out_f[i]);
+ int32_t val = static_cast<int32_t>(std::lroundf(65535 * out_f[i]));
JXL_DASSERT(val >= 0 && val <= 65535);
WriteICCUint16(val, tags->size(), tags);
}
diff --git a/third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h b/third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h
index 81f301a31d..1f85dcca41 100644
--- a/third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h
+++ b/third_party/jpeg-xl/lib/jxl/cms/tone_mapping.h
@@ -22,8 +22,8 @@ class Rec2408ToneMapperBase {
explicit Rec2408ToneMapperBase(std::pair<float, float> source_range,
std::pair<float, float> target_range,
const Vector3& primaries_luminances)
- : source_range_(source_range),
- target_range_(target_range),
+ : source_range_(std::move(source_range)),
+ target_range_(std::move(target_range)),
red_Y_(primaries_luminances[0]),
green_Y_(primaries_luminances[1]),
blue_Y_(primaries_luminances[2]) {}
@@ -56,7 +56,7 @@ class Rec2408ToneMapperBase {
}
protected:
- float InvEOTF(const float luminance) const {
+ static float InvEOTF(const float luminance) {
return TF_PQ_Base::EncodedFromDisplay(/*display_intensity_target=*/1.0,
luminance);
}
diff --git a/third_party/jpeg-xl/lib/jxl/cms/transfer_functions-inl.h b/third_party/jpeg-xl/lib/jxl/cms/transfer_functions-inl.h
index 84bcbb45ed..133e624c08 100644
--- a/third_party/jpeg-xl/lib/jxl/cms/transfer_functions-inl.h
+++ b/third_party/jpeg-xl/lib/jxl/cms/transfer_functions-inl.h
@@ -69,7 +69,7 @@ class TF_HLG : TF_HLG_Base {
class TF_709 {
public:
- JXL_INLINE double EncodedFromDisplay(const double d) const {
+ static JXL_INLINE double EncodedFromDisplay(const double d) {
if (d < kThresh) return kMulLow * d;
return kMulHi * std::pow(d, kPowHi) + kSub;
}
diff --git a/third_party/jpeg-xl/lib/jxl/color_management_test.cc b/third_party/jpeg-xl/lib/jxl/color_management_test.cc
index b2d47c73f9..77cbe56926 100644
--- a/third_party/jpeg-xl/lib/jxl/color_management_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/color_management_test.cc
@@ -434,7 +434,7 @@ TEST_F(ColorManagementTest, GoldenXYBCube) {
for (size_t ib = 0; ib < 2; ++ib) {
const jxl::cms::ColorCube0D& out_f = cube[ix][iy][ib];
for (int i = 0; i < 3; ++i) {
- int32_t val = static_cast<int32_t>(0.5f + 65535 * out_f[i]);
+ int32_t val = static_cast<int32_t>(std::lroundf(65535 * out_f[i]));
ASSERT_TRUE(val >= 0 && val <= 65535);
actual.push_back(val);
}
diff --git a/third_party/jpeg-xl/lib/jxl/convolve_test.cc b/third_party/jpeg-xl/lib/jxl/convolve_test.cc
index 09cbdc12a6..084398c5e1 100644
--- a/third_party/jpeg-xl/lib/jxl/convolve_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/convolve_test.cc
@@ -161,7 +161,7 @@ void TestConvolve() {
test::ThreadPoolForTests pool(4);
EXPECT_EQ(true,
RunOnPool(
- &pool, kConvolveMaxRadius, 40, ThreadPool::NoInit,
+ pool.get(), kConvolveMaxRadius, 40, ThreadPool::NoInit,
[](const uint32_t task, size_t /*thread*/) {
const size_t xsize = task;
Rng rng(129 + 13 * xsize);
@@ -176,15 +176,15 @@ void TestConvolve() {
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sym3------------------");
VerifySymmetric3(xsize, ysize, null_pool, &rng);
- VerifySymmetric3(xsize, ysize, &pool3, &rng);
+ VerifySymmetric3(xsize, ysize, pool3.get(), &rng);
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sym5------------------");
VerifySymmetric5(xsize, ysize, null_pool, &rng);
- VerifySymmetric5(xsize, ysize, &pool3, &rng);
+ VerifySymmetric5(xsize, ysize, pool3.get(), &rng);
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sep5------------------");
VerifySeparable5(xsize, ysize, null_pool, &rng);
- VerifySeparable5(xsize, ysize, &pool3, &rng);
+ VerifySeparable5(xsize, ysize, pool3.get(), &rng);
}
},
"TestConvolve"));
diff --git a/third_party/jpeg-xl/lib/jxl/dct_for_test.h b/third_party/jpeg-xl/lib/jxl/dct_for_test.h
index 58dd75e20e..b0cbffacad 100644
--- a/third_party/jpeg-xl/lib/jxl/dct_for_test.h
+++ b/third_party/jpeg-xl/lib/jxl/dct_for_test.h
@@ -22,7 +22,7 @@ static inline double alpha(int u) { return u == 0 ? 0.7071067811865475 : 1.0; }
// N-DCT on M columns, divided by sqrt(N). Matches the definition in the spec.
template <size_t N, size_t M>
-void DCT1D(double block[N * M], double out[N * M]) {
+void DCT1D(const double block[N * M], double out[N * M]) {
std::vector<double> matrix(N * N);
const double scale = std::sqrt(2.0) / N;
for (size_t y = 0; y < N; y++) {
@@ -43,7 +43,7 @@ void DCT1D(double block[N * M], double out[N * M]) {
// N-IDCT on M columns, multiplied by sqrt(N). Matches the definition in the
// spec.
template <size_t N, size_t M>
-void IDCT1D(double block[N * M], double out[N * M]) {
+void IDCT1D(const double block[N * M], double out[N * M]) {
std::vector<double> matrix(N * N);
const double scale = std::sqrt(2.0);
for (size_t y = 0; y < N; y++) {
@@ -63,7 +63,7 @@ void IDCT1D(double block[N * M], double out[N * M]) {
}
template <size_t N, size_t M>
-void TransposeBlock(double in[N * M], double out[M * N]) {
+void TransposeBlock(const double in[N * M], double out[M * N]) {
for (size_t x = 0; x < N; x++) {
for (size_t y = 0; y < M; y++) {
out[y * N + x] = in[x * M + y];
diff --git a/third_party/jpeg-xl/lib/jxl/dct_test.cc b/third_party/jpeg-xl/lib/jxl/dct_test.cc
index e4982e2f45..57ce9291e2 100644
--- a/third_party/jpeg-xl/lib/jxl/dct_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/dct_test.cc
@@ -160,7 +160,7 @@ void TestInverseT(float accuracy) {
test::ThreadPoolForTests pool(N < 32 ? 0 : 8);
enum { kBlockSize = N * N };
EXPECT_TRUE(RunOnPool(
- &pool, 0, kBlockSize, ThreadPool::NoInit,
+ pool.get(), 0, kBlockSize, ThreadPool::NoInit,
[accuracy](const uint32_t task, size_t /*thread*/) {
const size_t i = static_cast<size_t>(task);
HWY_ALIGN float x[kBlockSize] = {0.0f};
diff --git a/third_party/jpeg-xl/lib/jxl/dec_cache.cc b/third_party/jpeg-xl/lib/jxl/dec_cache.cc
index 2a89420018..639857d4f8 100644
--- a/third_party/jpeg-xl/lib/jxl/dec_cache.cc
+++ b/third_party/jpeg-xl/lib/jxl/dec_cache.cc
@@ -125,8 +125,8 @@ Status PassesDecoderState::PreparePipeline(const FrameHeader& frame_header,
if (frame_header.CanBeReferenced() &&
frame_header.save_before_color_transform) {
- builder.AddStage(GetWriteToImageBundleStage(
- &frame_storage_for_referencing, output_encoding_info.color_encoding));
+ builder.AddStage(GetWriteToImageBundleStage(&frame_storage_for_referencing,
+ output_encoding_info));
}
bool has_alpha = false;
@@ -181,7 +181,7 @@ Status PassesDecoderState::PreparePipeline(const FrameHeader& frame_header,
linear = false;
}
builder.AddStage(GetWriteToImageBundleStage(
- &frame_storage_for_referencing, output_encoding_info.color_encoding));
+ &frame_storage_for_referencing, output_encoding_info));
}
if (options.render_spotcolors &&
@@ -228,7 +228,7 @@ Status PassesDecoderState::PreparePipeline(const FrameHeader& frame_header,
if ((output_encoding_info.color_encoding_is_original) ||
(!output_encoding_info.cms_set) || mixing_color_and_grey) {
// in those cases we only need a linear stage in other cases we attempt
- // to obtain an cms stage: the cases are
+ // to obtain a cms stage: the cases are
// - output_encoding_info.color_encoding_is_original: no cms stage
// needed because it would be a no-op
// - !output_encoding_info.cms_set: can't use the cms, so no point in
@@ -255,8 +255,8 @@ Status PassesDecoderState::PreparePipeline(const FrameHeader& frame_header,
has_alpha, unpremul_alpha, alpha_c,
undo_orientation, extra_output));
} else {
- builder.AddStage(GetWriteToImageBundleStage(
- decoded, output_encoding_info.color_encoding));
+ builder.AddStage(
+ GetWriteToImageBundleStage(decoded, output_encoding_info));
}
}
JXL_ASSIGN_OR_RETURN(render_pipeline,
diff --git a/third_party/jpeg-xl/lib/jxl/dec_modular.cc b/third_party/jpeg-xl/lib/jxl/dec_modular.cc
index 49561e6ec2..80cc9d1360 100644
--- a/third_party/jpeg-xl/lib/jxl/dec_modular.cc
+++ b/third_party/jpeg-xl/lib/jxl/dec_modular.cc
@@ -362,7 +362,7 @@ Status ModularFrameDecoder::DecodeGroup(
// Undo global transforms that have been pushed to the group level
if (!use_full_image) {
JXL_ASSERT(render_pipeline_input);
- for (auto t : global_transform) {
+ for (const auto& t : global_transform) {
JXL_RETURN_IF_ERROR(t.Inverse(gi, global_header.wp_header));
}
JXL_RETURN_IF_ERROR(ModularImageToDecodedRect(
diff --git a/third_party/jpeg-xl/lib/jxl/decode_test.cc b/third_party/jpeg-xl/lib/jxl/decode_test.cc
index 33176cfd66..99b5871ccd 100644
--- a/third_party/jpeg-xl/lib/jxl/decode_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/decode_test.cc
@@ -2475,7 +2475,7 @@ void TestPartialStream(bool reconstructible_jpeg) {
TEST(DecodeTest, PixelPartialTest) { TestPartialStream(false); }
// Tests the return status when trying to decode JPEG bytes on incomplete file.
-TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGPartialTest)) {
+JXL_TRANSCODE_JPEG_TEST(DecodeTest, JPEGPartialTest) {
TEST_LIBJPEG_SUPPORT();
TestPartialStream(true);
}
@@ -4195,7 +4195,7 @@ TEST(DecodeTest, InputHandlingTestOneShot) {
}
}
-TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(InputHandlingTestJPEGOneshot)) {
+JXL_TRANSCODE_JPEG_TEST(DecodeTest, InputHandlingTestJPEGOneshot) {
TEST_LIBJPEG_SUPPORT();
size_t xsize = 123;
size_t ysize = 77;
@@ -4976,7 +4976,7 @@ void VerifyJPEGReconstruction(jxl::Span<const uint8_t> container,
EXPECT_EQ(0, memcmp(reconstructed_buffer.data(), jpeg_bytes.data(), used));
}
-TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructTestCodestream)) {
+JXL_TRANSCODE_JPEG_TEST(DecodeTest, JPEGReconstructTestCodestream) {
TEST_LIBJPEG_SUPPORT();
size_t xsize = 123;
size_t ysize = 77;
@@ -4994,7 +4994,7 @@ TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructTestCodestream)) {
VerifyJPEGReconstruction(jxl::Bytes(compressed), jxl::Bytes(jpeg_codestream));
}
-TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) {
+JXL_TRANSCODE_JPEG_TEST(DecodeTest, JPEGReconstructionTest) {
const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);
jxl::CodecInOut orig_io;
@@ -5024,7 +5024,7 @@ TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) {
VerifyJPEGReconstruction(jxl::Bytes(container), jxl::Bytes(orig));
}
-TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionMetadataTest)) {
+JXL_TRANSCODE_JPEG_TEST(DecodeTest, JPEGReconstructionMetadataTest) {
const std::string jpeg_path = "jxl/jpeg_reconstruction/1x1_exif_xmp.jpg";
const std::string jxl_path = "jxl/jpeg_reconstruction/1x1_exif_xmp.jxl";
const std::vector<uint8_t> jpeg = jxl::test::ReadTestData(jpeg_path);
@@ -5135,7 +5135,7 @@ TEST(DecodeTest, ExtentedBoxSizeTest) {
JxlDecoderDestroy(dec);
}
-TEST(DecodeTest, JXL_BOXES_TEST(BoxTest)) {
+JXL_BOXES_TEST(DecodeTest, BoxTest) {
size_t xsize = 1;
size_t ysize = 1;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
@@ -5212,7 +5212,7 @@ TEST(DecodeTest, JXL_BOXES_TEST(BoxTest)) {
JxlDecoderDestroy(dec);
}
-TEST(DecodeTest, JXL_BOXES_TEST(ExifBrobBoxTest)) {
+JXL_BOXES_TEST(DecodeTest, ExifBrobBoxTest) {
size_t xsize = 1;
size_t ysize = 1;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
@@ -5394,7 +5394,7 @@ TEST(DecodeTest, JXL_BOXES_TEST(ExifBrobBoxTest)) {
}
}
-TEST(DecodeTest, JXL_BOXES_TEST(PartialCodestreamBoxTest)) {
+JXL_BOXES_TEST(DecodeTest, PartialCodestreamBoxTest) {
size_t xsize = 23;
size_t ysize = 81;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
diff --git a/third_party/jpeg-xl/lib/jxl/enc_ar_control_field.cc b/third_party/jpeg-xl/lib/jxl/enc_ar_control_field.cc
index e80771248e..4f1d2dadb5 100644
--- a/third_party/jpeg-xl/lib/jxl/enc_ar_control_field.cc
+++ b/third_party/jpeg-xl/lib/jxl/enc_ar_control_field.cc
@@ -149,9 +149,9 @@ Status ProcessTile(const CompressParams& cparams,
float* JXL_RESTRICT row_out = sqrsum_00_row + y * sqrsum_00_stride;
for (size_t x = 0; x < rect.xsize() * 2; x++) {
auto sum = Zero(df4);
- for (size_t iy = 0; iy < 4; iy++) {
+ for (auto& row : rows_in) {
for (size_t ix = 0; ix < 4; ix += Lanes(df4)) {
- sum = Add(sum, LoadU(df4, rows_in[iy] + x * 4 + ix + 2));
+ sum = Add(sum, LoadU(df4, row + x * 4 + ix + 2));
}
}
row_out[x] = GetLane(Sqrt(SumOfLanes(df4, sum))) * (1.0f / 4.0f);
diff --git a/third_party/jpeg-xl/lib/jxl/enc_fast_lossless.cc b/third_party/jpeg-xl/lib/jxl/enc_fast_lossless.cc
index 58d0d00eaa..1797139428 100644
--- a/third_party/jpeg-xl/lib/jxl/enc_fast_lossless.cc
+++ b/third_party/jpeg-xl/lib/jxl/enc_fast_lossless.cc
@@ -202,8 +202,8 @@ size_t TOCBucket(size_t group_size) {
size_t TOCSize(const std::vector<size_t>& group_sizes) {
size_t toc_bits = 0;
- for (size_t i = 0; i < group_sizes.size(); i++) {
- toc_bits += kTOCBits[TOCBucket(group_sizes[i])];
+ for (size_t group_size : group_sizes) {
+ toc_bits += kTOCBits[TOCBucket(group_size)];
}
return (toc_bits + 7) / 8;
}
@@ -328,8 +328,8 @@ struct PrefixCode {
template <typename T>
static void ComputeCodeLengthsNonZeroImpl(const uint64_t* freqs, size_t n,
size_t precision, T infty,
- uint8_t* min_limit,
- uint8_t* max_limit,
+ const uint8_t* min_limit,
+ const uint8_t* max_limit,
uint8_t* nbits) {
assert(precision < 15);
assert(n <= kMaxNumSymbols);
@@ -454,8 +454,8 @@ struct PrefixCode {
uint8_t min_lengths[kNumLZ77] = {};
uint8_t l = 15 - level1_nbits[numraw];
uint8_t max_lengths[kNumLZ77];
- for (size_t i = 0; i < kNumLZ77; i++) {
- max_lengths[i] = l;
+ for (uint8_t& max_length : max_lengths) {
+ max_length = l;
}
size_t num_lz77 = kNumLZ77;
while (num_lz77 > 0 && lz77_counts[num_lz77 - 1] == 0) num_lz77--;
@@ -487,11 +487,11 @@ struct PrefixCode {
void WriteTo(BitWriter* writer) const {
uint64_t code_length_counts[18] = {};
code_length_counts[17] = 3 + 2 * (kNumLZ77 - 1);
- for (size_t i = 0; i < kNumRawSymbols; i++) {
- code_length_counts[raw_nbits[i]]++;
+ for (uint8_t raw_nbit : raw_nbits) {
+ code_length_counts[raw_nbit]++;
}
- for (size_t i = 0; i < kNumLZ77; i++) {
- code_length_counts[lz77_nbits[i]]++;
+ for (uint8_t lz77_nbit : lz77_nbits) {
+ code_length_counts[lz77_nbit]++;
}
uint8_t code_length_nbits[18] = {};
uint8_t code_length_nbits_min[18] = {};
@@ -527,9 +527,8 @@ struct PrefixCode {
code_length_bits, 18);
// Encode raw bit code lengths.
// Max bits written in this loop: 19 * 5 = 95
- for (size_t i = 0; i < kNumRawSymbols; i++) {
- writer->Write(code_length_nbits[raw_nbits[i]],
- code_length_bits[raw_nbits[i]]);
+ for (uint8_t raw_nbit : raw_nbits) {
+ writer->Write(code_length_nbits[raw_nbit], code_length_bits[raw_nbit]);
}
size_t num_lz77 = kNumLZ77;
while (lz77_nbits[num_lz77 - 1] == 0) {
@@ -590,8 +589,8 @@ struct JxlFastLosslessFrameState {
size_t JxlFastLosslessOutputSize(const JxlFastLosslessFrameState* frame) {
size_t total_size_groups = 0;
- for (size_t i = 0; i < frame->group_data.size(); i++) {
- total_size_groups += SectionSize(frame->group_data[i]);
+ for (const auto& section : frame->group_data) {
+ total_size_groups += SectionSize(section);
}
return frame->header.bytes_written + total_size_groups;
}
@@ -719,11 +718,10 @@ void JxlFastLosslessPrepareHeader(JxlFastLosslessFrameState* frame,
output->Write(1, 0); // No TOC permutation
output->ZeroPadToByte(); // TOC is byte-aligned.
assert(add_image_header || output->bytes_written <= kMaxFrameHeaderSize);
- for (size_t i = 0; i < frame->group_sizes.size(); i++) {
- size_t sz = frame->group_sizes[i];
- size_t bucket = TOCBucket(sz);
+ for (size_t group_size : frame->group_sizes) {
+ size_t bucket = TOCBucket(group_size);
output->Write(2, bucket);
- output->Write(kTOCBits[bucket] - 2, sz - kGroupSizeOffset[bucket]);
+ output->Write(kTOCBits[bucket] - 2, group_size - kGroupSizeOffset[bucket]);
}
output->ZeroPadToByte(); // Groups are byte-aligned.
}
diff --git a/third_party/jpeg-xl/lib/jxl/enc_frame.cc b/third_party/jpeg-xl/lib/jxl/enc_frame.cc
index 8587e1aed2..2a3389921b 100644
--- a/third_party/jpeg-xl/lib/jxl/enc_frame.cc
+++ b/third_party/jpeg-xl/lib/jxl/enc_frame.cc
@@ -1786,8 +1786,8 @@ size_t TOCBucket(size_t group_size) {
size_t TOCSize(const std::vector<size_t>& group_sizes) {
size_t toc_bits = 0;
- for (size_t i = 0; i < group_sizes.size(); i++) {
- toc_bits += kTOCBits[TOCBucket(group_sizes[i])];
+ for (size_t group_size : group_sizes) {
+ toc_bits += kTOCBits[TOCBucket(group_size)];
}
return (toc_bits + 7) / 8;
}
@@ -1795,8 +1795,8 @@ size_t TOCSize(const std::vector<size_t>& group_sizes) {
PaddedBytes EncodeTOC(const std::vector<size_t>& group_sizes, AuxOut* aux_out) {
BitWriter writer;
BitWriter::Allotment allotment(&writer, 32 * group_sizes.size());
- for (size_t i = 0; i < group_sizes.size(); i++) {
- JXL_CHECK(U32Coder::Write(kTocDist, group_sizes[i], &writer));
+ for (size_t group_size : group_sizes) {
+ JXL_CHECK(U32Coder::Write(kTocDist, group_size, &writer));
}
writer.ZeroPadToByte(); // before first group
allotment.ReclaimAndCharge(&writer, kLayerTOC, aux_out);
@@ -1854,13 +1854,13 @@ void RemoveUnusedHistograms(std::vector<uint8_t>& context_map,
EntropyEncodingData& codes) {
std::vector<int> remap(256, -1);
std::vector<uint8_t> inv_remap;
- for (size_t i = 0; i < context_map.size(); ++i) {
- const uint8_t histo_ix = context_map[i];
+ for (uint8_t& context : context_map) {
+ const uint8_t histo_ix = context;
if (remap[histo_ix] == -1) {
remap[histo_ix] = inv_remap.size();
inv_remap.push_back(histo_ix);
}
- context_map[i] = remap[histo_ix];
+ context = remap[histo_ix];
}
EntropyEncodingData new_codes;
new_codes.use_prefix_code = codes.use_prefix_code;
diff --git a/third_party/jpeg-xl/lib/jxl/enc_group.cc b/third_party/jpeg-xl/lib/jxl/enc_group.cc
index 1967fdaba9..2b60643e7c 100644
--- a/third_party/jpeg-xl/lib/jxl/enc_group.cc
+++ b/third_party/jpeg-xl/lib/jxl/enc_group.cc
@@ -43,7 +43,7 @@ using hwy::HWY_NAMESPACE::Round;
void QuantizeBlockAC(const Quantizer& quantizer, const bool error_diffusion,
size_t c, float qm_multiplier, size_t quant_kind,
size_t xsize, size_t ysize, float* thresholds,
- const float* JXL_RESTRICT block_in, int32_t* quant,
+ const float* JXL_RESTRICT block_in, const int32_t* quant,
int32_t* JXL_RESTRICT block_out) {
const float* JXL_RESTRICT qm = quantizer.InvDequantMatrix(quant_kind, c);
float qac = quantizer.Scale() * (*quant);
@@ -322,10 +322,8 @@ void QuantizeRoundtripYBlockAC(PassesEncoderState* enc_state, const size_t size,
int quant_orig = *quant;
float val[3] = {enc_state->x_qm_multiplier, 1.0f,
enc_state->b_qm_multiplier};
- int clut[3] = {1, 0, 2};
- for (int ii = 0; ii < 3; ++ii) {
+ for (int c : {1, 0, 2}) {
float thres[4] = {0.58f, 0.64f, 0.64f, 0.64f};
- int c = clut[ii];
*quant = quant_orig;
AdjustQuantBlockAC(quantizer, c, val[c], quant_kind, xsize, ysize,
&thres[0], inout + c * size, quant);
diff --git a/third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc b/third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc
index a29fb3f299..c3899600e5 100644
--- a/third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc
+++ b/third_party/jpeg-xl/lib/jxl/enc_icc_codec.cc
@@ -127,8 +127,8 @@ Status PredictICC(const uint8_t* icc, size_t size, PaddedBytes* result) {
}
if (size <= kICCHeaderSize) {
EncodeVarInt(0, result); // 0 commands
- for (size_t i = 0; i < data.size(); i++) {
- result->push_back(data[i]);
+ for (uint8_t b : data) {
+ result->push_back(b);
}
return true;
}
@@ -403,11 +403,11 @@ Status PredictICC(const uint8_t* icc, size_t size, PaddedBytes* result) {
data.push_back(icc[last0++]);
}
}
- for (size_t i = 0; i < commands_add.size(); i++) {
- commands.push_back(commands_add[i]);
+ for (uint8_t b : commands_add) {
+ commands.push_back(b);
}
- for (size_t i = 0; i < data_add.size(); i++) {
- data.push_back(data_add[i]);
+ for (uint8_t b : data_add) {
+ data.push_back(b);
}
last0 = pos;
}
@@ -417,11 +417,11 @@ Status PredictICC(const uint8_t* icc, size_t size, PaddedBytes* result) {
}
EncodeVarInt(commands.size(), result);
- for (size_t i = 0; i < commands.size(); i++) {
- result->push_back(commands[i]);
+ for (uint8_t b : commands) {
+ result->push_back(b);
}
- for (size_t i = 0; i < data.size(); i++) {
- result->push_back(data[i]);
+ for (uint8_t b : data) {
+ result->push_back(b);
}
return true;
diff --git a/third_party/jpeg-xl/lib/jxl/enc_modular.cc b/third_party/jpeg-xl/lib/jxl/enc_modular.cc
index dbd62d4a01..35fac3c827 100644
--- a/third_party/jpeg-xl/lib/jxl/enc_modular.cc
+++ b/third_party/jpeg-xl/lib/jxl/enc_modular.cc
@@ -300,13 +300,12 @@ bool do_transform(Image& image, const Transform& tr,
bool maybe_do_transform(Image& image, const Transform& tr,
const CompressParams& cparams,
- const weighted::Header& wp_header,
+ const weighted::Header& wp_header, float cost_before,
jxl::ThreadPool* pool = nullptr,
bool force_jxlart = false) {
if (force_jxlart || cparams.speed_tier >= SpeedTier::kSquirrel) {
return do_transform(image, tr, wp_header, pool, force_jxlart);
}
- float cost_before = EstimateCost(image);
bool did_it = do_transform(image, tr, wp_header, pool);
if (did_it) {
float cost_after = EstimateCost(image);
@@ -321,6 +320,110 @@ bool maybe_do_transform(Image& image, const Transform& tr,
return did_it;
}
+void try_palettes(Image& gi, int& max_bitdepth, int& maxval,
+ const CompressParams& cparams_, float channel_colors_percent,
+ jxl::ThreadPool* pool = nullptr) {
+ float cost_before = 0.f;
+ size_t did_palette = 0;
+ float nb_pixels = gi.channel[0].w * gi.channel[0].h;
+ int nb_chans = gi.channel.size() - gi.nb_meta_channels;
+ // arbitrary estimate: 4.8 bpp for 8-bit RGB
+ float arbitrary_bpp_estimate = 0.2f * gi.bitdepth * nb_chans;
+
+ if (cparams_.palette_colors != 0 || cparams_.lossy_palette) {
+ // when not estimating, assume some arbitrary bpp
+ cost_before = cparams_.speed_tier <= SpeedTier::kSquirrel
+ ? EstimateCost(gi)
+ : nb_pixels * arbitrary_bpp_estimate;
+ // all-channel palette (e.g. RGBA)
+ if (nb_chans > 1) {
+ Transform maybe_palette(TransformId::kPalette);
+ maybe_palette.begin_c = gi.nb_meta_channels;
+ maybe_palette.num_c = nb_chans;
+ // Heuristic choice of max colors for a palette:
+ // max_colors = nb_pixels * estimated_bpp_without_palette * 0.0005 +
+ // + nb_pixels / 128 + 128
+ // (estimated_bpp_without_palette = cost_before / nb_pixels)
+ // Rationale: small image with large palette is not effective;
+ // also if the entropy (estimated bpp) is low (e.g. mostly solid/gradient
+ // areas), palette is less useful and may even be counterproductive.
+ maybe_palette.nb_colors = std::min(
+ static_cast<int>(cost_before * 0.0005f + nb_pixels / 128 + 128),
+ std::abs(cparams_.palette_colors));
+ maybe_palette.ordered_palette = cparams_.palette_colors >= 0;
+ maybe_palette.lossy_palette =
+ (cparams_.lossy_palette && maybe_palette.num_c == 3);
+ if (maybe_palette.lossy_palette) {
+ maybe_palette.predictor = Predictor::Average4;
+ }
+ // TODO(veluca): use a custom weighted header if using the weighted
+ // predictor.
+ if (maybe_do_transform(gi, maybe_palette, cparams_, weighted::Header(),
+ cost_before, pool, cparams_.options.zero_tokens)) {
+ did_palette = 1;
+ };
+ }
+ // all-minus-one-channel palette (RGB with separate alpha, or CMY with
+ // separate K)
+ if (!did_palette && nb_chans > 3) {
+ Transform maybe_palette_3(TransformId::kPalette);
+ maybe_palette_3.begin_c = gi.nb_meta_channels;
+ maybe_palette_3.num_c = nb_chans - 1;
+ maybe_palette_3.nb_colors = std::min(
+ static_cast<int>(cost_before * 0.0005f + nb_pixels / 128 + 128),
+ std::abs(cparams_.palette_colors));
+ maybe_palette_3.ordered_palette = cparams_.palette_colors >= 0;
+ maybe_palette_3.lossy_palette = cparams_.lossy_palette;
+ if (maybe_palette_3.lossy_palette) {
+ maybe_palette_3.predictor = Predictor::Average4;
+ }
+ if (maybe_do_transform(gi, maybe_palette_3, cparams_, weighted::Header(),
+ cost_before, pool, cparams_.options.zero_tokens)) {
+ did_palette = 1;
+ }
+ }
+ }
+
+ if (channel_colors_percent > 0) {
+ // single channel palette (like FLIF's ChannelCompact)
+ size_t nb_channels = gi.channel.size() - gi.nb_meta_channels - did_palette;
+ int orig_bitdepth = max_bitdepth;
+ max_bitdepth = 0;
+ if (nb_channels > 0 && (did_palette || cost_before == 0)) {
+ cost_before =
+ cparams_.speed_tier < SpeedTier::kSquirrel ? EstimateCost(gi) : 0;
+ }
+ for (size_t i = did_palette; i < nb_channels + did_palette; i++) {
+ int32_t min;
+ int32_t max;
+ compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max);
+ int64_t colors = static_cast<int64_t>(max) - min + 1;
+ JXL_DEBUG_V(10, "Channel %" PRIuS ": range=%i..%i", i, min, max);
+ Transform maybe_palette_1(TransformId::kPalette);
+ maybe_palette_1.begin_c = i + gi.nb_meta_channels;
+ maybe_palette_1.num_c = 1;
+ // simple heuristic: if less than X percent of the values in the range
+ // actually occur, it is probably worth it to do a compaction
+ // (but only if the channel palette is less than 6% the size of the
+ // image itself)
+ maybe_palette_1.nb_colors =
+ std::min(static_cast<int>(nb_pixels / 16),
+ static_cast<int>(channel_colors_percent / 100. * colors));
+ if (maybe_do_transform(gi, maybe_palette_1, cparams_, weighted::Header(),
+ cost_before, pool)) {
+ // effective bit depth is lower, adjust quantization accordingly
+ compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max);
+ if (max < maxval) maxval = max;
+ int ch_bitdepth =
+ (max > 0 ? CeilLog2Nonzero(static_cast<uint32_t>(max)) : 0);
+ if (ch_bitdepth > max_bitdepth) max_bitdepth = ch_bitdepth;
+ } else {
+ max_bitdepth = orig_bitdepth;
+ }
+ }
+ }
+}
+
} // namespace
ModularFrameEncoder::ModularFrameEncoder(const FrameHeader& frame_header,
@@ -479,7 +582,6 @@ ModularFrameEncoder::ModularFrameEncoder(const FrameHeader& frame_header,
cparams_.options.predictor = Predictor::Gradient;
}
} else {
- delta_pred_ = cparams_.options.predictor;
if (cparams_.lossy_palette) cparams_.options.predictor = Predictor::Zero;
}
if (!cparams_.ModularPartIsLossless()) {
@@ -624,6 +726,7 @@ Status ModularFrameEncoder::ComputeEncodingData(
pixel_type* const JXL_RESTRICT row_out = gi.channel[c_out].Row(y);
pixel_type* const JXL_RESTRICT row_Y = gi.channel[0].Row(y);
for (size_t x = 0; x < xsize; ++x) {
+ // TODO(eustas): check if std::roundf is appropriate
row_out[x] = row_in[x] * factor + 0.5f;
row_out[x] -= row_Y[x];
// zero the lsb of B
@@ -720,81 +823,16 @@ Status ModularFrameEncoder::ComputeEncodingData(
cparams_.lossy_palette = false;
}
- // Global palette
- if ((cparams_.palette_colors != 0 || cparams_.lossy_palette) && !groupwise) {
- // all-channel palette (e.g. RGBA)
- if (gi.channel.size() - gi.nb_meta_channels > 1) {
- Transform maybe_palette(TransformId::kPalette);
- maybe_palette.begin_c = gi.nb_meta_channels;
- maybe_palette.num_c = gi.channel.size() - gi.nb_meta_channels;
- maybe_palette.nb_colors = std::min(static_cast<int>(xsize * ysize / 2),
- std::abs(cparams_.palette_colors));
- maybe_palette.ordered_palette = cparams_.palette_colors >= 0;
- maybe_palette.lossy_palette =
- (cparams_.lossy_palette && maybe_palette.num_c == 3);
- if (maybe_palette.lossy_palette) {
- maybe_palette.predictor = delta_pred_;
- }
- // TODO(veluca): use a custom weighted header if using the weighted
- // predictor.
- maybe_do_transform(gi, maybe_palette, cparams_, weighted::Header(), pool,
- cparams_.options.zero_tokens);
- }
- // all-minus-one-channel palette (RGB with separate alpha, or CMY with
- // separate K)
- if (gi.channel.size() - gi.nb_meta_channels > 3) {
- Transform maybe_palette_3(TransformId::kPalette);
- maybe_palette_3.begin_c = gi.nb_meta_channels;
- maybe_palette_3.num_c = gi.channel.size() - gi.nb_meta_channels - 1;
- maybe_palette_3.nb_colors = std::min(static_cast<int>(xsize * ysize / 3),
- std::abs(cparams_.palette_colors));
- maybe_palette_3.ordered_palette = cparams_.palette_colors >= 0;
- maybe_palette_3.lossy_palette = cparams_.lossy_palette;
- if (maybe_palette_3.lossy_palette) {
- maybe_palette_3.predictor = delta_pred_;
- }
- maybe_do_transform(gi, maybe_palette_3, cparams_, weighted::Header(),
- pool, cparams_.options.zero_tokens);
- }
- }
-
- // Global channel palette
- if (!groupwise && cparams_.channel_colors_pre_transform_percent > 0 &&
- !cparams_.lossy_palette &&
+ // Global palette transforms
+ float channel_colors_percent = 0;
+ if (!cparams_.lossy_palette &&
(cparams_.speed_tier <= SpeedTier::kThunder ||
(do_color && metadata.bit_depth.bits_per_sample > 8))) {
- // single channel palette (like FLIF's ChannelCompact)
- size_t nb_channels = gi.channel.size() - gi.nb_meta_channels;
- int orig_bitdepth = max_bitdepth;
- max_bitdepth = 0;
- for (size_t i = 0; i < nb_channels; i++) {
- int32_t min;
- int32_t max;
- compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max);
- int64_t colors = static_cast<int64_t>(max) - min + 1;
- JXL_DEBUG_V(10, "Channel %" PRIuS ": range=%i..%i", i, min, max);
- Transform maybe_palette_1(TransformId::kPalette);
- maybe_palette_1.begin_c = i + gi.nb_meta_channels;
- maybe_palette_1.num_c = 1;
- // simple heuristic: if less than X percent of the values in the range
- // actually occur, it is probably worth it to do a compaction
- // (but only if the channel palette is less than 6% the size of the
- // image itself)
- maybe_palette_1.nb_colors = std::min(
- static_cast<int>(xsize * ysize / 16),
- static_cast<int>(cparams_.channel_colors_pre_transform_percent /
- 100. * colors));
- if (maybe_do_transform(gi, maybe_palette_1, cparams_, weighted::Header(),
- pool)) {
- // effective bit depth is lower, adjust quantization accordingly
- compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max);
- if (max < maxval) maxval = max;
- int ch_bitdepth =
- (max > 0 ? CeilLog2Nonzero(static_cast<uint32_t>(max)) : 0);
- if (ch_bitdepth > max_bitdepth) max_bitdepth = ch_bitdepth;
- } else
- max_bitdepth = orig_bitdepth;
- }
+ channel_colors_percent = cparams_.channel_colors_pre_transform_percent;
+ }
+ if (!groupwise) {
+ try_palettes(gi, max_bitdepth, maxval, cparams_, channel_colors_percent,
+ pool);
}
// don't do an RCT if we're short on bits
@@ -1318,61 +1356,17 @@ Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect,
if (gi.channel.empty()) return true;
// Do some per-group transforms
- // Local palette
+ // Local palette transforms
// TODO(veluca): make this work with quantize-after-prediction in lossy
// mode.
- if (cparams_.butteraugli_distance == 0.f && cparams_.palette_colors != 0 &&
+ if (cparams_.butteraugli_distance == 0.f && !cparams_.lossy_palette &&
cparams_.speed_tier < SpeedTier::kCheetah) {
- // all-channel palette (e.g. RGBA)
- if (gi.channel.size() - gi.nb_meta_channels > 1) {
- Transform maybe_palette(TransformId::kPalette);
- maybe_palette.begin_c = gi.nb_meta_channels;
- maybe_palette.num_c = gi.channel.size() - gi.nb_meta_channels;
- maybe_palette.nb_colors = std::abs(cparams_.palette_colors);
- maybe_palette.ordered_palette = cparams_.palette_colors >= 0;
- maybe_do_transform(gi, maybe_palette, cparams_, weighted::Header());
- }
- // all-minus-one-channel palette (RGB with separate alpha, or CMY with
- // separate K)
- if (gi.channel.size() - gi.nb_meta_channels > 3) {
- Transform maybe_palette_3(TransformId::kPalette);
- maybe_palette_3.begin_c = gi.nb_meta_channels;
- maybe_palette_3.num_c = gi.channel.size() - gi.nb_meta_channels - 1;
- maybe_palette_3.nb_colors = std::abs(cparams_.palette_colors);
- maybe_palette_3.ordered_palette = cparams_.palette_colors >= 0;
- maybe_palette_3.lossy_palette = cparams_.lossy_palette;
- if (maybe_palette_3.lossy_palette) {
- maybe_palette_3.predictor = Predictor::Weighted;
- }
- maybe_do_transform(gi, maybe_palette_3, cparams_, weighted::Header());
- }
- }
-
- // Local channel palette
- if (cparams_.channel_colors_percent > 0 &&
- cparams_.butteraugli_distance == 0.f && !cparams_.lossy_palette &&
- cparams_.speed_tier < SpeedTier::kCheetah &&
- !(cparams_.responsive && cparams_.decoding_speed_tier >= 1)) {
- // single channel palette (like FLIF's ChannelCompact)
- size_t nb_channels = gi.channel.size() - gi.nb_meta_channels;
- for (size_t i = 0; i < nb_channels; i++) {
- int32_t min;
- int32_t max;
- compute_minmax(gi.channel[gi.nb_meta_channels + i], &min, &max);
- int64_t colors = static_cast<int64_t>(max) - min + 1;
- JXL_DEBUG_V(10, "Channel %" PRIuS ": range=%i..%i", i, min, max);
- Transform maybe_palette_1(TransformId::kPalette);
- maybe_palette_1.begin_c = i + gi.nb_meta_channels;
- maybe_palette_1.num_c = 1;
- // simple heuristic: if less than X percent of the values in the range
- // actually occur, it is probably worth it to do a compaction
- // (but only if the channel palette is less than 80% the size of the
- // image itself)
- maybe_palette_1.nb_colors = std::min(
- static_cast<int>(xsize * ysize * 0.8),
- static_cast<int>(cparams_.channel_colors_percent / 100. * colors));
- maybe_do_transform(gi, maybe_palette_1, cparams_, weighted::Header());
+ int max_bitdepth = 0, maxval = 0; // don't care about that here
+ float channel_color_percent = 0;
+ if (!(cparams_.responsive && cparams_.decoding_speed_tier >= 1)) {
+ channel_color_percent = cparams_.channel_colors_percent;
}
+ try_palettes(gi, max_bitdepth, maxval, cparams_, channel_color_percent);
}
}
diff --git a/third_party/jpeg-xl/lib/jxl/enc_modular.h b/third_party/jpeg-xl/lib/jxl/enc_modular.h
index 8e2015b226..c7a8421982 100644
--- a/third_party/jpeg-xl/lib/jxl/enc_modular.h
+++ b/third_party/jpeg-xl/lib/jxl/enc_modular.h
@@ -107,7 +107,6 @@ class ModularFrameEncoder {
std::vector<size_t> tree_splits_;
std::vector<std::vector<uint32_t>> gi_channel_;
std::vector<size_t> image_widths_;
- Predictor delta_pred_ = Predictor::Average4;
struct GroupParams {
Rect rect;
diff --git a/third_party/jpeg-xl/lib/jxl/enc_params.h b/third_party/jpeg-xl/lib/jxl/enc_params.h
index 162c59d04c..5e3ff7789c 100644
--- a/third_party/jpeg-xl/lib/jxl/enc_params.h
+++ b/third_party/jpeg-xl/lib/jxl/enc_params.h
@@ -106,9 +106,12 @@ struct CompressParams {
// modular mode options below
ModularOptions options;
+
+ // TODO(eustas): use Override?
int responsive = -1;
int colorspace = -1;
int move_to_front_from_channel = -1;
+
// Use Global channel palette if #colors < this percentage of range
float channel_colors_pre_transform_percent = 95.f;
// Use Local channel palette if #colors < this percentage of range
diff --git a/third_party/jpeg-xl/lib/jxl/enc_patch_dictionary.cc b/third_party/jpeg-xl/lib/jxl/enc_patch_dictionary.cc
index f19ba0dd9e..6fc5f7f49e 100644
--- a/third_party/jpeg-xl/lib/jxl/enc_patch_dictionary.cc
+++ b/third_party/jpeg-xl/lib/jxl/enc_patch_dictionary.cc
@@ -237,9 +237,9 @@ StatusOr<std::vector<PatchInfo>> FindTextLikePatches(
auto is_same = [&opsin_rows, opsin_stride](std::pair<uint32_t, uint32_t> p1,
std::pair<uint32_t, uint32_t> p2) {
- for (size_t c = 0; c < 3; c++) {
- float v1 = opsin_rows[c][p1.second * opsin_stride + p1.first];
- float v2 = opsin_rows[c][p2.second * opsin_stride + p2.first];
+ for (auto& opsin_row : opsin_rows) {
+ float v1 = opsin_row[p1.second * opsin_stride + p1.first];
+ float v2 = opsin_row[p2.second * opsin_stride + p2.first];
if (std::fabs(v1 - v2) > 1e-4) {
return false;
}
@@ -556,8 +556,8 @@ StatusOr<std::vector<PatchInfo>> FindTextLikePatches(
size_t max_patch_size = 0;
- for (size_t i = 0; i < info.size(); i++) {
- size_t pixels = info[i].first.xsize * info[i].first.ysize;
+ for (const auto& patch : info) {
+ size_t pixels = patch.first.xsize * patch.first.ysize;
if (pixels > max_patch_size) max_patch_size = pixels;
}
@@ -605,10 +605,10 @@ Status FindBestPatchDictionary(const Image3F& opsin,
size_t max_y_size = 0;
size_t total_pixels = 0;
- for (size_t i = 0; i < info.size(); i++) {
- size_t pixels = info[i].first.xsize * info[i].first.ysize;
- if (max_x_size < info[i].first.xsize) max_x_size = info[i].first.xsize;
- if (max_y_size < info[i].first.ysize) max_y_size = info[i].first.ysize;
+ for (const auto& patch : info) {
+ size_t pixels = patch.first.xsize * patch.first.ysize;
+ if (max_x_size < patch.first.xsize) max_x_size = patch.first.xsize;
+ if (max_y_size < patch.first.ysize) max_y_size = patch.first.ysize;
total_pixels += pixels;
}
diff --git a/third_party/jpeg-xl/lib/jxl/enc_quant_weights.cc b/third_party/jpeg-xl/lib/jxl/enc_quant_weights.cc
index 35e49d5993..978dfd5925 100644
--- a/third_party/jpeg-xl/lib/jxl/enc_quant_weights.cc
+++ b/third_party/jpeg-xl/lib/jxl/enc_quant_weights.cc
@@ -116,9 +116,9 @@ Status DequantMatricesEncode(const DequantMatrices& matrices, BitWriter* writer,
bool all_default = true;
const std::vector<QuantEncoding>& encodings = matrices.encodings();
- for (size_t i = 0; i < encodings.size(); i++) {
- if (encodings[i].mode != QuantEncoding::kQuantModeLibrary ||
- encodings[i].predefined != 0) {
+ for (const auto& encoding : encodings) {
+ if (encoding.mode != QuantEncoding::kQuantModeLibrary ||
+ encoding.predefined != 0) {
all_default = false;
}
}
diff --git a/third_party/jpeg-xl/lib/jxl/enc_splines.cc b/third_party/jpeg-xl/lib/jxl/enc_splines.cc
index fa15648ca5..186f19da93 100644
--- a/third_party/jpeg-xl/lib/jxl/enc_splines.cc
+++ b/third_party/jpeg-xl/lib/jxl/enc_splines.cc
@@ -34,8 +34,8 @@ class QuantizedSplineEncoder {
tokens->emplace_back(kDCTContext, PackSigned(dct[i]));
}
};
- for (int c = 0; c < 3; ++c) {
- encode_dct(spline.color_dct_[c]);
+ for (const auto& dct : spline.color_dct_) {
+ encode_dct(dct);
}
encode_dct(spline.sigma_dct_);
}
diff --git a/third_party/jpeg-xl/lib/jxl/enc_toc.cc b/third_party/jpeg-xl/lib/jxl/enc_toc.cc
index e79298ef31..4ecba8fdb1 100644
--- a/third_party/jpeg-xl/lib/jxl/enc_toc.cc
+++ b/third_party/jpeg-xl/lib/jxl/enc_toc.cc
@@ -32,9 +32,9 @@ Status WriteGroupOffsets(const std::vector<BitWriter>& group_codes,
}
writer->ZeroPadToByte(); // before TOC entries
- for (size_t i = 0; i < group_codes.size(); i++) {
- JXL_ASSERT(group_codes[i].BitsWritten() % kBitsPerByte == 0);
- const size_t group_size = group_codes[i].BitsWritten() / kBitsPerByte;
+ for (const auto& bw : group_codes) {
+ JXL_ASSERT(bw.BitsWritten() % kBitsPerByte == 0);
+ const size_t group_size = bw.BitsWritten() / kBitsPerByte;
JXL_RETURN_IF_ERROR(U32Coder::Write(kTocDist, group_size, writer));
}
writer->ZeroPadToByte(); // before first group
diff --git a/third_party/jpeg-xl/lib/jxl/encode.cc b/third_party/jpeg-xl/lib/jxl/encode.cc
index 4dbbeba4e7..28a925dfc1 100644
--- a/third_party/jpeg-xl/lib/jxl/encode.cc
+++ b/third_party/jpeg-xl/lib/jxl/encode.cc
@@ -549,8 +549,8 @@ int VerifyLevelSettings(const JxlEncoder* enc, std::string* debug_string) {
if (debug_string) *debug_string = "Too many extra channels";
return 10;
}
- for (size_t i = 0; i < m.extra_channel_info.size(); ++i) {
- if (m.extra_channel_info[i].type == jxl::ExtraChannel::kBlack) {
+ for (const auto& eci : m.extra_channel_info) {
+ if (eci.type == jxl::ExtraChannel::kBlack) {
if (debug_string) *debug_string = "CMYK channel not allowed";
return 10;
}
@@ -1514,6 +1514,10 @@ float JxlEncoderDistanceFromQuality(float quality) {
JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option,
int64_t value) {
+ // Tri-state to bool convertors.
+ const auto default_to_true = [](int64_t v) { return v != 0; };
+ const auto default_to_false = [](int64_t v) { return v == 1; };
+
// check if value is -1, 0 or 1 for Override-type options
switch (option) {
case JXL_ENC_FRAME_SETTING_NOISE:
@@ -1680,7 +1684,7 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
// See the logic in cjxl. Similar for other settings. This should be
// handled in the encoder during JxlEncoderProcessOutput (or,
// alternatively, in the cjxl binary like now)
- frame_settings->values.cparams.lossy_palette = (value == 1);
+ frame_settings->values.cparams.lossy_palette = default_to_false(value);
break;
case JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM:
if (value < -1 || value > 2) {
@@ -1734,11 +1738,8 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
}
break;
case JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL:
- if (value == -1) {
- frame_settings->values.cparams.force_cfl_jpeg_recompression = true;
- } else {
- frame_settings->values.cparams.force_cfl_jpeg_recompression = value;
- }
+ frame_settings->values.cparams.force_cfl_jpeg_recompression =
+ default_to_true(value);
break;
case JXL_ENC_FRAME_INDEX_BOX:
if (value < 0 || value > 1) {
@@ -1752,7 +1753,8 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
"Float option, try setting it with "
"JxlEncoderFrameSettingsSetFloatOption");
case JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES:
- frame_settings->values.cparams.jpeg_compress_boxes = value;
+ frame_settings->values.cparams.jpeg_compress_boxes =
+ default_to_true(value);
break;
case JXL_ENC_FRAME_SETTING_BUFFERING:
if (value < -1 || value > 3) {
@@ -1762,20 +1764,21 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
frame_settings->values.cparams.buffering = value;
break;
case JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF:
- frame_settings->values.cparams.jpeg_keep_exif = value;
+ frame_settings->values.cparams.jpeg_keep_exif = default_to_true(value);
break;
case JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP:
- frame_settings->values.cparams.jpeg_keep_xmp = value;
+ frame_settings->values.cparams.jpeg_keep_xmp = default_to_true(value);
break;
case JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF:
- frame_settings->values.cparams.jpeg_keep_jumbf = value;
+ frame_settings->values.cparams.jpeg_keep_jumbf = default_to_true(value);
break;
case JXL_ENC_FRAME_SETTING_USE_FULL_IMAGE_HEURISTICS:
if (value < 0 || value > 1) {
return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_NOT_SUPPORTED,
"Option value has to be 0 or 1");
}
- frame_settings->values.cparams.use_full_image_heuristics = value;
+ frame_settings->values.cparams.use_full_image_heuristics =
+ default_to_false(value);
break;
default:
diff --git a/third_party/jpeg-xl/lib/jxl/encode_test.cc b/third_party/jpeg-xl/lib/jxl/encode_test.cc
index 3e519cc45d..0aef5f8aff 100644
--- a/third_party/jpeg-xl/lib/jxl/encode_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/encode_test.cc
@@ -304,7 +304,7 @@ TEST(EncodeTest, CmsTest) {
EXPECT_TRUE(cms_called);
}
-TEST(EncodeTest, frame_settingsTest) {
+TEST(EncodeTest, FrameSettingsTest) {
{
JxlEncoderPtr enc = JxlEncoderMake(nullptr);
EXPECT_NE(nullptr, enc.get());
@@ -451,7 +451,7 @@ TEST(EncodeTest, frame_settingsTest) {
JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE));
- VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3000, false);
+ VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3600, false);
EXPECT_EQ(true, enc->last_used_cparams.IsLossless());
}
@@ -838,20 +838,20 @@ TEST(EncodeTest, SingleFrameBoundedJXLCTest) {
bool found_jxlc = false;
bool found_jxlp = false;
// The encoder is allowed to either emit a jxlc or one or more jxlp.
- for (size_t i = 0; i < container.boxes.size(); ++i) {
- if (memcmp("jxlc", container.boxes[i].type, 4) == 0) {
+ for (const auto& box : container.boxes) {
+ if (memcmp("jxlc", box.type, 4) == 0) {
EXPECT_EQ(false, found_jxlc); // Max 1 jxlc
EXPECT_EQ(false, found_jxlp); // Can't mix jxlc and jxlp
found_jxlc = true;
}
- if (memcmp("jxlp", container.boxes[i].type, 4) == 0) {
+ if (memcmp("jxlp", box.type, 4) == 0) {
EXPECT_EQ(false, found_jxlc); // Can't mix jxlc and jxlp
found_jxlp = true;
}
// The encoder shouldn't create an unbounded box in this case, with the
// single frame it knows the full size in time, so can help make decoding
// more efficient by giving the full box size of the final box.
- EXPECT_EQ(true, container.boxes[i].data_size_given);
+ EXPECT_EQ(true, box.data_size_given);
}
EXPECT_EQ(true, found_jxlc || found_jxlp);
}
@@ -940,7 +940,7 @@ TEST(EncodeTest, CodestreamLevelVerificationTest) {
EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
}
-TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) {
+JXL_TRANSCODE_JPEG_TEST(EncodeTest, JPEGReconstructionTest) {
const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);
@@ -980,7 +980,7 @@ TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) {
EXPECT_EQ(0, memcmp(decoded_jpeg_bytes.data(), orig.data(), orig.size()));
}
-TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(ProgressiveJPEGReconstructionTest)) {
+JXL_TRANSCODE_JPEG_TEST(EncodeTest, ProgressiveJPEGReconstructionTest) {
const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);
@@ -1345,7 +1345,7 @@ TEST(EncodeTest, CroppedFrameTest) {
struct EncodeBoxTest : public testing::TestWithParam<std::tuple<bool, size_t>> {
};
-TEST_P(EncodeBoxTest, JXL_BOXES_TEST(BoxTest)) {
+JXL_BOXES_TEST_P(EncodeBoxTest, BoxTest) {
// Test with uncompressed boxes and with brob boxes
bool compress_box = std::get<0>(GetParam());
size_t xml_box_size = std::get<1>(GetParam());
@@ -1485,7 +1485,7 @@ JXL_GTEST_INSTANTIATE_TEST_SUITE_P(
jxl::kLargeBoxContentSizeThreshold + 77)),
nameBoxTest);
-TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGFrameTest)) {
+JXL_TRANSCODE_JPEG_TEST(EncodeTest, JPEGFrameTest) {
TEST_LIBJPEG_SUPPORT();
for (int skip_basic_info = 0; skip_basic_info < 2; skip_basic_info++) {
for (int skip_color_encoding = 0; skip_color_encoding < 2;
diff --git a/third_party/jpeg-xl/lib/jxl/entropy_coder_test.cc b/third_party/jpeg-xl/lib/jxl/entropy_coder_test.cc
index d32fe1b26b..0389490d8e 100644
--- a/third_party/jpeg-xl/lib/jxl/entropy_coder_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/entropy_coder_test.cc
@@ -27,7 +27,7 @@ TEST(EntropyCoderTest, PackUnpack) {
struct MockBitReader {
uint32_t nbits, bits;
void Consume(uint32_t nbits) {}
- uint32_t PeekBits(uint32_t n) {
+ uint32_t PeekBits(uint32_t n) const {
EXPECT_EQ(n, nbits);
return bits;
}
diff --git a/third_party/jpeg-xl/lib/jxl/gradient_test.cc b/third_party/jpeg-xl/lib/jxl/gradient_test.cc
index d2c83619fc..e09b34603f 100644
--- a/third_party/jpeg-xl/lib/jxl/gradient_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/gradient_test.cc
@@ -186,13 +186,13 @@ constexpr bool fast_mode = true;
TEST(GradientTest, SteepGradient) {
test::ThreadPoolForTests pool(8);
// Relatively steep gradients, colors from the sky of stp.png
- TestGradient(&pool, 0xd99d58, 0x889ab1, 512, 512, 90, fast_mode, 3.0);
+ TestGradient(pool.get(), 0xd99d58, 0x889ab1, 512, 512, 90, fast_mode, 3.0);
}
TEST(GradientTest, SubtleGradient) {
test::ThreadPoolForTests pool(8);
// Very subtle gradient
- TestGradient(&pool, 0xb89b7b, 0xa89b8d, 512, 512, 90, fast_mode, 4.0);
+ TestGradient(pool.get(), 0xb89b7b, 0xa89b8d, 512, 512, 90, fast_mode, 4.0);
}
} // namespace
diff --git a/third_party/jpeg-xl/lib/jxl/icc_codec.h b/third_party/jpeg-xl/lib/jxl/icc_codec.h
index 8b880c7d3b..3b0b0c041b 100644
--- a/third_party/jpeg-xl/lib/jxl/icc_codec.h
+++ b/third_party/jpeg-xl/lib/jxl/icc_codec.h
@@ -28,7 +28,7 @@ struct ICCReader {
}
private:
- Status CheckEOI(BitReader* reader);
+ static Status CheckEOI(BitReader* reader);
size_t i_ = 0;
size_t bits_to_skip_ = 0;
size_t used_bits_base_ = 0;
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc
index 9763786453..a971eb3dcc 100644
--- a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc
@@ -106,15 +106,14 @@ Status DecodeJPEGData(Span<const uint8_t> encoded, JPEGData* jpeg_data) {
}
}
// TODO(eustas): actually inject ICC profile and check it fits perfectly.
- for (size_t i = 0; i < jpeg_data->com_data.size(); i++) {
- auto& marker = jpeg_data->com_data[i];
+ for (auto& marker : jpeg_data->com_data) {
JXL_RETURN_IF_ERROR(br_read(marker));
if (marker[1] * 256u + marker[2] + 1u != marker.size()) {
return JXL_FAILURE("Incorrect marker size");
}
}
- for (size_t i = 0; i < jpeg_data->inter_marker_data.size(); i++) {
- JXL_RETURN_IF_ERROR(br_read(jpeg_data->inter_marker_data[i]));
+ for (auto& data : jpeg_data->inter_marker_data) {
+ JXL_RETURN_IF_ERROR(br_read(data));
}
JXL_RETURN_IF_ERROR(br_read(jpeg_data->tail_data));
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc
index 31bb2dda23..77c8b885e1 100644
--- a/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc
@@ -385,8 +385,8 @@ bool EncodeDHT(const JPEGData& jpg, SerializationState* state) {
for (size_t i = state->dht_index; i < huffman_code.size(); ++i) {
const JPEGHuffmanCode& huff = huffman_code[i];
marker_len += kJpegHuffmanMaxBitLength;
- for (size_t j = 0; j < huff.counts.size(); ++j) {
- marker_len += huff.counts[j];
+ for (uint32_t count : huff.counts) {
+ marker_len += count;
}
if (huff.is_last) break;
}
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc
index d311908415..d7c6c2ad78 100644
--- a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data.cc
@@ -173,8 +173,7 @@ Status ParseChunkedMarker(const jpeg::JPEGData& src, uint8_t marker_type,
}
Status SetBlobsFromJpegData(const jpeg::JPEGData& jpeg_data, Blobs* blobs) {
- for (size_t i = 0; i < jpeg_data.app_data.size(); i++) {
- const auto& marker = jpeg_data.app_data[i];
+ for (const auto& marker : jpeg_data.app_data) {
if (marker.empty() || marker[0] != kApp1) {
continue;
}
@@ -318,11 +317,11 @@ Status EncodeJPEGData(JPEGData& jpeg_data, std::vector<uint8_t>* bytes,
}
total_data += jpeg_data.app_data[i].size();
}
- for (size_t i = 0; i < jpeg_data.com_data.size(); i++) {
- total_data += jpeg_data.com_data[i].size();
+ for (const auto& data : jpeg_data.com_data) {
+ total_data += data.size();
}
- for (size_t i = 0; i < jpeg_data.inter_marker_data.size(); i++) {
- total_data += jpeg_data.inter_marker_data[i].size();
+ for (const auto& data : jpeg_data.inter_marker_data) {
+ total_data += data.size();
}
total_data += jpeg_data.tail_data.size();
size_t brotli_capacity = BrotliEncoderMaxCompressedSize(total_data);
@@ -365,11 +364,11 @@ Status EncodeJPEGData(JPEGData& jpeg_data, std::vector<uint8_t>* bytes,
}
br_append(jpeg_data.app_data[i], /*last=*/false);
}
- for (size_t i = 0; i < jpeg_data.com_data.size(); i++) {
- br_append(jpeg_data.com_data[i], /*last=*/false);
+ for (const auto& data : jpeg_data.com_data) {
+ br_append(data, /*last=*/false);
}
- for (size_t i = 0; i < jpeg_data.inter_marker_data.size(); i++) {
- br_append(jpeg_data.inter_marker_data[i], /*last=*/false);
+ for (const auto& data : jpeg_data.inter_marker_data) {
+ br_append(data, /*last=*/false);
}
br_append(jpeg_data.tail_data, /*last=*/true);
BrotliEncoderDestroyInstance(brotli_enc);
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc
index d1e8476db6..8208bba675 100644
--- a/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/enc_jpeg_data_reader.cc
@@ -92,21 +92,21 @@ bool ProcessSOF(const uint8_t* data, const size_t len, JpegReadMode mode,
std::vector<bool> ids_seen(256, false);
int max_h_samp_factor = 1;
int max_v_samp_factor = 1;
- for (size_t i = 0; i < jpg->components.size(); ++i) {
+ for (auto& component : jpg->components) {
const int id = ReadUint8(data, pos);
if (ids_seen[id]) { // (cf. section B.2.2, syntax of Ci)
return JXL_FAILURE("Duplicate ID %d in SOF.", id);
}
ids_seen[id] = true;
- jpg->components[i].id = id;
+ component.id = id;
int factor = ReadUint8(data, pos);
int h_samp_factor = factor >> 4;
int v_samp_factor = factor & 0xf;
JXL_JPEG_VERIFY_INPUT(h_samp_factor, 1, kBrunsliMaxSampling, SAMP_FACTOR);
JXL_JPEG_VERIFY_INPUT(v_samp_factor, 1, kBrunsliMaxSampling, SAMP_FACTOR);
- jpg->components[i].h_samp_factor = h_samp_factor;
- jpg->components[i].v_samp_factor = v_samp_factor;
- jpg->components[i].quant_idx = ReadUint8(data, pos);
+ component.h_samp_factor = h_samp_factor;
+ component.v_samp_factor = v_samp_factor;
+ component.quant_idx = ReadUint8(data, pos);
max_h_samp_factor = std::max(max_h_samp_factor, h_samp_factor);
max_v_samp_factor = std::max(max_v_samp_factor, v_samp_factor);
}
@@ -116,18 +116,17 @@ bool ProcessSOF(const uint8_t* data, const size_t len, JpegReadMode mode,
int MCU_rows = DivCeil(jpg->height, max_v_samp_factor * 8);
int MCU_cols = DivCeil(jpg->width, max_h_samp_factor * 8);
// Compute the block dimensions for each component.
- for (size_t i = 0; i < jpg->components.size(); ++i) {
- JPEGComponent* c = &jpg->components[i];
- if (max_h_samp_factor % c->h_samp_factor != 0 ||
- max_v_samp_factor % c->v_samp_factor != 0) {
+ for (JPEGComponent& c : jpg->components) {
+ if (max_h_samp_factor % c.h_samp_factor != 0 ||
+ max_v_samp_factor % c.v_samp_factor != 0) {
return JXL_FAILURE("Non-integral subsampling ratios.");
}
- c->width_in_blocks = MCU_cols * c->h_samp_factor;
- c->height_in_blocks = MCU_rows * c->v_samp_factor;
+ c.width_in_blocks = MCU_cols * c.h_samp_factor;
+ c.height_in_blocks = MCU_rows * c.v_samp_factor;
const uint64_t num_blocks =
- static_cast<uint64_t>(c->width_in_blocks) * c->height_in_blocks;
+ static_cast<uint64_t>(c.width_in_blocks) * c.height_in_blocks;
if (mode == JpegReadMode::kReadAll) {
- c->coeffs.resize(num_blocks * kDCTBlockSize);
+ c.coeffs.resize(num_blocks * kDCTBlockSize);
}
}
JXL_JPEG_VERIFY_MARKER_END();
@@ -192,8 +191,8 @@ bool ProcessSOS(const uint8_t* data, const size_t len, size_t* pos,
for (size_t i = 0; i < comps_in_scan; ++i) {
bool found_dc_table = false;
bool found_ac_table = false;
- for (size_t j = 0; j < jpg->huffman_code.size(); ++j) {
- uint32_t slot_id = jpg->huffman_code[j].slot_id;
+ for (const auto& code : jpg->huffman_code) {
+ uint32_t slot_id = code.slot_id;
if (slot_id == scan_info.components[i].dc_tbl_idx) {
found_dc_table = true;
} else if (slot_id == scan_info.components[i].ac_tbl_idx + 16) {
@@ -757,11 +756,9 @@ bool ProcessScan(const uint8_t* data, const size_t len,
bool is_interleaved = (scan_info->num_components > 1);
int max_h_samp_factor = 1;
int max_v_samp_factor = 1;
- for (size_t i = 0; i < jpg->components.size(); ++i) {
- max_h_samp_factor =
- std::max(max_h_samp_factor, jpg->components[i].h_samp_factor);
- max_v_samp_factor =
- std::max(max_v_samp_factor, jpg->components[i].v_samp_factor);
+ for (const auto& component : jpg->components) {
+ max_h_samp_factor = std::max(max_h_samp_factor, component.h_samp_factor);
+ max_v_samp_factor = std::max(max_v_samp_factor, component.v_samp_factor);
}
int MCU_rows = DivCeil(jpg->height, max_v_samp_factor * 8);
diff --git a/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc b/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc
index aeb9914cca..3217115acb 100644
--- a/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc
+++ b/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc
@@ -78,8 +78,8 @@ Status JPEGData::VisitFields(Visitor* visitor) {
if (marker_order.size() > 16384) {
return JXL_FAILURE("Too many markers: %" PRIuS "\n", marker_order.size());
}
- for (size_t i = 0; i < marker_order.size(); i++) {
- JXL_RETURN_IF_ERROR(VisitMarker(&marker_order[i], visitor, &info));
+ for (uint8_t& marker : marker_order) {
+ JXL_RETURN_IF_ERROR(VisitMarker(&marker, visitor, &info));
}
if (!marker_order.empty()) {
// Last marker should always be EOI marker.
@@ -175,8 +175,8 @@ Status JPEGData::VisitFields(Visitor* visitor) {
components.resize(num_components);
}
if (component_type == JPEGComponentType::kCustom) {
- for (size_t i = 0; i < components.size(); i++) {
- JXL_RETURN_IF_ERROR(visitor->Bits(8, 0, &components[i].id));
+ for (auto& component : components) {
+ JXL_RETURN_IF_ERROR(visitor->Bits(8, 0, &component.id));
}
} else if (component_type == JPEGComponentType::kGray) {
components[0].id = 1;
@@ -322,11 +322,11 @@ Status JPEGData::VisitFields(Visitor* visitor) {
scan.extra_zero_runs.resize(num_extra_zero_runs);
}
last_block_idx = -1;
- for (size_t i = 0; i < scan.extra_zero_runs.size(); ++i) {
- uint32_t& block_idx = scan.extra_zero_runs[i].block_idx;
- JXL_RETURN_IF_ERROR(visitor->U32(
- Val(1), BitsOffset(2, 2), BitsOffset(4, 5), BitsOffset(8, 20), 1,
- &scan.extra_zero_runs[i].num_extra_zero_runs));
+ for (auto& extra_zero_run : scan.extra_zero_runs) {
+ uint32_t& block_idx = extra_zero_run.block_idx;
+ JXL_RETURN_IF_ERROR(visitor->U32(Val(1), BitsOffset(2, 2),
+ BitsOffset(4, 5), BitsOffset(8, 20), 1,
+ &extra_zero_run.num_extra_zero_runs));
block_idx -= last_block_idx + 1;
JXL_RETURN_IF_ERROR(visitor->U32(Val(0), BitsOffset(3, 1),
BitsOffset(5, 9), BitsOffset(28, 41), 0,
diff --git a/third_party/jpeg-xl/lib/jxl/jxl_test.cc b/third_party/jpeg-xl/lib/jxl/jxl_test.cc
index b0933d5d50..cf9857f462 100644
--- a/third_party/jpeg-xl/lib/jxl/jxl_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/jxl_test.cc
@@ -179,7 +179,8 @@ TEST(JxlTest, RoundtripResample2MT) {
cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 3); // kFalcon
PackedPixelFile ppf_out;
- EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 203300, 2000);
+ EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 203300,
+ 2000);
EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 340);
}
@@ -286,8 +287,8 @@ TEST(JxlTest, RoundtripMultiGroup) {
cparams.distance = target_distance;
PackedPixelFile ppf_out;
- EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), expected_size,
- 700);
+ EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out),
+ expected_size, 700);
EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out),
expected_distance);
};
@@ -302,7 +303,7 @@ TEST(JxlTest, RoundtripRGBToGrayscale) {
ThreadPoolForTests pool(4);
const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
- ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool));
+ ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get()));
io.ShrinkTo(600, 1024);
CompressParams cparams;
@@ -316,7 +317,7 @@ TEST(JxlTest, RoundtripRGBToGrayscale) {
EXPECT_FALSE(io.Main().IsGray());
size_t compressed_size;
JXL_EXPECT_OK(
- Roundtrip(&io, cparams, dparams, &io2, _, &compressed_size, &pool));
+ Roundtrip(&io, cparams, dparams, &io2, _, &compressed_size, pool.get()));
EXPECT_LE(compressed_size, 65000u);
EXPECT_TRUE(io2.Main().IsGray());
@@ -341,7 +342,7 @@ TEST(JxlTest, RoundtripRGBToGrayscale) {
EXPECT_SLIGHTLY_BELOW(
ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(),
*JxlGetDefaultCms(),
- /*distmap=*/nullptr, &pool),
+ /*distmap=*/nullptr, pool.get()),
1.4);
}
@@ -355,11 +356,12 @@ TEST(JxlTest, RoundtripLargeFast) {
cparams.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, 7); // kSquirrel
PackedPixelFile ppf_out;
- EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 503000, 12000);
+ EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 503000,
+ 12000);
EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 78);
}
-TEST(JxlTest, JXL_X86_64_TEST(RoundtripLargeEmptyModular)) {
+JXL_X86_64_TEST(JxlTest, RoundtripLargeEmptyModular) {
ThreadPoolForTests pool(8);
TestImage t;
t.SetDimensions(4096, 4096).SetDataType(JXL_TYPE_UINT8).SetChannels(4);
@@ -380,7 +382,7 @@ TEST(JxlTest, JXL_X86_64_TEST(RoundtripLargeEmptyModular)) {
cparams.AddOption(JXL_ENC_FRAME_SETTING_DECODING_SPEED, 2);
PackedPixelFile ppf_out;
- EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 3474795,
+ EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 3474795,
100000);
EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out),
#if JXL_HIGH_PRECISION
@@ -403,8 +405,8 @@ TEST(JxlTest, RoundtripOutputColorSpace) {
JXLDecompressParams dparams;
dparams.color_space = "RGB_D65_DCI_Rel_709";
PackedPixelFile ppf_out;
- EXPECT_NEAR(Roundtrip(t.ppf(), cparams, dparams, &pool, &ppf_out), 503000,
- 12000);
+ EXPECT_NEAR(Roundtrip(t.ppf(), cparams, dparams, pool.get(), &ppf_out),
+ 503000, 12000);
EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 78);
}
@@ -421,7 +423,8 @@ TEST(JxlTest, RoundtripDotsForceEpf) {
cparams.AddOption(JXL_ENC_FRAME_SETTING_DOTS, 1);
PackedPixelFile ppf_out;
- EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 41355, 300);
+ EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 41355,
+ 300);
EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 18);
}
@@ -443,10 +446,10 @@ TEST(JxlTest, RoundtripD2Consistent) {
t.SetDimensions(xsize, 15);
PackedPixelFile ppf2;
- const size_t size2 = Roundtrip(t.ppf(), cparams, {}, &pool, &ppf2);
+ const size_t size2 = Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf2);
PackedPixelFile ppf3;
- const size_t size3 = Roundtrip(t.ppf(), cparams, {}, &pool, &ppf3);
+ const size_t size3 = Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf3);
// Exact same compressed size.
EXPECT_EQ(size2, size3);
@@ -471,7 +474,7 @@ TEST(JxlTest, RoundtripLargeConsistent) {
auto roundtrip_and_compare = [&]() {
ThreadPoolForTests pool(8);
PackedPixelFile ppf2;
- size_t size = Roundtrip(t.ppf(), cparams, {}, &pool, &ppf2);
+ size_t size = Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf2);
double dist = ComputeDistance2(t.ppf(), ppf2);
return std::tuple<size_t, double>(size, dist);
};
@@ -985,7 +988,7 @@ TEST(JxlTest, RoundtripAlpha16) {
PackedPixelFile ppf_out;
// TODO(szabadka) Investigate big size difference on i686
// This still keeps happening (2023-04-18).
- EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 3666, 120);
+ EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 3666, 120);
EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 0.65);
}
@@ -1000,7 +1003,7 @@ JXLCompressParams CompressParamsForLossless() {
}
} // namespace
-TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8)) {
+JXL_SLOW_TEST(JxlTest, RoundtripLossless8) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
@@ -1012,11 +1015,11 @@ TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8)) {
dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
PackedPixelFile ppf_out;
- EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, &pool, &ppf_out), 223058);
+ EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, pool.get(), &ppf_out), 223058);
EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
}
-TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8ThunderGradient)) {
+JXL_SLOW_TEST(JxlTest, RoundtripLossless8ThunderGradient) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
@@ -1030,11 +1033,11 @@ TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8ThunderGradient)) {
dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
PackedPixelFile ppf_out;
- EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, &pool, &ppf_out), 261684);
+ EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, pool.get(), &ppf_out), 261684);
EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
}
-TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8LightningGradient)) {
+JXL_SLOW_TEST(JxlTest, RoundtripLossless8LightningGradient) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
@@ -1048,12 +1051,12 @@ TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8LightningGradient)) {
PackedPixelFile ppf_out;
// Lax comparison because different SIMD will cause different compression.
- EXPECT_SLIGHTLY_BELOW(Roundtrip(t.ppf(), cparams, dparams, &pool, &ppf_out),
- 286848u);
+ EXPECT_SLIGHTLY_BELOW(
+ Roundtrip(t.ppf(), cparams, dparams, pool.get(), &ppf_out), 286848u);
EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
}
-TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8Falcon)) {
+JXL_SLOW_TEST(JxlTest, RoundtripLossless8Falcon) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
@@ -1066,7 +1069,7 @@ TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8Falcon)) {
dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
PackedPixelFile ppf_out;
- EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, &pool, &ppf_out), 230766);
+ EXPECT_EQ(Roundtrip(t.ppf(), cparams, dparams, pool.get(), &ppf_out), 230766);
EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
}
@@ -1348,15 +1351,15 @@ void RoundtripJpegToPixels(const std::vector<uint8_t>& jpeg_in,
nullptr, ppf_out, nullptr));
}
-TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompression444) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("jxl/flower/flower.png.im_q85_444.jpg");
// JPEG size is 696,659 bytes.
- EXPECT_NEAR(RoundtripJpeg(orig, &pool), 568891u, 20);
+ EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 568891u, 20);
}
-TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionToPixels) {
TEST_LIBJPEG_SUPPORT();
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
@@ -1365,11 +1368,11 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels)) {
t.DecodeFromBytes(orig);
PackedPixelFile ppf_out;
- RoundtripJpegToPixels(orig, {}, &pool, &ppf_out);
+ RoundtripJpegToPixels(orig, {}, pool.get(), &ppf_out);
EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 12);
}
-TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionToPixels420) {
TEST_LIBJPEG_SUPPORT();
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
@@ -1378,12 +1381,12 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420)) {
t.DecodeFromBytes(orig);
PackedPixelFile ppf_out;
- RoundtripJpegToPixels(orig, {}, &pool, &ppf_out);
+ RoundtripJpegToPixels(orig, {}, pool.get(), &ppf_out);
EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 11);
}
-TEST(JxlTest,
- JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420EarlyFlush)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest,
+ RoundtripJpegRecompressionToPixels420EarlyFlush) {
TEST_LIBJPEG_SUPPORT();
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
@@ -1395,12 +1398,11 @@ TEST(JxlTest,
dparams.max_downsampling = 8;
PackedPixelFile ppf_out;
- RoundtripJpegToPixels(orig, dparams, &pool, &ppf_out);
+ RoundtripJpegToPixels(orig, dparams, pool.get(), &ppf_out);
EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 4410);
}
-TEST(JxlTest,
- JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420Mul16)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionToPixels420Mul16) {
TEST_LIBJPEG_SUPPORT();
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
@@ -1409,12 +1411,11 @@ TEST(JxlTest,
t.DecodeFromBytes(orig);
PackedPixelFile ppf_out;
- RoundtripJpegToPixels(orig, {}, &pool, &ppf_out);
+ RoundtripJpegToPixels(orig, {}, pool.get(), &ppf_out);
EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 4);
}
-TEST(JxlTest,
- JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels_asymmetric)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionToPixelsAsymmetric) {
TEST_LIBJPEG_SUPPORT();
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
@@ -1423,102 +1424,100 @@ TEST(JxlTest,
t.DecodeFromBytes(orig);
PackedPixelFile ppf_out;
- RoundtripJpegToPixels(orig, {}, &pool, &ppf_out);
+ RoundtripJpegToPixels(orig, {}, pool.get(), &ppf_out);
EXPECT_SLIGHTLY_BELOW(ComputeDistance2(t.ppf(), ppf_out), 10);
}
-TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionGray)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionGray) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("jxl/flower/flower.png.im_q85_gray.jpg");
// JPEG size is 456,528 bytes.
- EXPECT_NEAR(RoundtripJpeg(orig, &pool), 387496u, 200);
+ EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 387496u, 200);
}
-TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompression420) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("jxl/flower/flower.png.im_q85_420.jpg");
// JPEG size is 546,797 bytes.
- EXPECT_NEAR(RoundtripJpeg(orig, &pool), 455510u, 20);
+ EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 455510u, 20);
}
-TEST(JxlTest,
- JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_luma_subsample)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionLumaSubsample) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("jxl/flower/flower.png.im_q85_luma_subsample.jpg");
// JPEG size is 400,724 bytes.
- EXPECT_NEAR(RoundtripJpeg(orig, &pool), 325310u, 20);
+ EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 325310u, 20);
}
-TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444_12)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompression444wh12) {
// 444 JPEG that has an interesting sampling-factor (1x2, 1x2, 1x2).
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("jxl/flower/flower.png.im_q85_444_1x2.jpg");
// JPEG size is 703,874 bytes.
- EXPECT_NEAR(RoundtripJpeg(orig, &pool), 569630u, 20);
+ EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 569630u, 20);
}
-TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression422)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompression422) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("jxl/flower/flower.png.im_q85_422.jpg");
// JPEG size is 522,057 bytes.
- EXPECT_NEAR(RoundtripJpeg(orig, &pool), 499236u, 20);
+ EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 499236u, 20);
}
-TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression440)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompression440) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("jxl/flower/flower.png.im_q85_440.jpg");
// JPEG size is 603,623 bytes.
- EXPECT_NEAR(RoundtripJpeg(orig, &pool), 501101u, 20);
+ EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 501101u, 20);
}
-TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_asymmetric)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionAsymmetric) {
// 2x vertical downsample of one chroma channel, 2x horizontal downsample of
// the other.
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("jxl/flower/flower.png.im_q85_asymmetric.jpg");
// JPEG size is 604,601 bytes.
- EXPECT_NEAR(RoundtripJpeg(orig, &pool), 500548u, 20);
+ EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 500548u, 20);
}
-TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420Progr)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompression420Progr) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("jxl/flower/flower.png.im_q85_420_progr.jpg");
// JPEG size is 522,057 bytes.
- EXPECT_NEAR(RoundtripJpeg(orig, &pool), 455454u, 20);
+ EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 455454u, 20);
}
-TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionMetadata)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionMetadata) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("jxl/jpeg_reconstruction/1x1_exif_xmp.jpg");
// JPEG size is 4290 bytes
// 1370 on 386, so higher margin.
- EXPECT_NEAR(RoundtripJpeg(orig, &pool), 1334u, 100);
+ EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 1334u, 100);
}
-TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionRestarts)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionRestarts) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("jxl/jpeg_reconstruction/bicycles_restarts.jpg");
// JPEG size is 87478 bytes
- EXPECT_NEAR(RoundtripJpeg(orig, &pool), 76054u, 30);
+ EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 76054u, 30);
}
-TEST(JxlTest,
- JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionOrientationICC)) {
+JXL_TRANSCODE_JPEG_TEST(JxlTest, RoundtripJpegRecompressionOrientationICC) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig =
ReadTestData("jxl/jpeg_reconstruction/sideways_bench.jpg");
// JPEG size is 15252 bytes
- EXPECT_NEAR(RoundtripJpeg(orig, &pool), 12000u, 470);
+ EXPECT_NEAR(RoundtripJpeg(orig, pool.get()), 12000u, 470);
// TODO(jon): investigate why 'Cross-compiling i686-linux-gnu' produces a
// larger result
}
@@ -1535,7 +1534,8 @@ TEST(JxlTest, RoundtripProgressive) {
cparams.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1);
PackedPixelFile ppf_out;
- EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 70544, 750);
+ EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 70544,
+ 750);
EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 1.4);
}
@@ -1552,7 +1552,8 @@ TEST(JxlTest, RoundtripProgressiveLevel2Slow) {
cparams.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1);
PackedPixelFile ppf_out;
- EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, &pool, &ppf_out), 76666, 1000);
+ EXPECT_NEAR(Roundtrip(t.ppf(), cparams, {}, pool.get(), &ppf_out), 76666,
+ 1000);
EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(t.ppf(), ppf_out), 1.17);
}
@@ -1639,7 +1640,7 @@ TEST_P(JxlTest, LosslessSmallFewColors) {
dparams.accepted_formats.push_back(t.ppf().frames[0].color.format);
PackedPixelFile ppf_out;
- Roundtrip(t.ppf(), cparams, dparams, &pool, &ppf_out);
+ Roundtrip(t.ppf(), cparams, dparams, pool.get(), &ppf_out);
EXPECT_EQ(ComputeDistance2(t.ppf(), ppf_out), 0.0);
}
@@ -1708,7 +1709,7 @@ TEST_P(JxlStreamingTest, Roundtrip) {
ThreadPoolForTests pool(8);
PackedPixelFile ppf_out;
- Roundtrip(image.ppf(), cparams, {}, &pool, &ppf_out);
+ Roundtrip(image.ppf(), cparams, {}, pool.get(), &ppf_out);
EXPECT_GT(jxl::test::ComputePSNR(image.ppf(), ppf_out), p.max_psnr());
}
@@ -1763,7 +1764,7 @@ class JxlStreamingEncodingTest
: public ::testing::TestWithParam<StreamingEncodingTestParam> {};
// This is broken on mingw32, so we only enable it for x86_64 now.
-TEST_P(JxlStreamingEncodingTest, JXL_X86_64_TEST(StreamingSamePixels)) {
+JXL_X86_64_TEST_P(JxlStreamingEncodingTest, StreamingSamePixels) {
const auto param = GetParam();
const std::vector<uint8_t> orig = ReadTestData(param.file);
@@ -1780,11 +1781,11 @@ TEST_P(JxlStreamingEncodingTest, JXL_X86_64_TEST(StreamingSamePixels)) {
ThreadPoolForTests pool(8);
PackedPixelFile ppf_out;
- Roundtrip(image.ppf(), cparams, {}, &pool, &ppf_out);
+ Roundtrip(image.ppf(), cparams, {}, pool.get(), &ppf_out);
cparams.AddOption(JXL_ENC_FRAME_SETTING_BUFFERING, 3);
PackedPixelFile ppf_out_streaming;
- Roundtrip(image.ppf(), cparams, {}, &pool, &ppf_out_streaming);
+ Roundtrip(image.ppf(), cparams, {}, pool.get(), &ppf_out_streaming);
EXPECT_TRUE(jxl::test::SamePixels(ppf_out, ppf_out_streaming));
}
diff --git a/third_party/jpeg-xl/lib/jxl/lehmer_code_test.cc b/third_party/jpeg-xl/lib/jxl/lehmer_code_test.cc
index acda762545..c7752d8cfc 100644
--- a/third_party/jpeg-xl/lib/jxl/lehmer_code_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/lehmer_code_test.cc
@@ -88,10 +88,10 @@ void RoundtripSizeRange(ThreadPool* pool, uint32_t begin, uint32_t end) {
TEST(LehmerCodeTest, TestRoundtrips) {
test::ThreadPoolForTests pool(8);
- RoundtripSizeRange<uint16_t>(&pool, 1, 1026);
+ RoundtripSizeRange<uint16_t>(pool.get(), 1, 1026);
// Ensures PermutationT can fit > 16 bit values.
- RoundtripSizeRange<uint32_t>(&pool, 65536, 65540);
+ RoundtripSizeRange<uint32_t>(pool.get(), 65536, 65540);
}
} // namespace
diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/context_predict.h b/third_party/jpeg-xl/lib/jxl/modular/encoding/context_predict.h
index 7bec5128fc..df54a9425e 100644
--- a/third_party/jpeg-xl/lib/jxl/modular/encoding/context_predict.h
+++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/context_predict.h
@@ -63,7 +63,7 @@ struct State {
pixel_type_w pred = 0; // *before* removing the added bits.
std::vector<uint32_t> pred_errors[kNumPredictors];
std::vector<int32_t> error;
- const Header header;
+ const Header &header;
// Allows to approximate division by a number from 1 to 64.
// for (int i = 0; i < 64; i++) divlookup[i] = (1 << 24) / (i + 1);
@@ -82,7 +82,7 @@ struct State {
return static_cast<uint64_t>(x) << kPredExtraBits;
}
- State(Header header, size_t xsize, size_t ysize) : header(header) {
+ State(const Header &header, size_t xsize, size_t ysize) : header(header) {
// Extra margin to avoid out-of-bounds writes.
// All have space for two rows of data.
for (auto &pred_error : pred_errors) {
diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_ma.cc b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_ma.cc
index de629ad038..23100bba8b 100644
--- a/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_ma.cc
+++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/enc_ma.cc
@@ -197,7 +197,7 @@ void FindBestSplit(TreeSamples &tree_samples, float threshold,
float rcost = std::numeric_limits<float>::max();
Predictor lpred = Predictor::Zero;
Predictor rpred = Predictor::Zero;
- float Cost() { return lcost + rcost; }
+ float Cost() const { return lcost + rcost; }
};
SplitInfo best_split_static_constant;
@@ -242,14 +242,14 @@ void FindBestSplit(TreeSamples &tree_samples, float threshold,
// The multiplier ranges cut halfway through the current ranges of static
// properties. We do this even if the current node is not a leaf, to
// minimize the number of nodes in the resulting tree.
- for (size_t i = 0; i < mul_info.size(); i++) {
+ for (const auto &mmi : mul_info) {
uint32_t axis;
uint32_t val;
IntersectionType t =
- BoxIntersects(static_prop_range, mul_info[i].range, axis, val);
+ BoxIntersects(static_prop_range, mmi.range, axis, val);
if (t == IntersectionType::kNone) continue;
if (t == IntersectionType::kInside) {
- (*tree)[pos].multiplier = mul_info[i].multiplier;
+ (*tree)[pos].multiplier = mmi.multiplier;
break;
}
if (t == IntersectionType::kPartial) {
diff --git a/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc b/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc
index bb690b74ba..7e7aa019e3 100644
--- a/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc
+++ b/third_party/jpeg-xl/lib/jxl/modular/encoding/encoding.cc
@@ -113,7 +113,7 @@ FlatTree FilterTree(const Tree &global_tree,
}
}
- for (size_t j = 0; j < 2; j++) mark_property(flat.properties[j]);
+ for (int16_t property : flat.properties) mark_property(property);
mark_property(flat.property0);
output.push_back(flat);
}
@@ -159,9 +159,9 @@ Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
// From here on, tree lookup returns a *clustered* context ID.
// This avoids an extra memory lookup after tree traversal.
- for (size_t i = 0; i < tree.size(); i++) {
- if (tree[i].property0 == -1) {
- tree[i].childID = context_map[tree[i].childID];
+ for (auto &node : tree) {
+ if (node.property0 == -1) {
+ node.childID = context_map[node.childID];
}
}
@@ -538,8 +538,8 @@ Status ModularDecode(BitReader *br, Image &image, GroupHeader &header,
// Don't do/undo transforms if header is incomplete.
header.transforms.clear();
image.transform = header.transforms;
- for (size_t c = 0; c < image.channel.size(); c++) {
- ZeroFillImage(&image.channel[c].plane);
+ for (auto &ch : image.channel) {
+ ZeroFillImage(&ch.plane);
}
return Status(StatusCode::kNotEnoughBytes);
}
diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/enc_palette.cc b/third_party/jpeg-xl/lib/jxl/modular/transform/enc_palette.cc
index 24c64f5aad..7f9399d3c4 100644
--- a/third_party/jpeg-xl/lib/jxl/modular/transform/enc_palette.cc
+++ b/third_party/jpeg-xl/lib/jxl/modular/transform/enc_palette.cc
@@ -68,8 +68,8 @@ static int QuantizeColorToImplicitPaletteIndex(
int index = 0;
if (high_quality) {
int multiplier = 1;
- for (size_t c = 0; c < color.size(); c++) {
- int quantized = ((kLargeCube - 1) * color[c] + (1 << (bit_depth - 1))) /
+ for (int value : color) {
+ int quantized = ((kLargeCube - 1) * value + (1 << (bit_depth - 1))) /
((1 << bit_depth) - 1);
JXL_ASSERT((quantized % kLargeCube) == quantized);
index += quantized * multiplier;
@@ -78,8 +78,7 @@ static int QuantizeColorToImplicitPaletteIndex(
return index + palette_size + kLargeCubeOffset;
} else {
int multiplier = 1;
- for (size_t c = 0; c < color.size(); c++) {
- int value = color[c];
+ for (int value : color) {
value -= 1 << (std::max(0, bit_depth - 3));
value = std::max(0, value);
int quantized = ((kLargeCube - 1) * value + (1 << (bit_depth - 1))) /
@@ -171,6 +170,7 @@ Status FwdPaletteIteration(Image &input, uint32_t begin_c, uint32_t end_c,
size_t w = input.channel[begin_c].w;
size_t h = input.channel[begin_c].h;
+ if (!lossy && nb_colors < 2) return false;
if (!lossy && nb == 1) {
// Channel palette special case
@@ -321,6 +321,20 @@ Status FwdPaletteIteration(Image &input, uint32_t begin_c, uint32_t end_c,
}
}
+ std::map<std::vector<pixel_type>, bool> implicit_color;
+ std::vector<std::vector<pixel_type>> implicit_colors;
+ implicit_colors.reserve(palette_internal::kImplicitPaletteSize);
+ for (size_t k = 0; k < palette_internal::kImplicitPaletteSize; k++) {
+ for (size_t i = 0; i < nb; i++) {
+ color[i] = palette_internal::GetPaletteValue(nullptr, k, i, 0, 0,
+ input.bitdepth);
+ }
+ implicit_color[color] = true;
+ implicit_colors.push_back(color);
+ }
+
+ std::map<std::vector<pixel_type>, size_t> color_freq_map;
+ uint32_t implicit_colors_used = 0;
for (size_t y = 0; y < h; y++) {
for (uint32_t c = 0; c < nb; c++) {
p_in[c] = input.channel[begin_c + c].Row(y);
@@ -332,15 +346,39 @@ Status FwdPaletteIteration(Image &input, uint32_t begin_c, uint32_t end_c,
}
const bool new_color = candidate_palette.insert(color).second;
if (new_color) {
- candidate_palette_imageorder.push_back(color);
- }
- if (candidate_palette.size() > nb_colors) {
- return false; // too many colors
+ if (implicit_color[color]) {
+ implicit_colors_used++;
+ } else {
+ candidate_palette_imageorder.push_back(color);
+ if (candidate_palette_imageorder.size() > nb_colors) {
+ return false; // too many colors
+ }
+ }
}
+ color_freq_map[color] += 1;
}
}
- nb_colors = nb_deltas + candidate_palette.size();
+ nb_colors = nb_deltas + candidate_palette_imageorder.size();
+
+ // not useful to make a single-color palette
+ if (!lossy && nb_colors + implicit_colors_used == 1) return false;
+ // TODO(jon): if this happens (e.g. solid white group), special-case it for
+ // faster encode
+
+ for (size_t k = 0; k < palette_internal::kImplicitPaletteSize; k++) {
+ color = implicit_colors[k];
+ // still add the color to the explicit palette if it is frequent enough
+ if (color_freq_map[color] > 10) {
+ nb_colors++;
+ candidate_palette_imageorder.push_back(color);
+ }
+ }
+ for (size_t k = 0; k < palette_internal::kImplicitPaletteSize; k++) {
+ color = implicit_colors[k];
+ inv_palette[color] = nb_colors + k;
+ }
+
JXL_DEBUG_V(6, "Channels %i-%i can be represented using a %i-color palette.",
begin_c, end_c, nb_colors);
@@ -360,25 +398,33 @@ Status FwdPaletteIteration(Image &input, uint32_t begin_c, uint32_t end_c,
}
}
}
-
+ // Separate the palette in two buckets, first the common colors, then the
+ // rare colors.
+ // Within each bucket, the colors are sorted on luma (times alpha).
+ float freq_threshold = 4; // arbitrary threshold
int x = 0;
if (ordered && nb >= 3) {
JXL_DEBUG_V(7, "Palette of %i colors, using luma order", nb_colors);
// sort on luma (multiplied by alpha if available)
std::sort(candidate_palette_imageorder.begin(),
candidate_palette_imageorder.end(),
- [](std::vector<pixel_type> ap, std::vector<pixel_type> bp) {
+ [&](std::vector<pixel_type> ap, std::vector<pixel_type> bp) {
float ay;
float by;
ay = (0.299f * ap[0] + 0.587f * ap[1] + 0.114f * ap[2] + 0.1f);
if (ap.size() > 3) ay *= 1.f + ap[3];
by = (0.299f * bp[0] + 0.587f * bp[1] + 0.114f * bp[2] + 0.1f);
if (bp.size() > 3) by *= 1.f + bp[3];
+ // put common colors first, transparent dark to opaque bright,
+ // then rare colors, bright to dark
+ ay = color_freq_map[ap] > freq_threshold ? -ay : ay;
+ by = color_freq_map[bp] > freq_threshold ? -by : by;
return ay < by;
});
} else {
JXL_DEBUG_V(7, "Palette of %i colors, using image order", nb_colors);
}
+
for (auto pcol : candidate_palette_imageorder) {
JXL_DEBUG_V(9, " Color %i : ", x);
for (size_t i = 0; i < nb; i++) {
@@ -398,10 +444,10 @@ Status FwdPaletteIteration(Image &input, uint32_t begin_c, uint32_t end_c,
// beneficial for both precision and encoding speed.
std::vector<std::vector<float>> error_row[3];
if (lossy) {
- for (int i = 0; i < 3; ++i) {
- error_row[i].resize(nb);
+ for (auto &row : error_row) {
+ row.resize(nb);
for (size_t c = 0; c < nb; ++c) {
- error_row[i][c].resize(w + 4);
+ row[c].resize(w + 4);
}
}
}
@@ -424,13 +470,11 @@ Status FwdPaletteIteration(Image &input, uint32_t begin_c, uint32_t end_c,
std::vector<pixel_type> ideal_residual(nb, 0);
std::vector<pixel_type> quantized_val(nb);
std::vector<pixel_type> predictions(nb);
- static const double kDiffusionMultiplier[] = {0.55, 0.75};
- for (int diffusion_index = 0; diffusion_index < 2; ++diffusion_index) {
+ for (double diffusion_multiplier : {0.55, 0.75}) {
for (size_t c = 0; c < nb; c++) {
color_with_error[c] =
p_in[c][x] + (palette_iteration_data.final_run ? 1 : 0) *
- kDiffusionMultiplier[diffusion_index] *
- error_row[0][c][x + 2];
+ diffusion_multiplier * error_row[0][c][x + 2];
color[c] = Clamp1(lroundf(color_with_error[c]), 0l,
(1l << input.bitdepth) - 1);
}
diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/enc_squeeze.cc b/third_party/jpeg-xl/lib/jxl/modular/transform/enc_squeeze.cc
index 0d924c0ace..7371830743 100644
--- a/third_party/jpeg-xl/lib/jxl/modular/transform/enc_squeeze.cc
+++ b/third_party/jpeg-xl/lib/jxl/modular/transform/enc_squeeze.cc
@@ -124,13 +124,13 @@ Status FwdSqueeze(Image &input, std::vector<SqueezeParams> parameters,
}
// if nothing to do, don't do squeeze
if (parameters.empty()) return false;
- for (size_t i = 0; i < parameters.size(); i++) {
+ for (auto &parameter : parameters) {
JXL_RETURN_IF_ERROR(
- CheckMetaSqueezeParams(parameters[i], input.channel.size()));
- bool horizontal = parameters[i].horizontal;
- bool in_place = parameters[i].in_place;
- uint32_t beginc = parameters[i].begin_c;
- uint32_t endc = parameters[i].begin_c + parameters[i].num_c - 1;
+ CheckMetaSqueezeParams(parameter, input.channel.size()));
+ bool horizontal = parameter.horizontal;
+ bool in_place = parameter.in_place;
+ uint32_t beginc = parameter.begin_c;
+ uint32_t endc = parameter.begin_c + parameter.num_c - 1;
uint32_t offset;
if (in_place) {
offset = endc + 1;
diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/palette.h b/third_party/jpeg-xl/lib/jxl/modular/transform/palette.h
index e0405a2162..2a9e5c71f4 100644
--- a/third_party/jpeg-xl/lib/jxl/modular/transform/palette.h
+++ b/third_party/jpeg-xl/lib/jxl/modular/transform/palette.h
@@ -30,6 +30,8 @@ static constexpr int kSmallCube = 4;
static constexpr int kSmallCubeBits = 2;
// kSmallCube ** 3
static constexpr int kLargeCubeOffset = kSmallCube * kSmallCube * kSmallCube;
+static constexpr int kImplicitPaletteSize =
+ kLargeCubeOffset + kLargeCube * kLargeCube * kLargeCube;
static inline pixel_type Scale(uint64_t value, uint64_t bit_depth,
uint64_t denom) {
diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc b/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc
index 580829741a..b71c8dc248 100644
--- a/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc
+++ b/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.cc
@@ -385,21 +385,21 @@ void DefaultSqueezeParameters(std::vector<SqueezeParams> *parameters,
params.in_place = true;
if (!wide) {
- if (h > JXL_MAX_FIRST_PREVIEW_SIZE) {
+ if (h > kMaxFirstPreviewSize) {
params.horizontal = false;
parameters->push_back(params);
h = (h + 1) / 2;
JXL_DEBUG_V(7, "Vertical (%" PRIuS "x%" PRIuS "), ", w, h);
}
}
- while (w > JXL_MAX_FIRST_PREVIEW_SIZE || h > JXL_MAX_FIRST_PREVIEW_SIZE) {
- if (w > JXL_MAX_FIRST_PREVIEW_SIZE) {
+ while (w > kMaxFirstPreviewSize || h > kMaxFirstPreviewSize) {
+ if (w > kMaxFirstPreviewSize) {
params.horizontal = true;
parameters->push_back(params);
w = (w + 1) / 2;
JXL_DEBUG_V(7, "Horizontal (%" PRIuS "x%" PRIuS "), ", w, h);
}
- if (h > JXL_MAX_FIRST_PREVIEW_SIZE) {
+ if (h > kMaxFirstPreviewSize) {
params.horizontal = false;
parameters->push_back(params);
h = (h + 1) / 2;
@@ -424,13 +424,13 @@ Status MetaSqueeze(Image &image, std::vector<SqueezeParams> *parameters) {
DefaultSqueezeParameters(parameters, image);
}
- for (size_t i = 0; i < parameters->size(); i++) {
+ for (auto &parameter : *parameters) {
JXL_RETURN_IF_ERROR(
- CheckMetaSqueezeParams((*parameters)[i], image.channel.size()));
- bool horizontal = (*parameters)[i].horizontal;
- bool in_place = (*parameters)[i].in_place;
- uint32_t beginc = (*parameters)[i].begin_c;
- uint32_t endc = (*parameters)[i].begin_c + (*parameters)[i].num_c - 1;
+ CheckMetaSqueezeParams(parameter, image.channel.size()));
+ bool horizontal = parameter.horizontal;
+ bool in_place = parameter.in_place;
+ uint32_t beginc = parameter.begin_c;
+ uint32_t endc = parameter.begin_c + parameter.num_c - 1;
uint32_t offset;
if (beginc < image.nb_meta_channels) {
@@ -441,7 +441,7 @@ Status MetaSqueeze(Image &image, std::vector<SqueezeParams> *parameters) {
return JXL_FAILURE(
"Invalid squeeze: meta channels require in-place residuals");
}
- image.nb_meta_channels += (*parameters)[i].num_c;
+ image.nb_meta_channels += parameter.num_c;
}
if (in_place) {
offset = endc + 1;
diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.h b/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.h
index bbd16c59c0..f0333da6fd 100644
--- a/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.h
+++ b/third_party/jpeg-xl/lib/jxl/modular/transform/squeeze.h
@@ -29,10 +29,10 @@
#include "lib/jxl/modular/modular_image.h"
#include "lib/jxl/modular/transform/transform.h"
-#define JXL_MAX_FIRST_PREVIEW_SIZE 8
-
namespace jxl {
+constexpr size_t kMaxFirstPreviewSize = 8;
+
/*
int avg=(A+B)>>1;
int diff=(A-B);
diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/transform.cc b/third_party/jpeg-xl/lib/jxl/modular/transform/transform.cc
index 33f7a10cc9..a609cfb3fb 100644
--- a/third_party/jpeg-xl/lib/jxl/modular/transform/transform.cc
+++ b/third_party/jpeg-xl/lib/jxl/modular/transform/transform.cc
@@ -23,7 +23,7 @@ Transform::Transform(TransformId id) {
}
Status Transform::Inverse(Image &input, const weighted::Header &wp_header,
- ThreadPool *pool) {
+ ThreadPool *pool) const {
JXL_DEBUG_V(6, "Input channels (%" PRIuS ", %" PRIuS " meta): ",
input.channel.size(), input.nb_meta_channels);
switch (id) {
diff --git a/third_party/jpeg-xl/lib/jxl/modular/transform/transform.h b/third_party/jpeg-xl/lib/jxl/modular/transform/transform.h
index b68861706f..70c383834a 100644
--- a/third_party/jpeg-xl/lib/jxl/modular/transform/transform.h
+++ b/third_party/jpeg-xl/lib/jxl/modular/transform/transform.h
@@ -134,7 +134,7 @@ class Transform : public Fields {
JXL_FIELDS_NAME(Transform)
Status Inverse(Image &input, const weighted::Header &wp_header,
- ThreadPool *pool = nullptr);
+ ThreadPool *pool = nullptr) const;
Status MetaApply(Image &input);
};
diff --git a/third_party/jpeg-xl/lib/jxl/modular_test.cc b/third_party/jpeg-xl/lib/jxl/modular_test.cc
index bd1a947493..ceebf59c0b 100644
--- a/third_party/jpeg-xl/lib/jxl/modular_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/modular_test.cc
@@ -80,15 +80,15 @@ void TestLosslessGroups(size_t group_size_shift) {
TEST(ModularTest, RoundtripLosslessGroups128) { TestLosslessGroups(0); }
-TEST(ModularTest, JXL_TSAN_SLOW_TEST(RoundtripLosslessGroups512)) {
+JXL_TSAN_SLOW_TEST(ModularTest, RoundtripLosslessGroups512) {
TestLosslessGroups(2);
}
-TEST(ModularTest, JXL_TSAN_SLOW_TEST(RoundtripLosslessGroups1024)) {
+JXL_TSAN_SLOW_TEST(ModularTest, RoundtripLosslessGroups1024) {
TestLosslessGroups(3);
}
-TEST(ModularTest, RoundtripLosslessCustomWP_PermuteRCT) {
+TEST(ModularTest, RoundtripLosslessCustomWpPermuteRCT) {
const std::vector<uint8_t> orig =
ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
TestImage t;
@@ -144,6 +144,7 @@ TEST(ModularTest, RoundtripLossyDeltaPaletteWP) {
cparams.SetLossless();
cparams.lossy_palette = true;
cparams.palette_colors = 0;
+ // TODO(jon): this is currently ignored, and Avg4 is always used instead
cparams.options.predictor = jxl::Predictor::Weighted;
CodecInOut io_out;
@@ -154,12 +155,12 @@ TEST(ModularTest, RoundtripLossyDeltaPaletteWP) {
size_t compressed_size;
JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io_out, _, &compressed_size));
- EXPECT_LE(compressed_size, 7000u);
+ EXPECT_LE(compressed_size, 6500u);
EXPECT_SLIGHTLY_BELOW(
ButteraugliDistance(io.frames, io_out.frames, ButteraugliParams(),
*JxlGetDefaultCms(),
/*distmap=*/nullptr),
- 10.1);
+ 1.5);
}
TEST(ModularTest, RoundtripLossy) {
@@ -346,8 +347,8 @@ TEST_P(ModularTestParam, RoundtripLossless) {
const float* in = io.Main().color()->PlaneRow(c, y);
const float* out = io2.Main().color()->PlaneRow(c, y);
for (size_t x = 0; x < xsize; x++) {
- uint32_t uin = in[x] * factor + 0.5;
- uint32_t uout = out[x] * factor + 0.5;
+ uint32_t uin = std::lroundf(in[x] * factor);
+ uint32_t uout = std::lroundf(out[x] * factor);
// check that the integer values are identical
if (uin != uout) different++;
}
diff --git a/third_party/jpeg-xl/lib/jxl/passes_test.cc b/third_party/jpeg-xl/lib/jxl/passes_test.cc
index cb9164706f..e7a7547a0c 100644
--- a/third_party/jpeg-xl/lib/jxl/passes_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/passes_test.cc
@@ -80,7 +80,7 @@ TEST(PassesTest, RoundtripMultiGroupPasses) {
CodecInOut io;
{
ThreadPoolForTests pool(4);
- ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool));
+ ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get()));
}
io.ShrinkTo(600, 1024); // partial X, full Y group
@@ -92,11 +92,11 @@ TEST(PassesTest, RoundtripMultiGroupPasses) {
cparams.SetCms(*JxlGetDefaultCms());
CodecInOut io2;
JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2, _,
- /* compressed_size */ nullptr, &pool));
+ /* compressed_size */ nullptr, pool.get()));
EXPECT_SLIGHTLY_BELOW(
ButteraugliDistance(io.frames, io2.frames, ButteraugliParams(),
*JxlGetDefaultCms(),
- /*distmap=*/nullptr, &pool),
+ /*distmap=*/nullptr, pool.get()),
target_distance + threshold);
};
@@ -108,7 +108,7 @@ TEST(PassesTest, RoundtripLargeFastPasses) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
- ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool));
+ ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get()));
CompressParams cparams;
cparams.speed_tier = SpeedTier::kSquirrel;
@@ -117,7 +117,7 @@ TEST(PassesTest, RoundtripLargeFastPasses) {
CodecInOut io2;
JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2, _,
- /* compressed_size */ nullptr, &pool));
+ /* compressed_size */ nullptr, pool.get()));
}
// Checks for differing size/distance in two consecutive runs of distance 2,
@@ -127,7 +127,7 @@ TEST(PassesTest, RoundtripProgressiveConsistent) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
- ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool));
+ ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get()));
CompressParams cparams;
cparams.speed_tier = SpeedTier::kSquirrel;
@@ -141,11 +141,11 @@ TEST(PassesTest, RoundtripProgressiveConsistent) {
CodecInOut io2;
size_t size2;
- JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2, _, &size2, &pool));
+ JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io2, _, &size2, pool.get()));
CodecInOut io3;
size_t size3;
- JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io3, _, &size3, &pool));
+ JXL_EXPECT_OK(Roundtrip(&io, cparams, {}, &io3, _, &size3, pool.get()));
// Exact same compressed size.
EXPECT_EQ(size2, size3);
@@ -153,10 +153,10 @@ TEST(PassesTest, RoundtripProgressiveConsistent) {
// Exact same distance.
const float dist2 = ButteraugliDistance(
io.frames, io2.frames, ButteraugliParams(), *JxlGetDefaultCms(),
- /*distmap=*/nullptr, &pool);
+ /*distmap=*/nullptr, pool.get());
const float dist3 = ButteraugliDistance(
io.frames, io3.frames, ButteraugliParams(), *JxlGetDefaultCms(),
- /*distmap=*/nullptr, &pool);
+ /*distmap=*/nullptr, pool.get());
EXPECT_EQ(dist2, dist3);
}
}
@@ -166,7 +166,7 @@ TEST(PassesTest, AllDownsampleFeasible) {
const std::vector<uint8_t> orig =
ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
- ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool));
+ ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get()));
std::vector<uint8_t> compressed;
@@ -174,7 +174,7 @@ TEST(PassesTest, AllDownsampleFeasible) {
cparams.speed_tier = SpeedTier::kSquirrel;
cparams.progressive_mode = Override::kOn;
cparams.butteraugli_distance = 1.0;
- ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, &pool));
+ ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, pool.get()));
EXPECT_LE(compressed.size(), 240000u);
float target_butteraugli[9] = {};
@@ -202,7 +202,7 @@ TEST(PassesTest, AllDownsampleFeasible) {
target_butteraugli[downsampling])
<< "downsampling: " << downsampling;
};
- EXPECT_TRUE(RunOnPool(&pool, 0, downsamplings.size(), ThreadPool::NoInit,
+ EXPECT_TRUE(RunOnPool(pool.get(), 0, downsamplings.size(), ThreadPool::NoInit,
check, "TestDownsampling"));
}
@@ -211,7 +211,7 @@ TEST(PassesTest, AllDownsampleFeasibleQProgressive) {
const std::vector<uint8_t> orig =
ReadTestData("external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io;
- ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool));
+ ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get()));
std::vector<uint8_t> compressed;
@@ -219,7 +219,7 @@ TEST(PassesTest, AllDownsampleFeasibleQProgressive) {
cparams.speed_tier = SpeedTier::kSquirrel;
cparams.qprogressive_mode = Override::kOn;
cparams.butteraugli_distance = 1.0;
- ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, &pool));
+ ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, pool.get()));
EXPECT_LE(compressed.size(), 220000u);
@@ -247,7 +247,7 @@ TEST(PassesTest, AllDownsampleFeasibleQProgressive) {
target_butteraugli[downsampling])
<< "downsampling: " << downsampling;
};
- EXPECT_TRUE(RunOnPool(&pool, 0, downsamplings.size(), ThreadPool::NoInit,
+ EXPECT_TRUE(RunOnPool(pool.get(), 0, downsamplings.size(), ThreadPool::NoInit,
check, "TestQProgressive"));
}
@@ -256,7 +256,7 @@ TEST(PassesTest, ProgressiveDownsample2DegradesCorrectlyGrayscale) {
const std::vector<uint8_t> orig = ReadTestData(
"external/wesaturate/500px/cvo9xd_keong_macan_grayscale.png");
CodecInOut io_orig;
- ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_orig, &pool));
+ ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_orig, pool.get()));
Rect rect(0, 0, io_orig.xsize(), 128);
// need 2 DC groups for the DC frame to actually be progressive.
JXL_ASSIGN_OR_DIE(Image3F large, Image3F::Create(4242, rect.ysize()));
@@ -274,7 +274,7 @@ TEST(PassesTest, ProgressiveDownsample2DegradesCorrectlyGrayscale) {
cparams.responsive = JXL_TRUE;
cparams.qprogressive_mode = Override::kOn;
cparams.butteraugli_distance = 1.0;
- ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, &pool));
+ ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, pool.get()));
EXPECT_LE(compressed.size(), 10000u);
@@ -300,7 +300,7 @@ TEST(PassesTest, ProgressiveDownsample2DegradesCorrectly) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io_orig;
- ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_orig, &pool));
+ ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_orig, pool.get()));
Rect rect(0, 0, io_orig.xsize(), 128);
// need 2 DC groups for the DC frame to actually be progressive.
JXL_ASSIGN_OR_DIE(Image3F large, Image3F::Create(4242, rect.ysize()));
@@ -317,7 +317,7 @@ TEST(PassesTest, ProgressiveDownsample2DegradesCorrectly) {
cparams.responsive = JXL_TRUE;
cparams.qprogressive_mode = Override::kOn;
cparams.butteraugli_distance = 1.0;
- ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, &pool));
+ ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, pool.get()));
EXPECT_LE(compressed.size(), 220000u);
@@ -343,7 +343,7 @@ TEST(PassesTest, NonProgressiveDCImage) {
ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig = ReadTestData("jxl/flower/flower.png");
CodecInOut io;
- ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, &pool));
+ ASSERT_TRUE(SetFromBytes(Bytes(orig), &io, pool.get()));
std::vector<uint8_t> compressed;
@@ -351,14 +351,15 @@ TEST(PassesTest, NonProgressiveDCImage) {
cparams.speed_tier = SpeedTier::kSquirrel;
cparams.progressive_mode = Override::kOff;
cparams.butteraugli_distance = 2.0;
- ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, &pool));
+ ASSERT_TRUE(test::EncodeFile(cparams, &io, &compressed, pool.get()));
// Even in non-progressive mode, it should be possible to return a DC-only
// image.
extras::JXLDecompressParams dparams;
dparams.max_downsampling = 100;
CodecInOut output;
- ASSERT_TRUE(test::DecodeFile(dparams, Bytes(compressed), &output, &pool));
+ ASSERT_TRUE(
+ test::DecodeFile(dparams, Bytes(compressed), &output, pool.get()));
EXPECT_EQ(output.xsize(), io.xsize());
EXPECT_EQ(output.ysize(), io.ysize());
}
diff --git a/third_party/jpeg-xl/lib/jxl/quant_weights_test.cc b/third_party/jpeg-xl/lib/jxl/quant_weights_test.cc
index e92cbf2151..776a1c776d 100644
--- a/third_party/jpeg-xl/lib/jxl/quant_weights_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/quant_weights_test.cc
@@ -164,7 +164,7 @@ TEST(QuantWeightsTest, RAW) {
QuantEncoding::Library(0));
std::vector<int> matrix(3 * 32 * 32);
Rng rng(0);
- for (size_t i = 0; i < matrix.size(); i++) matrix[i] = rng.UniformI(1, 256);
+ for (int& v : matrix) v = rng.UniformI(1, 256);
encodings[DequantMatrices::kQuantTable[AcStrategy::DCT32X32]] =
QuantEncoding::RAW(matrix, 2);
RoundtripMatrices(encodings);
diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.cc
index 14bd363110..09e3dbab76 100644
--- a/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.cc
+++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.cc
@@ -20,7 +20,7 @@ void RenderPipeline::Builder::AddStage(
StatusOr<std::unique_ptr<RenderPipeline>> RenderPipeline::Builder::Finalize(
FrameDimensions frame_dimensions) && {
#if JXL_ENABLE_ASSERT
- // Check that the last stage is not an kInOut stage for any channel, and that
+ // Check that the last stage is not a kInOut stage for any channel, and that
// there is at least one stage.
JXL_ASSERT(!stages_.empty());
for (size_t c = 0; c < num_c_; c++) {
diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.cc
index 7f5a8ef00f..77ddb3d430 100644
--- a/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.cc
+++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.cc
@@ -22,12 +22,12 @@ Status SimpleRenderPipeline::PrepareForThreadsInternal(size_t num,
auto ch_size = [](size_t frame_size, size_t shift) {
return DivCeil(frame_size, 1 << shift) + kRenderPipelineXOffset * 2;
};
- for (size_t c = 0; c < channel_shifts_[0].size(); c++) {
+ for (auto& entry : channel_shifts_[0]) {
JXL_ASSIGN_OR_RETURN(
- ImageF ch, ImageF::Create(ch_size(frame_dimensions_.xsize_upsampled,
- channel_shifts_[0][c].first),
- ch_size(frame_dimensions_.ysize_upsampled,
- channel_shifts_[0][c].second)));
+ ImageF ch,
+ ImageF::Create(
+ ch_size(frame_dimensions_.xsize_upsampled, entry.first),
+ ch_size(frame_dimensions_.ysize_upsampled, entry.second)));
channel_data_.push_back(std::move(ch));
msan::PoisonImage(channel_data_.back());
}
diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_cms.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_cms.cc
index 3202a03e44..9ce65e1644 100644
--- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_cms.cc
+++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_cms.cc
@@ -45,7 +45,7 @@ class CmsStage : public RenderPipelineStage {
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
size_t thread_id) const final {
JXL_ASSERT(xsize <= xsize_);
- // TODO(firsching): handle grey case seperately
+ // TODO(firsching): handle grey case separately
// interleave
float* JXL_RESTRICT row0 = GetInputRow(input_rows, 0, 0);
float* JXL_RESTRICT row1 = GetInputRow(input_rows, 1, 0);
diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.cc
index d3030b02cb..b281e41794 100644
--- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.cc
+++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.cc
@@ -40,10 +40,10 @@ JXL_INLINE Vec<DF> Weight(Vec<DF> sad, Vec<DF> inv_sigma, Vec<DF> thres) {
// this filter a 7x7 filter.
class EPF0Stage : public RenderPipelineStage {
public:
- EPF0Stage(const LoopFilter& lf, const ImageF& sigma)
+ EPF0Stage(LoopFilter lf, const ImageF& sigma)
: RenderPipelineStage(RenderPipelineStage::Settings::Symmetric(
/*shift=*/0, /*border=*/3)),
- lf_(lf),
+ lf_(std::move(lf)),
sigma_(&sigma) {}
template <bool aligned>
@@ -72,7 +72,7 @@ class EPF0Stage : public RenderPipelineStage {
DF df;
using V = decltype(Zero(df));
- V t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, tA, tB;
+ V t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, tA, tB; // NOLINT
V* sads[12] = {&t0, &t1, &t2, &t3, &t4, &t5, &t6, &t7, &t8, &t9, &tA, &tB};
xextra = RoundUpTo(xextra, Lanes(df));
@@ -114,7 +114,7 @@ class EPF0Stage : public RenderPipelineStage {
const auto sm = Load(df, sad_mul + ix);
const auto inv_sigma = Mul(Set(df, row_sigma[bx]), sm);
- for (size_t i = 0; i < 12; i++) *sads[i] = Zero(df);
+ for (auto& sad : sads) *sad = Zero(df);
constexpr std::array<int, 2> sads_off[12] = {
{{-2, 0}}, {{-1, -1}}, {{-1, 0}}, {{-1, 1}}, {{0, -2}}, {{0, -1}},
{{0, 1}}, {{0, 2}}, {{1, -1}}, {{1, 0}}, {{1, 1}}, {{2, 0}},
@@ -128,12 +128,10 @@ class EPF0Stage : public RenderPipelineStage {
auto sad = Zero(df);
constexpr std::array<int, 2> plus_off[] = {
{{0, 0}}, {{-1, 0}}, {{0, -1}}, {{1, 0}}, {{0, 1}}};
- for (size_t j = 0; j < 5; j++) {
- const auto r11 =
- LoadU(df, rows[c][3 + plus_off[j][0]] + x + plus_off[j][1]);
- const auto c11 =
- LoadU(df, rows[c][3 + sads_off[i][0] + plus_off[j][0]] + x +
- sads_off[i][1] + plus_off[j][1]);
+ for (const auto& off : plus_off) {
+ const auto r11 = LoadU(df, rows[c][3 + off[0]] + x + off[1]);
+ const auto c11 = LoadU(df, rows[c][3 + sads_off[i][0] + off[0]] +
+ x + sads_off[i][1] + off[1]);
sad = Add(sad, AbsDiff(r11, c11));
}
*sads[i] = MulAdd(sad, scale, *sads[i]);
@@ -181,10 +179,10 @@ class EPF0Stage : public RenderPipelineStage {
// makes this filter a 5x5 filter.
class EPF1Stage : public RenderPipelineStage {
public:
- EPF1Stage(const LoopFilter& lf, const ImageF& sigma)
+ EPF1Stage(LoopFilter lf, const ImageF& sigma)
: RenderPipelineStage(RenderPipelineStage::Settings::Symmetric(
/*shift=*/0, /*border=*/2)),
- lf_(lf),
+ lf_(std::move(lf)),
sigma_(&sigma) {}
template <bool aligned>
@@ -362,10 +360,10 @@ class EPF1Stage : public RenderPipelineStage {
// filter.
class EPF2Stage : public RenderPipelineStage {
public:
- EPF2Stage(const LoopFilter& lf, const ImageF& sigma)
+ EPF2Stage(LoopFilter lf, const ImageF& sigma)
: RenderPipelineStage(RenderPipelineStage::Settings::Symmetric(
/*shift=*/0, /*border=*/1)),
- lf_(lf),
+ lf_(std::move(lf)),
sigma_(&sigma) {}
template <bool aligned>
diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_upsampling.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_upsampling.cc
index 897b20c4c6..e868f9f8e0 100644
--- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_upsampling.cc
+++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_upsampling.cc
@@ -110,7 +110,7 @@ class UpsamplingStage : public RenderPipelineStage {
ssize_t x0, ssize_t x1) const {
static HWY_FULL(float) df;
using V = hwy::HWY_NAMESPACE::Vec<HWY_FULL(float)>;
- V ups0, ups1, ups2, ups3, ups4, ups5, ups6, ups7;
+ V ups0, ups1, ups2, ups3, ups4, ups5, ups6, ups7; // NOLINT
(void)ups2, (void)ups3, (void)ups4, (void)ups5, (void)ups6, (void)ups7;
// Once we have C++17 available, change this back to `V* ups[N]` and
// initialize using `if constexpr` below.
diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.cc b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.cc
index c5a91e8efd..05cdd786a9 100644
--- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.cc
+++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.cc
@@ -11,6 +11,7 @@
#include "lib/jxl/base/common.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/dec_cache.h"
+#include "lib/jxl/dec_xyb.h"
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/sanitizers.h"
@@ -556,11 +557,11 @@ HWY_EXPORT(GetWriteToOutputStage);
namespace {
class WriteToImageBundleStage : public RenderPipelineStage {
public:
- explicit WriteToImageBundleStage(ImageBundle* image_bundle,
- ColorEncoding color_encoding)
+ explicit WriteToImageBundleStage(
+ ImageBundle* image_bundle, const OutputEncodingInfo& output_encoding_info)
: RenderPipelineStage(RenderPipelineStage::Settings()),
image_bundle_(image_bundle),
- color_encoding_(std::move(color_encoding)) {}
+ color_encoding_(output_encoding_info.color_encoding) {}
Status SetInputSizes(
const std::vector<std::pair<size_t, size_t>>& input_sizes) override {
@@ -658,9 +659,9 @@ class WriteToImage3FStage : public RenderPipelineStage {
} // namespace
std::unique_ptr<RenderPipelineStage> GetWriteToImageBundleStage(
- ImageBundle* image_bundle, ColorEncoding color_encoding) {
+ ImageBundle* image_bundle, const OutputEncodingInfo& output_encoding_info) {
return jxl::make_unique<WriteToImageBundleStage>(image_bundle,
- std::move(color_encoding));
+ output_encoding_info);
}
std::unique_ptr<RenderPipelineStage> GetWriteToImage3FStage(Image3F* image) {
diff --git a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.h b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.h
index c5f844ebe8..ba2c51ee97 100644
--- a/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.h
+++ b/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.h
@@ -9,13 +9,14 @@
#include <functional>
#include "lib/jxl/dec_cache.h"
+#include "lib/jxl/dec_xyb.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/render_pipeline/render_pipeline_stage.h"
namespace jxl {
std::unique_ptr<RenderPipelineStage> GetWriteToImageBundleStage(
- ImageBundle* image_bundle, ColorEncoding color_encoding);
+ ImageBundle* image_bundle, const OutputEncodingInfo& output_encoding_info);
// Gets a stage to write color channels to an Image3F.
std::unique_ptr<RenderPipelineStage> GetWriteToImage3FStage(Image3F* image);
diff --git a/third_party/jpeg-xl/lib/jxl/roundtrip_test.cc b/third_party/jpeg-xl/lib/jxl/roundtrip_test.cc
index a4a87bebb7..07e43e4ddf 100644
--- a/third_party/jpeg-xl/lib/jxl/roundtrip_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/roundtrip_test.cc
@@ -941,7 +941,7 @@ TEST(RoundtripTest, TestICCProfile) {
JxlDecoderDestroy(dec);
}
-TEST(RoundtripTest, JXL_TRANSCODE_JPEG_TEST(TestJPEGReconstruction)) {
+JXL_TRANSCODE_JPEG_TEST(RoundtripTest, TestJPEGReconstruction) {
TEST_LIBJPEG_SUPPORT();
const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";
const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);
diff --git a/third_party/jpeg-xl/lib/jxl/speed_tier_test.cc b/third_party/jpeg-xl/lib/jxl/speed_tier_test.cc
index 381367b54d..8783faec8f 100644
--- a/third_party/jpeg-xl/lib/jxl/speed_tier_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/speed_tier_test.cc
@@ -82,7 +82,6 @@ JXL_GTEST_INSTANTIATE_TEST_SUITE_P(
TEST_P(SpeedTierTest, Roundtrip) {
const SpeedTierTestParams& params = GetParam();
- test::ThreadPoolForTests pool(8);
const std::vector<uint8_t> orig = jxl::test::ReadTestData(
"external/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
test::TestImage t;
diff --git a/third_party/jpeg-xl/lib/jxl/splines.cc b/third_party/jpeg-xl/lib/jxl/splines.cc
index 1d4fc69e3e..2df2160d76 100644
--- a/third_party/jpeg-xl/lib/jxl/splines.cc
+++ b/third_party/jpeg-xl/lib/jxl/splines.cc
@@ -94,11 +94,11 @@ void DrawSegment(DF df, const SplineSegment& segment, const bool add,
void DrawSegment(const SplineSegment& segment, const bool add, const size_t y,
const ssize_t x0, ssize_t x1, float* JXL_RESTRICT rows[3]) {
- ssize_t x =
- std::max<ssize_t>(x0, segment.center_x - segment.maximum_distance + 0.5f);
+ ssize_t x = std::max<ssize_t>(
+ x0, std::llround(segment.center_x - segment.maximum_distance));
// one-past-the-end
- x1 =
- std::min<ssize_t>(x1, segment.center_x + segment.maximum_distance + 1.5f);
+ x1 = std::min<ssize_t>(
+ x1, std::llround(segment.center_x + segment.maximum_distance) + 1);
HWY_FULL(float) df;
for (; x + static_cast<ssize_t>(Lanes(df)) <= x1; x += Lanes(df)) {
DrawSegment(df, segment, add, y, x, rows);
@@ -550,8 +550,8 @@ Status QuantizedSpline::Decode(const std::vector<uint8_t>& context_map,
}
return true;
};
- for (int c = 0; c < 3; ++c) {
- JXL_RETURN_IF_ERROR(decode_dct(color_dct_[c]));
+ for (auto& dct : color_dct_) {
+ JXL_RETURN_IF_ERROR(decode_dct(dct));
}
JXL_RETURN_IF_ERROR(decode_dct(sigma_dct_));
return true;
diff --git a/third_party/jpeg-xl/lib/jxl/test_image.cc b/third_party/jpeg-xl/lib/jxl/test_image.cc
index 42f028d53a..d2e17c6ab0 100644
--- a/third_party/jpeg-xl/lib/jxl/test_image.cc
+++ b/third_party/jpeg-xl/lib/jxl/test_image.cc
@@ -288,8 +288,7 @@ TestImage& TestImage::SetAllBitDepths(uint32_t bits_per_sample,
ppf_.info.alpha_bits = bits_per_sample;
ppf_.info.alpha_exponent_bits = exponent_bits_per_sample;
}
- for (size_t i = 0; i < ppf_.extra_channels_info.size(); ++i) {
- extras::PackedExtraChannel& ec = ppf_.extra_channels_info[i];
+ for (auto& ec : ppf_.extra_channels_info) {
ec.ec_info.bits_per_sample = bits_per_sample;
ec.ec_info.exponent_bits_per_sample = exponent_bits_per_sample;
}
diff --git a/third_party/jpeg-xl/lib/jxl/test_utils.h b/third_party/jpeg-xl/lib/jxl/test_utils.h
index 15057cc92d..dc50490174 100644
--- a/third_party/jpeg-xl/lib/jxl/test_utils.h
+++ b/third_party/jpeg-xl/lib/jxl/test_utils.h
@@ -180,8 +180,7 @@ class ThreadPoolForTests {
}
ThreadPoolForTests(const ThreadPoolForTests&) = delete;
ThreadPoolForTests& operator&(const ThreadPoolForTests&) = delete;
- // TODO(eustas): avoid unary `&` overload?
- ThreadPool* operator&() { return pool_.get(); }
+ ThreadPool* get() { return pool_.get(); }
private:
JxlThreadParallelRunnerPtr runner_;
diff --git a/third_party/jpeg-xl/lib/jxl/testing.h b/third_party/jpeg-xl/lib/jxl/testing.h
index 1fac352a78..6e4978eb60 100644
--- a/third_party/jpeg-xl/lib/jxl/testing.h
+++ b/third_party/jpeg-xl/lib/jxl/testing.h
@@ -13,33 +13,37 @@
#include "lib/jxl/common.h"
#ifdef JXL_DISABLE_SLOW_TESTS
-#define JXL_SLOW_TEST(X) DISABLED_##X
+#define JXL_SLOW_TEST(T, C) TEST(T, DISABLED_##C)
#else
-#define JXL_SLOW_TEST(X) X
+#define JXL_SLOW_TEST(T, C) TEST(T, C)
#endif // JXL_DISABLE_SLOW_TESTS
#if JPEGXL_ENABLE_TRANSCODE_JPEG
-#define JXL_TRANSCODE_JPEG_TEST(X) X
+#define JXL_TRANSCODE_JPEG_TEST(T, C) TEST(T, C)
#else
-#define JXL_TRANSCODE_JPEG_TEST(X) DISABLED_##X
+#define JXL_TRANSCODE_JPEG_TEST(T, C) TEST(T, DISABLED_##C)
#endif // JPEGXL_ENABLE_TRANSCODE_JPEG
#if JPEGXL_ENABLE_BOXES
-#define JXL_BOXES_TEST(X) X
+#define JXL_BOXES_TEST(T, C) TEST(T, C)
+#define JXL_BOXES_TEST_P(T, C) TEST_P(T, C)
#else
-#define JXL_BOXES_TEST(X) DISABLED_##X
+#define JXL_BOXES_TEST(T, C) TEST(T, DISABLED_##C)
+#define JXL_BOXES_TEST_P(T, C) TEST_P(T, DISABLED_##C)
#endif // JPEGXL_ENABLE_BOXES
#ifdef THREAD_SANITIZER
-#define JXL_TSAN_SLOW_TEST(X) DISABLED_##X
+#define JXL_TSAN_SLOW_TEST(T, C) TEST(T, DISABLED_##C)
#else
-#define JXL_TSAN_SLOW_TEST(X) X
+#define JXL_TSAN_SLOW_TEST(T, C) TEST(T, C)
#endif // THREAD_SANITIZER
#if defined(__x86_64__)
-#define JXL_X86_64_TEST(X) X
+#define JXL_X86_64_TEST(T, C) TEST(T, C)
+#define JXL_X86_64_TEST_P(T, C) TEST_P(T, C)
#else
-#define JXL_X86_64_TEST(X) DISABLED_##X
+#define JXL_X86_64_TEST(T, C) TEST(T, DISABLED_##C)
+#define JXL_X86_64_TEST_P(T, C) TEST_P(T, C)
#endif // defined(__x86_64__)
// googletest before 1.10 didn't define INSTANTIATE_TEST_SUITE_P() but instead
diff --git a/third_party/jpeg-xl/lib/jxl/tf_gbench.cc b/third_party/jpeg-xl/lib/jxl/tf_gbench.cc
index e93a936c90..6cfd3734bb 100644
--- a/third_party/jpeg-xl/lib/jxl/tf_gbench.cc
+++ b/third_party/jpeg-xl/lib/jxl/tf_gbench.cc
@@ -46,7 +46,9 @@ namespace {
#define RUN_BENCHMARK_SCALAR(F, I) \
constexpr size_t kNum = 1 << 12; \
/* Three parallel runs, as this will run on R, G and B. */ \
- float sum1 = 0, sum2 = 0, sum3 = 0; \
+ float sum1 = 0; \
+ float sum2 = 0; \
+ float sum3 = 0; \
for (auto _ : state) { \
float x = 1e-5; \
float v1 = 1e-5; \
diff --git a/third_party/jpeg-xl/lib/jxl/version.h.in b/third_party/jpeg-xl/lib/jxl/version.h.in
index ad1eb24409..b5a462e52e 100644
--- a/third_party/jpeg-xl/lib/jxl/version.h.in
+++ b/third_party/jpeg-xl/lib/jxl/version.h.in
@@ -32,7 +32,7 @@
#define JPEGXL_COMPUTE_NUMERIC_VERSION(major,minor,patch) (((major)<<24) | ((minor)<<16) | ((patch)<<8) | 0)
/* Numeric representation of the version */
-#define JPEGXL_NUMERIC_VERSION JPEGXL_COMPUTE_NUMERIC_VERSION(JPEGXL_MAJOR_VERSION,JPEGXL_MINOR_VERSION,JPEGXL_PATCH_VERSION)
+#define JPEGXL_NUMERIC_VERSION JPEGXL_COMPUTE_NUMERIC_VERSION(JPEGXL_MAJOR_VERSION, JPEGXL_MINOR_VERSION, JPEGXL_PATCH_VERSION)
#endif /* JXL_VERSION_H_ */
diff --git a/third_party/jpeg-xl/lib/jxl/xorshift128plus_test.cc b/third_party/jpeg-xl/lib/jxl/xorshift128plus_test.cc
index 2ee4535284..c68ce4bb3b 100644
--- a/third_party/jpeg-xl/lib/jxl/xorshift128plus_test.cc
+++ b/third_party/jpeg-xl/lib/jxl/xorshift128plus_test.cc
@@ -294,7 +294,7 @@ void TestFloat() {
const uint32_t kMaxSeed = 4096;
#endif // JXL_DISABLE_SLOW_TESTS
EXPECT_TRUE(RunOnPool(
- &pool, 0, kMaxSeed, ThreadPool::NoInit,
+ pool.get(), 0, kMaxSeed, ThreadPool::NoInit,
[](const uint32_t seed, size_t /*thread*/) {
HWY_ALIGN Xorshift128Plus rng(seed);
@@ -340,7 +340,7 @@ void TestNotZero() {
const uint32_t kMaxSeed = 2000;
#endif // JXL_DISABLE_SLOW_TESTS
EXPECT_TRUE(RunOnPool(
- &pool, 0, kMaxSeed, ThreadPool::NoInit,
+ pool.get(), 0, kMaxSeed, ThreadPool::NoInit,
[](const uint32_t task, size_t /*thread*/) {
HWY_ALIGN uint64_t lanes[Xorshift128Plus::N];
diff --git a/third_party/jpeg-xl/lib/jxl_cms.cmake b/third_party/jpeg-xl/lib/jxl_cms.cmake
index 04980066c1..62d5b651fd 100644
--- a/third_party/jpeg-xl/lib/jxl_cms.cmake
+++ b/third_party/jpeg-xl/lib/jxl_cms.cmake
@@ -23,7 +23,7 @@ target_include_directories(jxl_cms PRIVATE
generate_export_header(jxl_cms
BASE_NAME JXL_CMS
EXPORT_FILE_NAME include/jxl/jxl_cms_export.h)
-target_include_directories(jxl_cms PUBLIC
+target_include_directories(jxl_cms BEFORE PUBLIC
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>")
set(JPEGXL_CMS_LIBRARY_REQUIRES "")
diff --git a/third_party/jpeg-xl/lib/jxl_extras.cmake b/third_party/jpeg-xl/lib/jxl_extras.cmake
index 597f691554..747c07eb9c 100644
--- a/third_party/jpeg-xl/lib/jxl_extras.cmake
+++ b/third_party/jpeg-xl/lib/jxl_extras.cmake
@@ -118,7 +118,7 @@ foreach(LIB ${JXL_EXTRAS_OBJECT_LIBRARIES})
target_compile_options("${LIB}" PRIVATE "${JPEGXL_INTERNAL_FLAGS}")
target_compile_definitions("${LIB}" PRIVATE -DJXL_EXPORT=)
set_property(TARGET "${LIB}" PROPERTY POSITION_INDEPENDENT_CODE ON)
- target_include_directories("${LIB}" PRIVATE
+ target_include_directories("${LIB}" BEFORE PRIVATE
${PROJECT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_BINARY_DIR}/include
diff --git a/third_party/jpeg-xl/lib/jxl_lists.bzl b/third_party/jpeg-xl/lib/jxl_lists.bzl
index 6c98ca15ed..a66706aa83 100644
--- a/third_party/jpeg-xl/lib/jxl_lists.bzl
+++ b/third_party/jpeg-xl/lib/jxl_lists.bzl
@@ -551,7 +551,7 @@ libjxl_major_version = 0
libjxl_minor_version = 10
-libjxl_patch_version = 0
+libjxl_patch_version = 2
libjxl_public_headers = [
"include/jxl/cms.h",
diff --git a/third_party/jpeg-xl/lib/threads/thread_parallel_runner_test.cc b/third_party/jpeg-xl/lib/threads/thread_parallel_runner_test.cc
index 7c8e602764..0762b3299b 100644
--- a/third_party/jpeg-xl/lib/threads/thread_parallel_runner_test.cc
+++ b/third_party/jpeg-xl/lib/threads/thread_parallel_runner_test.cc
@@ -34,7 +34,7 @@ TEST(ThreadParallelRunnerTest, TestPool) {
for (int begin = 0; begin < 32; ++begin) {
std::fill(mementos.begin(), mementos.end(), 0);
EXPECT_TRUE(RunOnPool(
- &pool, begin, begin + num_tasks, jxl::ThreadPool::NoInit,
+ pool.get(), begin, begin + num_tasks, jxl::ThreadPool::NoInit,
[begin, num_tasks, &mementos](const int task, const int thread) {
// Parameter is in the given range
EXPECT_GE(task, begin);
@@ -63,7 +63,7 @@ TEST(ThreadParallelRunnerTest, TestSmallAssignments) {
std::atomic<int> num_calls{0};
EXPECT_TRUE(RunOnPool(
- &pool, 0, num_threads, jxl::ThreadPool::NoInit,
+ pool.get(), 0, num_threads, jxl::ThreadPool::NoInit,
[&num_calls, num_threads, &id_bits](const int task, const int thread) {
num_calls.fetch_add(1, std::memory_order_relaxed);
@@ -101,7 +101,7 @@ TEST(ThreadParallelRunnerTest, TestCounter) {
const int kNumTasks = kNumThreads * 19;
EXPECT_TRUE(RunOnPool(
- &pool, 0, kNumTasks, jxl::ThreadPool::NoInit,
+ pool.get(), 0, kNumTasks, jxl::ThreadPool::NoInit,
[&counters](const int task, const int thread) {
counters[thread].counter += task;
},
diff --git a/third_party/jpeg-xl/plugins/gimp/file-jxl-load.cc b/third_party/jpeg-xl/plugins/gimp/file-jxl-load.cc
index 07acd524d2..41a5d45bfb 100644
--- a/third_party/jpeg-xl/plugins/gimp/file-jxl-load.cc
+++ b/third_party/jpeg-xl/plugins/gimp/file-jxl-load.cc
@@ -39,10 +39,10 @@ bool LoadJpegXlImage(const gchar *const filename, gint32 *const image_id) {
GimpColorProfile *profile_icc = nullptr;
GimpColorProfile *profile_int = nullptr;
bool is_linear = false;
- unsigned long xsize = 0;
- unsigned long ysize = 0;
- long crop_x0 = 0;
- long crop_y0 = 0;
+ uint32_t xsize = 0;
+ uint32_t ysize = 0;
+ int32_t crop_x0 = 0;
+ int32_t crop_y0 = 0;
size_t layer_idx = 0;
uint32_t frame_duration = 0;
double tps_denom = 1.f;
diff --git a/third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc b/third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc
index 284a9f2771..65b637d419 100644
--- a/third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc
+++ b/third_party/jpeg-xl/plugins/gimp/file-jxl-save.cc
@@ -17,12 +17,12 @@
#define PLUG_IN_BINARY "file-jxl"
#define SAVE_PROC "file-jxl-save"
-#define SCALE_WIDTH 200
-
namespace jxl {
namespace {
+constexpr size_t kScaleWidth = 200;
+
#ifndef g_clear_signal_handler
// g_clear_signal_handler was added in glib 2.62
void g_clear_signal_handler(gulong* handler, gpointer instance) {
@@ -292,7 +292,7 @@ bool JpegXlSaveGui::SaveDialog() {
"\n\td\u00A0=\u00A06\tPoor";
entry_distance = reinterpret_cast<GtkAdjustment*>(
- gimp_scale_entry_new(GTK_TABLE(table), 0, 0, "Distance", SCALE_WIDTH, 0,
+ gimp_scale_entry_new(GTK_TABLE(table), 0, 0, "Distance", kScaleWidth, 0,
jxl_save_opts.distance, 0.0, 15.0, 0.001, 1.0, 3,
true, 0.0, 0.0, distance_help, SAVE_PROC));
gimp_scale_entry_set_logarithmic(reinterpret_cast<GtkObject*>(entry_distance),
@@ -303,7 +303,7 @@ bool JpegXlSaveGui::SaveDialog() {
"JPEG-style Quality is remapped to distance. "
"Values roughly match libjpeg quality settings.";
entry_quality = reinterpret_cast<GtkAdjustment*>(gimp_scale_entry_new(
- GTK_TABLE(table), 0, 1, "Quality", SCALE_WIDTH, 0, jxl_save_opts.quality,
+ GTK_TABLE(table), 0, 1, "Quality", kScaleWidth, 0, jxl_save_opts.quality,
8.26, 100.0, 1.0, 10.0, 2, true, 0.0, 0.0, quality_help, SAVE_PROC));
// Distance and Quality Signals
@@ -325,7 +325,7 @@ bool JpegXlSaveGui::SaveDialog() {
"As\u00A0a\u00A0result, image quality may be decreased. "
"Default\u00A0=\u00A03.";
entry_effort = reinterpret_cast<GtkAdjustment*>(
- gimp_scale_entry_new(GTK_TABLE(table), 0, 3, "Speed", SCALE_WIDTH, 0,
+ gimp_scale_entry_new(GTK_TABLE(table), 0, 3, "Speed", kScaleWidth, 0,
10 - jxl_save_opts.encoding_effort, 1, 9, 1, 2, 0,
true, 0.0, 0.0, effort_help, SAVE_PROC));
@@ -419,7 +419,7 @@ bool JpegXlSaveGui::SaveDialog() {
entry_faster = reinterpret_cast<GtkAdjustment*>(
gimp_scale_entry_new(GTK_TABLE(table), 0, 0, "Faster Decoding",
- SCALE_WIDTH, 0, jxl_save_opts.faster_decoding, 0, 4,
+ kScaleWidth, 0, jxl_save_opts.faster_decoding, 0, 4,
1, 1, 0, true, 0.0, 0.0, faster_help, SAVE_PROC));
// Faster Decoding Signals
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!
diff --git a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/AUTHORS.md b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/AUTHORS.md
index 525116ee7e..525116ee7e 100644
--- a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/AUTHORS.md
+++ b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/AUTHORS.md
diff --git a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/LICENSE b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/LICENSE
index a612ad9813..a612ad9813 100644
--- a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/LICENSE
+++ b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/LICENSE
diff --git a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/METADATA b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/METADATA
index 1e31df3dd4..0bab2150ba 100644
--- a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/METADATA
+++ b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: glean-parser
-Version: 13.0.0
+Version: 13.0.1
Summary: Parser tools for Mozilla's Glean telemetry
Home-page: https://github.com/mozilla/glean_parser
Author: The Glean Team
@@ -79,6 +79,10 @@ $ glean_parser check < ping.json
## Unreleased
+## 13.0.1
+
+- Use faster C yaml parser if available ([#677](https://github.com/mozilla/glean_parser/pull/677))
+
## 13.0.0
- BREAKING CHANGE: Support metadata field `include_info_sections` ([bug 1866559](https://bugzilla.mozilla.org/show_bug.cgi?id=1866559))
diff --git a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/RECORD b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/RECORD
index 62e4bb6fbb..8ebf523fd7 100644
--- a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/RECORD
+++ b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/RECORD
@@ -2,23 +2,23 @@ glean_parser/__init__.py,sha256=bJljD052_0y-efcBhYpllICVCXOMHLcXRLNyrvfgt5A,533
glean_parser/__main__.py,sha256=Rw0PpuQtAvdHJMK1YLozeZkc6x1yjeNZwidu4faovdk,8633
glean_parser/coverage.py,sha256=2IwC4XMDtDamMkBFoYilmqJzW4gyypq65YVCur8SNas,4405
glean_parser/data_review.py,sha256=BweeeTkNNS6HrIDkztawhbDByrk_-Avxpg7YeST3VAs,2152
-glean_parser/go_server.py,sha256=SCcGrjRktlPyl79LbjIvtBeCNYVOXOW4Q8xkuks0bzE,5345
+glean_parser/go_server.py,sha256=s6lxK9IAFY55pNl3Rv4MHlV-nQwSoyhO9ppTQE9VCik,5346
glean_parser/javascript.py,sha256=w4ZhNBHBKWYk0h3t7G0Ud2tR__hRqzn9dlEXNKLdQrA,11230
-glean_parser/javascript_server.py,sha256=SDV9tPL1uZMyS1VSyo5lOFuNPFHZu-PZxr1vhND-GzM,7971
+glean_parser/javascript_server.py,sha256=x75JfOaveEkPQe3ozYXdtDb1Zks-PxzncDOizsJbYos,7972
glean_parser/kotlin.py,sha256=5z8_74xlqvHDsedwZhGf1_qb7swPEgIZumkJIuj3ef8,12598
glean_parser/lint.py,sha256=STqdgyOhR4Q3fHivSizgn9bOOyqrNHhzjaqyJxz6qzI,19948
glean_parser/markdown.py,sha256=GkCr1CrV6mnRQseT6FO1-JJ7Eup8X3lxUfRMBTxXpe4,9066
glean_parser/metrics.py,sha256=YAO8wPuRHTLkdT9M4zh9ZwoFI1_VS8O9oQqwZNYyDp0,14612
glean_parser/parser.py,sha256=cUOnvSXKfEBg8YTpRcWiPcMwpFpK1TTqsVO_zjUtpR4,15309
glean_parser/pings.py,sha256=AQ-fBmIx2GKQv6J2NyTFfHHZzSnApZZoC770LlstkoI,3180
-glean_parser/python_server.py,sha256=3ZsqeNJknKO9yvtBJWxe67JthzSMqNMuo9DfhgF2kvg,4790
-glean_parser/ruby_server.py,sha256=-bNXjfXWwHWUHmLJVvfi6jCyw8q0MBwx9VXVWQ3bU-A,5189
+glean_parser/python_server.py,sha256=ERpYcbSwF19xKFagxX0mZAvlR1y6D7Ah5DSvW8LipCY,4791
+glean_parser/ruby_server.py,sha256=e5lkfcLQAUMUBQDCjqNU82LkdUzT5x-G6HOnsUInbsU,5190
glean_parser/rust.py,sha256=UEHeIZlToxCBelfec5sl_l_uLZfk8f_OUXqa_ZoEvnk,7330
glean_parser/swift.py,sha256=T1BSGahd9wUd6VDeNC89SdN6M34jKXDlydMpSI0QLOs,8379
glean_parser/tags.py,sha256=bemKYvcbMO4JrghiNSe-A4BNNDtx_FlUPkgrPPJy84Y,1391
glean_parser/translate.py,sha256=luKQoraARZ2tjenHs0SVtCxflnYaMkzPYFfKEdKdSqQ,8403
glean_parser/translation_options.py,sha256=Lxzr6G7MP0tC_ZYlZXftS4j0SLiqO-5mGVTEc7ggXis,2037
-glean_parser/util.py,sha256=KgvmjETOV1IIGD4hF_o5zcUDE-wp3SHxrNHM1niU0CM,16033
+glean_parser/util.py,sha256=v81watw5nSPGRlFNNpTb7iUv9NZObiFIbyyg2oZ6EnY,16149
glean_parser/validate_ping.py,sha256=0TNvILH6dtzJDys3W8Kqorw6kk03me73OCUDtpoHcXU,2118
glean_parser/schemas/metrics.1-0-0.schema.yaml,sha256=cND3cvi6iBfPUVmtfIBQfGJV9AALpbvN7nu8E33_J-o,19566
glean_parser/schemas/metrics.2-0-0.schema.yaml,sha256=wx1q0L4C0-Vcwk1SPU6t8OfjDEQvgrwwEG6xfSHO1MI,26365
@@ -39,10 +39,10 @@ glean_parser/templates/qmldir.jinja2,sha256=m6IGsp-tgTiOfQ7VN8XW6GqX0gJqJkt3B6Pk
glean_parser/templates/ruby_server.jinja2,sha256=vm4BEenOqzomQNTLFfMOzlWHARnsWUjTBbnR-v2cadI,6247
glean_parser/templates/rust.jinja2,sha256=wlV0OZvV3Mk2ulrqFkN1vGjdsahsupEy2TQvWxQKzww,5439
glean_parser/templates/swift.jinja2,sha256=xkvVsTpfK0QK3tI32wGqzxm2hqFNaBQ6Y71rKIsCmAI,4944
-glean_parser-13.0.0.dist-info/AUTHORS.md,sha256=yxgj8MioO4wUnrh0gmfb8l3DJJrf-l4HmmEDbQsbbNI,455
-glean_parser-13.0.0.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
-glean_parser-13.0.0.dist-info/METADATA,sha256=BzYfW5GF-wZLrokfvUTiZg7JT5BTfB1E3xIDKW6h_BY,31493
-glean_parser-13.0.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
-glean_parser-13.0.0.dist-info/entry_points.txt,sha256=mf9d3sv8BwSjjR58x9KDnpVkONCnv3fPQC2NjJl15Xg,68
-glean_parser-13.0.0.dist-info/top_level.txt,sha256=q7T3duD-9tYZFyDry6Wv2LcdMsK2jGnzdDFhxWcT2Z8,13
-glean_parser-13.0.0.dist-info/RECORD,,
+glean_parser-13.0.1.dist-info/AUTHORS.md,sha256=yxgj8MioO4wUnrh0gmfb8l3DJJrf-l4HmmEDbQsbbNI,455
+glean_parser-13.0.1.dist-info/LICENSE,sha256=HyVuytGSiAUQ6ErWBHTqt1iSGHhLmlC8fO7jTCuR8dU,16725
+glean_parser-13.0.1.dist-info/METADATA,sha256=UYz6ZRXyv3ODi3yl2vRQHZVdm0XGerFp8pIOGWGwOKw,31604
+glean_parser-13.0.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
+glean_parser-13.0.1.dist-info/entry_points.txt,sha256=mf9d3sv8BwSjjR58x9KDnpVkONCnv3fPQC2NjJl15Xg,68
+glean_parser-13.0.1.dist-info/top_level.txt,sha256=q7T3duD-9tYZFyDry6Wv2LcdMsK2jGnzdDFhxWcT2Z8,13
+glean_parser-13.0.1.dist-info/RECORD,,
diff --git a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/WHEEL b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/WHEEL
index 98c0d20b7a..bab98d6758 100644
--- a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/WHEEL
+++ b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/WHEEL
@@ -1,5 +1,5 @@
Wheel-Version: 1.0
-Generator: bdist_wheel (0.42.0)
+Generator: bdist_wheel (0.43.0)
Root-Is-Purelib: true
Tag: py3-none-any
diff --git a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/entry_points.txt b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/entry_points.txt
index 08fde9d655..08fde9d655 100644
--- a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/entry_points.txt
+++ b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/entry_points.txt
diff --git a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/top_level.txt b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/top_level.txt
index a7f3a37918..a7f3a37918 100644
--- a/third_party/python/glean_parser/glean_parser-13.0.0.dist-info/top_level.txt
+++ b/third_party/python/glean_parser/glean_parser-13.0.1.dist-info/top_level.txt
diff --git a/third_party/python/glean_parser/glean_parser/go_server.py b/third_party/python/glean_parser/glean_parser/go_server.py
index 403a0d71f4..03abb72819 100644
--- a/third_party/python/glean_parser/glean_parser/go_server.py
+++ b/third_party/python/glean_parser/glean_parser/go_server.py
@@ -21,6 +21,7 @@ The generated code creates the following:
* Two methods for logging an Event metric
one with and one without user request info specified
"""
+
from collections import defaultdict
from pathlib import Path
from typing import Any, Dict, Optional, List
diff --git a/third_party/python/glean_parser/glean_parser/javascript_server.py b/third_party/python/glean_parser/glean_parser/javascript_server.py
index cdaa0cb403..f5099d2660 100644
--- a/third_party/python/glean_parser/glean_parser/javascript_server.py
+++ b/third_party/python/glean_parser/glean_parser/javascript_server.py
@@ -28,6 +28,7 @@ There are two patterns for event structure supported in this environment:
Therefore, unlike in other outputters, here we don't generate classes for each metric.
"""
+
from collections import defaultdict
from pathlib import Path
from typing import Any, Dict, Optional, List
diff --git a/third_party/python/glean_parser/glean_parser/python_server.py b/third_party/python/glean_parser/glean_parser/python_server.py
index 8ead0eb315..db9d39f540 100644
--- a/third_party/python/glean_parser/glean_parser/python_server.py
+++ b/third_party/python/glean_parser/glean_parser/python_server.py
@@ -20,6 +20,7 @@ see `SUPPORTED_METRIC_TYPES` below.
The generated code creates a `ServerEventLogger` class for each ping that has
at least one event metric. The class has a `record` method for each event metric.
"""
+
from collections import defaultdict
from pathlib import Path
from typing import Any, Dict, Optional, List
diff --git a/third_party/python/glean_parser/glean_parser/ruby_server.py b/third_party/python/glean_parser/glean_parser/ruby_server.py
index bbca3df80c..21c21544a6 100644
--- a/third_party/python/glean_parser/glean_parser/ruby_server.py
+++ b/third_party/python/glean_parser/glean_parser/ruby_server.py
@@ -18,6 +18,7 @@ Then it's the role of the ingestion pipeline to pick the messages up and process
Warning: this outputter supports a limited set of metrics,
see `SUPPORTED_METRIC_TYPES` below.
"""
+
from collections import defaultdict
from pathlib import Path
from typing import Any, Dict, List, Optional
diff --git a/third_party/python/glean_parser/glean_parser/util.py b/third_party/python/glean_parser/glean_parser/util.py
index 41cda8833d..f8bc7d4f53 100644
--- a/third_party/python/glean_parser/glean_parser/util.py
+++ b/third_party/python/glean_parser/glean_parser/util.py
@@ -20,6 +20,11 @@ import jsonschema # type: ignore
from jsonschema import _utils # type: ignore
import yaml
+try:
+ from yaml import CSafeLoader as SafeLoader
+except ImportError:
+ from yaml import SafeLoader # type: ignore
+
def date_fromisoformat(datestr: str) -> datetime.date:
return datetime.date.fromisoformat(datestr)
@@ -44,7 +49,7 @@ class DictWrapper(dict):
pass
-class _NoDatesSafeLoader(yaml.SafeLoader):
+class _NoDatesSafeLoader(SafeLoader):
@classmethod
def remove_implicit_resolver(cls, tag_to_remove):
"""
diff --git a/third_party/python/poetry.lock b/third_party/python/poetry.lock
index 67d13cdfc3..97513f8ba5 100644
--- a/third_party/python/poetry.lock
+++ b/third_party/python/poetry.lock
@@ -592,14 +592,14 @@ files = [
[[package]]
name = "glean-parser"
-version = "13.0.0"
+version = "13.0.1"
description = "Parser tools for Mozilla's Glean telemetry"
category = "main"
optional = false
python-versions = "*"
files = [
- {file = "glean_parser-13.0.0-py3-none-any.whl", hash = "sha256:1c1e9d33fae3b804fc066ae6b2ae7ae8f4148cac1e5b248f2c1e2bfc2e3ae520"},
- {file = "glean_parser-13.0.0.tar.gz", hash = "sha256:833780cab7e057034b352786203af94f21afcb0094cbed6010471f5dc21a5f91"},
+ {file = "glean_parser-13.0.1-py3-none-any.whl", hash = "sha256:8421c88f3673dd195d0cde635f4f09c9bfd0c9709ad3d28c8b201b3b7145e257"},
+ {file = "glean_parser-13.0.1.tar.gz", hash = "sha256:feead4cbec6930ed38a48df5bae9eb4ee486bb4026ddf2f3206b85f80279d1e7"},
]
[package.dependencies]
@@ -1161,6 +1161,7 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
@@ -1624,4 +1625,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
-content-hash = "2bb8ac6bdb09e709fe469807ee6ed832f281cbc78dee9edf5d932bee0fde5d4f"
+content-hash = "cef77da3299e7849f5039e8d9017216048d4ca56af298209e5bf3db7f92c2d4c"
diff --git a/third_party/python/requirements.in b/third_party/python/requirements.in
index 90a4049dcf..9915e91957 100644
--- a/third_party/python/requirements.in
+++ b/third_party/python/requirements.in
@@ -22,7 +22,7 @@ fluent.migrate==0.13.0
fluent.syntax==0.19.0
# Pin `frozenlist` as it is required for `aiohttp`. Use minimum required version.
frozenlist==1.1.1
-glean_parser==13.0.0
+glean_parser==13.0.1
importlib-metadata==6.0.0
# required for compatibility with Flask >= 2 in tools/tryselect/selectors/chooser
jinja2==3.1.2
diff --git a/third_party/python/requirements.txt b/third_party/python/requirements.txt
index e7df6a5d5a..eedc022c50 100644
--- a/third_party/python/requirements.txt
+++ b/third_party/python/requirements.txt
@@ -275,9 +275,9 @@ frozenlist==1.1.1 ; python_version >= "3.8" and python_version < "4.0" \
giturlparse==0.10.0 ; python_version >= "3.8" and python_version < "4.0" \
--hash=sha256:04ba1a3a099c3093fa8d24a422913c6a9b2c2cd22bcffc939cf72e3e98f672d7 \
--hash=sha256:2595ab291d30717cda8474b874c9fd509f1b9802ad7f6968c36a45e4b13eb337
-glean-parser==13.0.0 ; python_version >= "3.8" and python_version < "4.0" \
- --hash=sha256:1c1e9d33fae3b804fc066ae6b2ae7ae8f4148cac1e5b248f2c1e2bfc2e3ae520 \
- --hash=sha256:833780cab7e057034b352786203af94f21afcb0094cbed6010471f5dc21a5f91
+glean-parser==13.0.1 ; python_version >= "3.8" and python_version < "4.0" \
+ --hash=sha256:8421c88f3673dd195d0cde635f4f09c9bfd0c9709ad3d28c8b201b3b7145e257 \
+ --hash=sha256:feead4cbec6930ed38a48df5bae9eb4ee486bb4026ddf2f3206b85f80279d1e7
idna==2.10 ; python_version >= "3.8" and python_version < "4.0" \
--hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
--hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0
@@ -492,6 +492,7 @@ pyyaml==6.0.1 ; python_version >= "3.8" and python_version < "4.0" \
--hash=sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4 \
--hash=sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba \
--hash=sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8 \
+ --hash=sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef \
--hash=sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5 \
--hash=sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd \
--hash=sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3 \
diff --git a/third_party/rust/audio_thread_priority/.cargo-checksum.json b/third_party/rust/audio_thread_priority/.cargo-checksum.json
index e1a42d3fe7..fc49e6d1a8 100644
--- a/third_party/rust/audio_thread_priority/.cargo-checksum.json
+++ b/third_party/rust/audio_thread_priority/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"6bceb8b41d1646bd255b39c13dcca5bbcc0984f0f2aca73decb7ee5627443387","LICENSE":"32ee9dbf6196874fc9d406c54a888a6c4cbb9aa4a7f35b46befeaff43a78fe85","Makefile":"0f9a771cfb30c7c4b9961d82fdca4e9e229a955bb2e636474a4101389e18e938","README.md":"c123692b3b50dd621b896a8269814d609cbf1e532b461bf4a77854ddd607eb7a","atp_test.cpp":"8075a040941a65fb9e3f7cbf0535853ca6661c3ac442ec35569b42b24bbec797","audio_thread_priority.h":"f0ecaf1b674f794cde0dc834028e074d4e4675d22ae96acf08b2ae1dceb3474e","generate_osx_bindings.sh":"06e4e03450f788ced18d31fff5660919e6f6ec1119ddace363ffeb82f0518a71","src/lib.rs":"1d5c629d1ee2dab80f7bf422a3add07eaca203f5c1557fdd379361d632e14224","src/mach_sys.rs":"352560fcb9b41d877cff92e5b3b04d6dc68b1f30508ce4b9aed78940120a883e","src/rt_linux.rs":"e77db6d66fb76772dde6c12850ef2c50730cfe00decc0c67211e63f3e8a59c9f","src/rt_mach.rs":"29f8c0397f14cecbac1f76394c2abfe0e05903b54486cf735f9a94a10c168643","src/rt_win.rs":"f2ba097cebf65252c27d9d6dcfbe1fcc041c4b312724bd98e080e114a4de3bb6"},"package":"17fd24082f432a11ad4fde564af75fc9a0beef2f4199e7691b090ff0e065c4d0"} \ No newline at end of file
+{"files":{"Cargo.toml":"2dd36097338035819ea7fdb1de6d37009571d761fb659194423f7143a3ba25c7","LICENSE":"32ee9dbf6196874fc9d406c54a888a6c4cbb9aa4a7f35b46befeaff43a78fe85","Makefile":"0f9a771cfb30c7c4b9961d82fdca4e9e229a955bb2e636474a4101389e18e938","README.md":"c123692b3b50dd621b896a8269814d609cbf1e532b461bf4a77854ddd607eb7a","atp_test.cpp":"8075a040941a65fb9e3f7cbf0535853ca6661c3ac442ec35569b42b24bbec797","audio_thread_priority.h":"f0ecaf1b674f794cde0dc834028e074d4e4675d22ae96acf08b2ae1dceb3474e","generate_osx_bindings.sh":"06e4e03450f788ced18d31fff5660919e6f6ec1119ddace363ffeb82f0518a71","src/lib.rs":"142a4f40a0deba0544673c5c8cb4398525a7ead2cc5addb4e69dd9a81e771ded","src/mach_sys.rs":"352560fcb9b41d877cff92e5b3b04d6dc68b1f30508ce4b9aed78940120a883e","src/rt_linux.rs":"e77db6d66fb76772dde6c12850ef2c50730cfe00decc0c67211e63f3e8a59c9f","src/rt_mach.rs":"29f8c0397f14cecbac1f76394c2abfe0e05903b54486cf735f9a94a10c168643","src/rt_win.rs":"0c85d771c544e4bb2b8fb60bdffe5a3080c112896f42360eb92cd3b85c010ca0"},"package":"e3632611da7e79f8fc8fd75840f1ccfa7792dbf1e25d00791344a4450dd8834f"} \ No newline at end of file
diff --git a/third_party/rust/audio_thread_priority/Cargo.toml b/third_party/rust/audio_thread_priority/Cargo.toml
index 30861cdafd..e1fa1a342f 100644
--- a/third_party/rust/audio_thread_priority/Cargo.toml
+++ b/third_party/rust/audio_thread_priority/Cargo.toml
@@ -12,7 +12,7 @@
[package]
edition = "2018"
name = "audio_thread_priority"
-version = "0.31.0"
+version = "0.32.0"
authors = ["Paul Adenot <paul@paul.cx>"]
description = "Bump a thread to real-time priority, for audio work, on Linux, Windows and macOS"
readme = "README.md"
@@ -61,5 +61,5 @@ version = "0.3"
version = "0.52"
features = [
"Win32_Foundation",
- "Win32_System_Threading",
+ "Win32_System_LibraryLoader",
]
diff --git a/third_party/rust/audio_thread_priority/src/lib.rs b/third_party/rust/audio_thread_priority/src/lib.rs
index eaae33d9d1..6006f7966e 100644
--- a/third_party/rust/audio_thread_priority/src/lib.rs
+++ b/third_party/rust/audio_thread_priority/src/lib.rs
@@ -647,6 +647,15 @@ mod tests {
// automatically deallocated, but not demoted until the thread exits.
}
}
+
+ #[test]
+ fn it_works_in_different_threads() {
+ let handles: Vec<_> = (0..32).map(|_| std::thread::spawn(it_works)).collect();
+ for handle in handles {
+ handle.join().unwrap()
+ }
+ }
+
cfg_if! {
if #[cfg(target_os = "linux")] {
use nix::unistd::*;
diff --git a/third_party/rust/audio_thread_priority/src/rt_win.rs b/third_party/rust/audio_thread_priority/src/rt_win.rs
index 3a2bbf29d9..480c5bd75c 100644
--- a/third_party/rust/audio_thread_priority/src/rt_win.rs
+++ b/third_party/rust/audio_thread_priority/src/rt_win.rs
@@ -1,14 +1,11 @@
-use windows_sys::s;
-use windows_sys::Win32::Foundation::GetLastError;
-use windows_sys::Win32::Foundation::FALSE;
-use windows_sys::Win32::Foundation::HANDLE;
-use windows_sys::Win32::System::Threading::{
- AvRevertMmThreadCharacteristics, AvSetMmThreadCharacteristicsA,
-};
-
+use self::avrt_lib::AvRtLibrary;
use crate::AudioThreadPriorityError;
-
use log::info;
+use std::sync::OnceLock;
+use windows_sys::{
+ w,
+ Win32::Foundation::{HANDLE, WIN32_ERROR},
+};
#[derive(Debug)]
pub struct RtPriorityHandleInternal {
@@ -17,7 +14,7 @@ pub struct RtPriorityHandleInternal {
}
impl RtPriorityHandleInternal {
- pub fn new(mmcss_task_index: u32, task_handle: HANDLE) -> RtPriorityHandleInternal {
+ fn new(mmcss_task_index: u32, task_handle: HANDLE) -> RtPriorityHandleInternal {
RtPriorityHandleInternal {
mmcss_task_index,
task_handle,
@@ -25,45 +22,205 @@ impl RtPriorityHandleInternal {
}
}
+fn avrt() -> Result<&'static AvRtLibrary, AudioThreadPriorityError> {
+ static AV_RT_LIBRARY: OnceLock<Result<AvRtLibrary, WIN32_ERROR>> = OnceLock::new();
+ AV_RT_LIBRARY
+ .get_or_init(AvRtLibrary::try_new)
+ .as_ref()
+ .map_err(|win32_error| {
+ AudioThreadPriorityError::new(&format!("Unable to load avrt.dll ({win32_error})"))
+ })
+}
+
+pub fn promote_current_thread_to_real_time_internal(
+ _audio_buffer_frames: u32,
+ _audio_samplerate_hz: u32,
+) -> Result<RtPriorityHandleInternal, AudioThreadPriorityError> {
+ avrt()?
+ .set_mm_thread_characteristics(w!("Audio"))
+ .map(|(mmcss_task_index, task_handle)| {
+ info!("task {mmcss_task_index} bumped to real time priority.");
+ RtPriorityHandleInternal::new(mmcss_task_index, task_handle)
+ })
+ .map_err(|win32_error| {
+ AudioThreadPriorityError::new(&format!(
+ "Unable to bump the thread priority ({win32_error})"
+ ))
+ })
+}
+
pub fn demote_current_thread_from_real_time_internal(
rt_priority_handle: RtPriorityHandleInternal,
) -> Result<(), AudioThreadPriorityError> {
- let rv = unsafe { AvRevertMmThreadCharacteristics(rt_priority_handle.task_handle) };
- if rv == FALSE {
- return Err(AudioThreadPriorityError::new(&format!(
- "Unable to restore the thread priority ({:?})",
- unsafe { GetLastError() }
- )));
+ let RtPriorityHandleInternal {
+ mmcss_task_index,
+ task_handle,
+ } = rt_priority_handle;
+ avrt()?
+ .revert_mm_thread_characteristics(task_handle)
+ .map(|_| {
+ info!("task {mmcss_task_index} priority restored.");
+ })
+ .map_err(|win32_error| {
+ AudioThreadPriorityError::new(&format!(
+ "Unable to restore the thread priority for task {mmcss_task_index} ({win32_error})"
+ ))
+ })
+}
+
+mod avrt_lib {
+ use super::win32_utils::{win32_error_if, OwnedLibrary};
+ use std::sync::Once;
+ use windows_sys::{
+ core::PCWSTR,
+ s, w,
+ Win32::Foundation::{BOOL, FALSE, HANDLE, WIN32_ERROR},
+ };
+
+ type AvSetMmThreadCharacteristicsWFn = unsafe extern "system" fn(PCWSTR, *mut u32) -> HANDLE;
+ type AvRevertMmThreadCharacteristicsFn = unsafe extern "system" fn(HANDLE) -> BOOL;
+
+ #[derive(Debug)]
+ pub(super) struct AvRtLibrary {
+ // This field is never read because only used for its Drop behavior
+ #[allow(dead_code)]
+ module: OwnedLibrary,
+
+ av_set_mm_thread_characteristics_w: AvSetMmThreadCharacteristicsWFn,
+ av_revert_mm_thread_characteristics: AvRevertMmThreadCharacteristicsFn,
}
- info!(
- "task {} priority restored.",
- rt_priority_handle.mmcss_task_index
- );
+ impl AvRtLibrary {
+ pub(super) fn try_new() -> Result<Self, WIN32_ERROR> {
+ let module = OwnedLibrary::try_new(w!("avrt.dll"))?;
+ let av_set_mm_thread_characteristics_w = unsafe {
+ std::mem::transmute::<_, AvSetMmThreadCharacteristicsWFn>(
+ module.get_proc(s!("AvSetMmThreadCharacteristicsW"))?,
+ )
+ };
+ let av_revert_mm_thread_characteristics = unsafe {
+ std::mem::transmute::<_, AvRevertMmThreadCharacteristicsFn>(
+ module.get_proc(s!("AvRevertMmThreadCharacteristics"))?,
+ )
+ };
+ Ok(Self {
+ module,
+ av_set_mm_thread_characteristics_w,
+ av_revert_mm_thread_characteristics,
+ })
+ }
+
+ pub(super) fn set_mm_thread_characteristics(
+ &self,
+ task_name: PCWSTR,
+ ) -> Result<(u32, HANDLE), WIN32_ERROR> {
+ // Ensure that the first call never runs in parallel with other calls. This
+ // seems necessary to guarantee the success of these other calls. We saw them
+ // fail with an error code of ERROR_PATH_NOT_FOUND in tests, presumably on a
+ // machine where the MMCSS service was initially inactive.
+ static FIRST_CALL: Once = Once::new();
+ let mut first_call_result = None;
+ FIRST_CALL.call_once(|| {
+ first_call_result = Some(self.set_mm_thread_characteristics_internal(task_name))
+ });
+ first_call_result
+ .unwrap_or_else(|| self.set_mm_thread_characteristics_internal(task_name))
+ }
+
+ fn set_mm_thread_characteristics_internal(
+ &self,
+ task_name: PCWSTR,
+ ) -> Result<(u32, HANDLE), WIN32_ERROR> {
+ let mut mmcss_task_index = 0u32;
+ let task_handle = unsafe {
+ (self.av_set_mm_thread_characteristics_w)(task_name, &mut mmcss_task_index)
+ };
+ win32_error_if(task_handle == 0)?;
+ Ok((mmcss_task_index, task_handle))
+ }
- Ok(())
+ pub(super) fn revert_mm_thread_characteristics(
+ &self,
+ handle: HANDLE,
+ ) -> Result<(), WIN32_ERROR> {
+ let rv = unsafe { (self.av_revert_mm_thread_characteristics)(handle) };
+ win32_error_if(rv == FALSE)
+ }
+ }
}
-pub fn promote_current_thread_to_real_time_internal(
- _audio_buffer_frames: u32,
- _audio_samplerate_hz: u32,
-) -> Result<RtPriorityHandleInternal, AudioThreadPriorityError> {
- let mut task_index = 0u32;
+mod win32_utils {
+ use windows_sys::{
+ core::{PCSTR, PCWSTR},
+ Win32::{
+ Foundation::{FreeLibrary, GetLastError, HMODULE, WIN32_ERROR},
+ System::LibraryLoader::{GetProcAddress, LoadLibraryW},
+ },
+ };
+
+ pub(super) fn win32_error_if(condition: bool) -> Result<(), WIN32_ERROR> {
+ if condition {
+ Err(unsafe { GetLastError() })
+ } else {
+ Ok(())
+ }
+ }
+
+ #[derive(Debug)]
+ pub(super) struct OwnedLibrary(HMODULE);
+
+ impl OwnedLibrary {
+ pub(super) fn try_new(lib_file_name: PCWSTR) -> Result<Self, WIN32_ERROR> {
+ let module = unsafe { LoadLibraryW(lib_file_name) };
+ win32_error_if(module == 0)?;
+ Ok(Self(module))
+ }
- let handle = unsafe { AvSetMmThreadCharacteristicsA(s!("Audio"), &mut task_index) };
- let handle = RtPriorityHandleInternal::new(task_index, handle);
+ fn raw(&self) -> HMODULE {
+ self.0
+ }
- if handle.task_handle == 0 {
- return Err(AudioThreadPriorityError::new(&format!(
- "Unable to restore the thread priority ({:?})",
- unsafe { GetLastError() }
- )));
+ /// SAFETY: The caller must transmute the value wrapped in a Ok(_) to the correct
+ /// function type, with the correct extern specifier.
+ pub(super) unsafe fn get_proc(
+ &self,
+ proc_name: PCSTR,
+ ) -> Result<unsafe extern "system" fn() -> isize, WIN32_ERROR> {
+ let proc = unsafe { GetProcAddress(self.raw(), proc_name) };
+ win32_error_if(proc.is_none())?;
+ Ok(proc.unwrap())
+ }
}
- info!(
- "task {} bumped to real time priority.",
- handle.mmcss_task_index
- );
+ impl Drop for OwnedLibrary {
+ fn drop(&mut self) {
+ unsafe {
+ FreeLibrary(self.raw());
+ }
+ }
+ }
+}
- Ok(handle)
+#[cfg(test)]
+mod tests {
+ use super::{
+ avrt, demote_current_thread_from_real_time_internal,
+ promote_current_thread_to_real_time_internal,
+ };
+
+ #[test]
+ fn test_successful_avrt_library_load() {
+ assert!(avrt().is_ok())
+ }
+
+ #[test]
+ fn test_successful_api_use() {
+ let handle = promote_current_thread_to_real_time_internal(512, 44100);
+ println!("handle: {handle:?}");
+ assert!(handle.is_ok());
+
+ let result = demote_current_thread_from_real_time_internal(handle.unwrap());
+ println!("result: {result:?}");
+ assert!(result.is_ok());
+ }
}
diff --git a/third_party/rust/audioipc2-client/.cargo-checksum.json b/third_party/rust/audioipc2-client/.cargo-checksum.json
index 00f6ff0c0e..c524aec67f 100644
--- a/third_party/rust/audioipc2-client/.cargo-checksum.json
+++ b/third_party/rust/audioipc2-client/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"f4852af58bcfd24732469ffbdddfc9404bc0d8c1d2cef279f6a6b6ddd3a080a8","cbindgen.toml":"fb6abe1671497f432a06e40b1db7ed7cd2cceecbd9a2382193ad7534e8855e34","src/context.rs":"a0559e92b554ef3156ab2bf2f1424555c8ef4a7977b9f43ac8500a9f399f8d99","src/lib.rs":"c87d9d57a16a9286cde730978db692df0fbc70cc69dd4f4677198d6843031fd8","src/send_recv.rs":"859abe75b521eb4297c84b30423814b5b87f3c7741ad16fe72189212e123e1ac","src/stream.rs":"90dc6a85552f3569ab1847de4247a46bcff2f5aef0c4d43fa2376589df015b25"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"e94c46bbd290f02adccc7ae932285416d7e021bfde80abb2fb31a2c05426e732","cbindgen.toml":"fb6abe1671497f432a06e40b1db7ed7cd2cceecbd9a2382193ad7534e8855e34","src/context.rs":"a0559e92b554ef3156ab2bf2f1424555c8ef4a7977b9f43ac8500a9f399f8d99","src/lib.rs":"c87d9d57a16a9286cde730978db692df0fbc70cc69dd4f4677198d6843031fd8","src/send_recv.rs":"859abe75b521eb4297c84b30423814b5b87f3c7741ad16fe72189212e123e1ac","src/stream.rs":"90dc6a85552f3569ab1847de4247a46bcff2f5aef0c4d43fa2376589df015b25"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/audioipc2-client/Cargo.toml b/third_party/rust/audioipc2-client/Cargo.toml
index 2002062c27..b776fb47e0 100644
--- a/third_party/rust/audioipc2-client/Cargo.toml
+++ b/third_party/rust/audioipc2-client/Cargo.toml
@@ -25,7 +25,7 @@ cubeb-backend = "0.12"
log = "0.4"
[dependencies.audio_thread_priority]
-version = "0.31"
+version = "0.32"
default-features = false
[dependencies.audioipc]
diff --git a/third_party/rust/audioipc2-server/.cargo-checksum.json b/third_party/rust/audioipc2-server/.cargo-checksum.json
index 907a296910..092d47fc83 100644
--- a/third_party/rust/audioipc2-server/.cargo-checksum.json
+++ b/third_party/rust/audioipc2-server/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"03e2fd801658feeb4bc399194a001acd1f4880967bc2993ef3f1ef20d5545bb9","cbindgen.toml":"fb6abe1671497f432a06e40b1db7ed7cd2cceecbd9a2382193ad7534e8855e34","src/lib.rs":"06aff4fd1326aeabb16b01f81a6f3c59c1717ebe96285a063724830cdf30303a","src/server.rs":"31e0c763527a16dfc281399e8a6c9eb8c1bac71ab9d7866288ec87297f4316e0"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"77997660e305851d9c0e656aac7159b999452a36f3436d8b2f402edd36fef853","cbindgen.toml":"fb6abe1671497f432a06e40b1db7ed7cd2cceecbd9a2382193ad7534e8855e34","src/lib.rs":"d70079c66de72c3469504f1f0c9cf5e510644cac17f2d8300b8d12218740e07b","src/server.rs":"187e2236aa9f2fb6cc4a533d40714a71504afa5ef9d849ac28b7f26032859c29"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/audioipc2-server/Cargo.toml b/third_party/rust/audioipc2-server/Cargo.toml
index 26c5d8bdc8..e5436e4e6a 100644
--- a/third_party/rust/audioipc2-server/Cargo.toml
+++ b/third_party/rust/audioipc2-server/Cargo.toml
@@ -27,7 +27,7 @@ once_cell = "1.2.0"
slab = "0.4"
[dependencies.audio_thread_priority]
-version = "0.31"
+version = "0.32"
default-features = false
[dependencies.audioipc]
diff --git a/third_party/rust/audioipc2-server/src/lib.rs b/third_party/rust/audioipc2-server/src/lib.rs
index c90de38c66..05396e8e29 100644
--- a/third_party/rust/audioipc2-server/src/lib.rs
+++ b/third_party/rust/audioipc2-server/src/lib.rs
@@ -35,8 +35,8 @@ static G_CUBEB_CONTEXT_PARAMS: Lazy<Mutex<CubebContextParams>> = Lazy::new(|| {
});
#[allow(deprecated)]
+#[allow(clippy::upper_case_acronyms)]
pub mod errors {
- #![allow(clippy::upper_case_acronyms)]
error_chain! {
links {
AudioIPC(::audioipc::errors::Error, ::audioipc::errors::ErrorKind);
@@ -159,7 +159,7 @@ pub unsafe extern "C" fn audioipc2_server_start(
assert!(!init_params.is_null());
let mut params = G_CUBEB_CONTEXT_PARAMS.lock().unwrap();
if !context_name.is_null() {
- params.context_name = CStr::from_ptr(context_name).to_owned();
+ CStr::from_ptr(context_name).clone_into(&mut params.context_name);
}
if !backend_name.is_null() {
let backend_string = CStr::from_ptr(backend_name).to_owned();
diff --git a/third_party/rust/audioipc2-server/src/server.rs b/third_party/rust/audioipc2-server/src/server.rs
index 256540a6fd..96a8fc3883 100644
--- a/third_party/rust/audioipc2-server/src/server.rs
+++ b/third_party/rust/audioipc2-server/src/server.rs
@@ -16,7 +16,7 @@ use audioipc::{ipccore, rpccore, sys, PlatformHandle};
use cubeb::InputProcessingParams;
use cubeb_core as cubeb;
use cubeb_core::ffi;
-use std::convert::{From, TryInto};
+use std::convert::TryInto;
use std::ffi::CStr;
use std::mem::size_of;
use std::os::raw::{c_long, c_void};
diff --git a/third_party/rust/audioipc2/.cargo-checksum.json b/third_party/rust/audioipc2/.cargo-checksum.json
index f7e319830e..92cc622efe 100644
--- a/third_party/rust/audioipc2/.cargo-checksum.json
+++ b/third_party/rust/audioipc2/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"4cdefda1488b3ec6e65d11f02d228b1ccd5e190a802d792e524df99450de4832","benches/serialization.rs":"d56855d868dab6aa22c8b03a61084535351b76c94b68d8b1d20764e352fe473f","build.rs":"65df9a97c6cdaa3faf72581f04ac289197b0b1797d69d22c1796e957ff1089e2","src/codec.rs":"38408b512d935cd7889a03b25dd14b36083ec4e6d2fcabd636182cf45e3d50bc","src/errors.rs":"67a4a994d0724397657581cde153bdfc05ce86e7efc467f23fafc8f64df80fa4","src/ipccore.rs":"db73e916468c54d3497d75ffcab3bf23067771ed7b2e1a23c714429f56f59ec3","src/lib.rs":"a6fcac8b44318435db60313d3ef32ff3fada390bea8978c8414c40744998b98b","src/messages.rs":"f1d5568c3095ee9f557412c2f4c217abfa5696a5035e4f7cb2dbc8cce700f1c4","src/rpccore.rs":"025b6614f1c42b96b0a8e74fd7881032d338c66e0d67ec0af70f910a9e30ebe1","src/shm.rs":"c00d16f4af510d12e704ae865f7348ad64ddef180e42b18e7dd95c4be35a9c80","src/sys/mod.rs":"e6fa1d260abf093e1f7b50185195e2d3aee0eb8c9774c6f253953b5896d838f3","src/sys/unix/cmsg.rs":"6f0236bf6cd66ccd237b268348a826ae1d266073c2adadcfaae703c556230065","src/sys/unix/cmsghdr.c":"d7344b3dc15cdce410c68669b848bb81f7fe36362cd3699668cb613fa05180f8","src/sys/unix/mod.rs":"59835f0d5509940078b1820a54f49fc5514adeb3e45e7d21e3ab917431da2e74","src/sys/unix/msg.rs":"0e297d73bae9414184f85c2209cca0a3fde6d999a3f1d3f42faa3f56b6d57233","src/sys/windows/mod.rs":"7eaabb76e62c6962b636320e2bbf79a78fce61659c799a798f7dd6d56b0be8a1"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"dff67ba050be15275d357b7f001df6caa6ae1f93b2acd61ac7d6ee269a1f5960","benches/serialization.rs":"d56855d868dab6aa22c8b03a61084535351b76c94b68d8b1d20764e352fe473f","build.rs":"65df9a97c6cdaa3faf72581f04ac289197b0b1797d69d22c1796e957ff1089e2","src/codec.rs":"86068272e220696d8d7e369072326349e7598e5a24223d98179c3251bb7b3ff1","src/errors.rs":"67a4a994d0724397657581cde153bdfc05ce86e7efc467f23fafc8f64df80fa4","src/ipccore.rs":"db73e916468c54d3497d75ffcab3bf23067771ed7b2e1a23c714429f56f59ec3","src/lib.rs":"a6fcac8b44318435db60313d3ef32ff3fada390bea8978c8414c40744998b98b","src/messages.rs":"d4f6d4f41b7fd3cc7deae726657e1100f315f4cd10c5fe6ce8a57c03c8e26ca9","src/rpccore.rs":"025b6614f1c42b96b0a8e74fd7881032d338c66e0d67ec0af70f910a9e30ebe1","src/shm.rs":"c00d16f4af510d12e704ae865f7348ad64ddef180e42b18e7dd95c4be35a9c80","src/sys/mod.rs":"e6fa1d260abf093e1f7b50185195e2d3aee0eb8c9774c6f253953b5896d838f3","src/sys/unix/cmsg.rs":"9529e8f8429db86f7c5df132953d3054e603852270f3c6938cdb5f630b2711f1","src/sys/unix/cmsghdr.c":"d7344b3dc15cdce410c68669b848bb81f7fe36362cd3699668cb613fa05180f8","src/sys/unix/mod.rs":"59835f0d5509940078b1820a54f49fc5514adeb3e45e7d21e3ab917431da2e74","src/sys/unix/msg.rs":"0e297d73bae9414184f85c2209cca0a3fde6d999a3f1d3f42faa3f56b6d57233","src/sys/windows/mod.rs":"7eaabb76e62c6962b636320e2bbf79a78fce61659c799a798f7dd6d56b0be8a1"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/audioipc2/Cargo.toml b/third_party/rust/audioipc2/Cargo.toml
index d6b66fd4e7..62be642df5 100644
--- a/third_party/rust/audioipc2/Cargo.toml
+++ b/third_party/rust/audioipc2/Cargo.toml
@@ -63,7 +63,7 @@ cc = "1.0"
ashmem = "0.1.2"
[target."cfg(target_os = \"linux\")".dependencies.audio_thread_priority]
-version = "0.31"
+version = "0.32"
default-features = false
[target."cfg(unix)".dependencies]
diff --git a/third_party/rust/audioipc2/src/codec.rs b/third_party/rust/audioipc2/src/codec.rs
index 2c61faa6ea..eb172fbc51 100644
--- a/third_party/rust/audioipc2/src/codec.rs
+++ b/third_party/rust/audioipc2/src/codec.rs
@@ -10,7 +10,7 @@
// This should be fixed in Rust 1.68, after which the following `allow` can be deleted.
#![allow(clippy::uninlined_format_args)]
-use bincode::{self, Options};
+use bincode::Options;
use byteorder::{ByteOrder, LittleEndian};
use bytes::{Buf, BufMut, BytesMut};
use serde::de::DeserializeOwned;
diff --git a/third_party/rust/audioipc2/src/messages.rs b/third_party/rust/audioipc2/src/messages.rs
index 9ba9d8df3c..ef99bd4149 100644
--- a/third_party/rust/audioipc2/src/messages.rs
+++ b/third_party/rust/audioipc2/src/messages.rs
@@ -8,7 +8,7 @@ use crate::PlatformHandleType;
use crate::INVALID_HANDLE_VALUE;
#[cfg(target_os = "linux")]
use audio_thread_priority::RtPriorityThreadInfo;
-use cubeb::{self, ffi};
+use cubeb::ffi;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use std::ffi::{CStr, CString};
diff --git a/third_party/rust/audioipc2/src/sys/unix/cmsg.rs b/third_party/rust/audioipc2/src/sys/unix/cmsg.rs
index 88a133772b..48f202957c 100644
--- a/third_party/rust/audioipc2/src/sys/unix/cmsg.rs
+++ b/third_party/rust/audioipc2/src/sys/unix/cmsg.rs
@@ -5,7 +5,7 @@
use crate::sys::HANDLE_QUEUE_LIMIT;
use bytes::{BufMut, BytesMut};
-use libc::{self, cmsghdr};
+use libc::cmsghdr;
use std::convert::TryInto;
use std::os::unix::io::RawFd;
use std::{mem, slice};
diff --git a/third_party/rust/bumpalo/.cargo-checksum.json b/third_party/rust/bumpalo/.cargo-checksum.json
index d747ed0ac6..c9c971e9d8 100644
--- a/third_party/rust/bumpalo/.cargo-checksum.json
+++ b/third_party/rust/bumpalo/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"CHANGELOG.md":"8b5a7a49c720ba2678c07184f50b3608e2165fbf6704da494fba23c864e691e0","Cargo.toml":"8d5fd21d2b3ed1d7149e864d43f843fd469ccdcd9893ac3c2bef8518294a61dd","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee","README.md":"00c9224790248ec71d1505615429699fd685b0290a0c2b6d7c0df0214e7f80eb","src/alloc.rs":"ab0f23fa11c26efdd8f0596ebdf0e3faa75d097881fb59639b0fb23340c106bc","src/boxed.rs":"5fc935f8e1a7bc1b8f6a39b2bcc4355a2be4743f2308fe3ffd557455a3a27cb2","src/collections/collect_in.rs":"0588a4ff3967a4323abb4218bbd615af4b123639ab4fae9130c6590c258b3d15","src/collections/mod.rs":"d58dc46eb4f9fcdde574f09bc5b8646f53e42d49c169561d98e0c23e5b36848a","src/collections/raw_vec.rs":"8829cc9a693fde38aa93e47a7bbbc2dac247620d07f60519f2e6cb44f5494bc5","src/collections/str/lossy.rs":"c5d62b16e01071e2a574ae41ef6693ad12f1e6c786c5d38f7a13ebd6cb23c088","src/collections/str/mod.rs":"d82a8bd417fbf52a589d89a16ea2a0ac4f6ac920c3976ab1f5b6ac0c8493c4f2","src/collections/string.rs":"388d39b999788baf5c14ccc3f5cb57da728060ea3295ddfc28f0f2e1ca5858ec","src/collections/vec.rs":"2eaf52e085e6d04767e97b224e82688dd0debd231c6536d6034f431376aa8bf0","src/lib.rs":"9eb2bdb8359b368a6f3091a66b3a5eb1216672ec1605cb18d5da28292c381cb9"},"package":"0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"} \ No newline at end of file
+{"files":{"CHANGELOG.md":"78962e256666b2ca4b3ad5393da51da94decbc465e4d283a882ffdb0400973b8","Cargo.toml":"480d1eff4ff1840deaedf5670ff0cec6d5cfad8e818545072942a0a72ddba8c0","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"65f94e99ddaf4f5d1782a6dae23f35d4293a9a01444a13135a6887017d353cee","README.md":"19edaf495926291be237a1d8f958dd736940a2bbb75181ffefaeca3d2ce81046","src/alloc.rs":"3a9645d9e8db1f2a8549ee928cafa5263a828f25c88ce4d2b07996ecc14bfa81","src/boxed.rs":"5fc935f8e1a7bc1b8f6a39b2bcc4355a2be4743f2308fe3ffd557455a3a27cb2","src/collections/collect_in.rs":"0588a4ff3967a4323abb4218bbd615af4b123639ab4fae9130c6590c258b3d15","src/collections/mod.rs":"d58dc46eb4f9fcdde574f09bc5b8646f53e42d49c169561d98e0c23e5b36848a","src/collections/raw_vec.rs":"a37069763ff1434bb12356318d0a00cc25a273f0c2fc0bfea35615785808d1c6","src/collections/str/lossy.rs":"c5d62b16e01071e2a574ae41ef6693ad12f1e6c786c5d38f7a13ebd6cb23c088","src/collections/str/mod.rs":"d82a8bd417fbf52a589d89a16ea2a0ac4f6ac920c3976ab1f5b6ac0c8493c4f2","src/collections/string.rs":"39b2a94b552a82066fa4996d65d1dea4073e2a6724b5c237d530ec46e16bc222","src/collections/vec.rs":"a224894cd743954a90f275a5f19e7127414694bfa1d49c4647ebf789aab1721a","src/lib.rs":"c71735f5eac817d378fa47d9013056cb9feb55d15eb8247e50607bfb4ea4cdbd"},"package":"7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"} \ No newline at end of file
diff --git a/third_party/rust/bumpalo/CHANGELOG.md b/third_party/rust/bumpalo/CHANGELOG.md
index afc142eb90..3f9e366032 100644
--- a/third_party/rust/bumpalo/CHANGELOG.md
+++ b/third_party/rust/bumpalo/CHANGELOG.md
@@ -28,6 +28,126 @@ Released YYYY-MM-DD.
--------------------------------------------------------------------------------
+## 3.15.4
+
+Released 2024-03-07.
+
+### Added
+
+* Added the `bumpalo::collections::Vec::extend_from_slices_copy` method, which
+ is a faster way to extend a vec from multiple slices when the element is
+ `Copy` than calling `extend_from_slice_copy` N times.
+
+--------------------------------------------------------------------------------
+
+## 3.15.3
+
+Released 2024-02-22.
+
+### Added
+
+* Added additional performance improvements to `bumpalo::collections::Vec`
+ related to reserving capacity.
+
+--------------------------------------------------------------------------------
+
+## 3.15.2
+
+Released 2024-02-21.
+
+### Added
+
+* Add a `bumpalo::collections::Vec::extend_from_slice_copy` method. This doesn't
+ exist on the standard library's `Vec` but they have access to specialization,
+ so their regular `extend_from_slice` has a specialization for `Copy`
+ types. Using this new method for `Copy` types is a ~80x performance
+ improvement over the plain `extend_from_slice` method.
+
+--------------------------------------------------------------------------------
+
+## 3.15.1
+
+Released 2024-02-20.
+
+### Fixed
+
+* Fixed the MSRV listed in `Cargo.toml`, whose update was forgotten when the
+ MSRV bumped in release 3.15.0.
+
+--------------------------------------------------------------------------------
+
+## 3.15.0
+
+Released 2024-02-15.
+
+### Changed
+
+* The minimum supported Rust version (MSRV) is now 1.73.0.
+* `bumpalo::collections::String::push_str` and
+ `bumpalo::collections::String::from_str_in` received significant performance
+ improvements.
+* Allocator trait methods are now marked `#[inline]`, increasing performance for
+ some callers.
+
+### Fixed
+
+* Fixed an edge-case bug in the `Allocator::shrink` method.
+
+--------------------------------------------------------------------------------
+
+## 3.14.0
+
+Released 2023-09-14.
+
+### Added
+
+* Added the `std` cargo feature, which enables implementations of `std` traits
+ for various things. Right now that is just `std::io::Write` for
+ `bumpalo::collections::Vec`, but could be more in the future.
+
+--------------------------------------------------------------------------------
+
+## 3.13.0
+
+Released 2023-05-22.
+
+### Added
+
+* New `"allocator-api2"` feature enables the use of the allocator API on
+ stable. This feature uses a crate that mirrors the API of the unstable Rust
+ `allocator_api` feature. If the feature is enabled, references to `Bump` will
+ implement `allocator_api2::Allocator`. This allows `Bump` to be used as an
+ allocator for collection types from `allocator-api2` and any other crates that
+ support `allocator-api2`.
+
+### Changed
+
+* The minimum supported Rust version (MSRV) is now 1.63.0.
+
+--------------------------------------------------------------------------------
+
+## 3.12.2
+
+Released 2023-05-09.
+
+### Changed
+
+* Added `rust-version` metadata to `Cargo.toml` which helps `cargo` with version
+ resolution.
+
+--------------------------------------------------------------------------------
+
+## 3.12.1
+
+Released 2023-04-21.
+
+### Fixed
+
+* Fixed a bug where `Bump::try_with_capacity(n)` where `n > isize::MAX` could
+ lead to attempts to create invalid `Layout`s.
+
+--------------------------------------------------------------------------------
+
## 3.12.0
Released 2023-01-17.
@@ -489,7 +609,7 @@ Released 2019-12-20.
from the allocated chunks are slightly different from the old
`each_allocated_chunk`: only up to 16-byte alignment is supported now. If you
allocate anything with greater alignment than that into the bump arena, there
- might be uninitilized padding inserted in the chunks, and therefore it is no
+ might be uninitialized padding inserted in the chunks, and therefore it is no
longer safe to read them via `MaybeUninit::assume_init`. See also the note
about bump direction in the "changed" section; if you're iterating chunks,
you're likely affected by that change!
@@ -528,7 +648,7 @@ Released 2019-05-20.
* Fixed a bug where chunks were always deallocated with the default chunk
layout, not the layout that the chunk was actually allocated with (i.e. if we
- started growing largers chunks with larger layouts, we would deallocate those
+ started growing larger chunks with larger layouts, we would deallocate those
chunks with an incorrect layout).
--------------------------------------------------------------------------------
diff --git a/third_party/rust/bumpalo/Cargo.toml b/third_party/rust/bumpalo/Cargo.toml
index 02ec679c2b..53fc06bb97 100644
--- a/third_party/rust/bumpalo/Cargo.toml
+++ b/third_party/rust/bumpalo/Cargo.toml
@@ -11,8 +11,9 @@
[package]
edition = "2021"
+rust-version = "1.73.0"
name = "bumpalo"
-version = "3.12.0"
+version = "3.15.4"
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
exclude = [
"/.github/*",
@@ -29,7 +30,7 @@ categories = [
"rust-patterns",
"no-std",
]
-license = "MIT/Apache-2.0"
+license = "MIT OR Apache-2.0"
repository = "https://github.com/fitzgen/bumpalo"
[package.metadata.docs.rs]
@@ -50,6 +51,11 @@ path = "benches/benches.rs"
harness = false
required-features = ["collections"]
+[dependencies.allocator-api2]
+version = "0.2.8"
+optional = true
+default-features = false
+
[dev-dependencies.criterion]
version = "0.3.6"
@@ -64,3 +70,4 @@ allocator_api = []
boxed = []
collections = []
default = []
+std = []
diff --git a/third_party/rust/bumpalo/README.md b/third_party/rust/bumpalo/README.md
index 3d73e2967e..d0da14c87d 100644
--- a/third_party/rust/bumpalo/README.md
+++ b/third_party/rust/bumpalo/README.md
@@ -155,7 +155,14 @@ in its space itself.
### `#![no_std]` Support
-Bumpalo is a `no_std` crate. It depends only on the `alloc` and `core` crates.
+Bumpalo is a `no_std` crate by default. It depends only on the `alloc` and `core` crates.
+
+### `std` Support
+
+You can optionally decide to enable the `std` feature in order to enable some
+std only trait implementations for some collections:
+
+* `std::io::Write` for `Vec<'bump, u8>`
### Thread support
@@ -179,7 +186,7 @@ First, enable the `allocator_api` feature in your `Cargo.toml`:
```toml
[dependencies]
-bumpalo = { version = "3.9", features = ["allocator_api"] }
+bumpalo = { version = "3", features = ["allocator_api"] }
```
Next, enable the `allocator_api` nightly Rust feature in your `src/lib.rs` or
@@ -207,9 +214,17 @@ v.push(2);
[`Allocator`]: https://doc.rust-lang.org/std/alloc/trait.Allocator.html
-#### Minimum Supported Rust Version (MSRV)
+### Using the `Allocator` API on Stable Rust
+
+You can enable the `allocator-api2` Cargo feature and `bumpalo` will use [the
+`allocator-api2` crate](https://crates.io/crates/allocator-api2) to implement
+the unstable nightly`Allocator` API on stable Rust. This means that
+`bumpalo::Bump` will be usable with any collection that is generic over
+`allocator_api2::Allocator`.
+
+### Minimum Supported Rust Version (MSRV)
-This crate is guaranteed to compile on stable Rust **1.56** and up. It might
+This crate is guaranteed to compile on stable Rust **1.73** and up. It might
compile with older versions but that may change in any new patch release.
We reserve the right to increment the MSRV on minor releases, however we will
diff --git a/third_party/rust/bumpalo/src/alloc.rs b/third_party/rust/bumpalo/src/alloc.rs
index 0bcc21f22c..6947e2a6cf 100644
--- a/third_party/rust/bumpalo/src/alloc.rs
+++ b/third_party/rust/bumpalo/src/alloc.rs
@@ -752,7 +752,7 @@ pub unsafe trait Alloc {
match (Layout::array::<T>(n_old), Layout::array::<T>(n_new)) {
(Ok(ref k_old), Ok(ref k_new)) if k_old.size() > 0 && k_new.size() > 0 => {
debug_assert!(k_old.align() == k_new.align());
- self.realloc(ptr.cast(), k_old.clone(), k_new.size())
+ self.realloc(ptr.cast(), *k_old, k_new.size())
.map(NonNull::cast)
}
_ => Err(AllocErr),
diff --git a/third_party/rust/bumpalo/src/collections/raw_vec.rs b/third_party/rust/bumpalo/src/collections/raw_vec.rs
index ac3bd0758c..456829d447 100644
--- a/third_party/rust/bumpalo/src/collections/raw_vec.rs
+++ b/third_party/rust/bumpalo/src/collections/raw_vec.rs
@@ -319,7 +319,7 @@ impl<'a, T> RawVec<'a, T> {
used_cap: usize,
needed_extra_cap: usize,
) -> Result<(), CollectionAllocErr> {
- self.reserve_internal(used_cap, needed_extra_cap, Fallible, Exact)
+ self.fallible_reserve_internal(used_cap, needed_extra_cap, Exact)
}
/// Ensures that the buffer contains at least enough space to hold
@@ -343,11 +343,7 @@ impl<'a, T> RawVec<'a, T> {
///
/// Aborts on OOM
pub fn reserve_exact(&mut self, used_cap: usize, needed_extra_cap: usize) {
- match self.reserve_internal(used_cap, needed_extra_cap, Infallible, Exact) {
- Err(CapacityOverflow) => capacity_overflow(),
- Err(AllocErr) => unreachable!(),
- Ok(()) => { /* yay */ }
- }
+ self.infallible_reserve_internal(used_cap, needed_extra_cap, Exact)
}
/// Calculates the buffer's new size given that it'll hold `used_cap +
@@ -374,7 +370,7 @@ impl<'a, T> RawVec<'a, T> {
used_cap: usize,
needed_extra_cap: usize,
) -> Result<(), CollectionAllocErr> {
- self.reserve_internal(used_cap, needed_extra_cap, Fallible, Amortized)
+ self.fallible_reserve_internal(used_cap, needed_extra_cap, Amortized)
}
/// Ensures that the buffer contains at least enough space to hold
@@ -429,13 +425,11 @@ impl<'a, T> RawVec<'a, T> {
/// # vector.push_all(&[1, 3, 5, 7, 9]);
/// # }
/// ```
+ #[inline(always)]
pub fn reserve(&mut self, used_cap: usize, needed_extra_cap: usize) {
- match self.reserve_internal(used_cap, needed_extra_cap, Infallible, Amortized) {
- Err(CapacityOverflow) => capacity_overflow(),
- Err(AllocErr) => unreachable!(),
- Ok(()) => { /* yay */ }
- }
+ self.infallible_reserve_internal(used_cap, needed_extra_cap, Amortized)
}
+
/// Attempts to ensure that the buffer contains at least enough space to hold
/// `used_cap + needed_extra_cap` elements. If it doesn't already have
/// enough capacity, will reallocate in place enough space plus comfortable slack
@@ -593,6 +587,68 @@ enum ReserveStrategy {
use self::ReserveStrategy::*;
impl<'a, T> RawVec<'a, T> {
+ #[inline(always)]
+ fn fallible_reserve_internal(
+ &mut self,
+ used_cap: usize,
+ needed_extra_cap: usize,
+ strategy: ReserveStrategy,
+ ) -> Result<(), CollectionAllocErr> {
+ // This portion of the method should always be inlined.
+ if self.cap().wrapping_sub(used_cap) >= needed_extra_cap {
+ return Ok(());
+ }
+ // This portion of the method should never be inlined, and will only be called when
+ // the check above has confirmed that it is necessary.
+ self.reserve_internal_or_error(used_cap, needed_extra_cap, Fallible, strategy)
+ }
+
+ #[inline(always)]
+ fn infallible_reserve_internal(
+ &mut self,
+ used_cap: usize,
+ needed_extra_cap: usize,
+ strategy: ReserveStrategy,
+ ) {
+ // This portion of the method should always be inlined.
+ if self.cap().wrapping_sub(used_cap) >= needed_extra_cap {
+ return;
+ }
+ // This portion of the method should never be inlined, and will only be called when
+ // the check above has confirmed that it is necessary.
+ self.reserve_internal_or_panic(used_cap, needed_extra_cap, strategy)
+ }
+
+ #[inline(never)]
+ fn reserve_internal_or_panic(
+ &mut self,
+ used_cap: usize,
+ needed_extra_cap: usize,
+ strategy: ReserveStrategy,
+ ) {
+ // Delegates the call to `reserve_internal_or_error` and panics in the event of an error.
+ // This allows the method to have a return type of `()`, simplifying the assembly at the
+ // call site.
+ match self.reserve_internal(used_cap, needed_extra_cap, Infallible, strategy) {
+ Err(CapacityOverflow) => capacity_overflow(),
+ Err(AllocErr) => unreachable!(),
+ Ok(()) => { /* yay */ }
+ }
+ }
+
+ #[inline(never)]
+ fn reserve_internal_or_error(
+ &mut self,
+ used_cap: usize,
+ needed_extra_cap: usize,
+ fallibility: Fallibility,
+ strategy: ReserveStrategy,)-> Result<(), CollectionAllocErr> {
+ // Delegates the call to `reserve_internal`, which can be inlined.
+ self.reserve_internal(used_cap, needed_extra_cap, fallibility, strategy)
+ }
+
+ /// Helper method to reserve additional space, reallocating the backing memory.
+ /// The caller is responsible for confirming that there is not already enough space available.
fn reserve_internal(
&mut self,
used_cap: usize,
@@ -608,12 +664,6 @@ impl<'a, T> RawVec<'a, T> {
// If we make it past the first branch then we are guaranteed to
// panic.
- // Don't actually need any more capacity.
- // Wrapping in case they gave a bad `used_cap`.
- if self.cap().wrapping_sub(used_cap) >= needed_extra_cap {
- return Ok(());
- }
-
// Nothing we can really do about these checks :(
let new_cap = match strategy {
Exact => used_cap
diff --git a/third_party/rust/bumpalo/src/collections/string.rs b/third_party/rust/bumpalo/src/collections/string.rs
index ffd1db92de..e9fafbf204 100644
--- a/third_party/rust/bumpalo/src/collections/string.rs
+++ b/third_party/rust/bumpalo/src/collections/string.rs
@@ -680,8 +680,19 @@ impl<'bump> String<'bump> {
/// assert_eq!(s, "hello");
/// ```
pub fn from_str_in(s: &str, bump: &'bump Bump) -> String<'bump> {
- let mut t = String::with_capacity_in(s.len(), bump);
- t.push_str(s);
+ let len = s.len();
+ let mut t = String::with_capacity_in(len, bump);
+ // SAFETY:
+ // * `src` is valid for reads of `s.len()` bytes by virtue of being an allocated `&str`.
+ // * `dst` is valid for writes of `s.len()` bytes as `String::with_capacity_in(s.len(), bump)`
+ // above guarantees that.
+ // * Alignment is not relevant as `u8` has no alignment requirements.
+ // * Source and destination ranges cannot overlap as we just reserved the destination
+ // range from the bump.
+ unsafe { ptr::copy_nonoverlapping(s.as_ptr(), t.vec.as_mut_ptr(), len) };
+ // SAFETY: We reserved sufficent capacity for the string above.
+ // The elements at `0..len` were initialized by `copy_nonoverlapping` above.
+ unsafe { t.vec.set_len(len) };
t
}
@@ -925,7 +936,7 @@ impl<'bump> String<'bump> {
/// ```
#[inline]
pub fn push_str(&mut self, string: &str) {
- self.vec.extend_from_slice(string.as_bytes())
+ self.vec.extend_from_slice_copy(string.as_bytes())
}
/// Returns this `String`'s capacity, in bytes.
diff --git a/third_party/rust/bumpalo/src/collections/vec.rs b/third_party/rust/bumpalo/src/collections/vec.rs
index 312aa055b9..0dab700727 100644
--- a/third_party/rust/bumpalo/src/collections/vec.rs
+++ b/third_party/rust/bumpalo/src/collections/vec.rs
@@ -104,6 +104,8 @@ use core::ops::{Index, IndexMut, RangeBounds};
use core::ptr;
use core::ptr::NonNull;
use core::slice;
+#[cfg(feature = "std")]
+use std::io;
unsafe fn arith_offset<T>(p: *const T, offset: isize) -> *const T {
p.offset(offset)
@@ -1775,6 +1777,132 @@ impl<'bump, T: 'bump + Clone> Vec<'bump, T> {
}
}
+impl<'bump, T: 'bump + Copy> Vec<'bump, T> {
+ /// Helper method to copy all of the items in `other` and append them to the end of `self`.
+ ///
+ /// SAFETY:
+ /// * The caller is responsible for:
+ /// * calling [`reserve`](Self::reserve) beforehand to guarantee that there is enough
+ /// capacity to store `other.len()` more items.
+ /// * guaranteeing that `self` and `other` do not overlap.
+ unsafe fn extend_from_slice_copy_unchecked(&mut self, other: &[T]) {
+ let old_len = self.len();
+ debug_assert!(old_len + other.len() <= self.capacity());
+
+ // SAFETY:
+ // * `src` is valid for reads of `other.len()` values by virtue of being a `&[T]`.
+ // * `dst` is valid for writes of `other.len()` bytes because the caller of this
+ // method is required to `reserve` capacity to store at least `other.len()` items
+ // beforehand.
+ // * Because `src` is a `&[T]` and dst is a `&[T]` within the `Vec<T>`,
+ // `copy_nonoverlapping`'s alignment requirements are met.
+ // * Caller is required to guarantee that the source and destination ranges cannot overlap
+ unsafe {
+ let src = other.as_ptr();
+ let dst = self.as_mut_ptr().add(old_len);
+ ptr::copy_nonoverlapping(src, dst, other.len());
+ self.set_len(old_len + other.len());
+ }
+ }
+
+
+ /// Copies all elements in the slice `other` and appends them to the `Vec`.
+ ///
+ /// Note that this function is same as [`extend_from_slice`] except that it is optimized for
+ /// slices of types that implement the `Copy` trait. If and when Rust gets specialization
+ /// this function will likely be deprecated (but still available).
+ ///
+ /// To copy and append the data from multiple source slices at once, see
+ /// [`extend_from_slices_copy`].
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use bumpalo::{Bump, collections::Vec};
+ ///
+ /// let b = Bump::new();
+ ///
+ /// let mut vec = bumpalo::vec![in &b; 1];
+ /// vec.extend_from_slice_copy(&[2, 3, 4]);
+ /// assert_eq!(vec, [1, 2, 3, 4]);
+ /// ```
+ ///
+ /// ```
+ /// use bumpalo::{Bump, collections::Vec};
+ ///
+ /// let b = Bump::new();
+ ///
+ /// let mut vec = bumpalo::vec![in &b; 'H' as u8];
+ /// vec.extend_from_slice_copy("ello, world!".as_bytes());
+ /// assert_eq!(vec, "Hello, world!".as_bytes());
+ /// ```
+ ///
+ /// [`extend_from_slice`]: #method.extend_from_slice
+ /// [`extend_from_slices`]: #method.extend_from_slices
+ pub fn extend_from_slice_copy(&mut self, other: &[T]) {
+ // Reserve space in the Vec for the values to be added
+ self.reserve(other.len());
+
+ // Copy values into the space that was just reserved
+ // SAFETY:
+ // * `self` has enough capacity to store `other.len()` more items as `self.reserve(other.len())`
+ // above guarantees that.
+ // * Source and destination data ranges cannot overlap as we just reserved the destination
+ // range from the bump.
+ unsafe {
+ self.extend_from_slice_copy_unchecked(other);
+ }
+ }
+
+ /// For each slice in `slices`, copies all elements in the slice and appends them to the `Vec`.
+ ///
+ /// This method is equivalent to calling [`extend_from_slice_copy`] in a loop, but is able
+ /// to precompute the total amount of space to reserve in advance. This reduces the potential
+ /// maximum number of reallocations needed from one-per-slice to just one.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use bumpalo::{Bump, collections::Vec};
+ ///
+ /// let b = Bump::new();
+ ///
+ /// let mut vec = bumpalo::vec![in &b; 1];
+ /// vec.extend_from_slices_copy(&[&[2, 3], &[], &[4]]);
+ /// assert_eq!(vec, [1, 2, 3, 4]);
+ /// ```
+ ///
+ /// ```
+ /// use bumpalo::{Bump, collections::Vec};
+ ///
+ /// let b = Bump::new();
+ ///
+ /// let mut vec = bumpalo::vec![in &b; 'H' as u8];
+ /// vec.extend_from_slices_copy(&["ello,".as_bytes(), &[], " world!".as_bytes()]);
+ /// assert_eq!(vec, "Hello, world!".as_bytes());
+ /// ```
+ ///
+ /// [`extend_from_slice_copy`]: #method.extend_from_slice_copy
+ pub fn extend_from_slices_copy(&mut self, slices: &[&[T]]) {
+ // Reserve the total amount of capacity we'll need to safely append the aggregated contents
+ // of each slice in `slices`.
+ let capacity_to_reserve: usize = slices.iter().map(|slice| slice.len()).sum();
+ self.reserve(capacity_to_reserve);
+
+ // SAFETY:
+ // * `dst` is valid for writes of `capacity_to_reserve` items as
+ // `self.reserve(capacity_to_reserve)` above guarantees that.
+ // * Source and destination ranges cannot overlap as we just reserved the destination
+ // range from the bump.
+ unsafe {
+ // Copy the contents of each slice onto the end of `self`
+ slices.iter().for_each(|slice| {
+ self.extend_from_slice_copy_unchecked(slice);
+ });
+ }
+ }
+}
+
// This code generalises `extend_with_{element,default}`.
trait ExtendWith<T> {
fn next(&mut self) -> T;
@@ -2612,3 +2740,23 @@ where
}
}
}
+
+#[cfg(feature = "std")]
+impl<'bump> io::Write for Vec<'bump, u8> {
+ #[inline]
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.extend_from_slice_copy(buf);
+ Ok(buf.len())
+ }
+
+ #[inline]
+ fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+ self.extend_from_slice_copy(buf);
+ Ok(())
+ }
+
+ #[inline]
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
diff --git a/third_party/rust/bumpalo/src/lib.rs b/third_party/rust/bumpalo/src/lib.rs
index 74dfcd4361..b23cfeabc8 100644..100755
--- a/third_party/rust/bumpalo/src/lib.rs
+++ b/third_party/rust/bumpalo/src/lib.rs
@@ -1,11 +1,8 @@
#![doc = include_str!("../README.md")]
#![deny(missing_debug_implementations)]
#![deny(missing_docs)]
-#![no_std]
-#![cfg_attr(
- feature = "allocator_api",
- feature(allocator_api, nonnull_slice_from_raw_parts)
-)]
+#![cfg_attr(not(feature = "std"), no_std)]
+#![cfg_attr(feature = "allocator_api", feature(allocator_api))]
#[doc(hidden)]
pub extern crate alloc as core_alloc;
@@ -26,9 +23,13 @@ use core::ptr::{self, NonNull};
use core::slice;
use core::str;
use core_alloc::alloc::{alloc, dealloc, Layout};
+
#[cfg(feature = "allocator_api")]
use core_alloc::alloc::{AllocError, Allocator};
+#[cfg(all(feature = "allocator-api2", not(feature = "allocator_api")))]
+use allocator_api2::alloc::{AllocError, Allocator};
+
pub use alloc::AllocErr;
/// An error returned from [`Bump::try_alloc_try_with`].
@@ -354,7 +355,7 @@ static EMPTY_CHUNK: EmptyChunkFooter = EmptyChunkFooter(ChunkFooter {
impl EmptyChunkFooter {
fn get(&'static self) -> NonNull<ChunkFooter> {
- unsafe { NonNull::new_unchecked(&self.0 as *const ChunkFooter as *mut ChunkFooter) }
+ NonNull::from(&self.0)
}
}
@@ -406,6 +407,15 @@ unsafe fn dealloc_chunk_list(mut footer: NonNull<ChunkFooter>) {
unsafe impl Send for Bump {}
#[inline]
+fn is_pointer_aligned_to<T>(pointer: *mut T, align: usize) -> bool {
+ debug_assert!(align.is_power_of_two());
+
+ let pointer = pointer as usize;
+ let pointer_aligned = round_down_to(pointer, align);
+ pointer == pointer_aligned
+}
+
+#[inline]
pub(crate) fn round_up_to(n: usize, divisor: usize) -> Option<usize> {
debug_assert!(divisor > 0);
debug_assert!(divisor.is_power_of_two());
@@ -419,6 +429,14 @@ pub(crate) fn round_down_to(n: usize, divisor: usize) -> usize {
n & !(divisor - 1)
}
+/// Same as `round_down_to` but preserves pointer provenance.
+#[inline]
+pub(crate) fn round_mut_ptr_down_to(ptr: *mut u8, divisor: usize) -> *mut u8 {
+ debug_assert!(divisor > 0);
+ debug_assert!(divisor.is_power_of_two());
+ ptr.wrapping_sub(ptr as usize & (divisor - 1))
+}
+
// After this point, we try to hit page boundaries instead of powers of 2
const PAGE_STRATEGY_CUTOFF: usize = 0x1000;
@@ -463,12 +481,8 @@ struct NewChunkMemoryDetails {
/// Wrapper around `Layout::from_size_align` that adds debug assertions.
#[inline]
-unsafe fn layout_from_size_align(size: usize, align: usize) -> Layout {
- if cfg!(debug_assertions) {
- Layout::from_size_align(size, align).unwrap()
- } else {
- Layout::from_size_align_unchecked(size, align)
- }
+fn layout_from_size_align(size: usize, align: usize) -> Result<Layout, AllocErr> {
+ Layout::from_size_align(size, align).map_err(|_| AllocErr)
}
#[inline(never)]
@@ -476,12 +490,6 @@ fn allocation_size_overflow<T>() -> T {
panic!("requested allocation size overflowed")
}
-// This can be migrated to directly use `usize::abs_diff` when the MSRV
-// reaches `1.60`
-fn abs_diff(a: usize, b: usize) -> usize {
- usize::max(a, b) - usize::min(a, b)
-}
-
impl Bump {
/// Construct a new arena to bump allocate into.
///
@@ -535,7 +543,7 @@ impl Bump {
});
}
- let layout = unsafe { layout_from_size_align(capacity, 1) };
+ let layout = layout_from_size_align(capacity, 1)?;
let chunk_footer = unsafe {
Self::new_chunk(
@@ -589,7 +597,7 @@ impl Bump {
/// assert!(bump.try_alloc(5).is_err());
/// ```
pub fn set_allocation_limit(&self, limit: Option<usize>) {
- self.allocation_limit.set(limit)
+ self.allocation_limit.set(limit);
}
/// How much headroom an arena has before it hits its allocation
@@ -600,7 +608,7 @@ impl Bump {
if allocated_bytes > allocation_limit {
None
} else {
- Some(abs_diff(allocation_limit, allocated_bytes))
+ Some(usize::abs_diff(allocation_limit, allocated_bytes))
}
})
}
@@ -682,7 +690,7 @@ impl Bump {
size,
} = new_chunk_memory_details;
- let layout = layout_from_size_align(size, align);
+ let layout = layout_from_size_align(size, align).ok()?;
debug_assert!(size >= requested_layout.size());
@@ -801,7 +809,6 @@ impl Bump {
/// assert_eq!(*x, "hello");
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn alloc<T>(&self, val: T) -> &mut T {
self.alloc_with(|| val)
}
@@ -821,7 +828,6 @@ impl Bump {
/// assert_eq!(x, Ok(&mut "hello"));
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn try_alloc<T>(&self, val: T) -> Result<&mut T, AllocErr> {
self.try_alloc_with(|| val)
}
@@ -846,7 +852,6 @@ impl Bump {
/// assert_eq!(*x, "hello");
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn alloc_with<F, T>(&self, f: F) -> &mut T
where
F: FnOnce() -> T,
@@ -866,7 +871,7 @@ impl Bump {
// directly into the heap instead. It seems we get it to realize
// this most consistently if we put this critical line into it's
// own function instead of inlining it into the surrounding code.
- ptr::write(ptr, f())
+ ptr::write(ptr, f());
}
let layout = Layout::new::<T>();
@@ -899,7 +904,6 @@ impl Bump {
/// assert_eq!(x, Ok(&mut "hello"));
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn try_alloc_with<F, T>(&self, f: F) -> Result<&mut T, AllocErr>
where
F: FnOnce() -> T,
@@ -919,7 +923,7 @@ impl Bump {
// directly into the heap instead. It seems we get it to realize
// this most consistently if we put this critical line into it's
// own function instead of inlining it into the surrounding code.
- ptr::write(ptr, f())
+ ptr::write(ptr, f());
}
//SAFETY: Self-contained:
@@ -971,7 +975,6 @@ impl Bump {
/// # Result::<_, ()>::Ok(())
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn alloc_try_with<F, T, E>(&self, f: F) -> Result<&mut T, E>
where
F: FnOnce() -> Result<T, E>,
@@ -1080,7 +1083,6 @@ impl Bump {
/// # Result::<_, bumpalo::AllocOrInitError<()>>::Ok(())
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn try_alloc_try_with<F, T, E>(&self, f: F) -> Result<&mut T, AllocOrInitError<E>>
where
F: FnOnce() -> Result<T, E>,
@@ -1165,7 +1167,6 @@ impl Bump {
/// assert_eq!(x, &[1, 2, 3]);
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn alloc_slice_copy<T>(&self, src: &[T]) -> &mut [T]
where
T: Copy,
@@ -1205,7 +1206,6 @@ impl Bump {
/// assert_eq!(originals, clones);
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn alloc_slice_clone<T>(&self, src: &[T]) -> &mut [T]
where
T: Clone,
@@ -1236,7 +1236,6 @@ impl Bump {
/// assert_eq!("hello world", hello);
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn alloc_str(&self, src: &str) -> &mut str {
let buffer = self.alloc_slice_copy(src.as_bytes());
unsafe {
@@ -1263,7 +1262,6 @@ impl Bump {
/// assert_eq!(x, &[5, 10, 15, 20, 25]);
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_with<T, F>(&self, len: usize, mut f: F) -> &mut [T]
where
F: FnMut(usize) -> T,
@@ -1299,7 +1297,6 @@ impl Bump {
/// assert_eq!(x, &[42, 42, 42, 42, 42]);
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_copy<T: Copy>(&self, len: usize, value: T) -> &mut [T] {
self.alloc_slice_fill_with(len, |_| value)
}
@@ -1324,7 +1321,6 @@ impl Bump {
/// assert_eq!(&x[1], &s);
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_clone<T: Clone>(&self, len: usize, value: &T) -> &mut [T] {
self.alloc_slice_fill_with(len, |_| value.clone())
}
@@ -1347,7 +1343,6 @@ impl Bump {
/// assert_eq!(x, [4, 9, 25]);
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_iter<T, I>(&self, iter: I) -> &mut [T]
where
I: IntoIterator<Item = T>,
@@ -1378,7 +1373,6 @@ impl Bump {
/// assert_eq!(x, &[0, 0, 0, 0, 0]);
/// ```
#[inline(always)]
- #[allow(clippy::mut_from_ref)]
pub fn alloc_slice_fill_default<T: Default>(&self, len: usize) -> &mut [T] {
self.alloc_slice_fill_with(len, |_| T::default())
}
@@ -1435,11 +1429,10 @@ impl Bump {
}
let ptr = ptr.wrapping_sub(layout.size());
- let rem = ptr as usize % layout.align();
- let aligned_ptr = ptr.wrapping_sub(rem);
+ let aligned_ptr = round_mut_ptr_down_to(ptr, layout.align());
if aligned_ptr >= start {
- let aligned_ptr = NonNull::new_unchecked(aligned_ptr as *mut u8);
+ let aligned_ptr = NonNull::new_unchecked(aligned_ptr);
footer.ptr.set(aligned_ptr);
Some(aligned_ptr)
} else {
@@ -1464,12 +1457,13 @@ impl Bump {
let current_footer = self.current_chunk_footer.get();
let current_footer = unsafe { current_footer.as_ref() };
- current_footer as *const _ as usize - current_footer.data.as_ptr() as usize
+ current_footer.ptr.get().as_ptr() as usize - current_footer.data.as_ptr() as usize
}
/// Slow path allocation for when we need to allocate a new chunk from the
/// parent bump set because there isn't enough room in our current chunk.
#[inline(never)]
+ #[cold]
fn alloc_layout_slow(&self, layout: Layout) -> Option<NonNull<u8>> {
unsafe {
let size = layout.size();
@@ -1488,21 +1482,14 @@ impl Bump {
.checked_mul(2)?
.max(min_new_chunk_size);
let chunk_memory_details = iter::from_fn(|| {
- let bypass_min_chunk_size_for_small_limits = match self.allocation_limit() {
- Some(limit)
- if layout.size() < limit
+ let bypass_min_chunk_size_for_small_limits = matches!(self.allocation_limit(), Some(limit) if layout.size() < limit
&& base_size >= layout.size()
&& limit < DEFAULT_CHUNK_SIZE_WITHOUT_FOOTER
- && self.allocated_bytes() == 0 =>
- {
- true
- }
- _ => false,
- };
+ && self.allocated_bytes() == 0);
if base_size >= min_new_chunk_size || bypass_min_chunk_size_for_small_limits {
let size = base_size;
- base_size = base_size / 2;
+ base_size /= 2;
Bump::new_chunk_memory_details(Some(size), layout)
} else {
None
@@ -1537,14 +1524,14 @@ impl Bump {
// at least the requested size.
let mut ptr = new_footer.ptr.get().as_ptr().sub(size);
// Round the pointer down to the requested alignment.
- ptr = ptr.sub(ptr as usize % layout.align());
+ ptr = round_mut_ptr_down_to(ptr, layout.align());
debug_assert!(
ptr as *const _ <= new_footer,
"{:p} <= {:p}",
ptr,
new_footer
);
- let ptr = NonNull::new_unchecked(ptr as *mut u8);
+ let ptr = NonNull::new_unchecked(ptr);
new_footer.ptr.set(ptr);
// Return a pointer to the freshly allocated region in this chunk.
@@ -1696,6 +1683,16 @@ impl Bump {
unsafe { footer.as_ref().allocated_bytes }
}
+ /// Calculates the number of bytes requested from the Rust allocator for this `Bump`.
+ ///
+ /// This number is equal to the [`allocated_bytes()`](Self::allocated_bytes) plus
+ /// the size of the bump metadata.
+ pub fn allocated_bytes_including_metadata(&self) -> usize {
+ let metadata_size =
+ unsafe { self.iter_allocated_chunks_raw().count() * mem::size_of::<ChunkFooter>() };
+ self.allocated_bytes() + metadata_size
+ }
+
#[inline]
unsafe fn is_last_allocation(&self, ptr: NonNull<u8>) -> bool {
let footer = self.current_chunk_footer.get();
@@ -1720,13 +1717,31 @@ impl Bump {
old_layout: Layout,
new_layout: Layout,
) -> Result<NonNull<u8>, AllocErr> {
+ // If the new layout demands greater alignment than the old layout has,
+ // then either
+ //
+ // 1. the pointer happens to satisfy the new layout's alignment, so we
+ // got lucky and can return the pointer as-is, or
+ //
+ // 2. the pointer is not aligned to the new layout's demanded alignment,
+ // and we are unlucky.
+ //
+ // In the case of (2), to successfully "shrink" the allocation, we would
+ // have to allocate a whole new region for the new layout, without being
+ // able to free the old region. That is unacceptable, so simply return
+ // an allocation failure error instead.
+ if old_layout.align() < new_layout.align() {
+ if is_pointer_aligned_to(ptr.as_ptr(), new_layout.align()) {
+ return Ok(ptr);
+ } else {
+ return Err(AllocErr);
+ }
+ }
+
+ debug_assert!(is_pointer_aligned_to(ptr.as_ptr(), new_layout.align()));
+
let old_size = old_layout.size();
let new_size = new_layout.size();
- let align_is_compatible = old_layout.align() >= new_layout.align();
-
- if !align_is_compatible {
- return Err(AllocErr);
- }
// This is how much space we would *actually* reclaim while satisfying
// the requested alignment.
@@ -1736,7 +1751,32 @@ impl Bump {
// Only reclaim the excess space (which requires a copy) if it
// is worth it: we are actually going to recover "enough" space
// and we can do a non-overlapping copy.
- && delta >= old_size / 2
+ //
+ // We do `(old_size + 1) / 2` so division rounds up rather than
+ // down. Consider when:
+ //
+ // old_size = 5
+ // new_size = 3
+ //
+ // If we do not take care to round up, this will result in:
+ //
+ // delta = 2
+ // (old_size / 2) = (5 / 2) = 2
+ //
+ // And the the check will succeed even though we are have
+ // overlapping ranges:
+ //
+ // |--------old-allocation-------|
+ // |------from-------|
+ // |-------to--------|
+ // +-----+-----+-----+-----+-----+
+ // | a | b | c | . | . |
+ // +-----+-----+-----+-----+-----+
+ //
+ // But we MUST NOT have overlapping ranges because we use
+ // `copy_nonoverlapping` below! Therefore, we round the division
+ // up to avoid this issue.
+ && delta >= (old_size + 1) / 2
{
let footer = self.current_chunk_footer.get();
let footer = footer.as_ref();
@@ -1751,9 +1791,11 @@ impl Bump {
ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), new_size);
return Ok(new_ptr);
- } else {
- return Ok(ptr);
}
+
+ // If this wasn't the last allocation, or shrinking wasn't worth it,
+ // simply return the old pointer as-is.
+ Ok(ptr)
}
#[inline]
@@ -1772,7 +1814,7 @@ impl Bump {
// reuse the currently allocated space.
let delta = new_size - old_size;
if let Some(p) =
- self.try_alloc_layout_fast(layout_from_size_align(delta, old_layout.align()))
+ self.try_alloc_layout_fast(layout_from_size_align(delta, old_layout.align())?)
{
ptr::copy(ptr.as_ptr(), p.as_ptr(), old_size);
return Ok(p);
@@ -1867,7 +1909,7 @@ unsafe impl<'a> alloc::Alloc for &'a Bump {
#[inline]
unsafe fn dealloc(&mut self, ptr: NonNull<u8>, layout: Layout) {
- Bump::dealloc(self, ptr, layout)
+ Bump::dealloc(self, ptr, layout);
}
#[inline]
@@ -1883,7 +1925,7 @@ unsafe impl<'a> alloc::Alloc for &'a Bump {
return self.try_alloc_layout(layout);
}
- let new_layout = layout_from_size_align(new_size, layout.align());
+ let new_layout = layout_from_size_align(new_size, layout.align())?;
if new_size <= old_size {
self.shrink(ptr, layout, new_layout)
} else {
@@ -1892,18 +1934,23 @@ unsafe impl<'a> alloc::Alloc for &'a Bump {
}
}
-#[cfg(feature = "allocator_api")]
+#[cfg(any(feature = "allocator_api", feature = "allocator-api2"))]
unsafe impl<'a> Allocator for &'a Bump {
+ #[inline]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
self.try_alloc_layout(layout)
- .map(|p| NonNull::slice_from_raw_parts(p, layout.size()))
+ .map(|p| unsafe {
+ NonNull::new_unchecked(ptr::slice_from_raw_parts_mut(p.as_ptr(), layout.size()))
+ })
.map_err(|_| AllocError)
}
+ #[inline]
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
Bump::dealloc(self, ptr, layout)
}
+ #[inline]
unsafe fn shrink(
&self,
ptr: NonNull<u8>,
@@ -1911,10 +1958,13 @@ unsafe impl<'a> Allocator for &'a Bump {
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
Bump::shrink(self, ptr, old_layout, new_layout)
- .map(|p| NonNull::slice_from_raw_parts(p, new_layout.size()))
+ .map(|p| unsafe {
+ NonNull::new_unchecked(ptr::slice_from_raw_parts_mut(p.as_ptr(), new_layout.size()))
+ })
.map_err(|_| AllocError)
}
+ #[inline]
unsafe fn grow(
&self,
ptr: NonNull<u8>,
@@ -1922,10 +1972,13 @@ unsafe impl<'a> Allocator for &'a Bump {
new_layout: Layout,
) -> Result<NonNull<[u8]>, AllocError> {
Bump::grow(self, ptr, old_layout, new_layout)
- .map(|p| NonNull::slice_from_raw_parts(p, new_layout.size()))
+ .map(|p| unsafe {
+ NonNull::new_unchecked(ptr::slice_from_raw_parts_mut(p.as_ptr(), new_layout.size()))
+ })
.map_err(|_| AllocError)
}
+ #[inline]
unsafe fn grow_zeroed(
&self,
ptr: NonNull<u8>,
@@ -1953,7 +2006,6 @@ mod tests {
// Uses private `alloc` module.
#[test]
- #[allow(clippy::cognitive_complexity)]
fn test_realloc() {
use crate::alloc::Alloc;
diff --git a/third_party/rust/coreaudio-sys-utils/.cargo-checksum.json b/third_party/rust/coreaudio-sys-utils/.cargo-checksum.json
index 5459f53214..f22a44473c 100644
--- a/third_party/rust/coreaudio-sys-utils/.cargo-checksum.json
+++ b/third_party/rust/coreaudio-sys-utils/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"87292d055a2fc0f070f54abd549a5f79ec8ac33611ecde80ba394f256b88294c","src/aggregate_device.rs":"7d2bd5f5fd7f3d008ebb69ad81f522ca0cb73db6d7b3e50ed1a63ea26ff721f4","src/audio_device_extensions.rs":"2852f9ce65581cb5cf3f8e581f2652087eae0a569ed429be362e636db6441b6b","src/audio_object.rs":"5447179330a862659a25bceedfdc5d29a1296f63490908d1c868c6b21c5f95a1","src/audio_unit.rs":"d783878930df4923b57ad230138c0f3fd6b0b9bb80a39725092ff4c6615162d8","src/cf_mutable_dict.rs":"fc42edd270c6dfb02f123214d2d8e487bbd62b5bd923b71eec13190fd0104d2a","src/dispatch.rs":"f6267fe587217c3d3ad5fe7f3a35955221c936103bf853c477a2e44eba5f1e46","src/lib.rs":"c93ed1411dd6cc39db44f57e0d7683bbc54745f84a3c9f9533a088895ec97abe","src/string.rs":"28f88b816c768bcfcc674a60d962b93f1c94e5e0f4cc8ed2a1301138b91039e7"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"87292d055a2fc0f070f54abd549a5f79ec8ac33611ecde80ba394f256b88294c","src/aggregate_device.rs":"7d2bd5f5fd7f3d008ebb69ad81f522ca0cb73db6d7b3e50ed1a63ea26ff721f4","src/audio_device_extensions.rs":"5c869d791947d15eec8bffe0bb302fe32d0578111ffe0049213e720eb60a34e1","src/audio_object.rs":"34f7e038c1ed30d503d669d89f01864ae90e009a2fa74ef50fac343a53113ff2","src/audio_unit.rs":"d38007faed2ce4d88efb70054a1fdfadf8249d0e55b900eb3ac8eae04355bf2b","src/cf_mutable_dict.rs":"fc42edd270c6dfb02f123214d2d8e487bbd62b5bd923b71eec13190fd0104d2a","src/dispatch.rs":"24b6bcf0dcaa6618e03039cd060a274c8f9ed48264e14de465ae3aacb2daad57","src/lib.rs":"c93ed1411dd6cc39db44f57e0d7683bbc54745f84a3c9f9533a088895ec97abe","src/string.rs":"28f88b816c768bcfcc674a60d962b93f1c94e5e0f4cc8ed2a1301138b91039e7"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/coreaudio-sys-utils/src/audio_device_extensions.rs b/third_party/rust/coreaudio-sys-utils/src/audio_device_extensions.rs
index 04eeaaf566..8224d87c28 100644
--- a/third_party/rust/coreaudio-sys-utils/src/audio_device_extensions.rs
+++ b/third_party/rust/coreaudio-sys-utils/src/audio_device_extensions.rs
@@ -1,3 +1,4 @@
+use crate::dispatch::*;
use coreaudio_sys::*;
// See https://opensource.apple.com/source/WebCore/WebCore-7604.5.6/platform/spi/cf/CoreAudioSPI.h.auto.html
@@ -18,5 +19,6 @@ pub fn audio_device_duck(
in_start_time: *const AudioTimeStamp,
in_ramp_duration: f32,
) -> OSStatus {
+ debug_assert_running_serially();
unsafe { AudioDeviceDuck(in_device, in_ducked_level, in_start_time, in_ramp_duration) }
}
diff --git a/third_party/rust/coreaudio-sys-utils/src/audio_object.rs b/third_party/rust/coreaudio-sys-utils/src/audio_object.rs
index 368d6caadc..b8d74c80fc 100644
--- a/third_party/rust/coreaudio-sys-utils/src/audio_object.rs
+++ b/third_party/rust/coreaudio-sys-utils/src/audio_object.rs
@@ -1,3 +1,4 @@
+use crate::dispatch::*;
use coreaudio_sys::*;
use std::fmt;
use std::os::raw::c_void;
@@ -77,6 +78,7 @@ pub fn audio_object_set_property_data<T>(
size: usize,
data: *const T,
) -> OSStatus {
+ debug_assert_running_serially();
unsafe {
AudioObjectSetPropertyData(
id,
@@ -99,6 +101,7 @@ pub fn audio_object_add_property_listener<T>(
listener: audio_object_property_listener_proc,
data: *mut T,
) -> OSStatus {
+ debug_assert_running_serially();
unsafe { AudioObjectAddPropertyListener(id, address, Some(listener), data as *mut c_void) }
}
@@ -108,6 +111,7 @@ pub fn audio_object_remove_property_listener<T>(
listener: audio_object_property_listener_proc,
data: *mut T,
) -> OSStatus {
+ debug_assert_running_serially();
unsafe { AudioObjectRemovePropertyListener(id, address, Some(listener), data as *mut c_void) }
}
diff --git a/third_party/rust/coreaudio-sys-utils/src/audio_unit.rs b/third_party/rust/coreaudio-sys-utils/src/audio_unit.rs
index 059a58f26b..30e5a3cf4b 100644
--- a/third_party/rust/coreaudio-sys-utils/src/audio_unit.rs
+++ b/third_party/rust/coreaudio-sys-utils/src/audio_unit.rs
@@ -1,3 +1,4 @@
+use crate::dispatch::debug_assert_running_serially;
use coreaudio_sys::*;
use std::convert::TryFrom;
use std::os::raw::c_void;
@@ -13,6 +14,7 @@ pub fn audio_unit_get_property_info(
) -> OSStatus {
assert!(!unit.is_null());
assert!(UInt32::try_from(*size).is_ok()); // Check if `size` can be converted to a UInt32.
+ debug_assert_running_serially();
unsafe {
AudioUnitGetPropertyInfo(
unit,
@@ -35,6 +37,7 @@ pub fn audio_unit_get_property<T>(
) -> OSStatus {
assert!(!unit.is_null());
assert!(UInt32::try_from(*size).is_ok()); // Check if `size` can be converted to a UInt32.
+ debug_assert_running_serially();
unsafe {
AudioUnitGetProperty(
unit,
@@ -56,6 +59,7 @@ pub fn audio_unit_set_property<T>(
size: usize,
) -> OSStatus {
assert!(!unit.is_null());
+ debug_assert_running_serially();
unsafe {
AudioUnitSetProperty(
unit,
@@ -76,6 +80,7 @@ pub fn audio_unit_get_parameter(
value: &mut AudioUnitParameterValue,
) -> OSStatus {
assert!(!unit.is_null());
+ debug_assert_running_serially();
unsafe {
AudioUnitGetParameter(
unit,
@@ -96,30 +101,36 @@ pub fn audio_unit_set_parameter(
buffer_offset_in_frames: UInt32,
) -> OSStatus {
assert!(!unit.is_null());
+ debug_assert_running_serially();
unsafe { AudioUnitSetParameter(unit, id, scope, element, value, buffer_offset_in_frames) }
}
pub fn audio_unit_initialize(unit: AudioUnit) -> OSStatus {
assert!(!unit.is_null());
+ debug_assert_running_serially();
unsafe { AudioUnitInitialize(unit) }
}
pub fn audio_unit_uninitialize(unit: AudioUnit) -> OSStatus {
assert!(!unit.is_null());
+ debug_assert_running_serially();
unsafe { AudioUnitUninitialize(unit) }
}
pub fn dispose_audio_unit(unit: AudioUnit) -> OSStatus {
+ debug_assert_running_serially();
unsafe { AudioComponentInstanceDispose(unit) }
}
pub fn audio_output_unit_start(unit: AudioUnit) -> OSStatus {
assert!(!unit.is_null());
+ debug_assert_running_serially();
unsafe { AudioOutputUnitStart(unit) }
}
pub fn audio_output_unit_stop(unit: AudioUnit) -> OSStatus {
assert!(!unit.is_null());
+ debug_assert_running_serially();
unsafe { AudioOutputUnitStop(unit) }
}
@@ -155,6 +166,7 @@ pub fn audio_unit_add_property_listener<T>(
data: *mut T,
) -> OSStatus {
assert!(!unit.is_null());
+ debug_assert_running_serially();
unsafe { AudioUnitAddPropertyListener(unit, id, Some(listener), data as *mut c_void) }
}
@@ -165,6 +177,7 @@ pub fn audio_unit_remove_property_listener_with_user_data<T>(
data: *mut T,
) -> OSStatus {
assert!(!unit.is_null());
+ debug_assert_running_serially();
unsafe {
AudioUnitRemovePropertyListenerWithUserData(unit, id, Some(listener), data as *mut c_void)
}
diff --git a/third_party/rust/coreaudio-sys-utils/src/dispatch.rs b/third_party/rust/coreaudio-sys-utils/src/dispatch.rs
index 602304bde8..c5da137cab 100644
--- a/third_party/rust/coreaudio-sys-utils/src/dispatch.rs
+++ b/third_party/rust/coreaudio-sys-utils/src/dispatch.rs
@@ -3,40 +3,118 @@ use coreaudio_sys::*;
use std::ffi::CString;
use std::mem;
use std::os::raw::c_void;
+use std::panic;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Mutex, OnceLock};
+#[cfg(test)]
+use std::thread;
+#[cfg(test)]
+use std::time::Duration;
+use std::time::Instant;
-// Queue: A wrapper around `dispatch_queue_t`.
+pub const DISPATCH_QUEUE_LABEL: &str = "org.mozilla.cubeb";
+
+pub fn get_serial_queue_singleton() -> &'static Queue {
+ static SERIAL_QUEUE: OnceLock<Queue> = OnceLock::new();
+ SERIAL_QUEUE.get_or_init(|| Queue::new(DISPATCH_QUEUE_LABEL))
+}
+
+pub fn debug_assert_running_serially() {
+ get_serial_queue_singleton().debug_assert_is_current();
+}
+
+pub fn debug_assert_not_running_serially() {
+ get_serial_queue_singleton().debug_assert_is_not_current();
+}
+
+pub fn run_serially<F, B>(work: F) -> B
+where
+ F: FnOnce() -> B,
+{
+ get_serial_queue_singleton().run_sync(|| work()).unwrap()
+}
+
+pub fn run_serially_forward_panics<F, B>(work: F) -> B
+where
+ F: panic::UnwindSafe + FnOnce() -> B,
+{
+ match run_serially(|| panic::catch_unwind(|| work())) {
+ Ok(res) => res,
+ Err(e) => panic::resume_unwind(e),
+ }
+}
+
+// Queue: A wrapper around `dispatch_queue_t` that is always serial.
// ------------------------------------------------------------------------------------------------
#[derive(Debug)]
-pub struct Queue(dispatch_queue_t);
+pub struct Queue {
+ queue: Mutex<dispatch_queue_t>,
+ owned: AtomicBool,
+}
impl Queue {
- pub fn new(label: &str) -> Self {
+ pub fn new_with_target(label: &str, target: &Queue) -> Self {
const DISPATCH_QUEUE_SERIAL: dispatch_queue_attr_t =
ptr::null_mut::<dispatch_queue_attr_s>();
let label = CString::new(label).unwrap();
let c_string = label.as_ptr();
- let queue = Self(unsafe { dispatch_queue_create(c_string, DISPATCH_QUEUE_SERIAL) });
+ let queue = {
+ let target_guard = target.queue.lock().unwrap();
+ Self {
+ queue: Mutex::new(unsafe {
+ dispatch_queue_create_with_target(
+ c_string,
+ DISPATCH_QUEUE_SERIAL,
+ *target_guard,
+ )
+ }),
+ owned: AtomicBool::new(true),
+ }
+ };
queue.set_should_cancel(Box::new(AtomicBool::new(false)));
queue
}
+ pub fn new(label: &str) -> Self {
+ Queue::new_with_target(label, &Queue::get_global_queue())
+ }
+
+ pub fn get_global_queue() -> Self {
+ Self {
+ queue: Mutex::new(unsafe { dispatch_get_global_queue(QOS_CLASS_DEFAULT as isize, 0) }),
+ owned: AtomicBool::new(false),
+ }
+ }
+
#[cfg(debug_assertions)]
pub fn debug_assert_is_current(&self) {
+ let guard = self.queue.lock().unwrap();
unsafe {
- dispatch_assert_queue(self.0);
+ dispatch_assert_queue(*guard);
}
}
#[cfg(not(debug_assertions))]
pub fn debug_assert_is_current(&self) {}
+ #[cfg(debug_assertions)]
+ pub fn debug_assert_is_not_current(&self) {
+ let guard = self.queue.lock().unwrap();
+ unsafe {
+ dispatch_assert_queue_not(*guard);
+ }
+ }
+
+ #[cfg(not(debug_assertions))]
+ pub fn debug_assert_is_not_current(&self) {}
+
pub fn run_async<F>(&self, work: F)
where
F: Send + FnOnce(),
{
- let should_cancel = self.get_should_cancel();
+ let guard = self.queue.lock().unwrap();
+ let should_cancel = self.get_should_cancel(*guard);
let (closure, executor) = Self::create_closure_and_executor(|| {
if should_cancel.map_or(false, |v| v.load(Ordering::SeqCst)) {
return;
@@ -44,15 +122,22 @@ impl Queue {
work();
});
unsafe {
- dispatch_async_f(self.0, closure, executor);
+ dispatch_async_f(*guard, closure, executor);
}
}
- pub fn run_sync<F>(&self, work: F)
+ pub fn run_after<F>(&self, when: Instant, work: F)
where
F: Send + FnOnce(),
{
- let should_cancel = self.get_should_cancel();
+ let now = Instant::now();
+ if when <= now {
+ return self.run_async(work);
+ }
+ let nanos = (when - now).as_nanos() as i64;
+ let when = unsafe { dispatch_time(DISPATCH_TIME_NOW.into(), nanos) };
+ let guard = self.queue.lock().unwrap();
+ let should_cancel = self.get_should_cancel(*guard);
let (closure, executor) = Self::create_closure_and_executor(|| {
if should_cancel.map_or(false, |v| v.load(Ordering::SeqCst)) {
return;
@@ -60,38 +145,85 @@ impl Queue {
work();
});
unsafe {
- dispatch_sync_f(self.0, closure, executor);
+ dispatch_after_f(when, *guard, closure, executor);
}
}
- pub fn run_final<F>(&self, work: F)
+ pub fn run_sync<F, B>(&self, work: F) -> Option<B>
where
- F: Send + FnOnce(),
+ F: FnOnce() -> B,
{
- let should_cancel = self.get_should_cancel();
- let (closure, executor) = Self::create_closure_and_executor(|| {
- work();
- should_cancel
- .expect("dispatch context should be allocated!")
- .store(true, Ordering::SeqCst);
- });
+ let queue: Option<dispatch_queue_t>;
+ let mut res: Option<B> = None;
+ let cex: Option<(*mut c_void, dispatch_function_t)>;
+ {
+ let guard = self.queue.lock().unwrap();
+ queue = Some(*guard);
+ let should_cancel = self.get_should_cancel(*guard);
+ cex = Some(Self::create_closure_and_executor(|| {
+ if should_cancel.map_or(false, |v| v.load(Ordering::SeqCst)) {
+ return;
+ }
+ res = Some(work());
+ }));
+ }
+ let (closure, executor) = cex.unwrap();
+ unsafe {
+ dispatch_sync_f(queue.unwrap(), closure, executor);
+ }
+ res
+ }
+
+ pub fn run_final<F, B>(&self, work: F) -> Option<B>
+ where
+ F: FnOnce() -> B,
+ {
+ assert!(
+ self.owned.load(Ordering::SeqCst),
+ "Doesn't make sense to finalize global queue"
+ );
+ let queue: Option<dispatch_queue_t>;
+ let mut res: Option<B> = None;
+ let cex: Option<(*mut c_void, dispatch_function_t)>;
+ {
+ let guard = self.queue.lock().unwrap();
+ queue = Some(*guard);
+ let should_cancel = self.get_should_cancel(*guard);
+ debug_assert!(
+ should_cancel.is_some(),
+ "dispatch context should be allocated!"
+ );
+ cex = Some(Self::create_closure_and_executor(|| {
+ res = Some(work());
+ should_cancel
+ .expect("dispatch context should be allocated!")
+ .store(true, Ordering::SeqCst);
+ }));
+ }
+ let (closure, executor) = cex.unwrap();
unsafe {
- dispatch_sync_f(self.0, closure, executor);
+ dispatch_sync_f(queue.unwrap(), closure, executor);
}
+ res
}
- fn get_should_cancel(&self) -> Option<&mut AtomicBool> {
+ fn get_should_cancel(&self, queue: dispatch_queue_t) -> Option<&mut AtomicBool> {
+ if !self.owned.load(Ordering::SeqCst) {
+ return None;
+ }
unsafe {
- let context = dispatch_get_context(
- mem::transmute::<dispatch_queue_t, dispatch_object_t>(self.0),
- ) as *mut AtomicBool;
+ let context =
+ dispatch_get_context(mem::transmute::<dispatch_queue_t, dispatch_object_t>(queue))
+ as *mut AtomicBool;
context.as_mut()
}
}
fn set_should_cancel(&self, context: Box<AtomicBool>) {
+ assert!(self.owned.load(Ordering::SeqCst));
unsafe {
- let queue = mem::transmute::<dispatch_queue_t, dispatch_object_t>(self.0);
+ let guard = self.queue.lock().unwrap();
+ let queue = mem::transmute::<dispatch_queue_t, dispatch_object_t>(*guard);
// Leak the context from Box.
dispatch_set_context(queue, Box::into_raw(context) as *mut c_void);
@@ -106,13 +238,13 @@ impl Queue {
}
fn release(&self) {
+ let guard = self.queue.lock().unwrap();
+ let queue = *guard;
unsafe {
// This will release the inner `dispatch_queue_t` asynchronously.
// TODO: It's incredibly unsafe to call `transmute` directly.
// Find another way to release the queue.
- dispatch_release(mem::transmute::<dispatch_queue_t, dispatch_object_t>(
- self.0,
- ));
+ dispatch_release(mem::transmute::<dispatch_queue_t, dispatch_object_t>(queue));
}
}
@@ -143,23 +275,35 @@ impl Queue {
impl Drop for Queue {
fn drop(&mut self) {
- self.release();
+ if self.owned.load(Ordering::SeqCst) {
+ self.release();
+ }
}
}
impl Clone for Queue {
fn clone(&self) -> Self {
+ assert!(
+ self.owned.load(Ordering::SeqCst),
+ "No need to clone a static queue"
+ );
+ let guard = self.queue.lock().unwrap();
+ let queue = *guard;
// TODO: It's incredibly unsafe to call `transmute` directly.
// Find another way to release the queue.
unsafe {
- dispatch_retain(mem::transmute::<dispatch_queue_t, dispatch_object_t>(
- self.0,
- ));
+ dispatch_retain(mem::transmute::<dispatch_queue_t, dispatch_object_t>(queue));
+ }
+ Self {
+ queue: Mutex::new(queue),
+ owned: AtomicBool::new(true),
}
- Self(self.0)
}
}
+unsafe impl Send for Queue {}
+unsafe impl Sync for Queue {}
+
#[test]
fn run_tasks_in_order() {
let mut visited = Vec::<u32>::new();
@@ -176,12 +320,12 @@ fn run_tasks_in_order() {
let queue = Queue::new("Run tasks in order");
- queue.run_sync(move || visit(1, ptr));
- queue.run_sync(move || visit(2, ptr));
- queue.run_async(move || visit(3, ptr));
- queue.run_async(move || visit(4, ptr));
+ queue.run_sync(|| visit(1, ptr));
+ queue.run_sync(|| visit(2, ptr));
+ queue.run_async(|| visit(3, ptr));
+ queue.run_async(|| visit(4, ptr));
// Call sync here to block the current thread and make sure all the tasks are done.
- queue.run_sync(move || visit(5, ptr));
+ queue.run_sync(|| visit(5, ptr));
assert_eq!(visited, vec![1, 2, 3, 4, 5]);
}
@@ -203,14 +347,52 @@ fn run_final_task() {
let queue = Queue::new("Task after run_final will be cancelled");
- queue.run_sync(move || visit(1, ptr));
- queue.run_async(move || visit(2, ptr));
- queue.run_final(move || visit(3, ptr));
- queue.run_async(move || visit(4, ptr));
- queue.run_sync(move || visit(5, ptr));
+ queue.run_sync(|| visit(1, ptr));
+ queue.run_async(|| visit(2, ptr));
+ queue.run_final(|| visit(3, ptr));
+ queue.run_async(|| visit(4, ptr));
+ queue.run_sync(|| visit(5, ptr));
}
// `queue` will be dropped asynchronously and then the `finalizer` of the `queue`
// should be fired to clean up the `context` set in the `queue`.
assert_eq!(visited, vec![1, 2, 3]);
}
+
+#[test]
+fn sync_return_value() {
+ let q = Queue::new("Test queue");
+ assert_eq!(q.run_sync(|| 42), Some(42));
+ assert_eq!(q.run_final(|| "foo"), Some("foo"));
+ assert_eq!(q.run_sync(|| Ok::<(), u32>(())), None);
+}
+
+#[test]
+fn run_after() {
+ let mut visited = Vec::<u32>::new();
+
+ {
+ // Rust compilter doesn't allow a pointer to be passed across threads.
+ // A hacky way to do that is to cast the pointer into a value, then
+ // the value, which is actually an address, can be copied into threads.
+ let ptr = &mut visited as *mut Vec<u32> as usize;
+
+ fn visit(v: u32, visited_ptr: usize) {
+ let visited = unsafe { &mut *(visited_ptr as *mut Vec<u32>) };
+ visited.push(v);
+ }
+
+ let queue = Queue::new("Task after run_final will be cancelled");
+
+ queue.run_async(|| visit(1, ptr));
+ queue.run_after(Instant::now() + Duration::from_millis(10), || visit(2, ptr));
+ queue.run_after(Instant::now() + Duration::from_secs(1), || visit(3, ptr));
+ queue.run_async(|| visit(4, ptr));
+ thread::sleep(Duration::from_millis(100));
+ queue.run_final(|| visit(5, ptr));
+ }
+ // `queue` will be dropped asynchronously and then the `finalizer` of the `queue`
+ // should be fired to clean up the `context` set in the `queue`.
+
+ assert_eq!(visited, vec![1, 4, 2, 5]);
+}
diff --git a/third_party/rust/cubeb-coreaudio/.cargo-checksum.json b/third_party/rust/cubeb-coreaudio/.cargo-checksum.json
index 5c8366f60a..fa6f229b4c 100644
--- a/third_party/rust/cubeb-coreaudio/.cargo-checksum.json
+++ b/third_party/rust/cubeb-coreaudio/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{".circleci/config.yml":"7f3dc865105ca8f33965a7958b1fe2e627ae2d5a703f3b2a4ab6e2e796018597",".editorconfig":"4e53b182bcc78b83d7e1b5c03efa14d22d4955c4ed2514d1ba4e99c1eb1a50ba",".githooks/pre-push":"8b8b26544cd56f54c0c33812551f786bb25cb08c86dbfeb6bf3daad881c826a1",".github/workflows/test.yml":"aa1998a3b104ad131805ca3513832cef3f65300192824f8b1efc9a5a0cc108f6",".travis.yml":"dc07bac53f70f16c9bdf52264bdc58500ae6018c1b4c567bc7642f6b4ca3cc35","Cargo.toml":"d7e757e664c23fae52028f1dfc5917f92523c08702e3a1f95e1fd38ed714416c","LICENSE":"6e6f56aff5bbf3cbc60747e152fb1a719bd0716aaf6d711c554f57d92e96297c","README.md":"0007782a05a5330f739ad789c19c82562c82e32386b0447000fc72c0d48405bc","build-audiounit-rust-in-cubeb.sh":"d228a05985dcd02ec1ecac66a2b64dae5a530804a25a7054ccc95905aedfb7ef","install_git_hook.sh":"d38c8e51e636f6b90b489621ac34ccd1d1b1f40dccce3d178ed1da1c5068f16d","install_rustfmt_clippy.sh":"4ae90d8dcb9757cb3ae4ae142ef80e5377c0dde61c63f4a3c32418646e80ca7b","run_device_tests.sh":"d717e598c96e4911d9494b18382d6bd3a8d5038b7d68d3166ad4336e237a97d8","run_sanitizers.sh":"84e93a0da137803018f37403511e8c92760be730426bf6cea34419d93d1a7ff8","run_tests.sh":"916a7ae4a406d2274417d6eca939a878db5adcb6144e5680d9d148bf90178f1c","src/backend/aggregate_device.rs":"43511107ba2a75a19340ac663c981362ca1b75b679b6c295d88b5035bd7e3619","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/buffer_manager.rs":"e9bcf964347daa8952f98caa2746e34a31ea8908375204896593f56e4b6147ca","src/backend/device_property.rs":"a7622feaa41db1cd76fd35a85a022e44f4894e396a104a59008d5b8757d2ab4e","src/backend/mixer.rs":"ed299d3954e2a823060c870a8244673a7d4bca530830cb66b964d047a80ee3af","src/backend/mod.rs":"1591669c30a3d07754bfb39c9cb042cdd101f0ab89be13f6cdf74d376e441cf8","src/backend/resampler.rs":"48bf8f56ae8d60dbabca6417b768000619abee8731ac3902164b45651ac08a4d","src/backend/tests/aggregate_device.rs":"e3f94e118e1dd47941fbba4417de40bddc4254d9f06b1e938f58d8f1aa566a5c","src/backend/tests/api.rs":"cd7e7551e2e82b19da883621a494d2a6779c373f3ff2d12ee52fae8efec1e7b8","src/backend/tests/backlog.rs":"3b189a7e036543c467cc242af0ed3332721179ee2b1c8847a6db563546f1ac52","src/backend/tests/device_change.rs":"f68c2eaa55c3ec2a58894832fbca1e2a2e79e740b145f76a0f45452af465a934","src/backend/tests/device_property.rs":"ea0be5f8834be494cb33f854ce9d334b5763dc5287f949bcb4bd025d8a8b2d3b","src/backend/tests/interfaces.rs":"af8e3fdeb58226621699b29f1a90621b2260e3f17292dac54860cd05fe4eec71","src/backend/tests/manual.rs":"4a1634e86beb145d2703722a8be057a762953241329c82ee09acf7dc0f0d9d0c","src/backend/tests/mod.rs":"8dba770023d7f9c4228f0e11915347f0e07da5fd818e3ee4478c4b197af9aa2a","src/backend/tests/parallel.rs":"59632744e70616ab7037facb0787db339b96800c8cc397d203241548c5cfb7f5","src/backend/tests/tone.rs":"779cc14fc2a362bf7f26ce66ad70c0639501176175655a99b7fefb3c59d56c7a","src/backend/tests/utils.rs":"efb8b3709aff7ed5e2923566084de3e0709f3bd9c18a04f3310d7a3b86fa4b71","src/backend/utils.rs":"6c3ffbcd602e6cc9f56deb9ecb07b2eef2e6f074ef924178e466f380aae5c595","src/capi.rs":"21b66b70545bf04ec719928004d1d9adb45b24ced51288f5b2993d79aaf78f5f","src/lib.rs":"5e586d45cd6b3722f0a6736d9252593299269817a153eef1930a5fb9bfbb56f5","todo.md":"efc1f012eb9a331a040cad4ac03aa79307f25885f71b6fb38f3ad7af8d7d515c"},"package":null} \ No newline at end of file
+{"files":{".circleci/config.yml":"7f3dc865105ca8f33965a7958b1fe2e627ae2d5a703f3b2a4ab6e2e796018597",".editorconfig":"4e53b182bcc78b83d7e1b5c03efa14d22d4955c4ed2514d1ba4e99c1eb1a50ba",".githooks/pre-push":"8b8b26544cd56f54c0c33812551f786bb25cb08c86dbfeb6bf3daad881c826a1",".github/workflows/test.yml":"ac8f4cf5b7631b5c738d50c0cf78113bd395940b9e76593904bbaf2d02d16a70",".travis.yml":"dc07bac53f70f16c9bdf52264bdc58500ae6018c1b4c567bc7642f6b4ca3cc35","Cargo.toml":"0fb7c56c04e05dacffa5176f885cb8019ee6ab7f885479be501aba0eaac2148f","LICENSE":"6e6f56aff5bbf3cbc60747e152fb1a719bd0716aaf6d711c554f57d92e96297c","README.md":"0007782a05a5330f739ad789c19c82562c82e32386b0447000fc72c0d48405bc","build-audiounit-rust-in-cubeb.sh":"d228a05985dcd02ec1ecac66a2b64dae5a530804a25a7054ccc95905aedfb7ef","install_git_hook.sh":"d38c8e51e636f6b90b489621ac34ccd1d1b1f40dccce3d178ed1da1c5068f16d","install_rustfmt_clippy.sh":"4ae90d8dcb9757cb3ae4ae142ef80e5377c0dde61c63f4a3c32418646e80ca7b","run_device_tests.sh":"90c2542fa3ff8a35fed894fae3a1aa0157117b7f0e28df14b8e6f7b1f1f43797","run_sanitizers.sh":"84e93a0da137803018f37403511e8c92760be730426bf6cea34419d93d1a7ff8","run_tests.sh":"bae82f66dd47a060b6fdcc238520084aec1079d5b1b1d66d103baa1ffaa8773d","src/backend/aggregate_device.rs":"db7d644358090b1d65ff2d53ad854369790ae4ad7dfa12b79888c0002c1b4950","src/backend/auto_release.rs":"050fdcee74cf46b9a8a85a877e166d72a853d33220f59cf734cbb6ea09daa441","src/backend/buffer_manager.rs":"e9bcf964347daa8952f98caa2746e34a31ea8908375204896593f56e4b6147ca","src/backend/device_property.rs":"a7622feaa41db1cd76fd35a85a022e44f4894e396a104a59008d5b8757d2ab4e","src/backend/mixer.rs":"ed299d3954e2a823060c870a8244673a7d4bca530830cb66b964d047a80ee3af","src/backend/mod.rs":"e52b79a17dbf7faa072ec87cc3e4201b907772104c3be777498275733b9c334e","src/backend/resampler.rs":"48bf8f56ae8d60dbabca6417b768000619abee8731ac3902164b45651ac08a4d","src/backend/tests/aggregate_device.rs":"770cf90f32b5ab2203476031c1fbc8379b713baa97bec36f7fd0d77fef1efd60","src/backend/tests/api.rs":"d72d7c0de8d12e880966948be4686bcf8c789f0ef19cb435c242fd72f2d252f9","src/backend/tests/backlog.rs":"3b189a7e036543c467cc242af0ed3332721179ee2b1c8847a6db563546f1ac52","src/backend/tests/device_change.rs":"babf50326fb38db24fe80f24f546e1b6ad04319ae8835bb372d893fc9b3038a2","src/backend/tests/device_property.rs":"73c25f579a995e8a59c9b7d391813afb75f739b5e2f825480cba04499a1d46e8","src/backend/tests/interfaces.rs":"654333cd6d6023e72ba392d98872d33bc55f8f052205a9f701aec72069449e24","src/backend/tests/manual.rs":"e550cc8bb7619bb80b68e49bf7f475c029e0f1b34323d1d30edcbe322cf4efc7","src/backend/tests/mod.rs":"8dba770023d7f9c4228f0e11915347f0e07da5fd818e3ee4478c4b197af9aa2a","src/backend/tests/parallel.rs":"a7ebd579339c40ca64c0757cc9da6baec641e670f226e1b2ec5049894700bd7a","src/backend/tests/tone.rs":"b028c67777b6453a26190b6a49785dfe28556adcbe179cb10862ce0d47ee8509","src/backend/tests/utils.rs":"80d7e4ebc06b23c63a4d2867e0c80e0bfe05449fa55edd21e785ed2c089bf7d5","src/backend/utils.rs":"6c3ffbcd602e6cc9f56deb9ecb07b2eef2e6f074ef924178e466f380aae5c595","src/capi.rs":"21b66b70545bf04ec719928004d1d9adb45b24ced51288f5b2993d79aaf78f5f","src/lib.rs":"5e586d45cd6b3722f0a6736d9252593299269817a153eef1930a5fb9bfbb56f5","todo.md":"efc1f012eb9a331a040cad4ac03aa79307f25885f71b6fb38f3ad7af8d7d515c"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/cubeb-coreaudio/.github/workflows/test.yml b/third_party/rust/cubeb-coreaudio/.github/workflows/test.yml
index 06fc86fa33..2bbb9eab5d 100644
--- a/third_party/rust/cubeb-coreaudio/.github/workflows/test.yml
+++ b/third_party/rust/cubeb-coreaudio/.github/workflows/test.yml
@@ -4,36 +4,52 @@ on: [push, pull_request]
jobs:
build:
- runs-on: macOS-latest
+ runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
+ os: [macos-12, macos-13, macos-14]
rust: [stable]
experimental: [false]
include:
- - rust: nightly
+ - os: macos-14
+ rust: nightly
experimental: true
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Rust
run: rustup toolchain install ${{ matrix.rust }} --profile minimal --component rustfmt clippy
-
- - name: Setup
+
+ - name: Setup Rust
run: |
rustup default ${{ matrix.rust }}
toolchain=$(rustup default)
echo "Use Rust toolchain: $toolchain"
rustc --version
cargo --version
-
+
+ - name: Setup Audio
+ if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-14' }}
+ run: |
+ brew install switchaudio-osx
+ brew install blackhole-2ch
+ SwitchAudioSource -s "BlackHole 2ch" -t input
+ SwitchAudioSource -s "BlackHole 2ch" -t output
+
+ - name: Grant microphone access
+ if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-14' }}
+ env:
+ tcc_extra_columns: ${{ matrix.os == 'macos-14' && ',NULL,NULL,''UNUSED'',1687786159' || '' }}
+ run: sqlite3 $HOME/Library/Application\ Support/com.apple.TCC/TCC.db "INSERT OR IGNORE INTO access VALUES ('kTCCServiceMicrophone','/usr/local/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159${{ env.tcc_extra_columns }});"
+
- name: Build
run: cargo build --verbose
-
+
- name: Regular Test
run: sh run_tests.sh
diff --git a/third_party/rust/cubeb-coreaudio/Cargo.toml b/third_party/rust/cubeb-coreaudio/Cargo.toml
index f9067d3c08..8a73548f57 100644
--- a/third_party/rust/cubeb-coreaudio/Cargo.toml
+++ b/third_party/rust/cubeb-coreaudio/Cargo.toml
@@ -36,6 +36,7 @@ libc = "0.2"
mach = "0.3"
ringbuf = "0.2.6"
triple_buffer = "5.0.5"
+whatsys = "0.3"
[dependencies.coreaudio-sys-utils]
path = "coreaudio-sys-utils"
diff --git a/third_party/rust/cubeb-coreaudio/run_device_tests.sh b/third_party/rust/cubeb-coreaudio/run_device_tests.sh
index ae6df49713..69bb3385f4 100755
--- a/third_party/rust/cubeb-coreaudio/run_device_tests.sh
+++ b/third_party/rust/cubeb-coreaudio/run_device_tests.sh
@@ -15,8 +15,6 @@ cargo test test_plug_and_unplug_device -- --ignored --nocapture
cargo test test_register_device_changed_callback_to_check_default_device_changed_input -- --ignored --nocapture
cargo test test_register_device_changed_callback_to_check_default_device_changed_output -- --ignored --nocapture
cargo test test_register_device_changed_callback_to_check_default_device_changed_duplex -- --ignored --nocapture
-cargo test test_register_device_changed_callback_to_check_input_alive_changed_input -- --ignored --nocapture
-cargo test test_register_device_changed_callback_to_check_input_alive_changed_duplex -- --ignored --nocapture
cargo test test_destroy_input_stream_after_unplugging_a_nondefault_input_device -- --ignored --nocapture
cargo test test_suspend_input_stream_by_unplugging_a_nondefault_input_device -- --ignored --nocapture
diff --git a/third_party/rust/cubeb-coreaudio/run_tests.sh b/third_party/rust/cubeb-coreaudio/run_tests.sh
index e119da1f03..2b048f4790 100755
--- a/third_party/rust/cubeb-coreaudio/run_tests.sh
+++ b/third_party/rust/cubeb-coreaudio/run_tests.sh
@@ -39,8 +39,9 @@ cargo clippy -- -D warnings
# Regular Tests
cargo test --verbose
-cargo test test_configure_output -- --ignored
-cargo test test_aggregate -- --ignored --test-threads=1
+
+# Timing sensitive tests must run serially so they cannot be impacted by other tasks on the queue
+cargo test test_ops_timing_sensitive -- --ignored --test-threads=1
# Parallel Tests
cargo test test_parallel -- --ignored --nocapture --test-threads=1
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/aggregate_device.rs b/third_party/rust/cubeb-coreaudio/src/backend/aggregate_device.rs
index 2738631b87..782e76de2f 100644
--- a/third_party/rust/cubeb-coreaudio/src/backend/aggregate_device.rs
+++ b/third_party/rust/cubeb-coreaudio/src/backend/aggregate_device.rs
@@ -69,6 +69,7 @@ impl AggregateDevice {
input_id: AudioObjectID,
output_id: AudioObjectID,
) -> std::result::Result<Self, Error> {
+ debug_assert_running_serially();
let plugin_id = Self::get_system_plugin_id()?;
let device_id = Self::create_blank_device_sync(plugin_id)?;
@@ -399,12 +400,12 @@ impl AggregateDevice {
let sub_devices = CFArrayCreateMutable(ptr::null(), 0, &kCFTypeArrayCallBacks);
// The order of the items in the array is significant and is used to determine the order of the streams
// of the AudioAggregateDevice.
- for device in output_sub_devices {
+ for device in input_sub_devices {
let uid = get_device_global_uid(device)?;
CFArrayAppendValue(sub_devices, uid.get_raw() as *const c_void);
}
- for device in input_sub_devices {
+ for device in output_sub_devices {
let uid = get_device_global_uid(device)?;
CFArrayAppendValue(sub_devices, uid.get_raw() as *const c_void);
}
@@ -466,6 +467,28 @@ impl AggregateDevice {
}
}
+ pub fn get_master_device_uid(device_id: AudioDeviceID) -> std::result::Result<String, Error> {
+ let address = AudioObjectPropertyAddress {
+ mSelector: kAudioAggregateDevicePropertyMainSubDevice,
+ mScope: kAudioObjectPropertyScopeGlobal,
+ mElement: kAudioObjectPropertyElementMaster,
+ };
+
+ let mut master: CFStringRef = ptr::null_mut();
+ let mut size = mem::size_of::<CFStringRef>();
+ let status = audio_object_get_property_data(device_id, &address, &mut size, &mut master);
+ if status != NO_ERR {
+ return Err(Error::from(status));
+ }
+
+ if master.is_null() {
+ return Ok(String::default());
+ }
+
+ let master = StringRef::new(master as _);
+ Ok(master.into_string())
+ }
+
pub fn set_master_device(
device_id: AudioDeviceID,
primary_id: AudioDeviceID,
@@ -480,12 +503,12 @@ impl AggregateDevice {
);
let address = AudioObjectPropertyAddress {
- mSelector: kAudioAggregateDevicePropertyMasterSubDevice,
+ mSelector: kAudioAggregateDevicePropertyMainSubDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
- // Master become the 1st sub device of the primary device
+ // The master device will be the 1st sub device of the primary device.
let output_sub_devices = Self::get_sub_devices(primary_id)?;
assert!(!output_sub_devices.is_empty());
let master_sub_device_uid = get_device_global_uid(output_sub_devices[0]).unwrap();
@@ -548,16 +571,23 @@ impl AggregateDevice {
return Err(Error::from(status));
}
+ let master_sub_device_uid = Self::get_master_device_uid(device_id)?;
+
let address = AudioObjectPropertyAddress {
mSelector: kAudioSubDevicePropertyDriftCompensation,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
- // Start from the second device since the first is the master clock
- for device in &sub_devices[1..] {
+ for &device in &sub_devices {
+ let uid = get_device_global_uid(device)
+ .map(|sr| sr.into_string())
+ .unwrap_or_default();
+ if uid == master_sub_device_uid {
+ continue;
+ }
let status = audio_object_set_property_data(
- *device,
+ device,
&address,
mem::size_of::<u32>(),
&DRIFT_COMPENSATION,
@@ -671,6 +701,7 @@ impl Default for AggregateDevice {
impl Drop for AggregateDevice {
fn drop(&mut self) {
+ debug_assert_running_serially();
if self.plugin_id != kAudioObjectUnknown && self.device_id != kAudioObjectUnknown {
if let Err(r) = Self::destroy_device(self.plugin_id, self.device_id) {
cubeb_log!(
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/mod.rs b/third_party/rust/cubeb-coreaudio/src/backend/mod.rs
index 61ae44fea1..e6be028a2e 100644
--- a/third_party/rust/cubeb-coreaudio/src/backend/mod.rs
+++ b/third_party/rust/cubeb-coreaudio/src/backend/mod.rs
@@ -46,15 +46,15 @@ use std::mem;
use std::os::raw::{c_uint, c_void};
use std::ptr;
use std::slice;
+use std::str::FromStr;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
-use std::sync::{Arc, Condvar, Mutex};
-use std::time::Duration;
+use std::sync::{Arc, Condvar, Mutex, MutexGuard, Weak};
+use std::time::{Duration, Instant};
const NO_ERR: OSStatus = 0;
const AU_OUT_BUS: AudioUnitElement = 0;
const AU_IN_BUS: AudioUnitElement = 1;
-const DISPATCH_QUEUE_LABEL: &str = "org.mozilla.cubeb";
const PRIVATE_AGGREGATE_DEVICE_NAME: &str = "CubebAggregateDevice";
const VOICEPROCESSING_AGGREGATE_DEVICE_NAME: &str = "VPAUAggregateAudioDevice";
@@ -65,6 +65,34 @@ const APPLE_STUDIO_DISPLAY_USB_ID: &str = "05AC:1114";
const SAFE_MIN_LATENCY_FRAMES: u32 = 128;
const SAFE_MAX_LATENCY_FRAMES: u32 = 512;
+const VPIO_IDLE_TIMEOUT: Duration = Duration::from_secs(10);
+
+const MACOS_KERNEL_MAJOR_VERSION_MONTEREY: u32 = 21;
+
+#[derive(Debug, PartialEq)]
+enum ParseMacOSKernelVersionError {
+ SysCtl,
+ Malformed,
+ Parsing,
+}
+
+fn macos_kernel_major_version() -> std::result::Result<u32, ParseMacOSKernelVersionError> {
+ let ver = whatsys::kernel_version();
+ if ver.is_none() {
+ return Err(ParseMacOSKernelVersionError::SysCtl);
+ }
+ let ver = ver.unwrap();
+ let major = ver.split('.').next();
+ if major.is_none() {
+ return Err(ParseMacOSKernelVersionError::Malformed);
+ }
+ let parsed_major = u32::from_str(major.unwrap());
+ if parsed_major.is_err() {
+ return Err(ParseMacOSKernelVersionError::Parsing);
+ }
+ Ok(parsed_major.unwrap())
+}
+
bitflags! {
#[allow(non_camel_case_types)]
#[derive(Clone, Debug, PartialEq, Copy)]
@@ -191,6 +219,7 @@ fn set_notification_runloop() {
fn create_device_info(devid: AudioDeviceID, devtype: DeviceType) -> Option<device_info> {
assert_ne!(devid, kAudioObjectSystemObject);
+ debug_assert_running_serially();
let mut flags = match devtype {
DeviceType::INPUT => device_flags::DEV_INPUT,
@@ -457,7 +486,6 @@ extern "C" fn audiounit_input_callback(
assert!(!user_ptr.is_null());
let stm = unsafe { &mut *(user_ptr as *mut AudioUnitStream) };
- let using_voice_processing_unit = stm.core_stream_data.using_voice_processing_unit();
if unsafe { *flags | kAudioTimeStampHostTimeValid } != 0 {
let now = unsafe { mach_absolute_time() };
@@ -597,17 +625,20 @@ extern "C" fn audiounit_input_callback(
ErrorHandle::Return(NO_ERR)
};
- // If the input (input-only stream) or the output is drained (duplex stream),
- // cancel this callback. Note that for voice processing cases (a single unit),
- // the output callback handles stopping the unit and notifying of state.
- if !using_voice_processing_unit && stm.draining.load(Ordering::SeqCst) {
- let r = stop_audiounit(stm.core_stream_data.input_unit);
- assert!(r.is_ok());
- // Only fire state-changed callback for input-only stream.
- // The state-changed callback for the duplex stream is fired in the output callback.
- if stm.core_stream_data.output_unit.is_null() {
- stm.notify_state_changed(State::Drained);
- }
+ // If the input (input-only stream) is drained, cancel this callback. Whenever an output
+ // is involved, the output callback handles stopping all units and notifying of state.
+ if stm.core_stream_data.output_unit.is_null() && stm.draining.load(Ordering::SeqCst) {
+ stm.stopped.store(true, Ordering::SeqCst);
+ cubeb_alog!("({:p}) Input-only drained.", stm as *const AudioUnitStream);
+ stm.notify_state_changed(State::Drained);
+ let queue = stm.queue.clone();
+ // Use a new thread, through the queue, to avoid deadlock when calling
+ // AudioOutputUnitStop method from inside render callback
+ let stm_ptr = user_ptr as usize;
+ queue.run_async(move || {
+ let stm = unsafe { &mut *(stm_ptr as *mut AudioUnitStream) };
+ stm.core_stream_data.stop_audiounits();
+ });
}
match handle {
@@ -681,11 +712,17 @@ extern "C" fn audiounit_output_callback(
}
if stm.draining.load(Ordering::SeqCst) {
- // Cancel the output callback only. For duplex stream,
- // the input callback will be cancelled in its own callback.
- let r = stop_audiounit(stm.core_stream_data.output_unit);
- assert!(r.is_ok());
+ // Cancel all callbacks. For input-only streams, the input callback handles
+ // cancelling itself.
+ stm.stopped.store(true, Ordering::SeqCst);
+ cubeb_alog!("({:p}) output drained.", stm as *const AudioUnitStream);
stm.notify_state_changed(State::Drained);
+ let queue = stm.queue.clone();
+ // Use a new thread, through the queue, to avoid deadlock when calling
+ // AudioOutputUnitStop method from inside render callback
+ queue.run_async(move || {
+ stm.core_stream_data.stop_audiounits();
+ });
audiounit_make_silent(&buffers[0]);
return NO_ERR;
}
@@ -1157,38 +1194,56 @@ fn create_audiounit(device: &device_info) -> Result<AudioUnit> {
Ok(unit)
}
-fn create_voiceprocessing_audiounit(
+fn get_voiceprocessing_audiounit(
+ shared_voice_processing_unit: &mut SharedVoiceProcessingUnitManager,
in_device: &device_info,
out_device: &device_info,
-) -> Result<AudioUnit> {
+) -> Result<OwningHandle<VoiceProcessingUnit>> {
+ debug_assert_running_serially();
assert!(in_device.flags.contains(device_flags::DEV_INPUT));
assert!(!in_device.flags.contains(device_flags::DEV_OUTPUT));
assert!(!out_device.flags.contains(device_flags::DEV_INPUT));
- assert!(out_device.flags.contains(device_flags::DEV_OUTPUT));
-
- let unit = create_typed_audiounit(kAudioUnitSubType_VoiceProcessingIO)?;
- if let Err(e) = set_device_to_audiounit(unit, in_device.id, AU_IN_BUS) {
+ let unit_handle = shared_voice_processing_unit.take_or_create();
+ if let Err(e) = unit_handle {
cubeb_log!(
- "Failed to set in device {} to the created audiounit. Error: {}",
- in_device.id,
+ "Failed to create shared voiceprocessing audiounit. Error: {}",
e
);
- dispose_audio_unit(unit);
return Err(Error::error());
}
+ let mut unit_handle = unit_handle.unwrap();
- if let Err(e) = set_device_to_audiounit(unit, out_device.id, AU_OUT_BUS) {
+ if let Err(e) = set_device_to_audiounit(unit_handle.as_mut().unit, in_device.id, AU_IN_BUS) {
cubeb_log!(
- "Failed to set out device {} to the created audiounit. Error: {}",
- out_device.id,
+ "Failed to set in device {} to the created audiounit. Error: {}",
+ in_device.id,
e
);
- dispose_audio_unit(unit);
return Err(Error::error());
}
- Ok(unit)
+ let has_output = out_device.id != kAudioObjectUnknown;
+ if let Err(e) =
+ enable_audiounit_scope(unit_handle.as_mut().unit, DeviceType::OUTPUT, has_output)
+ {
+ cubeb_log!("Failed to enable audiounit input scope. Error: {}", e);
+ return Err(Error::error());
+ }
+ if has_output {
+ if let Err(e) =
+ set_device_to_audiounit(unit_handle.as_mut().unit, out_device.id, AU_OUT_BUS)
+ {
+ cubeb_log!(
+ "Failed to set out device {} to the created audiounit. Error: {}",
+ out_device.id,
+ e
+ );
+ return Err(Error::error());
+ }
+ }
+
+ Ok(unit_handle)
}
fn enable_audiounit_scope(
@@ -1276,6 +1331,31 @@ fn create_blank_audiounit() -> Result<AudioUnit> {
return create_typed_audiounit(kAudioUnitSubType_RemoteIO);
}
+fn create_voiceprocessing_audiounit() -> Result<VoiceProcessingUnit> {
+ let res = create_typed_audiounit(kAudioUnitSubType_VoiceProcessingIO);
+ if res.is_err() {
+ return Err(Error::error());
+ }
+
+ match get_default_device(DeviceType::OUTPUT) {
+ None => {
+ cubeb_log!("Could not get default output device in order to undo vpio ducking");
+ }
+ Some(id) => {
+ let r = audio_device_duck(id, 1.0, ptr::null_mut(), 0.5);
+ if r != NO_ERR {
+ cubeb_log!(
+ "Failed to undo ducking of voiceprocessing on output device {}. Proceeding... Error: {}",
+ id,
+ r
+ );
+ }
+ }
+ };
+
+ res.map(|unit| VoiceProcessingUnit { unit })
+}
+
fn get_buffer_size(unit: AudioUnit, devtype: DeviceType) -> std::result::Result<u32, OSStatus> {
assert!(!unit.is_null());
let (scope, element) = match devtype {
@@ -1558,7 +1638,7 @@ fn get_range_of_sample_rates(
if rates.is_empty() {
return Err(String::from("No data"));
}
- let (mut min, mut max) = (std::f64::MAX, std::f64::MIN);
+ let (mut min, mut max) = (f64::MAX, f64::MIN);
for rate in rates {
if rate.mMaximum > max {
max = rate.mMaximum;
@@ -2056,7 +2136,7 @@ struct LatencyController {
}
impl LatencyController {
- fn add_stream(&mut self, latency: u32) -> Option<u32> {
+ fn add_stream(&mut self, latency: u32) -> u32 {
self.streams += 1;
// For the 1st stream set anything within safe min-max
if self.streams == 1 {
@@ -2065,16 +2145,322 @@ impl LatencyController {
// synthetize the clock from the callbacks, and we want the clock to update often.
self.latency = Some(latency.clamp(SAFE_MIN_LATENCY_FRAMES, SAFE_MAX_LATENCY_FRAMES));
}
- self.latency
+ self.latency.unwrap_or(latency)
}
- fn subtract_stream(&mut self) -> Option<u32> {
+ fn subtract_stream(&mut self) {
self.streams -= 1;
if self.streams == 0 {
assert!(self.latency.is_some());
self.latency = None;
}
- self.latency
+ }
+}
+
+// SharedStorage<T> below looks generic but has evolved to be pretty tailored
+// the observed behavior of VoiceProcessingIO audio units on macOS 14.
+// Some key points are:
+// - Creating the first VoiceProcessingIO unit in a process takes a long time, often > 3s.
+// - Creating a second VoiceProcessingIO unit in a process is significantly faster, < 1s.
+// - Disposing of a VoiceProcessingIO unit when all other VoiceProcessingIO units are
+// uninitialized will take significantly longer than disposing the remaining
+// VoiceProcessingIO units, and will have other side effects: starting another
+// VoiceProcessingIO unit after this is on par with creating the first one in the
+// process, bluetooth devices will move away from the handsfree profile, etc.
+// The takeaway is that there is something internal to the VoiceProcessingIO audio unit
+// that is costly to create and dispose of and its creation is triggered by creation of
+// the first VoiceProcessingIO unit, and its disposal is triggered by the disposal of
+// the first VoiceProcessingIO unit when no other VoiceProcessingIO units are initialized.
+//
+// The intended behavior of SharedStorage<T> and SharedVoiceProcessingUnitManager is therefore:
+// - Retain ideally just one VoiceProcessingIO unit after stream destruction, so device
+// switching is fast. The benefit of retaining more than one is unclear.
+// - Dispose of either all VoiceProcessingIO units, or none at all, such that the retained
+// VoiceProcessingIO unit really helps speed up creating and starting the next. In practice
+// this means we retain all VoiceProcessingIO units until they can all be disposed of.
+
+#[derive(Debug)]
+struct SharedStorageInternal<T> {
+ // Storage for shared elements.
+ elements: Vec<T>,
+ // Number of elements in use, i.e. all elements created/taken and not recycled.
+ outstanding_element_count: usize,
+ // Used for invalidation of in-flight tasks to clear elements.
+ // Incremented when something takes a shared element.
+ generation: usize,
+}
+
+#[derive(Debug)]
+struct SharedStorage<T> {
+ queue: Queue,
+ idle_timeout: Duration,
+ storage: Mutex<SharedStorageInternal<T>>,
+}
+
+impl<T: Send> SharedStorage<T> {
+ fn with_idle_timeout(queue: Queue, idle_timeout: Duration) -> Self {
+ Self {
+ queue,
+ idle_timeout,
+ storage: Mutex::new(SharedStorageInternal::<T> {
+ elements: Vec::default(),
+ outstanding_element_count: 0,
+ generation: 0,
+ }),
+ }
+ }
+
+ fn take_locked(guard: &mut MutexGuard<'_, SharedStorageInternal<T>>) -> Result<T> {
+ if let Some(e) = guard.elements.pop() {
+ cubeb_log!("Taking shared element #{}.", guard.elements.len());
+ guard.outstanding_element_count += 1;
+ guard.generation += 1;
+ return Ok(e);
+ }
+
+ Err(Error::not_supported())
+ }
+
+ fn create_with_locked<F>(
+ guard: &mut MutexGuard<'_, SharedStorageInternal<T>>,
+ f: F,
+ ) -> Result<T>
+ where
+ F: FnOnce() -> Result<T>,
+ {
+ let start = Instant::now();
+ match f() {
+ Ok(obj) => {
+ cubeb_log!(
+ "Just created shared element #{}. Took {}s.",
+ guard.outstanding_element_count,
+ (Instant::now() - start).as_secs_f32()
+ );
+ guard.outstanding_element_count += 1;
+ guard.generation += 1;
+ Ok(obj)
+ }
+ Err(_) => {
+ cubeb_log!("Creating shared element failed");
+ Err(Error::error())
+ }
+ }
+ }
+
+ #[cfg(test)]
+ fn take(&self) -> Result<T> {
+ let mut guard = self.storage.lock().unwrap();
+ SharedStorage::take_locked(&mut guard)
+ }
+
+ fn take_or_create_with<F>(&self, f: F) -> Result<T>
+ where
+ F: FnOnce() -> Result<T>,
+ {
+ let mut guard = self.storage.lock().unwrap();
+ SharedStorage::take_locked(&mut guard)
+ .or_else(|_| SharedStorage::create_with_locked(&mut guard, f))
+ }
+
+ fn recycle(&self, obj: T) {
+ let mut guard = self.storage.lock().unwrap();
+ guard.outstanding_element_count -= 1;
+ cubeb_log!(
+ "Recycling shared element #{}. Nr of live elements now {}.",
+ guard.elements.len(),
+ guard.outstanding_element_count
+ );
+ guard.elements.push(obj);
+ }
+
+ fn clear_locked(guard: &mut MutexGuard<'_, SharedStorageInternal<T>>) {
+ let count = guard.elements.len();
+ let start = Instant::now();
+ guard.elements.clear();
+ cubeb_log!(
+ "Cleared {} shared element{}. Took {}s.",
+ count,
+ if count == 1 { "" } else { "s" },
+ (Instant::now() - start).as_secs_f32()
+ );
+ }
+
+ fn clear(&self) {
+ debug_assert_running_serially();
+ let mut guard = self.storage.lock().unwrap();
+ SharedStorage::clear_locked(&mut guard);
+ }
+
+ fn clear_if_all_idle_async(storage: &Arc<SharedStorage<T>>) {
+ let (queue, outstanding_element_count, generation) = {
+ let guard = storage.storage.lock().unwrap();
+ (
+ storage.queue.clone(),
+ guard.outstanding_element_count,
+ guard.generation,
+ )
+ };
+ if outstanding_element_count > 0 {
+ cubeb_log!(
+ "Not clearing shared voiceprocessing unit storage because {} elements are in use. Generation={}.",
+ outstanding_element_count,
+ generation
+ );
+ return;
+ }
+ cubeb_log!(
+ "Clearing shared voiceprocessing unit storage in {}s if still at generation {}.",
+ storage.idle_timeout.as_secs_f32(),
+ generation
+ );
+ let storage = storage.clone();
+ queue.run_after(Instant::now() + storage.idle_timeout, move || {
+ let mut guard = storage.storage.lock().unwrap();
+ if generation != guard.generation {
+ cubeb_log!(
+ "Not clearing shared voiceprocessing unit storage for generation {} as we're now at {}.",
+ generation,
+ guard.generation
+ );
+ return;
+ }
+ SharedStorage::clear_locked(&mut guard);
+ });
+ }
+}
+
+#[derive(Debug)]
+struct OwningHandle<T>
+where
+ T: Send,
+{
+ storage: Weak<SharedStorage<T>>,
+ obj: Option<T>,
+}
+
+impl<T: Send> OwningHandle<T> {
+ fn new(storage: Weak<SharedStorage<T>>, obj: T) -> Self {
+ Self {
+ storage,
+ obj: Some(obj),
+ }
+ }
+}
+
+impl<T: Send> AsRef<T> for OwningHandle<T> {
+ fn as_ref(&self) -> &T {
+ self.obj.as_ref().unwrap()
+ }
+}
+
+impl<T: Send> AsMut<T> for OwningHandle<T> {
+ fn as_mut(&mut self) -> &mut T {
+ self.obj.as_mut().unwrap()
+ }
+}
+
+impl<T: Send> Drop for OwningHandle<T> {
+ fn drop(&mut self) {
+ let storage = self.storage.upgrade();
+ assert!(
+ storage.is_some(),
+ "Storage must outlive the handle, but didn't"
+ );
+ let storage = storage.unwrap();
+ if self.obj.is_none() {
+ return;
+ }
+ let obj = self.obj.take().unwrap();
+ storage.recycle(obj);
+ SharedStorage::clear_if_all_idle_async(&storage);
+ }
+}
+
+#[derive(Debug)]
+struct VoiceProcessingUnit {
+ unit: AudioUnit,
+}
+
+impl Drop for VoiceProcessingUnit {
+ fn drop(&mut self) {
+ assert!(!self.unit.is_null());
+ dispose_audio_unit(self.unit);
+ }
+}
+
+unsafe impl Send for VoiceProcessingUnit {}
+
+#[derive(Debug)]
+struct SharedVoiceProcessingUnitManager {
+ sync_storage: Mutex<Option<Arc<SharedStorage<VoiceProcessingUnit>>>>,
+ queue: Queue,
+ idle_timeout: Duration,
+}
+
+impl SharedVoiceProcessingUnitManager {
+ fn with_idle_timeout(queue: Queue, idle_timeout: Duration) -> Self {
+ Self {
+ sync_storage: Mutex::new(None),
+ queue,
+ idle_timeout,
+ }
+ }
+
+ fn new(queue: Queue) -> Self {
+ SharedVoiceProcessingUnitManager::with_idle_timeout(queue, VPIO_IDLE_TIMEOUT)
+ }
+
+ fn ensure_storage_locked(
+ &self,
+ guard: &mut MutexGuard<Option<Arc<SharedStorage<VoiceProcessingUnit>>>>,
+ ) {
+ if guard.is_some() {
+ return;
+ }
+ cubeb_log!("Creating shared voiceprocessing storage.");
+ let storage = SharedStorage::<VoiceProcessingUnit>::with_idle_timeout(
+ self.queue.clone(),
+ self.idle_timeout,
+ );
+ let old_storage = guard.replace(Arc::from(storage));
+ assert!(old_storage.is_none());
+ }
+
+ // Take an already existing, shared, vpio unit, if one is available.
+ #[cfg(test)]
+ fn take(&mut self) -> Result<OwningHandle<VoiceProcessingUnit>> {
+ debug_assert_running_serially();
+ let mut guard = self.sync_storage.lock().unwrap();
+ self.ensure_storage_locked(&mut guard);
+ let storage = guard.as_mut().unwrap();
+ let res = storage.take();
+ res.map(|u| OwningHandle::new(Arc::downgrade(storage), u))
+ }
+
+ // Take an already existing, shared, vpio unit, or create one if none are available.
+ fn take_or_create(&mut self) -> Result<OwningHandle<VoiceProcessingUnit>> {
+ debug_assert_running_serially();
+ let mut guard = self.sync_storage.lock().unwrap();
+ self.ensure_storage_locked(&mut guard);
+ let storage = guard.as_mut().unwrap();
+ let res = storage.take_or_create_with(create_voiceprocessing_audiounit);
+ res.map(|u| OwningHandle::new(Arc::downgrade(storage), u))
+ }
+}
+
+unsafe impl Send for SharedVoiceProcessingUnitManager {}
+unsafe impl Sync for SharedVoiceProcessingUnitManager {}
+
+impl Drop for SharedVoiceProcessingUnitManager {
+ fn drop(&mut self) {
+ debug_assert_not_running_serially();
+ self.queue.run_final(|| {
+ let mut guard = self.sync_storage.lock().unwrap();
+ if guard.is_none() {
+ return;
+ }
+ guard.as_mut().unwrap().clear();
+ });
}
}
@@ -2091,15 +2477,26 @@ pub struct AudioUnitContext {
serial_queue: Queue,
latency_controller: Mutex<LatencyController>,
devices: Mutex<SharedDevices>,
+ // Storage for a context-global vpio unit. Duplex streams that need one will take this
+ // and return it when done.
+ shared_voice_processing_unit: SharedVoiceProcessingUnitManager,
}
impl AudioUnitContext {
fn new() -> Self {
+ let queue_label = format!("{}.context", DISPATCH_QUEUE_LABEL);
+ let serial_queue =
+ Queue::new_with_target(queue_label.as_str(), get_serial_queue_singleton());
+ let shared_vp_queue = Queue::new_with_target(
+ format!("{}.context.shared_vpio", DISPATCH_QUEUE_LABEL).as_str(),
+ &serial_queue,
+ );
Self {
_ops: &OPS as *const _,
- serial_queue: Queue::new(DISPATCH_QUEUE_LABEL),
+ serial_queue,
latency_controller: Mutex::new(LatencyController::default()),
devices: Mutex::new(SharedDevices::default()),
+ shared_voice_processing_unit: SharedVoiceProcessingUnitManager::new(shared_vp_queue),
}
}
@@ -2108,14 +2505,14 @@ impl AudioUnitContext {
controller.streams
}
- fn update_latency_by_adding_stream(&self, latency_frames: u32) -> Option<u32> {
+ fn update_latency_by_adding_stream(&self, latency_frames: u32) -> u32 {
let mut controller = self.latency_controller.lock().unwrap();
controller.add_stream(latency_frames)
}
- fn update_latency_by_removing_stream(&self) -> Option<u32> {
+ fn update_latency_by_removing_stream(&self) {
let mut controller = self.latency_controller.lock().unwrap();
- controller.subtract_stream()
+ controller.subtract_stream();
}
fn add_devices_changed_listener(
@@ -2228,8 +2625,16 @@ impl AudioUnitContext {
impl ContextOps for AudioUnitContext {
fn init(_context_name: Option<&CStr>) -> Result<Context> {
- set_notification_runloop();
- let ctx = Box::new(AudioUnitContext::new());
+ run_serially(set_notification_runloop);
+ let mut ctx = Box::new(AudioUnitContext::new());
+ let queue_label = format!("{}.context.{:p}", DISPATCH_QUEUE_LABEL, ctx.as_ref());
+ ctx.serial_queue =
+ Queue::new_with_target(queue_label.as_str(), get_serial_queue_singleton());
+ let shared_vp_queue = Queue::new_with_target(
+ format!("{}.shared_vpio", queue_label).as_str(),
+ &ctx.serial_queue,
+ );
+ ctx.shared_voice_processing_unit = SharedVoiceProcessingUnitManager::new(shared_vp_queue);
Ok(unsafe { Context::from_ptr(Box::into_raw(ctx) as *mut _) })
}
@@ -2379,28 +2784,18 @@ impl ContextOps for AudioUnitContext {
return Err(Error::invalid_parameter());
}
- // Latency cannot change if another stream is operating in parallel. In this case
- // latency is set to the other stream value.
- let global_latency_frames = self
- .update_latency_by_adding_stream(latency_frames)
- .unwrap();
- if global_latency_frames != latency_frames {
- cubeb_log!(
- "Use global latency {} instead of the requested latency {}.",
- global_latency_frames,
- latency_frames
- );
- }
-
let in_stm_settings = if let Some(params) = input_stream_params {
- let in_device =
- match create_device_info(input_device as AudioDeviceID, DeviceType::INPUT) {
- None => {
- cubeb_log!("Fail to create device info for input");
- return Err(Error::error());
- }
- Some(d) => d,
- };
+ let in_device = match self
+ .serial_queue
+ .run_sync(|| create_device_info(input_device as AudioDeviceID, DeviceType::INPUT))
+ .unwrap()
+ {
+ None => {
+ cubeb_log!("Fail to create device info for input");
+ return Err(Error::error());
+ }
+ Some(d) => d,
+ };
let stm_params = StreamParams::from(unsafe { *params.as_ptr() });
Some((stm_params, in_device))
} else {
@@ -2408,20 +2803,34 @@ impl ContextOps for AudioUnitContext {
};
let out_stm_settings = if let Some(params) = output_stream_params {
- let out_device =
- match create_device_info(output_device as AudioDeviceID, DeviceType::OUTPUT) {
- None => {
- cubeb_log!("Fail to create device info for output");
- return Err(Error::error());
- }
- Some(d) => d,
- };
+ let out_device = match self
+ .serial_queue
+ .run_sync(|| create_device_info(output_device as AudioDeviceID, DeviceType::OUTPUT))
+ .unwrap()
+ {
+ None => {
+ cubeb_log!("Fail to create device info for output");
+ return Err(Error::error());
+ }
+ Some(d) => d,
+ };
let stm_params = StreamParams::from(unsafe { *params.as_ptr() });
Some((stm_params, out_device))
} else {
None
};
+ // Latency cannot change if another stream is operating in parallel. In this case
+ // latency is set to the other stream value.
+ let global_latency_frames = self.update_latency_by_adding_stream(latency_frames);
+ if global_latency_frames != latency_frames {
+ cubeb_log!(
+ "Use global latency {} instead of the requested latency {}.",
+ global_latency_frames,
+ latency_frames
+ );
+ }
+
let mut boxed_stream = Box::new(AudioUnitStream::new(
self,
user_ptr,
@@ -2431,16 +2840,25 @@ impl ContextOps for AudioUnitContext {
));
// Rename the task queue to be an unique label.
- let queue_label = format!("{}.{:p}", DISPATCH_QUEUE_LABEL, boxed_stream.as_ref());
- boxed_stream.queue = Queue::new(queue_label.as_str());
+ let queue_label = format!(
+ "{}.stream.{:p}",
+ DISPATCH_QUEUE_LABEL,
+ boxed_stream.as_ref()
+ );
+ boxed_stream.queue = Queue::new_with_target(queue_label.as_str(), &boxed_stream.queue);
boxed_stream.core_stream_data =
CoreStreamData::new(boxed_stream.as_ref(), in_stm_settings, out_stm_settings);
- let mut result = Ok(());
- boxed_stream.queue.clone().run_sync(|| {
- result = boxed_stream.core_stream_data.setup();
- });
+ let result = boxed_stream
+ .queue
+ .clone()
+ .run_sync(|| {
+ boxed_stream
+ .core_stream_data
+ .setup(&mut boxed_stream.context.shared_voice_processing_unit)
+ })
+ .unwrap();
if let Err(r) = result {
cubeb_log!(
"({:p}) Could not setup the audiounit stream.",
@@ -2465,25 +2883,43 @@ impl ContextOps for AudioUnitContext {
if devtype == DeviceType::UNKNOWN {
return Err(Error::invalid_parameter());
}
- if collection_changed_callback.is_some() {
- self.add_devices_changed_listener(devtype, collection_changed_callback, user_ptr)
- } else {
- self.remove_devices_changed_listener(devtype)
- }
+ self.serial_queue
+ .clone()
+ .run_sync(|| {
+ if collection_changed_callback.is_some() {
+ self.add_devices_changed_listener(
+ devtype,
+ collection_changed_callback,
+ user_ptr,
+ )
+ } else {
+ self.remove_devices_changed_listener(devtype)
+ }
+ })
+ .unwrap()
}
}
impl Drop for AudioUnitContext {
fn drop(&mut self) {
- let devices = self.devices.lock().unwrap();
- assert!(
+ assert!({
+ let devices = self.devices.lock().unwrap();
devices.input.changed_callback.is_none() && devices.output.changed_callback.is_none()
- );
+ });
+
+ self.shared_voice_processing_unit =
+ SharedVoiceProcessingUnitManager::new(self.serial_queue.clone());
+
+ // Make sure all the pending (device-collection-changed-callback) tasks
+ // in queue are done, and cancel all the tasks appended after `drop` is executed.
+ let queue = self.serial_queue.clone();
+ queue.run_final(|| {});
{
let controller = self.latency_controller.lock().unwrap();
- // Disabling this assert for bug 1083664 -- we seem to leak a stream
+ // Disabling this assert in release for bug 1083664 -- we seem to leak a stream
// assert(controller.streams == 0);
+ debug_assert!(controller.streams == 0);
if controller.streams > 0 {
cubeb_log!(
"({:p}) API misuse, {} streams active when context destroyed!",
@@ -2492,10 +2928,6 @@ impl Drop for AudioUnitContext {
);
}
}
- // Make sure all the pending (device-collection-changed-callback) tasks
- // in queue are done, and cancel all the tasks appended after `drop` is executed.
- let queue = self.serial_queue.clone();
- queue.run_final(|| {});
}
}
@@ -2562,6 +2994,8 @@ struct CoreStreamData<'ctx> {
// I/O AudioUnits.
input_unit: AudioUnit,
output_unit: AudioUnit,
+ // Handle to shared voiceprocessing AudioUnit, if in use.
+ voiceprocessing_unit_handle: Option<OwningHandle<VoiceProcessingUnit>>,
// Info of the I/O devices.
input_device: device_info,
output_device: device_info,
@@ -2603,6 +3037,7 @@ impl<'ctx> Default for CoreStreamData<'ctx> {
output_dev_desc: AudioStreamBasicDescription::default(),
input_unit: ptr::null_mut(),
output_unit: ptr::null_mut(),
+ voiceprocessing_unit_handle: None,
input_device: device_info::default(),
output_device: device_info::default(),
input_processing_params: InputProcessingParams::NONE,
@@ -2649,6 +3084,7 @@ impl<'ctx> CoreStreamData<'ctx> {
output_dev_desc: AudioStreamBasicDescription::default(),
input_unit: ptr::null_mut(),
output_unit: ptr::null_mut(),
+ voiceprocessing_unit_handle: None,
input_device: in_dev,
output_device: out_dev,
input_processing_params: InputProcessingParams::NONE,
@@ -2716,7 +3152,7 @@ impl<'ctx> CoreStreamData<'ctx> {
}
fn using_voice_processing_unit(&self) -> bool {
- !self.input_unit.is_null() && self.input_unit == self.output_unit
+ self.voiceprocessing_unit_handle.is_some()
}
fn same_clock_domain(&self) -> bool {
@@ -2744,6 +3180,22 @@ impl<'ctx> CoreStreamData<'ctx> {
input_domain == output_domain
}
+ #[allow(non_upper_case_globals)]
+ fn should_force_vpio_for_input_device(&self, in_device: &device_info) -> bool {
+ assert!(in_device.id != kAudioObjectUnknown);
+ self.debug_assert_is_on_stream_queue();
+ match get_device_transport_type(in_device.id, DeviceType::INPUT) {
+ Ok(kAudioDeviceTransportTypeBuiltIn) => {
+ cubeb_log!(
+ "Forcing VPIO because input device is built in, and its volume \
+ is known to be very low without VPIO whenever VPIO is hooked up to it elsewhere."
+ );
+ true
+ }
+ _ => false,
+ }
+ }
+
fn should_block_vpio_for_device_pair(
&self,
in_device: &device_info,
@@ -2751,7 +3203,10 @@ impl<'ctx> CoreStreamData<'ctx> {
) -> bool {
self.debug_assert_is_on_stream_queue();
cubeb_log!("Evaluating device pair against VPIO block list");
- let log_device = |id, devtype| -> std::result::Result<(), OSStatus> {
+ let log_device_and_get_model_uid = |id, devtype| -> String {
+ let device_model_uid = get_device_model_uid(id, devtype)
+ .map(|s| s.into_string())
+ .unwrap_or_default();
cubeb_log!("{} uid=\"{}\", model_uid=\"{}\", transport_type={:?}, source={:?}, source_name=\"{}\", name=\"{}\", manufacturer=\"{}\"",
if devtype == DeviceType::INPUT {
"Input"
@@ -2760,43 +3215,59 @@ impl<'ctx> CoreStreamData<'ctx> {
"Output"
},
get_device_uid(id, devtype).map(|s| s.into_string()).unwrap_or_default(),
- get_device_model_uid(id, devtype).map(|s| s.into_string()).unwrap_or_default(),
+ device_model_uid,
convert_uint32_into_string(get_device_transport_type(id, devtype).unwrap_or(0)),
convert_uint32_into_string(get_device_source(id, devtype).unwrap_or(0)),
get_device_source_name(id, devtype).map(|s| s.into_string()).unwrap_or_default(),
get_device_name(id, devtype).map(|s| s.into_string()).unwrap_or_default(),
get_device_manufacturer(id, devtype).map(|s| s.into_string()).unwrap_or_default());
- Ok(())
+ device_model_uid
};
- log_device(in_device.id, DeviceType::INPUT);
- log_device(out_device.id, DeviceType::OUTPUT);
- match (
- get_device_model_uid(in_device.id, DeviceType::INPUT).map(|s| s.to_string()),
- get_device_model_uid(out_device.id, DeviceType::OUTPUT).map(|s| s.to_string()),
- ) {
- (Ok(in_model_uid), Ok(out_model_uid))
- if in_model_uid.contains(APPLE_STUDIO_DISPLAY_USB_ID)
- && out_model_uid.contains(APPLE_STUDIO_DISPLAY_USB_ID) =>
- {
- cubeb_log!("Both input and output device is an Apple Studio Display. BLOCKED");
- true
- }
- _ => {
- cubeb_log!("Device pair is not blocked");
- false
- }
+
+ #[allow(non_upper_case_globals)]
+ let in_id = match in_device.id {
+ kAudioObjectUnknown => None,
+ id => Some(id),
+ };
+ #[allow(non_upper_case_globals)]
+ let out_id = match out_device.id {
+ kAudioObjectUnknown => None,
+ id => Some(id),
+ };
+
+ let (in_model_uid, out_model_uid) = (
+ in_id
+ .map(|id| log_device_and_get_model_uid(id, DeviceType::INPUT))
+ .unwrap_or_default(),
+ out_id
+ .map(|id| log_device_and_get_model_uid(id, DeviceType::OUTPUT))
+ .unwrap_or_default(),
+ );
+
+ if in_model_uid.contains(APPLE_STUDIO_DISPLAY_USB_ID)
+ && out_model_uid.contains(APPLE_STUDIO_DISPLAY_USB_ID)
+ {
+ cubeb_log!("Both input and output device is an Apple Studio Display. BLOCKED");
+ return true;
}
+
+ cubeb_log!("Device pair is not blocked");
+ false
}
- fn create_audiounits(&mut self) -> Result<(device_info, device_info)> {
+ fn create_audiounits(
+ &mut self,
+ shared_voice_processing_unit: &mut SharedVoiceProcessingUnitManager,
+ ) -> Result<(device_info, device_info)> {
self.debug_assert_is_on_stream_queue();
let should_use_voice_processing_unit = self.has_input()
- && self.has_output()
- && self
+ && (self
.input_stream_params
.prefs()
.contains(StreamPrefs::VOICE)
- && !self.should_block_vpio_for_device_pair(&self.input_device, &self.output_device);
+ || self.should_force_vpio_for_input_device(&self.input_device))
+ && !self.should_block_vpio_for_device_pair(&self.input_device, &self.output_device)
+ && macos_kernel_major_version() != Ok(MACOS_KERNEL_MAJOR_VERSION_MONTEREY);
let should_use_aggregate_device = {
// It's impossible to create an aggregate device from an aggregate device, and it's
@@ -2843,16 +3314,20 @@ impl<'ctx> CoreStreamData<'ctx> {
// - As last resort, create regular AudioUnits. This is also the normal non-duplex path.
if should_use_voice_processing_unit {
- if let Ok(au) =
- create_voiceprocessing_audiounit(&self.input_device, &self.output_device)
- {
- cubeb_log!("({:p}) Using VoiceProcessingIO AudioUnit", self.stm_ptr);
- self.input_unit = au;
- self.output_unit = au;
+ if let Ok(mut au_handle) = get_voiceprocessing_audiounit(
+ shared_voice_processing_unit,
+ &self.input_device,
+ &self.output_device,
+ ) {
+ self.input_unit = au_handle.as_mut().unit;
+ if self.has_output() {
+ self.output_unit = au_handle.as_mut().unit;
+ }
+ self.voiceprocessing_unit_handle = Some(au_handle);
return Ok((self.input_device.clone(), self.output_device.clone()));
}
cubeb_log!(
- "({:p}) Failed to create VoiceProcessingIO AudioUnit. Trying a regular one.",
+ "({:p}) Failed to get VoiceProcessingIO AudioUnit. Trying a regular one.",
self.stm_ptr
);
}
@@ -2954,7 +3429,10 @@ impl<'ctx> CoreStreamData<'ctx> {
}
#[allow(clippy::cognitive_complexity)] // TODO: Refactoring.
- fn setup(&mut self) -> Result<()> {
+ fn setup(
+ &mut self,
+ shared_voice_processing_unit: &mut SharedVoiceProcessingUnitManager,
+ ) -> Result<()> {
self.debug_assert_is_on_stream_queue();
if self
.input_stream_params
@@ -2970,7 +3448,7 @@ impl<'ctx> CoreStreamData<'ctx> {
}
let same_clock_domain = self.same_clock_domain();
- let (in_dev_info, out_dev_info) = self.create_audiounits()?;
+ let (in_dev_info, out_dev_info) = self.create_audiounits(shared_voice_processing_unit)?;
let using_voice_processing_unit = self.using_voice_processing_unit();
assert!(!self.stm_ptr.is_null());
@@ -3155,6 +3633,25 @@ impl<'ctx> CoreStreamData<'ctx> {
);
}
+ if self.has_input() && !self.has_output() && using_voice_processing_unit {
+ // We must configure the output side of VPIO to match the input side, even if we don't use it.
+ let r = audio_unit_set_property(
+ self.input_unit,
+ kAudioUnitProperty_StreamFormat,
+ kAudioUnitScope_Input,
+ AU_OUT_BUS,
+ &self.input_dev_desc,
+ mem::size_of::<AudioStreamBasicDescription>(),
+ );
+ if r != NO_ERR {
+ cubeb_log!(
+ "AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat rv={}",
+ r
+ );
+ return Err(Error::error());
+ }
+ }
+
if self.has_output() {
assert!(!self.output_unit.is_null());
@@ -3463,15 +3960,31 @@ impl<'ctx> CoreStreamData<'ctx> {
// NOTE: On MacOS 14 the ducking happens on creation of the VPIO AudioUnit.
// On MacOS 10.15 it happens on both creation and initialization, which
// is why we defer the unducking until now.
- let r = audio_device_duck(self.output_device.id, 1.0, ptr::null_mut(), 0.5);
- if r != NO_ERR {
- cubeb_log!(
- "({:p}) Failed to undo ducking of voiceprocessing on output device {}. Proceeding... Error: {}",
- self.stm_ptr,
- self.output_device.id,
- r
- );
- }
+ #[allow(non_upper_case_globals)]
+ let mut device = match self.output_device.id {
+ kAudioObjectUnknown => None,
+ id => Some(id),
+ };
+ device = device.or_else(|| get_default_device(DeviceType::OUTPUT));
+ match device {
+ None => {
+ cubeb_log!(
+ "({:p}) No output device to undo vpio ducking on",
+ self.stm_ptr
+ );
+ }
+ Some(id) => {
+ let r = audio_device_duck(id, 1.0, ptr::null_mut(), 0.5);
+ if r != NO_ERR {
+ cubeb_log!(
+ "({:p}) Failed to undo ducking of voiceprocessing on output device {}. Proceeding... Error: {}",
+ self.stm_ptr,
+ id,
+ r
+ );
+ }
+ }
+ };
// Always try to remember the applied input mute state. If it cannot be applied
// to the new device pair, we notify the client of an error and it will have to
@@ -3558,10 +4071,16 @@ impl<'ctx> CoreStreamData<'ctx> {
}
if !self.input_unit.is_null() {
- dispose_audio_unit(self.input_unit);
+ if !self.using_voice_processing_unit() {
+ // The VPIO unit is shared and must not be disposed.
+ dispose_audio_unit(self.input_unit);
+ }
self.input_unit = ptr::null_mut();
}
+ // Return the VPIO unit if present.
+ self.voiceprocessing_unit_handle = None;
+
self.resampler.destroy();
self.mixer = None;
self.aggregate_device = None;
@@ -3873,8 +4392,6 @@ struct OutputCallbackTimingData {
// #[repr(C)] is used to prevent any padding from being added in the beginning of the AudioUnitStream.
#[repr(C)]
#[derive(Debug)]
-// Allow exposing this private struct in public interfaces when running tests.
-#[cfg_attr(test, allow(private_in_public))]
struct AudioUnitStream<'ctx> {
context: &'ctx mut AudioUnitContext,
user_ptr: *mut c_void,
@@ -3927,10 +4444,11 @@ impl<'ctx> AudioUnitStream<'ctx> {
});
let (output_callback_timing_data_write, output_callback_timing_data_read) =
output_callback_timing_data.split();
+ let queue = context.serial_queue.clone();
AudioUnitStream {
context,
user_ptr,
- queue: Queue::new(DISPATCH_QUEUE_LABEL),
+ queue,
data_callback,
state_callback,
device_changed_callback: Mutex::new(None),
@@ -4046,10 +4564,12 @@ impl<'ctx> AudioUnitStream<'ctx> {
}
}
- self.core_stream_data.setup().map_err(|e| {
- cubeb_log!("({:p}) Setup failed.", self.core_stream_data.stm_ptr);
- e
- })?;
+ self.core_stream_data
+ .setup(&mut self.context.shared_voice_processing_unit)
+ .map_err(|e| {
+ cubeb_log!("({:p}) Setup failed.", self.core_stream_data.stm_ptr);
+ e
+ })?;
if let Ok(volume) = vol_rv {
set_volume(self.core_stream_data.output_unit, volume);
@@ -4171,7 +4691,7 @@ impl<'ctx> AudioUnitStream<'ctx> {
impl<'ctx> Drop for AudioUnitStream<'ctx> {
fn drop(&mut self) {
// Execute destroy in serial queue to avoid collision with reinit when un/plug devices
- self.queue.clone().run_final(move || {
+ self.queue.clone().run_final(|| {
self.destroy();
self.core_stream_data = CoreStreamData::default();
});
@@ -4184,12 +4704,10 @@ impl<'ctx> StreamOps for AudioUnitStream<'ctx> {
self.draining.store(false, Ordering::SeqCst);
// Execute start in serial queue to avoid racing with destroy or reinit.
- let mut result = Err(Error::error());
- let started = &mut result;
- let stream = &self;
- self.queue.run_sync(move || {
- *started = stream.core_stream_data.start_audiounits();
- });
+ let result = self
+ .queue
+ .run_sync(|| self.core_stream_data.start_audiounits())
+ .unwrap();
result?;
@@ -4205,10 +4723,8 @@ impl<'ctx> StreamOps for AudioUnitStream<'ctx> {
self.stopped.store(true, Ordering::SeqCst);
// Execute stop in serial queue to avoid racing with destroy or reinit.
- let stream = &self;
- self.queue.run_sync(move || {
- stream.core_stream_data.stop_audiounits();
- });
+ self.queue
+ .run_sync(|| self.core_stream_data.stop_audiounits());
self.notify_state_changed(State::Stopped);
@@ -4285,12 +4801,10 @@ impl<'ctx> StreamOps for AudioUnitStream<'ctx> {
}
fn set_volume(&mut self, volume: f32) -> Result<()> {
// Execute set_volume in serial queue to avoid racing with destroy or reinit.
- let mut result = Err(Error::error());
- let set = &mut result;
- let stream = &self;
- self.queue.run_sync(move || {
- *set = set_volume(stream.core_stream_data.output_unit, volume);
- });
+ let result = self
+ .queue
+ .run_sync(|| set_volume(self.core_stream_data.output_unit, volume))
+ .unwrap();
result?;
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/aggregate_device.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/aggregate_device.rs
index 1d3c341ae8..6c5e494059 100644
--- a/third_party/rust/cubeb-coreaudio/src/backend/tests/aggregate_device.rs
+++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/aggregate_device.rs
@@ -3,6 +3,8 @@ use super::utils::{
test_get_drift_compensations, test_get_master_device, DeviceFilter, Scope,
};
use super::*;
+use std::iter::zip;
+use std::panic;
// AggregateDevice::set_sub_devices
// ------------------------------------
@@ -19,21 +21,27 @@ fn test_aggregate_set_sub_devices_for_an_unknown_aggregate_device() {
let default_input = default_input.unwrap();
let default_output = default_output.unwrap();
assert!(
- AggregateDevice::set_sub_devices(kAudioObjectUnknown, default_input, default_output)
- .is_err()
+ run_serially_forward_panics(|| AggregateDevice::set_sub_devices(
+ kAudioObjectUnknown,
+ default_input,
+ default_output
+ ))
+ .is_err()
);
}
#[test]
#[should_panic]
fn test_aggregate_set_sub_devices_for_unknown_devices() {
- // If aggregate device id is kAudioObjectUnknown, we are unable to set device list.
- assert!(AggregateDevice::set_sub_devices(
- kAudioObjectUnknown,
- kAudioObjectUnknown,
- kAudioObjectUnknown
- )
- .is_err());
+ run_serially_forward_panics(|| {
+ // If aggregate device id is kAudioObjectUnknown, we are unable to set device list.
+ assert!(AggregateDevice::set_sub_devices(
+ kAudioObjectUnknown,
+ kAudioObjectUnknown,
+ kAudioObjectUnknown
+ )
+ .is_err());
+ });
}
// AggregateDevice::get_sub_devices
@@ -48,7 +56,13 @@ fn test_aggregate_get_sub_devices() {
// containing `device` itself if it's not an aggregate device. This test assumes devices
// is not an empty aggregate device (Test will panic when calling get_sub_devices with
// an empty aggregate device).
- let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
+ println!(
+ "get_sub_devices({}={})",
+ device,
+ run_serially_forward_panics(|| get_device_uid(device))
+ );
+ let sub_devices =
+ run_serially_forward_panics(|| AggregateDevice::get_sub_devices(device).unwrap());
// TODO: If the device is a blank aggregate device, then the assertion fails!
assert!(!sub_devices.is_empty());
}
@@ -57,8 +71,10 @@ fn test_aggregate_get_sub_devices() {
#[test]
#[should_panic]
fn test_aggregate_get_sub_devices_for_a_unknown_device() {
- let devices = AggregateDevice::get_sub_devices(kAudioObjectUnknown).unwrap();
- assert!(devices.is_empty());
+ run_serially_forward_panics(|| {
+ let devices = AggregateDevice::get_sub_devices(kAudioObjectUnknown).unwrap();
+ assert!(devices.is_empty());
+ });
}
// AggregateDevice::set_master_device
@@ -66,7 +82,11 @@ fn test_aggregate_get_sub_devices_for_a_unknown_device() {
#[test]
#[should_panic]
fn test_aggregate_set_master_device_for_an_unknown_aggregate_device() {
- assert!(AggregateDevice::set_master_device(kAudioObjectUnknown, kAudioObjectUnknown).is_err());
+ run_serially_forward_panics(|| {
+ assert!(
+ AggregateDevice::set_master_device(kAudioObjectUnknown, kAudioObjectUnknown).is_err()
+ );
+ });
}
// AggregateDevice::activate_clock_drift_compensation
@@ -74,7 +94,9 @@ fn test_aggregate_set_master_device_for_an_unknown_aggregate_device() {
#[test]
#[should_panic]
fn test_aggregate_activate_clock_drift_compensation_for_an_unknown_aggregate_device() {
- assert!(AggregateDevice::activate_clock_drift_compensation(kAudioObjectUnknown).is_err());
+ run_serially_forward_panics(|| {
+ assert!(AggregateDevice::activate_clock_drift_compensation(kAudioObjectUnknown).is_err());
+ });
}
// AggregateDevice::destroy_device
@@ -82,60 +104,55 @@ fn test_aggregate_activate_clock_drift_compensation_for_an_unknown_aggregate_dev
#[test]
#[should_panic]
fn test_aggregate_destroy_device_for_unknown_plugin_and_aggregate_devices() {
- assert!(AggregateDevice::destroy_device(kAudioObjectUnknown, kAudioObjectUnknown).is_err())
+ run_serially_forward_panics(|| {
+ assert!(AggregateDevice::destroy_device(kAudioObjectUnknown, kAudioObjectUnknown).is_err())
+ });
}
#[test]
#[should_panic]
fn test_aggregate_destroy_aggregate_device_for_a_unknown_aggregate_device() {
- let plugin = AggregateDevice::get_system_plugin_id().unwrap();
- assert!(AggregateDevice::destroy_device(plugin, kAudioObjectUnknown).is_err());
+ run_serially_forward_panics(|| {
+ let plugin = AggregateDevice::get_system_plugin_id().unwrap();
+ assert!(AggregateDevice::destroy_device(plugin, kAudioObjectUnknown).is_err());
+ });
}
-// Default Ignored Tests
-// ================================================================================================
-// The following tests that calls `AggregateDevice::create_blank_device` are marked `ignore` by
-// default since the device-collection-changed callbacks will be fired upon
-// `AggregateDevice::create_blank_device` is called (it will plug a new device in system!).
-// Some tests rely on the device-collection-changed callbacks in a certain way. The callbacks
-// fired from a unexpected `AggregateDevice::create_blank_device` will break those tests.
-
// AggregateDevice::create_blank_device_sync
// ------------------------------------
#[test]
-#[ignore]
fn test_aggregate_create_blank_device() {
// TODO: Test this when there is no available devices.
- let plugin = AggregateDevice::get_system_plugin_id().unwrap();
- let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
+ let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap();
+ let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap();
let devices = test_get_all_devices(DeviceFilter::IncludeAll);
let device = devices.into_iter().find(|dev| dev == &device).unwrap();
- let uid = get_device_global_uid(device).unwrap().into_string();
+ let uid = run_serially(|| get_device_global_uid(device).unwrap().into_string());
assert!(uid.contains(PRIVATE_AGGREGATE_DEVICE_NAME));
- assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok());
}
// AggregateDevice::get_sub_devices
// ------------------------------------
#[test]
-#[ignore]
#[should_panic]
fn test_aggregate_get_sub_devices_for_blank_aggregate_devices() {
- // TODO: Test this when there is no available devices.
- let plugin = AggregateDevice::get_system_plugin_id().unwrap();
- let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
- // There is no sub device in a blank aggregate device!
- // AggregateDevice::get_sub_devices guarantees returning a non-empty devices vector, so
- // the following call will panic!
- let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
- assert!(sub_devices.is_empty());
- assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ run_serially_forward_panics(|| {
+ // TODO: Test this when there is no available devices.
+ let plugin = AggregateDevice::get_system_plugin_id().unwrap();
+ let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
+ // There is no sub device in a blank aggregate device!
+ // AggregateDevice::get_sub_devices guarantees returning a non-empty devices vector, so
+ // the following call will panic!
+ let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
+ assert!(sub_devices.is_empty());
+ assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ });
}
// AggregateDevice::set_sub_devices_sync
// ------------------------------------
#[test]
-#[ignore]
fn test_aggregate_set_sub_devices() {
let input_device = test_get_default_device(Scope::Input);
let output_device = test_get_default_device(Scope::Output);
@@ -147,13 +164,20 @@ fn test_aggregate_set_sub_devices() {
let input_device = input_device.unwrap();
let output_device = output_device.unwrap();
- let plugin = AggregateDevice::get_system_plugin_id().unwrap();
- let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
- assert!(AggregateDevice::set_sub_devices_sync(device, input_device, output_device).is_ok());
-
- let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
- let input_sub_devices = AggregateDevice::get_sub_devices(input_device).unwrap();
- let output_sub_devices = AggregateDevice::get_sub_devices(output_device).unwrap();
+ let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap();
+ let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap();
+ assert!(run_serially(|| AggregateDevice::set_sub_devices_sync(
+ device,
+ input_device,
+ output_device
+ ))
+ .is_ok());
+
+ let sub_devices = run_serially(|| AggregateDevice::get_sub_devices(device)).unwrap();
+ let input_sub_devices =
+ run_serially(|| AggregateDevice::get_sub_devices(input_device)).unwrap();
+ let output_sub_devices =
+ run_serially(|| AggregateDevice::get_sub_devices(output_device)).unwrap();
// TODO: There may be overlapping devices between input_sub_devices and output_sub_devices,
// but now AggregateDevice::set_sub_devices will add them directly.
@@ -168,10 +192,10 @@ fn test_aggregate_set_sub_devices() {
assert!(sub_devices.contains(dev));
}
- let onwed_devices = test_get_all_onwed_devices(device);
- let onwed_device_uids = get_device_uids(&onwed_devices);
- let input_sub_device_uids = get_device_uids(&input_sub_devices);
- let output_sub_device_uids = get_device_uids(&output_sub_devices);
+ let onwed_devices = run_serially(|| test_get_all_onwed_devices(device));
+ let onwed_device_uids = run_serially(|| get_device_uids(&onwed_devices));
+ let input_sub_device_uids = run_serially(|| get_device_uids(&input_sub_devices));
+ let output_sub_device_uids = run_serially(|| get_device_uids(&output_sub_devices));
for uid in &input_sub_device_uids {
assert!(onwed_device_uids.contains(uid));
}
@@ -179,11 +203,10 @@ fn test_aggregate_set_sub_devices() {
assert!(onwed_device_uids.contains(uid));
}
- assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok());
}
#[test]
-#[ignore]
#[should_panic]
fn test_aggregate_set_sub_devices_for_unknown_input_devices() {
let output_device = test_get_default_device(Scope::Output);
@@ -192,16 +215,19 @@ fn test_aggregate_set_sub_devices_for_unknown_input_devices() {
}
let output_device = output_device.unwrap();
- let plugin = AggregateDevice::get_system_plugin_id().unwrap();
- let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
+ run_serially_forward_panics(|| {
+ let plugin = AggregateDevice::get_system_plugin_id().unwrap();
+ let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
- assert!(AggregateDevice::set_sub_devices(device, kAudioObjectUnknown, output_device).is_err());
+ assert!(
+ AggregateDevice::set_sub_devices(device, kAudioObjectUnknown, output_device).is_err()
+ );
- assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ });
}
#[test]
-#[ignore]
#[should_panic]
fn test_aggregate_set_sub_devices_for_unknown_output_devices() {
let input_device = test_get_default_device(Scope::Input);
@@ -210,12 +236,16 @@ fn test_aggregate_set_sub_devices_for_unknown_output_devices() {
}
let input_device = input_device.unwrap();
- let plugin = AggregateDevice::get_system_plugin_id().unwrap();
- let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
+ run_serially_forward_panics(|| {
+ let plugin = AggregateDevice::get_system_plugin_id().unwrap();
+ let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
- assert!(AggregateDevice::set_sub_devices(device, input_device, kAudioObjectUnknown).is_err());
+ assert!(
+ AggregateDevice::set_sub_devices(device, input_device, kAudioObjectUnknown).is_err()
+ );
- assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ });
}
fn get_device_uids(devices: &Vec<AudioObjectID>) -> Vec<String> {
@@ -228,7 +258,6 @@ fn get_device_uids(devices: &Vec<AudioObjectID>) -> Vec<String> {
// AggregateDevice::set_master_device
// ------------------------------------
#[test]
-#[ignore]
fn test_aggregate_set_master_device() {
let input_device = test_get_default_device(Scope::Input);
let output_device = test_get_default_device(Scope::Output);
@@ -240,22 +269,28 @@ fn test_aggregate_set_master_device() {
let input_device = input_device.unwrap();
let output_device = output_device.unwrap();
- let plugin = AggregateDevice::get_system_plugin_id().unwrap();
- let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
- assert!(AggregateDevice::set_sub_devices_sync(device, input_device, output_device).is_ok());
- assert!(AggregateDevice::set_master_device(device, output_device).is_ok());
-
- // Check if master is set to the first sub device of the default output device.
- let first_output_sub_device_uid =
- get_device_uid(AggregateDevice::get_sub_devices(device).unwrap()[0]);
- let master_device_uid = test_get_master_device(device);
+ let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap();
+ let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap();
+ assert!(run_serially(|| AggregateDevice::set_sub_devices_sync(
+ device,
+ input_device,
+ output_device
+ ))
+ .is_ok());
+ assert!(run_serially(|| AggregateDevice::set_master_device(device, output_device)).is_ok());
+
+ let output_sub_devices =
+ run_serially(|| AggregateDevice::get_sub_devices(output_device)).unwrap();
+ let first_output_sub_device_uid = run_serially(|| get_device_uid(output_sub_devices[0]));
+
+ // Check that the first sub device of the output device is set as master device.
+ let master_device_uid = run_serially(|| test_get_master_device(device));
assert_eq!(first_output_sub_device_uid, master_device_uid);
- assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok());
}
#[test]
-#[ignore]
fn test_aggregate_set_master_device_for_a_blank_aggregate_device() {
let output_device = test_get_default_device(Scope::Output);
if output_device.is_none() {
@@ -263,9 +298,11 @@ fn test_aggregate_set_master_device_for_a_blank_aggregate_device() {
return;
}
- let plugin = AggregateDevice::get_system_plugin_id().unwrap();
- let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
- assert!(AggregateDevice::set_master_device(device, output_device.unwrap()).is_ok());
+ let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap();
+ let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap();
+ assert!(
+ run_serially(|| AggregateDevice::set_master_device(device, output_device.unwrap())).is_ok()
+ );
// TODO: it's really weird the aggregate device actually own nothing
// but its master device can be set successfully!
@@ -275,17 +312,16 @@ fn test_aggregate_set_master_device_for_a_blank_aggregate_device() {
// The CFStringRef of the master device returned from `test_get_master_device` is actually
// non-null.
- assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok());
}
fn get_device_uid(id: AudioObjectID) -> String {
- get_device_global_uid(id).unwrap().into_string()
+ get_device_global_uid(id).map_or(String::new(), |uid| uid.into_string())
}
// AggregateDevice::activate_clock_drift_compensation
// ------------------------------------
#[test]
-#[ignore]
fn test_aggregate_activate_clock_drift_compensation() {
let input_device = test_get_default_device(Scope::Input);
let output_device = test_get_default_device(Scope::Output);
@@ -297,27 +333,40 @@ fn test_aggregate_activate_clock_drift_compensation() {
let input_device = input_device.unwrap();
let output_device = output_device.unwrap();
- let plugin = AggregateDevice::get_system_plugin_id().unwrap();
- let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
- assert!(AggregateDevice::set_sub_devices_sync(device, input_device, output_device).is_ok());
- assert!(AggregateDevice::set_master_device(device, output_device).is_ok());
- assert!(AggregateDevice::activate_clock_drift_compensation(device).is_ok());
+ let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap();
+ let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap();
+ assert!(run_serially(|| AggregateDevice::set_sub_devices_sync(
+ device,
+ input_device,
+ output_device
+ ))
+ .is_ok());
+ assert!(run_serially(|| AggregateDevice::set_master_device(device, output_device)).is_ok());
+ assert!(run_serially(|| AggregateDevice::activate_clock_drift_compensation(device)).is_ok());
// Check the compensations.
- let devices = test_get_all_onwed_devices(device);
- let compensations = get_drift_compensations(&devices);
+ let devices = run_serially(|| test_get_all_onwed_devices(device));
+ let compensations = run_serially(|| get_drift_compensations(&devices));
+ let master_device_uid = run_serially(|| test_get_master_device(device));
assert!(!compensations.is_empty());
assert_eq!(devices.len(), compensations.len());
- for (i, compensation) in compensations.iter().enumerate() {
- assert_eq!(*compensation, if i == 0 { 0 } else { DRIFT_COMPENSATION });
+ for (device, compensation) in zip(devices, compensations) {
+ let uid = get_device_uid(device);
+ assert_eq!(
+ compensation,
+ if uid == master_device_uid {
+ 0
+ } else {
+ DRIFT_COMPENSATION
+ }
+ );
}
- assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok());
}
#[test]
-#[ignore]
fn test_aggregate_activate_clock_drift_compensation_for_an_aggregate_device_without_master_device()
{
let input_device = test_get_default_device(Scope::Input);
@@ -330,25 +379,32 @@ fn test_aggregate_activate_clock_drift_compensation_for_an_aggregate_device_with
let input_device = input_device.unwrap();
let output_device = output_device.unwrap();
- let plugin = AggregateDevice::get_system_plugin_id().unwrap();
- let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
- assert!(AggregateDevice::set_sub_devices_sync(device, input_device, output_device).is_ok());
-
- // TODO: Is the master device the first output sub device by default if we
- // don't set that ? Is it because we add the output sub device list
- // before the input's one ? (See implementation of
- // AggregateDevice::set_sub_devices).
- let first_output_sub_device_uid =
- get_device_uid(AggregateDevice::get_sub_devices(output_device).unwrap()[0]);
- let master_device_uid = test_get_master_device(device);
- assert_eq!(first_output_sub_device_uid, master_device_uid);
+ let plugin = run_serially(|| AggregateDevice::get_system_plugin_id()).unwrap();
+ let device = run_serially(|| AggregateDevice::create_blank_device_sync(plugin)).unwrap();
+ assert!(run_serially(|| AggregateDevice::set_sub_devices_sync(
+ device,
+ input_device,
+ output_device
+ ))
+ .is_ok());
+
+ // The master device is by default the first sub device in the list.
+ // This happens to be the first sub device of the input device, see implementation of
+ // AggregateDevice::set_sub_devices.
+ let first_input_sub_device_uid =
+ run_serially(|| get_device_uid(AggregateDevice::get_sub_devices(input_device).unwrap()[0]));
+ let first_sub_device_uid =
+ run_serially(|| get_device_uid(AggregateDevice::get_sub_devices(device).unwrap()[0]));
+ assert_eq!(first_input_sub_device_uid, first_sub_device_uid);
+ let master_device_uid = run_serially(|| test_get_master_device(device));
+ assert_eq!(first_sub_device_uid, master_device_uid);
// Compensate the drift directly without setting master device.
- assert!(AggregateDevice::activate_clock_drift_compensation(device).is_ok());
+ assert!(run_serially(|| AggregateDevice::activate_clock_drift_compensation(device)).is_ok());
// Check the compensations.
- let devices = test_get_all_onwed_devices(device);
- let compensations = get_drift_compensations(&devices);
+ let devices = run_serially(|| test_get_all_onwed_devices(device));
+ let compensations = run_serially(|| get_drift_compensations(&devices));
assert!(!compensations.is_empty());
assert_eq!(devices.len(), compensations.len());
@@ -356,25 +412,26 @@ fn test_aggregate_activate_clock_drift_compensation_for_an_aggregate_device_with
assert_eq!(*compensation, if i == 0 { 0 } else { DRIFT_COMPENSATION });
}
- assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ assert!(run_serially(|| AggregateDevice::destroy_device(plugin, device)).is_ok());
}
#[test]
#[should_panic]
-#[ignore]
fn test_aggregate_activate_clock_drift_compensation_for_a_blank_aggregate_device() {
- let plugin = AggregateDevice::get_system_plugin_id().unwrap();
- let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
+ run_serially_forward_panics(|| {
+ let plugin = AggregateDevice::get_system_plugin_id().unwrap();
+ let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
- let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
- assert!(sub_devices.is_empty());
- let onwed_devices = test_get_all_onwed_devices(device);
- assert!(onwed_devices.is_empty());
+ let sub_devices = AggregateDevice::get_sub_devices(device).unwrap();
+ assert!(sub_devices.is_empty());
+ let onwed_devices = test_get_all_onwed_devices(device);
+ assert!(onwed_devices.is_empty());
- // Get a panic since no sub devices to be set compensation.
- assert!(AggregateDevice::activate_clock_drift_compensation(device).is_err());
+ // Get a panic since no sub devices to be set compensation.
+ assert!(AggregateDevice::activate_clock_drift_compensation(device).is_err());
- assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ assert!(AggregateDevice::destroy_device(plugin, device).is_ok());
+ });
}
fn get_drift_compensations(devices: &Vec<AudioObjectID>) -> Vec<u32> {
@@ -391,10 +448,56 @@ fn get_drift_compensations(devices: &Vec<AudioObjectID>) -> Vec<u32> {
// AggregateDevice::destroy_device
// ------------------------------------
#[test]
-#[ignore]
#[should_panic]
fn test_aggregate_destroy_aggregate_device_for_a_unknown_plugin_device() {
- let plugin = AggregateDevice::get_system_plugin_id().unwrap();
- let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
- assert!(AggregateDevice::destroy_device(kAudioObjectUnknown, device).is_err());
+ run_serially_forward_panics(|| {
+ let plugin = AggregateDevice::get_system_plugin_id().unwrap();
+ let device = AggregateDevice::create_blank_device_sync(plugin).unwrap();
+ assert!(AggregateDevice::destroy_device(kAudioObjectUnknown, device).is_err());
+ });
+}
+
+// AggregateDevice::new
+// ------------------------------------
+#[test]
+fn test_aggregate_new() {
+ let input_device = test_get_default_device(Scope::Input);
+ let output_device = test_get_default_device(Scope::Output);
+ if input_device.is_none() || output_device.is_none() || input_device == output_device {
+ println!("No input or output device to create an aggregate device.");
+ return;
+ }
+
+ run_serially_forward_panics(|| {
+ let input_device = input_device.unwrap();
+ let output_device = output_device.unwrap();
+
+ let aggr = AggregateDevice::new(input_device, output_device).unwrap();
+
+ // Check main device
+ let output_sub_devices = AggregateDevice::get_sub_devices(output_device).unwrap();
+ let first_output_sub_device_uid = get_device_uid(output_sub_devices[0]);
+ let master_device_uid = test_get_master_device(aggr.get_device_id());
+ assert_eq!(first_output_sub_device_uid, master_device_uid);
+
+ // Check drift compensation
+ let devices = test_get_all_onwed_devices(aggr.get_device_id());
+ let compensations = get_drift_compensations(&devices);
+ assert!(!compensations.is_empty());
+ assert_eq!(devices.len(), compensations.len());
+
+ let device_uids = devices.iter().map(|&id| get_device_uid(id));
+ for (uid, compensation) in zip(device_uids, compensations) {
+ assert_eq!(
+ compensation,
+ if uid == master_device_uid {
+ 0
+ } else {
+ DRIFT_COMPENSATION
+ },
+ "Unexpected drift value for device with uid {}",
+ uid
+ );
+ }
+ });
}
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs
index 4cd86c094e..5ce2374a3e 100644
--- a/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs
+++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/api.rs
@@ -56,7 +56,7 @@ fn test_increase_and_decrease_context_streams() {
assert_eq!(context.active_streams(), STREAMS);
check_streams(&context, STREAMS);
- check_latency(&context, latencies[0]);
+ check_latency(&context, Some(latencies[0]));
for i in 0..latencies.len() - 1 {
assert_eq!(latencies[i], latencies[i + 1]);
}
@@ -149,7 +149,8 @@ fn test_minimum_resampling_input_frames_equal_input_output_rate() {
#[test]
fn test_create_device_info_from_unknown_input_device() {
if let Some(default_device_id) = test_get_default_device(Scope::Input) {
- let default_device = create_device_info(kAudioObjectUnknown, DeviceType::INPUT).unwrap();
+ let default_device =
+ run_serially(|| create_device_info(kAudioObjectUnknown, DeviceType::INPUT).unwrap());
assert_eq!(default_device.id, default_device_id);
assert_eq!(
default_device.flags,
@@ -163,7 +164,8 @@ fn test_create_device_info_from_unknown_input_device() {
#[test]
fn test_create_device_info_from_unknown_output_device() {
if let Some(default_device_id) = test_get_default_device(Scope::Output) {
- let default_device = create_device_info(kAudioObjectUnknown, DeviceType::OUTPUT).unwrap();
+ let default_device =
+ run_serially(|| create_device_info(kAudioObjectUnknown, DeviceType::OUTPUT)).unwrap();
assert_eq!(default_device.id, default_device_id);
assert_eq!(
default_device.flags,
@@ -177,13 +179,17 @@ fn test_create_device_info_from_unknown_output_device() {
#[test]
#[should_panic]
fn test_set_device_info_to_system_input_device() {
- let _device = create_device_info(kAudioObjectSystemObject, DeviceType::INPUT);
+ let _device = run_serially_forward_panics(|| {
+ create_device_info(kAudioObjectSystemObject, DeviceType::INPUT)
+ });
}
#[test]
#[should_panic]
fn test_set_device_info_to_system_output_device() {
- let _device = create_device_info(kAudioObjectSystemObject, DeviceType::OUTPUT);
+ let _device = run_serially_forward_panics(|| {
+ create_device_info(kAudioObjectSystemObject, DeviceType::OUTPUT)
+ });
}
// FIXME: Is it ok to set input device to a nonexistent device ?
@@ -192,7 +198,8 @@ fn test_set_device_info_to_system_output_device() {
#[should_panic]
fn test_set_device_info_to_nonexistent_input_device() {
let nonexistent_id = std::u32::MAX;
- let _device = create_device_info(nonexistent_id, DeviceType::INPUT);
+ let _device =
+ run_serially_forward_panics(|| create_device_info(nonexistent_id, DeviceType::INPUT));
}
// FIXME: Is it ok to set output device to a nonexistent device ?
@@ -201,7 +208,8 @@ fn test_set_device_info_to_nonexistent_input_device() {
#[should_panic]
fn test_set_device_info_to_nonexistent_output_device() {
let nonexistent_id = std::u32::MAX;
- let _device = create_device_info(nonexistent_id, DeviceType::OUTPUT);
+ let _device =
+ run_serially_forward_panics(|| create_device_info(nonexistent_id, DeviceType::OUTPUT));
}
// add_listener (for default output device)
@@ -227,10 +235,10 @@ fn test_add_listener_unknown_device() {
),
callback,
);
- let mut res: OSStatus = 0;
- stream
+ let res = stream
.queue
- .run_sync(|| res = stream.add_device_listener(&listener));
+ .run_sync(|| stream.add_device_listener(&listener))
+ .unwrap();
assert_eq!(res, kAudioHardwareBadObjectError as OSStatus);
});
}
@@ -258,14 +266,15 @@ fn test_add_listener_then_remove_system_device() {
),
callback,
);
- let mut res: OSStatus = 0;
- stream
+ let res = stream
.queue
- .run_sync(|| res = stream.add_device_listener(&listener));
+ .run_sync(|| stream.add_device_listener(&listener))
+ .unwrap();
assert_eq!(res, NO_ERR);
- stream
+ let res = stream
.queue
- .run_sync(|| res = stream.remove_device_listener(&listener));
+ .run_sync(|| stream.remove_device_listener(&listener))
+ .unwrap();
assert_eq!(res, NO_ERR);
});
}
@@ -291,10 +300,10 @@ fn test_remove_listener_without_adding_any_listener_before_system_device() {
),
callback,
);
- let mut res: OSStatus = 0;
- stream
+ let res = stream
.queue
- .run_sync(|| res = stream.remove_device_listener(&listener));
+ .run_sync(|| stream.remove_device_listener(&listener))
+ .unwrap();
assert_eq!(res, NO_ERR);
});
}
@@ -320,10 +329,10 @@ fn test_remove_listener_unknown_device() {
),
callback,
);
- let mut res: OSStatus = 0;
- stream
+ let res = stream
.queue
- .run_sync(|| res = stream.remove_device_listener(&listener));
+ .run_sync(|| stream.remove_device_listener(&listener))
+ .unwrap();
assert_eq!(res, kAudioHardwareBadObjectError as OSStatus);
});
}
@@ -334,14 +343,14 @@ fn test_remove_listener_unknown_device() {
fn test_get_default_device_id() {
if test_get_default_device(Scope::Input).is_some() {
assert_ne!(
- get_default_device_id(DeviceType::INPUT).unwrap(),
+ run_serially(|| get_default_device_id(DeviceType::INPUT)).unwrap(),
kAudioObjectUnknown,
);
}
if test_get_default_device(Scope::Output).is_some() {
assert_ne!(
- get_default_device_id(DeviceType::OUTPUT).unwrap(),
+ run_serially(|| get_default_device_id(DeviceType::OUTPUT)).unwrap(),
kAudioObjectUnknown,
);
}
@@ -350,13 +359,16 @@ fn test_get_default_device_id() {
#[test]
#[should_panic]
fn test_get_default_device_id_with_unknown_type() {
- assert!(get_default_device_id(DeviceType::UNKNOWN).is_err());
+ assert!(run_serially_forward_panics(|| get_default_device_id(DeviceType::UNKNOWN)).is_err());
}
#[test]
#[should_panic]
fn test_get_default_device_id_with_inout_type() {
- assert!(get_default_device_id(DeviceType::INPUT | DeviceType::OUTPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_default_device_id(
+ DeviceType::INPUT | DeviceType::OUTPUT
+ ))
+ .is_err());
}
// convert_channel_layout
@@ -724,9 +736,11 @@ fn test_convert_channel_layout() {
#[test]
fn test_get_preferred_channel_layout_output() {
match test_get_default_audiounit(Scope::Output) {
- Some(unit) => assert!(!audiounit_get_preferred_channel_layout(unit.get_inner())
- .unwrap()
- .is_empty()),
+ Some(unit) => assert!(!run_serially(|| audiounit_get_preferred_channel_layout(
+ unit.get_inner()
+ ))
+ .unwrap()
+ .is_empty()),
None => println!("No output audiounit for test."),
}
}
@@ -736,9 +750,15 @@ fn test_get_preferred_channel_layout_output() {
#[test]
fn test_get_current_channel_layout_output() {
match test_get_default_audiounit(Scope::Output) {
- Some(unit) => assert!(!audiounit_get_current_channel_layout(unit.get_inner())
- .unwrap()
- .is_empty()),
+ Some(unit) => {
+ assert!(
+ !run_serially_forward_panics(|| audiounit_get_current_channel_layout(
+ unit.get_inner()
+ ))
+ .unwrap()
+ .is_empty()
+ )
+ }
None => println!("No output audiounit for test."),
}
}
@@ -814,10 +834,30 @@ fn test_enable_audiounit_scope() {
// for the unit whose subtype is kAudioUnitSubType_HALOutput
// even when there is no available input or output devices.
if let Some(unit) = test_create_audiounit(ComponentSubType::HALOutput) {
- assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, true).is_ok());
- assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, false).is_ok());
- assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, true).is_ok());
- assert!(enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, false).is_ok());
+ assert!(run_serially_forward_panics(|| enable_audiounit_scope(
+ unit.get_inner(),
+ DeviceType::OUTPUT,
+ true
+ ))
+ .is_ok());
+ assert!(run_serially_forward_panics(|| enable_audiounit_scope(
+ unit.get_inner(),
+ DeviceType::OUTPUT,
+ false
+ ))
+ .is_ok());
+ assert!(run_serially_forward_panics(|| enable_audiounit_scope(
+ unit.get_inner(),
+ DeviceType::INPUT,
+ true
+ ))
+ .is_ok());
+ assert!(run_serially_forward_panics(|| enable_audiounit_scope(
+ unit.get_inner(),
+ DeviceType::INPUT,
+ false
+ ))
+ .is_ok());
} else {
println!("No audiounit to perform test.");
}
@@ -827,19 +867,23 @@ fn test_enable_audiounit_scope() {
fn test_enable_audiounit_scope_for_default_output_unit() {
if let Some(unit) = test_create_audiounit(ComponentSubType::DefaultOutput) {
assert_eq!(
- enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, true).unwrap_err(),
+ run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, true))
+ .unwrap_err(),
kAudioUnitErr_InvalidProperty
);
assert_eq!(
- enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, false).unwrap_err(),
+ run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::OUTPUT, false))
+ .unwrap_err(),
kAudioUnitErr_InvalidProperty
);
assert_eq!(
- enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, true).unwrap_err(),
+ run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, true))
+ .unwrap_err(),
kAudioUnitErr_InvalidProperty
);
assert_eq!(
- enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, false).unwrap_err(),
+ run_serially(|| enable_audiounit_scope(unit.get_inner(), DeviceType::INPUT, false))
+ .unwrap_err(),
kAudioUnitErr_InvalidProperty
);
}
@@ -849,7 +893,10 @@ fn test_enable_audiounit_scope_for_default_output_unit() {
#[should_panic]
fn test_enable_audiounit_scope_with_null_unit() {
let unit: AudioUnit = ptr::null_mut();
- assert!(enable_audiounit_scope(unit, DeviceType::INPUT, false).is_err());
+ assert!(
+ run_serially_forward_panics(|| enable_audiounit_scope(unit, DeviceType::INPUT, false))
+ .is_err()
+ );
}
// create_audiounit
@@ -868,29 +915,29 @@ fn test_for_create_audiounit() {
// Check the output scope is enabled.
if device.flags.contains(device_flags::DEV_OUTPUT) && default_output.is_some() {
device.id = default_output.unwrap();
- let unit = create_audiounit(&device).unwrap();
+ let unit = run_serially(|| create_audiounit(&device).unwrap());
assert!(!unit.is_null());
assert!(test_audiounit_scope_is_enabled(unit, Scope::Output));
// Destroy the AudioUnit.
- unsafe {
+ run_serially(|| unsafe {
AudioUnitUninitialize(unit);
AudioComponentInstanceDispose(unit);
- }
+ });
}
// Check the input scope is enabled.
if device.flags.contains(device_flags::DEV_INPUT) && default_input.is_some() {
let device_id = default_input.unwrap();
device.id = device_id;
- let unit = create_audiounit(&device).unwrap();
+ let unit = run_serially(|| create_audiounit(&device).unwrap());
assert!(!unit.is_null());
assert!(test_audiounit_scope_is_enabled(unit, Scope::Input));
// Destroy the AudioUnit.
- unsafe {
+ run_serially(|| unsafe {
AudioUnitUninitialize(unit);
AudioComponentInstanceDispose(unit);
- }
+ });
}
}
}
@@ -899,7 +946,7 @@ fn test_for_create_audiounit() {
#[should_panic]
fn test_create_audiounit_with_unknown_scope() {
let device = device_info::default();
- let _unit = create_audiounit(&device);
+ let _unit = run_serially_forward_panics(|| create_audiounit(&device));
}
// set_buffer_size_sync
@@ -927,9 +974,12 @@ fn test_set_buffer_size_sync() {
.unwrap();
assert_ne!(buffer_frames, 0);
buffer_frames *= 2;
- assert!(
- set_buffer_size_sync(unit.get_inner(), scope.clone().into(), buffer_frames).is_ok()
- );
+ assert!(run_serially(|| set_buffer_size_sync(
+ unit.get_inner(),
+ scope.clone().into(),
+ buffer_frames
+ ))
+ .is_ok());
let new_buffer_frames =
test_audiounit_get_buffer_frame_size(unit.get_inner(), scope.clone(), prop_scope)
.unwrap();
@@ -951,7 +1001,9 @@ fn test_set_buffer_size_sync_for_output_with_null_output_unit() {
fn test_set_buffer_size_sync_by_scope_with_null_unit(scope: Scope) {
let unit: AudioUnit = ptr::null_mut();
- assert!(set_buffer_size_sync(unit, scope.into(), 2048).is_err());
+ assert!(
+ run_serially_forward_panics(|| set_buffer_size_sync(unit, scope.into(), 2048)).is_err()
+ );
}
// get_volume, set_volume
@@ -960,8 +1012,11 @@ fn test_set_buffer_size_sync_by_scope_with_null_unit(scope: Scope) {
fn test_stream_get_volume() {
if let Some(unit) = test_get_default_audiounit(Scope::Output) {
let expected_volume: f32 = 0.5;
- set_volume(unit.get_inner(), expected_volume);
- assert_eq!(expected_volume, get_volume(unit.get_inner()).unwrap());
+ run_serially(|| set_volume(unit.get_inner(), expected_volume));
+ assert_eq!(
+ expected_volume,
+ run_serially(|| get_volume(unit.get_inner()).unwrap())
+ );
} else {
println!("No output audiounit.");
}
@@ -988,7 +1043,9 @@ fn test_get_channel_count() {
fn test_channel_count(scope: Scope) {
if let Some(device) = test_get_default_device(scope.clone()) {
- let channels = get_channel_count(device, DeviceType::from(scope.clone())).unwrap();
+ let channels =
+ run_serially(|| get_channel_count(device, DeviceType::from(scope.clone())))
+ .unwrap();
assert!(channels > 0);
assert_eq!(
channels,
@@ -1008,7 +1065,7 @@ fn test_get_channel_count_of_input_for_a_output_only_deivce() {
if test_device_in_scope(device, Scope::Input) {
continue;
}
- let count = get_channel_count(device, DeviceType::INPUT).unwrap();
+ let count = run_serially(|| get_channel_count(device, DeviceType::INPUT)).unwrap();
assert_eq!(count, 0);
}
}
@@ -1021,7 +1078,7 @@ fn test_get_channel_count_of_output_for_a_input_only_deivce() {
if test_device_in_scope(device, Scope::Output) {
continue;
}
- let count = get_channel_count(device, DeviceType::OUTPUT).unwrap();
+ let count = run_serially(|| get_channel_count(device, DeviceType::OUTPUT)).unwrap();
assert_eq!(count, 0);
}
}
@@ -1029,7 +1086,11 @@ fn test_get_channel_count_of_output_for_a_input_only_deivce() {
#[test]
#[should_panic]
fn test_get_channel_count_of_unknown_device() {
- assert!(get_channel_count(kAudioObjectUnknown, DeviceType::OUTPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_channel_count(
+ kAudioObjectUnknown,
+ DeviceType::OUTPUT
+ ))
+ .is_err());
}
#[test]
@@ -1039,14 +1100,16 @@ fn test_get_channel_count_of_inout_type() {
fn test_channel_count(scope: Scope) {
if let Some(device) = test_get_default_device(scope.clone()) {
- assert_eq!(
- get_channel_count(device, DeviceType::INPUT | DeviceType::OUTPUT),
- get_channel_count(device, DeviceType::INPUT).map(|c| c + get_channel_count(
- device,
- DeviceType::OUTPUT
- )
- .unwrap_or(0))
- );
+ run_serially_forward_panics(|| {
+ assert_eq!(
+ get_channel_count(device, DeviceType::INPUT | DeviceType::OUTPUT),
+ get_channel_count(device, DeviceType::INPUT).map(|c| c + get_channel_count(
+ device,
+ DeviceType::OUTPUT
+ )
+ .unwrap_or(0))
+ );
+ });
} else {
println!("No device for {:?}.", scope);
}
@@ -1095,7 +1158,9 @@ fn test_get_range_of_sample_rates() {
];
let mut ranges = Vec::new();
for scope in scopes.iter() {
- ranges.push(get_range_of_sample_rates(id, *scope).unwrap());
+ ranges.push(
+ run_serially_forward_panics(|| get_range_of_sample_rates(id, *scope)).unwrap(),
+ );
}
ranges
}
@@ -1117,7 +1182,7 @@ fn test_get_device_presentation_latency() {
fn test_get_device_presentation_latencies_in_scope(scope: Scope) {
if let Some(device) = test_get_default_device(scope.clone()) {
// TODO: The latencies very from devices to devices. Check nothing here.
- let latency = get_fixed_latency(device, scope.clone().into());
+ let latency = run_serially(|| get_fixed_latency(device, scope.clone().into()));
println!(
"present latency on the device {} in scope {:?}: {}",
device, scope, latency
@@ -1133,7 +1198,7 @@ fn test_get_device_presentation_latency() {
#[test]
fn test_get_device_group_id() {
if let Some(device) = test_get_default_device(Scope::Input) {
- match get_device_group_id(device, DeviceType::INPUT) {
+ match run_serially(|| get_device_group_id(device, DeviceType::INPUT)) {
Ok(id) => println!("input group id: {:?}", id),
Err(e) => println!("No input group id. Error: {}", e),
}
@@ -1142,7 +1207,7 @@ fn test_get_device_group_id() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
- match get_device_group_id(device, DeviceType::OUTPUT) {
+ match run_serially(|| get_device_group_id(device, DeviceType::OUTPUT)) {
Ok(id) => println!("output group id: {:?}", id),
Err(e) => println!("No output group id. Error: {}", e),
}
@@ -1165,8 +1230,8 @@ fn test_get_same_group_id_for_builtin_device_pairs() {
let mut input_group_ids = HashMap::<u32, String>::new();
let input_devices = test_get_devices_in_scope(Scope::Input);
for device in input_devices.iter() {
- match get_device_source(*device, DeviceType::INPUT) {
- Ok(source) => match get_device_group_id(*device, DeviceType::INPUT) {
+ match run_serially(|| get_device_source(*device, DeviceType::INPUT)) {
+ Ok(source) => match run_serially(|| get_device_group_id(*device, DeviceType::INPUT)) {
Ok(id) => assert!(input_group_ids
.insert(source, id.into_string().unwrap())
.is_none()),
@@ -1181,8 +1246,8 @@ fn test_get_same_group_id_for_builtin_device_pairs() {
let mut output_group_ids = HashMap::<u32, String>::new();
let output_devices = test_get_devices_in_scope(Scope::Output);
for device in output_devices.iter() {
- match get_device_source(*device, DeviceType::OUTPUT) {
- Ok(source) => match get_device_group_id(*device, DeviceType::OUTPUT) {
+ match run_serially(|| get_device_source(*device, DeviceType::OUTPUT)) {
+ Ok(source) => match run_serially(|| get_device_group_id(*device, DeviceType::OUTPUT)) {
Ok(id) => assert!(output_group_ids
.insert(source, id.into_string().unwrap())
.is_none()),
@@ -1210,7 +1275,11 @@ fn test_get_same_group_id_for_builtin_device_pairs() {
#[test]
#[should_panic]
fn test_get_device_group_id_by_unknown_device() {
- assert!(get_device_group_id(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_device_group_id(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err());
}
// get_device_label
@@ -1218,14 +1287,14 @@ fn test_get_device_group_id_by_unknown_device() {
#[test]
fn test_get_device_label() {
if let Some(device) = test_get_default_device(Scope::Input) {
- let name = get_device_label(device, DeviceType::INPUT).unwrap();
+ let name = run_serially(|| get_device_label(device, DeviceType::INPUT)).unwrap();
println!("input device label: {}", name.into_string());
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
- let name = get_device_label(device, DeviceType::OUTPUT).unwrap();
+ let name = run_serially(|| get_device_label(device, DeviceType::OUTPUT)).unwrap();
println!("output device label: {}", name.into_string());
} else {
println!("No output device.");
@@ -1235,7 +1304,11 @@ fn test_get_device_label() {
#[test]
#[should_panic]
fn test_get_device_label_by_unknown_device() {
- assert!(get_device_label(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_device_label(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err());
}
// get_device_global_uid
@@ -1244,14 +1317,14 @@ fn test_get_device_label_by_unknown_device() {
fn test_get_device_global_uid() {
// Input device.
if let Some(input) = test_get_default_device(Scope::Input) {
- let uid = get_device_global_uid(input).unwrap();
+ let uid = run_serially(|| get_device_global_uid(input)).unwrap();
let uid = uid.into_string();
assert!(!uid.is_empty());
}
// Output device.
if let Some(output) = test_get_default_device(Scope::Output) {
- let uid = get_device_global_uid(output).unwrap();
+ let uid = run_serially(|| get_device_global_uid(output)).unwrap();
let uid = uid.into_string();
assert!(!uid.is_empty());
}
@@ -1285,7 +1358,7 @@ fn test_create_cubeb_device_info() {
if is_input {
let mut input_device_info = input_result.unwrap();
check_device_info_by_device(&input_device_info, device, Scope::Input);
- destroy_cubeb_device_info(&mut input_device_info);
+ run_serially(|| destroy_cubeb_device_info(&mut input_device_info));
} else {
assert_eq!(input_result.unwrap_err(), Error::error());
}
@@ -1294,7 +1367,7 @@ fn test_create_cubeb_device_info() {
if is_output {
let mut output_device_info = output_result.unwrap();
check_device_info_by_device(&output_device_info, device, Scope::Output);
- destroy_cubeb_device_info(&mut output_device_info);
+ run_serially(|| destroy_cubeb_device_info(&mut output_device_info));
} else {
assert_eq!(output_result.unwrap_err(), Error::error());
}
@@ -1309,7 +1382,7 @@ fn test_create_cubeb_device_info() {
let dev_types = [DeviceType::INPUT, DeviceType::OUTPUT];
let mut results = VecDeque::new();
for dev_type in dev_types.iter() {
- results.push_back(create_cubeb_device_info(id, *dev_type));
+ results.push_back(run_serially(|| create_cubeb_device_info(id, *dev_type)));
}
results
}
@@ -1369,7 +1442,9 @@ fn test_create_device_info_with_unknown_type() {
fn test_create_device_info_with_unknown_type_by_scope(scope: Scope) {
if let Some(device) = test_get_default_device(scope.clone()) {
- assert!(create_cubeb_device_info(device, DeviceType::UNKNOWN).is_err());
+ assert!(
+ run_serially(|| create_cubeb_device_info(device, DeviceType::UNKNOWN)).is_err()
+ );
}
}
}
@@ -1401,9 +1476,11 @@ fn test_create_device_from_hwdev_with_inout_type() {
fn test_create_device_from_hwdev_with_inout_type_by_scope(scope: Scope) {
if let Some(device) = test_get_default_device(scope.clone()) {
// Get a kAudioHardwareUnknownPropertyError in get_channel_count actually.
- assert!(
- create_cubeb_device_info(device, DeviceType::INPUT | DeviceType::OUTPUT).is_err()
- );
+ assert!(run_serially(|| create_cubeb_device_info(
+ device,
+ DeviceType::INPUT | DeviceType::OUTPUT
+ ))
+ .is_err());
} else {
println!("No device for {:?}.", scope);
}
@@ -1416,9 +1493,10 @@ fn test_create_device_from_hwdev_with_inout_type() {
fn test_get_devices_of_type() {
use std::collections::HashSet;
- let all_devices = audiounit_get_devices_of_type(DeviceType::INPUT | DeviceType::OUTPUT);
- let input_devices = audiounit_get_devices_of_type(DeviceType::INPUT);
- let output_devices = audiounit_get_devices_of_type(DeviceType::OUTPUT);
+ let all_devices =
+ run_serially(|| audiounit_get_devices_of_type(DeviceType::INPUT | DeviceType::OUTPUT));
+ let input_devices = run_serially(|| audiounit_get_devices_of_type(DeviceType::INPUT));
+ let output_devices = run_serially(|| audiounit_get_devices_of_type(DeviceType::OUTPUT));
let mut expected_all = test_get_all_devices(DeviceFilter::ExcludeCubebAggregateAndVPIO);
expected_all.sort();
@@ -1443,8 +1521,10 @@ fn test_get_devices_of_type() {
#[test]
#[should_panic]
fn test_get_devices_of_type_unknown() {
- let no_devs = audiounit_get_devices_of_type(DeviceType::UNKNOWN);
- assert!(no_devs.is_empty());
+ run_serially_forward_panics(|| {
+ let no_devs = audiounit_get_devices_of_type(DeviceType::UNKNOWN);
+ assert!(no_devs.is_empty());
+ });
}
// add_devices_changed_listener
@@ -1468,9 +1548,10 @@ fn test_add_devices_changed_listener() {
assert!(get_devices_changed_callback(context, Scope::Output).is_none());
// Register a callback within a specific scope.
- assert!(context
- .add_devices_changed_listener(*devtype, Some(*callback), ptr::null_mut())
- .is_ok());
+ assert!(run_serially(|| {
+ context.add_devices_changed_listener(*devtype, Some(*callback), ptr::null_mut())
+ })
+ .is_ok());
if devtype.contains(DeviceType::INPUT) {
let cb = get_devices_changed_callback(context, Scope::Input);
@@ -1491,9 +1572,10 @@ fn test_add_devices_changed_listener() {
}
// Unregister the callbacks within all scopes.
- assert!(context
- .remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
- .is_ok());
+ assert!(run_serially(|| {
+ context.remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
+ })
+ .is_ok());
assert!(get_devices_changed_callback(context, Scope::Input).is_none());
assert!(get_devices_changed_callback(context, Scope::Output).is_none());
@@ -1547,9 +1629,12 @@ fn test_remove_devices_changed_listener() {
// Register callbacks within all scopes.
for (scope, listener) in map.iter() {
- assert!(context
- .add_devices_changed_listener(*scope, Some(*listener), ptr::null_mut())
- .is_ok());
+ assert!(run_serially(|| context.add_devices_changed_listener(
+ *scope,
+ Some(*listener),
+ ptr::null_mut()
+ ))
+ .is_ok());
}
let input_callback = get_devices_changed_callback(context, Scope::Input);
@@ -1566,7 +1651,7 @@ fn test_remove_devices_changed_listener() {
);
// Unregister the callbacks within one specific scopes.
- assert!(context.remove_devices_changed_listener(*devtype).is_ok());
+ assert!(run_serially(|| context.remove_devices_changed_listener(*devtype)).is_ok());
if devtype.contains(DeviceType::INPUT) {
let cb = get_devices_changed_callback(context, Scope::Input);
@@ -1587,9 +1672,10 @@ fn test_remove_devices_changed_listener() {
}
// Unregister the callbacks within all scopes.
- assert!(context
- .remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
- .is_ok());
+ assert!(run_serially(
+ || context.remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
+ )
+ .is_ok());
}
});
}
@@ -1602,7 +1688,7 @@ fn test_remove_devices_changed_listener_without_adding_listeners() {
DeviceType::OUTPUT,
DeviceType::INPUT | DeviceType::OUTPUT,
] {
- assert!(context.remove_devices_changed_listener(*devtype).is_ok());
+ assert!(run_serially(|| context.remove_devices_changed_listener(*devtype)).is_ok());
}
});
}
@@ -1625,9 +1711,12 @@ fn test_remove_devices_changed_listener_within_all_scopes() {
assert!(get_devices_changed_callback(context, Scope::Input).is_none());
assert!(get_devices_changed_callback(context, Scope::Output).is_none());
- assert!(context
- .add_devices_changed_listener(*devtype, Some(*callback), ptr::null_mut())
- .is_ok());
+ assert!(run_serially(|| context.add_devices_changed_listener(
+ *devtype,
+ Some(*callback),
+ ptr::null_mut()
+ ))
+ .is_ok());
if devtype.contains(DeviceType::INPUT) {
let cb = get_devices_changed_callback(context, Scope::Input);
@@ -1641,9 +1730,10 @@ fn test_remove_devices_changed_listener_within_all_scopes() {
assert_eq!(cb.unwrap(), *callback);
}
- assert!(context
- .remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
- .is_ok());
+ assert!(run_serially(
+ || context.remove_devices_changed_listener(DeviceType::INPUT | DeviceType::OUTPUT)
+ )
+ .is_ok());
assert!(get_devices_changed_callback(context, Scope::Input).is_none());
assert!(get_devices_changed_callback(context, Scope::Output).is_none());
@@ -1661,3 +1751,135 @@ fn get_devices_changed_callback(
Scope::Output => devices_guard.output.changed_callback,
}
}
+
+// SharedVoiceProcessingUnitManager
+// ------------------------------------
+#[test]
+fn test_shared_voice_processing_unit() {
+ let queue = Queue::new_with_target(
+ "test_shared_voice_processing_unit",
+ get_serial_queue_singleton(),
+ );
+ let mut shared = SharedVoiceProcessingUnitManager::new(queue.clone());
+ let r1 = queue.run_sync(|| shared.take()).unwrap();
+ assert!(r1.is_err());
+ let r2 = queue.run_sync(|| shared.take_or_create()).unwrap();
+ assert!(r2.is_ok());
+ {
+ let _handle = r2.unwrap();
+ let r3 = queue.run_sync(|| shared.take()).unwrap();
+ assert!(r3.is_err());
+ }
+ let r4 = queue.run_sync(|| shared.take()).unwrap();
+ assert!(r4.is_ok());
+}
+
+#[test]
+#[should_panic]
+fn test_shared_voice_processing_unit_bad_release_order() {
+ let queue = Queue::new_with_target(
+ "test_shared_voice_processing_unit_bad_release_order",
+ get_serial_queue_singleton(),
+ );
+ let mut shared = SharedVoiceProcessingUnitManager::new(queue.clone());
+ let r1 = queue.run_sync(|| shared.take()).unwrap();
+ assert!(r1.is_ok());
+ drop(shared);
+ run_serially_forward_panics(|| drop(r1));
+}
+
+#[test]
+fn test_shared_voice_processing_multiple_units() {
+ let queue = Queue::new_with_target(
+ "test_shared_voice_processing_multiple_units",
+ get_serial_queue_singleton(),
+ );
+ let mut shared = SharedVoiceProcessingUnitManager::new(queue.clone());
+ let r1 = queue.run_sync(|| shared.take_or_create()).unwrap();
+ assert!(r1.is_ok());
+ let r2 = queue.run_sync(|| shared.take_or_create()).unwrap();
+ assert!(r2.is_ok());
+ {
+ let _handle1 = r1.unwrap();
+ let _handle2 = r2.unwrap();
+ let r3 = queue.run_sync(|| shared.take()).unwrap();
+ assert!(r3.is_err());
+ }
+ let r1 = queue.run_sync(|| shared.take()).unwrap();
+ assert!(r1.is_ok());
+ let r2 = queue.run_sync(|| shared.take()).unwrap();
+ assert!(r2.is_ok());
+ let r3 = queue.run_sync(|| shared.take()).unwrap();
+ assert!(r3.is_err());
+}
+
+#[test]
+fn test_shared_voice_processing_release_on_idle() {
+ let queue = Queue::new_with_target(
+ "test_shared_voice_processing_release_on_idle",
+ get_serial_queue_singleton(),
+ );
+ let mut shared = SharedVoiceProcessingUnitManager::with_idle_timeout(
+ queue.clone(),
+ Duration::from_millis(0),
+ );
+ let r = queue.run_sync(|| shared.take_or_create()).unwrap();
+ assert!(r.is_ok());
+ {
+ let _handle = r.unwrap();
+ }
+ queue.run_sync(|| {});
+ let r = queue.run_sync(|| shared.take()).unwrap();
+ assert!(r.is_err());
+}
+
+#[test]
+fn test_shared_voice_processing_no_release_on_outstanding() {
+ let queue = Queue::new_with_target(
+ "test_shared_voice_processing_no_release_on_outstanding",
+ get_serial_queue_singleton(),
+ );
+ let mut shared = SharedVoiceProcessingUnitManager::with_idle_timeout(
+ queue.clone(),
+ Duration::from_millis(0),
+ );
+ let r1 = queue.run_sync(|| shared.take_or_create()).unwrap();
+ assert!(r1.is_ok());
+ let r2 = queue.run_sync(|| shared.take_or_create()).unwrap();
+ assert!(r2.is_ok());
+ {
+ let _handle1 = r1.unwrap();
+ }
+ queue.run_sync(|| {});
+ let r1 = queue.run_sync(|| shared.take()).unwrap();
+ assert!(r1.is_ok());
+}
+
+#[test]
+fn test_shared_voice_processing_release_on_idle_cancel_on_take() {
+ let queue = Queue::new_with_target(
+ "test_shared_voice_processing_release_on_idle_cancel_on_take",
+ get_serial_queue_singleton(),
+ );
+ let mut shared = SharedVoiceProcessingUnitManager::with_idle_timeout(
+ queue.clone(),
+ Duration::from_millis(0),
+ );
+ let r1 = queue.run_sync(|| shared.take_or_create()).unwrap();
+ assert!(r1.is_ok());
+ let r2 = queue.run_sync(|| shared.take_or_create()).unwrap();
+ assert!(r2.is_ok());
+ let r1 = queue
+ .run_sync(|| {
+ {
+ let _handle1 = r1.unwrap();
+ let _handle2 = r2.unwrap();
+ }
+ shared.take()
+ })
+ .unwrap();
+ assert!(r1.is_ok());
+ queue.run_sync(|| {});
+ let r2 = queue.run_sync(|| shared.take()).unwrap();
+ assert!(r2.is_ok());
+}
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/device_change.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/device_change.rs
index c27dada7ad..1201c446ea 100644
--- a/third_party/rust/cubeb-coreaudio/src/backend/tests/device_change.rs
+++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/device_change.rs
@@ -21,7 +21,7 @@ use super::utils::{
test_set_default_device, Scope, StreamType, TestDevicePlugger, TestDeviceSwitcher,
};
use super::*;
-use std::sync::{LockResult, MutexGuard, WaitTimeoutResult};
+use std::sync::{LockResult, WaitTimeoutResult};
// Switch default devices used by the active streams, to test stream reinitialization
// ================================================================================================
@@ -49,19 +49,21 @@ fn test_switch_device_in_scope(scope: Scope) {
let notifier = Arc::new(Notifier::new(0));
let also_notifier = notifier.clone();
- let listener = test_create_device_change_listener(scope.clone(), move |_addresses| {
- let mut cnt = notifier.lock().unwrap();
- *cnt += 1;
- notifier.notify(cnt);
- NO_ERR
+ let listener = run_serially(|| {
+ test_create_device_change_listener(scope.clone(), move |_addresses| {
+ let mut cnt = notifier.lock().unwrap();
+ *cnt += 1;
+ notifier.notify(cnt);
+ NO_ERR
+ })
});
- listener.start();
+ run_serially(|| listener.start());
let changed_watcher = Watcher::new(&also_notifier);
test_get_started_stream_in_scope(scope.clone(), move |_stream| loop {
- let mut guard = changed_watcher.lock().unwrap();
- let start_cnt = guard.clone();
+ let start_cnt = changed_watcher.lock().unwrap().clone();
device_switcher.next();
+ let mut guard = changed_watcher.lock().unwrap();
guard = changed_watcher
.wait_while(guard, |cnt| *cnt == start_cnt)
.unwrap();
@@ -709,11 +711,7 @@ fn test_unplug_a_device_on_an_active_stream(
state_callback,
device_changed_callback,
|stream| {
- stream.start();
-
- let changed_watcher = Watcher::new(&notifier);
- let mut data_guard = notifier.lock().unwrap();
- assert_eq!(data_guard.states.last().unwrap(), &ffi::CUBEB_STATE_STARTED);
+ assert_eq!(stream.start(), Ok(()));
println!(
"Stream runs on the device {} for {:?}",
@@ -722,13 +720,20 @@ fn test_unplug_a_device_on_an_active_stream(
);
let dev = plugger.get_device_id();
- let start_changed_count = data_guard.changed_count.clone();
+ let start_changed_count = {
+ let guard = notifier.lock().unwrap();
+ assert_eq!(guard.states.last().unwrap(), &ffi::CUBEB_STATE_STARTED);
+ guard.changed_count.clone()
+ };
assert!(plugger.unplug().is_ok());
+ let changed_watcher = Watcher::new(&notifier);
+
if set_device_to_default {
// The stream will be reinitialized if it follows the default input or output device.
println!("Waiting for default device to change and reinit");
+ let mut data_guard = notifier.lock().unwrap();
data_guard = changed_watcher
.wait_while(data_guard, |data| {
data.changed_count == start_changed_count
@@ -740,6 +745,7 @@ fn test_unplug_a_device_on_an_active_stream(
// stream can be dropped immediately before device-changed callback
// so we only check the states if we wait for it explicitly.
println!("Waiting for non-default device to enter error state");
+ let mut data_guard = notifier.lock().unwrap();
let (new_guard, timeout_res) = changed_watcher
.wait_timeout_while(data_guard, Duration::from_millis(wait_up_to_ms), |data| {
data.states.last().unwrap_or(&ffi::CUBEB_STATE_STARTED)
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/device_property.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/device_property.rs
index 8277a7642d..a974aee64b 100644
--- a/third_party/rust/cubeb-coreaudio/src/backend/tests/device_property.rs
+++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/device_property.rs
@@ -7,14 +7,14 @@ use super::*;
fn test_get_device_uid() {
// Input device.
if let Some(input) = test_get_default_device(Scope::Input) {
- let uid = get_device_uid(input, DeviceType::INPUT).unwrap();
+ let uid = run_serially(|| get_device_uid(input, DeviceType::INPUT)).unwrap();
let uid = uid.into_string();
assert!(!uid.is_empty());
}
// Output device.
if let Some(output) = test_get_default_device(Scope::Output) {
- let uid = get_device_uid(output, DeviceType::OUTPUT).unwrap();
+ let uid = run_serially(|| get_device_uid(output, DeviceType::OUTPUT)).unwrap();
let uid = uid.into_string();
assert!(!uid.is_empty());
}
@@ -24,7 +24,10 @@ fn test_get_device_uid() {
#[should_panic]
fn test_get_device_uid_by_unknwon_device() {
// Unknown device.
- assert!(get_device_uid(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(
+ run_serially_forward_panics(|| get_device_uid(kAudioObjectUnknown, DeviceType::INPUT))
+ .is_err()
+ );
}
// get_device_model_uid
@@ -33,7 +36,7 @@ fn test_get_device_uid_by_unknwon_device() {
#[test]
fn test_get_device_model_uid() {
if let Some(device) = test_get_default_device(Scope::Input) {
- match get_device_model_uid(device, DeviceType::INPUT) {
+ match run_serially(|| get_device_model_uid(device, DeviceType::INPUT)) {
Ok(uid) => println!("input model uid: {}", uid.into_string()),
Err(e) => println!("No input model uid. Error: {}", e),
}
@@ -42,7 +45,7 @@ fn test_get_device_model_uid() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
- match get_device_model_uid(device, DeviceType::OUTPUT) {
+ match run_serially(|| get_device_model_uid(device, DeviceType::OUTPUT)) {
Ok(uid) => println!("output model uid: {}", uid.into_string()),
Err(e) => println!("No output model uid. Error: {}", e),
}
@@ -54,7 +57,11 @@ fn test_get_device_model_uid() {
#[test]
#[should_panic]
fn test_get_device_model_uid_by_unknown_device() {
- assert!(get_device_model_uid(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_device_model_uid(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err());
}
// get_device_transport_type
@@ -62,7 +69,7 @@ fn test_get_device_model_uid_by_unknown_device() {
#[test]
fn test_get_device_transport_type() {
if let Some(device) = test_get_default_device(Scope::Input) {
- match get_device_transport_type(device, DeviceType::INPUT) {
+ match run_serially(|| get_device_transport_type(device, DeviceType::INPUT)) {
Ok(trans_type) => println!(
"input transport type: {:X}, {:?}",
trans_type,
@@ -75,7 +82,7 @@ fn test_get_device_transport_type() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
- match get_device_transport_type(device, DeviceType::OUTPUT) {
+ match run_serially(|| get_device_transport_type(device, DeviceType::OUTPUT)) {
Ok(trans_type) => println!(
"output transport type: {:X}, {:?}",
trans_type,
@@ -91,7 +98,11 @@ fn test_get_device_transport_type() {
#[test]
#[should_panic]
fn test_get_device_transport_type_by_unknown_device() {
- assert!(get_device_transport_type(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_device_transport_type(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err());
}
// get_device_source
@@ -100,7 +111,7 @@ fn test_get_device_transport_type_by_unknown_device() {
#[test]
fn test_get_device_source() {
if let Some(device) = test_get_default_device(Scope::Input) {
- match get_device_source(device, DeviceType::INPUT) {
+ match run_serially(|| get_device_source(device, DeviceType::INPUT)) {
Ok(source) => println!(
"input source: {:X}, {:?}",
source,
@@ -113,7 +124,7 @@ fn test_get_device_source() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
- match get_device_source(device, DeviceType::OUTPUT) {
+ match run_serially(|| get_device_source(device, DeviceType::OUTPUT)) {
Ok(source) => println!(
"output source: {:X}, {:?}",
source,
@@ -129,7 +140,11 @@ fn test_get_device_source() {
#[test]
#[should_panic]
fn test_get_device_source_by_unknown_device() {
- assert!(get_device_source(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_device_source(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err());
}
// get_device_source_name
@@ -137,7 +152,7 @@ fn test_get_device_source_by_unknown_device() {
#[test]
fn test_get_device_source_name() {
if let Some(device) = test_get_default_device(Scope::Input) {
- match get_device_source_name(device, DeviceType::INPUT) {
+ match run_serially(|| get_device_source_name(device, DeviceType::INPUT)) {
Ok(name) => println!("input: {}", name.into_string()),
Err(e) => println!("No input data source name. Error: {}", e),
}
@@ -146,7 +161,7 @@ fn test_get_device_source_name() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
- match get_device_source_name(device, DeviceType::OUTPUT) {
+ match run_serially(|| get_device_source_name(device, DeviceType::OUTPUT)) {
Ok(name) => println!("output: {}", name.into_string()),
Err(e) => println!("No output data source name. Error: {}", e),
}
@@ -158,7 +173,11 @@ fn test_get_device_source_name() {
#[test]
#[should_panic]
fn test_get_device_source_name_by_unknown_device() {
- assert!(get_device_source_name(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_device_source_name(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err());
}
// get_device_name
@@ -166,14 +185,14 @@ fn test_get_device_source_name_by_unknown_device() {
#[test]
fn test_get_device_name() {
if let Some(device) = test_get_default_device(Scope::Input) {
- let name = get_device_name(device, DeviceType::INPUT).unwrap();
+ let name = run_serially(|| get_device_name(device, DeviceType::INPUT)).unwrap();
println!("input device name: {}", name.into_string());
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
- let name = get_device_name(device, DeviceType::OUTPUT).unwrap();
+ let name = run_serially(|| get_device_name(device, DeviceType::OUTPUT).unwrap());
println!("output device name: {}", name.into_string());
} else {
println!("No output device.");
@@ -183,7 +202,11 @@ fn test_get_device_name() {
#[test]
#[should_panic]
fn test_get_device_name_by_unknown_device() {
- assert!(get_device_name(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_device_name(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err());
}
// get_device_manufacturer
@@ -193,7 +216,7 @@ fn test_get_device_manufacturer() {
if let Some(device) = test_get_default_device(Scope::Input) {
// Some devices like AirPods cannot get the vendor info so we print the error directly.
// TODO: Replace `map` and `unwrap_or_else` by `map_or_else`
- let name = get_device_manufacturer(device, DeviceType::INPUT)
+ let name = run_serially(|| get_device_manufacturer(device, DeviceType::INPUT))
.map(|name| name.into_string())
.unwrap_or_else(|e| format!("Error: {}", e));
println!("input device vendor: {}", name);
@@ -204,9 +227,10 @@ fn test_get_device_manufacturer() {
if let Some(device) = test_get_default_device(Scope::Output) {
// Some devices like AirPods cannot get the vendor info so we print the error directly.
// TODO: Replace `map` and `unwrap_or_else` by `map_or_else`
- let name = get_device_manufacturer(device, DeviceType::OUTPUT)
- .map(|name| name.into_string())
- .unwrap_or_else(|e| format!("Error: {}", e));
+ let name =
+ run_serially_forward_panics(|| get_device_manufacturer(device, DeviceType::OUTPUT))
+ .map(|name| name.into_string())
+ .unwrap_or_else(|e| format!("Error: {}", e));
println!("output device vendor: {}", name);
} else {
println!("No output device.");
@@ -216,7 +240,11 @@ fn test_get_device_manufacturer() {
#[test]
#[should_panic]
fn test_get_device_manufacturer_by_unknown_device() {
- assert!(get_device_manufacturer(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_device_manufacturer(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err());
}
// get_device_buffer_frame_size_range
@@ -224,7 +252,8 @@ fn test_get_device_manufacturer_by_unknown_device() {
#[test]
fn test_get_device_buffer_frame_size_range() {
if let Some(device) = test_get_default_device(Scope::Input) {
- let range = get_device_buffer_frame_size_range(device, DeviceType::INPUT).unwrap();
+ let range =
+ run_serially(|| get_device_buffer_frame_size_range(device, DeviceType::INPUT)).unwrap();
println!(
"range of input buffer frame size: {}-{}",
range.mMinimum, range.mMaximum
@@ -234,7 +263,8 @@ fn test_get_device_buffer_frame_size_range() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
- let range = get_device_buffer_frame_size_range(device, DeviceType::OUTPUT).unwrap();
+ let range = run_serially(|| get_device_buffer_frame_size_range(device, DeviceType::OUTPUT))
+ .unwrap();
println!(
"range of output buffer frame size: {}-{}",
range.mMinimum, range.mMaximum
@@ -247,7 +277,13 @@ fn test_get_device_buffer_frame_size_range() {
#[test]
#[should_panic]
fn test_get_device_buffer_frame_size_range_by_unknown_device() {
- assert!(get_device_buffer_frame_size_range(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(
+ run_serially_forward_panics(|| get_device_buffer_frame_size_range(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err()
+ );
}
// get_device_latency
@@ -255,14 +291,14 @@ fn test_get_device_buffer_frame_size_range_by_unknown_device() {
#[test]
fn test_get_device_latency() {
if let Some(device) = test_get_default_device(Scope::Input) {
- let latency = get_device_latency(device, DeviceType::INPUT).unwrap();
+ let latency = run_serially(|| get_device_latency(device, DeviceType::INPUT)).unwrap();
println!("latency of input device: {}", latency);
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
- let latency = get_device_latency(device, DeviceType::OUTPUT).unwrap();
+ let latency = run_serially(|| get_device_latency(device, DeviceType::OUTPUT)).unwrap();
println!("latency of output device: {}", latency);
} else {
println!("No output device.");
@@ -272,7 +308,11 @@ fn test_get_device_latency() {
#[test]
#[should_panic]
fn test_get_device_latency_by_unknown_device() {
- assert!(get_device_latency(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_device_latency(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err());
}
// get_device_streams
@@ -280,7 +320,7 @@ fn test_get_device_latency_by_unknown_device() {
#[test]
fn test_get_device_streams() {
if let Some(device) = test_get_default_device(Scope::Input) {
- let streams = get_device_streams(device, DeviceType::INPUT).unwrap();
+ let streams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap();
println!("streams on the input device: {:?}", streams);
assert!(!streams.is_empty());
} else {
@@ -288,7 +328,7 @@ fn test_get_device_streams() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
- let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap();
+ let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap();
println!("streams on the output device: {:?}", streams);
assert!(!streams.is_empty());
} else {
@@ -299,7 +339,11 @@ fn test_get_device_streams() {
#[test]
#[should_panic]
fn test_get_device_streams_by_unknown_device() {
- assert!(get_device_streams(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_device_streams(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err());
}
// get_device_sample_rate
@@ -307,14 +351,14 @@ fn test_get_device_streams_by_unknown_device() {
#[test]
fn test_get_device_sample_rate() {
if let Some(device) = test_get_default_device(Scope::Input) {
- let rate = get_device_sample_rate(device, DeviceType::INPUT).unwrap();
+ let rate = run_serially(|| get_device_sample_rate(device, DeviceType::INPUT)).unwrap();
println!("input sample rate: {}", rate);
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
- let rate = get_device_sample_rate(device, DeviceType::OUTPUT).unwrap();
+ let rate = run_serially(|| get_device_sample_rate(device, DeviceType::OUTPUT).unwrap());
println!("output sample rate: {}", rate);
} else {
println!("No output device.");
@@ -324,7 +368,11 @@ fn test_get_device_sample_rate() {
#[test]
#[should_panic]
fn test_get_device_sample_rate_by_unknown_device() {
- assert!(get_device_sample_rate(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(run_serially_forward_panics(|| get_device_sample_rate(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err());
}
// get_ranges_of_device_sample_rate
@@ -332,14 +380,16 @@ fn test_get_device_sample_rate_by_unknown_device() {
#[test]
fn test_get_ranges_of_device_sample_rate() {
if let Some(device) = test_get_default_device(Scope::Input) {
- let ranges = get_ranges_of_device_sample_rate(device, DeviceType::INPUT).unwrap();
+ let ranges =
+ run_serially(|| get_ranges_of_device_sample_rate(device, DeviceType::INPUT)).unwrap();
println!("ranges of input sample rate: {:?}", ranges);
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
- let ranges = get_ranges_of_device_sample_rate(device, DeviceType::OUTPUT).unwrap();
+ let ranges =
+ run_serially(|| get_ranges_of_device_sample_rate(device, DeviceType::OUTPUT)).unwrap();
println!("ranges of output sample rate: {:?}", ranges);
} else {
println!("No output device.");
@@ -349,7 +399,13 @@ fn test_get_ranges_of_device_sample_rate() {
#[test]
#[should_panic]
fn test_get_ranges_of_device_sample_rate_by_unknown_device() {
- assert!(get_ranges_of_device_sample_rate(kAudioObjectUnknown, DeviceType::INPUT).is_err());
+ assert!(
+ run_serially_forward_panics(|| get_ranges_of_device_sample_rate(
+ kAudioObjectUnknown,
+ DeviceType::INPUT
+ ))
+ .is_err()
+ );
}
// get_stream_latency
@@ -357,9 +413,9 @@ fn test_get_ranges_of_device_sample_rate_by_unknown_device() {
#[test]
fn test_get_stream_latency() {
if let Some(device) = test_get_default_device(Scope::Input) {
- let streams = get_device_streams(device, DeviceType::INPUT).unwrap();
+ let streams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap();
for stream in streams {
- let latency = get_stream_latency(stream).unwrap();
+ let latency = run_serially(|| get_stream_latency(stream)).unwrap();
println!("latency of the input stream {} is {}", stream, latency);
}
} else {
@@ -367,9 +423,9 @@ fn test_get_stream_latency() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
- let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap();
+ let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap();
for stream in streams {
- let latency = get_stream_latency(stream).unwrap();
+ let latency = run_serially(|| get_stream_latency(stream)).unwrap();
println!("latency of the output stream {} is {}", stream, latency);
}
} else {
@@ -388,10 +444,10 @@ fn test_get_stream_latency_by_unknown_device() {
#[test]
fn test_get_stream_virtual_format() {
if let Some(device) = test_get_default_device(Scope::Input) {
- let streams = get_device_streams(device, DeviceType::INPUT).unwrap();
+ let streams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap();
let formats = streams
.iter()
- .map(|s| get_stream_virtual_format(*s))
+ .map(|s| run_serially(|| get_stream_virtual_format(*s)))
.collect::<Vec<std::result::Result<AudioStreamBasicDescription, OSStatus>>>();
println!("input stream formats: {:?}", formats);
assert!(!formats.is_empty());
@@ -400,10 +456,10 @@ fn test_get_stream_virtual_format() {
}
if let Some(device) = test_get_default_device(Scope::Output) {
- let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap();
+ let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap();
let formats = streams
.iter()
- .map(|s| get_stream_virtual_format(*s))
+ .map(|s| run_serially(|| get_stream_virtual_format(*s)))
.collect::<Vec<std::result::Result<AudioStreamBasicDescription, OSStatus>>>();
println!("output stream formats: {:?}", formats);
assert!(!formats.is_empty());
@@ -415,7 +471,9 @@ fn test_get_stream_virtual_format() {
#[test]
#[should_panic]
fn test_get_stream_virtual_format_by_unknown_stream() {
- assert!(get_stream_virtual_format(kAudioObjectUnknown).is_err());
+ assert!(
+ run_serially_forward_panics(|| get_stream_virtual_format(kAudioObjectUnknown)).is_err()
+ );
}
// get_stream_terminal_type
@@ -442,25 +500,21 @@ fn test_get_stream_terminal_type() {
}
}
if let Some(device) = test_get_default_device(Scope::Input) {
- let streams = get_device_streams(device, DeviceType::INPUT).unwrap();
- for stream in streams {
- assert_eq!(
- terminal_type_to_device_type(get_stream_terminal_type(stream).unwrap()),
- Some(DeviceType::INPUT)
- );
- }
+ let streams = run_serially(|| get_device_streams(device, DeviceType::INPUT)).unwrap();
+ assert!(streams.iter().any(|&s| {
+ terminal_type_to_device_type(run_serially(|| get_stream_terminal_type(s)).unwrap())
+ == Some(DeviceType::INPUT)
+ }));
} else {
println!("No input device.");
}
if let Some(device) = test_get_default_device(Scope::Output) {
- let streams = get_device_streams(device, DeviceType::OUTPUT).unwrap();
- for stream in streams {
- assert_eq!(
- terminal_type_to_device_type(get_stream_terminal_type(stream).unwrap()),
- Some(DeviceType::OUTPUT)
- );
- }
+ let streams = run_serially(|| get_device_streams(device, DeviceType::OUTPUT)).unwrap();
+ assert!(streams.iter().any(|&s| {
+ terminal_type_to_device_type(run_serially(|| get_stream_terminal_type(s)).unwrap())
+ == Some(DeviceType::OUTPUT)
+ }));
} else {
println!("No output device.");
}
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs
index 340fec002d..aa15b7428b 100644
--- a/third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs
+++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/interfaces.rs
@@ -2,10 +2,12 @@ extern crate itertools;
use self::itertools::iproduct;
use super::utils::{
- get_devices_info_in_scope, noop_data_callback, test_device_channels_in_scope,
- test_get_default_device, test_ops_context_operation, test_ops_stream_operation, Scope,
+ draining_data_callback, get_devices_info_in_scope, noop_data_callback,
+ test_device_channels_in_scope, test_get_default_device, test_ops_context_operation,
+ test_ops_stream_operation, test_ops_stream_operation_on_context, Scope,
};
use super::*;
+use std::thread;
// Context Operations
// ------------------------------------------------------------------------------------------------
@@ -368,9 +370,6 @@ fn test_ops_context_register_device_collection_changed() {
#[test]
fn test_ops_context_register_device_collection_changed_with_a_duplex_stream() {
- use std::thread;
- use std::time::Duration;
-
extern "C" fn callback(_: *mut ffi::cubeb, got_called_ptr: *mut c_void) {
let got_called = unsafe { &mut *(got_called_ptr as *mut bool) };
*got_called = true;
@@ -667,14 +666,20 @@ fn test_ops_context_stream_init_channel_rate_combinations() {
ffi::CUBEB_OK
);
assert!(!stream.is_null());
+
+ unsafe { OPS.stream_destroy.unwrap()(stream) };
}
});
}
// Stream Operations
// ------------------------------------------------------------------------------------------------
-fn test_default_output_stream_operation<F>(name: &'static str, operation: F)
-where
+fn test_default_output_stream_operation_on_context_with_callback<F>(
+ name: &'static str,
+ context_ptr: *mut ffi::cubeb,
+ data_callback: ffi::cubeb_data_callback,
+ operation: F,
+) where
F: FnOnce(*mut ffi::cubeb_stream),
{
// Make sure the parameters meet the requirements of AudioUnitContext::stream_init
@@ -686,24 +691,53 @@ where
output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
- test_ops_stream_operation(
+ test_ops_stream_operation_on_context(
name,
+ context_ptr,
ptr::null_mut(), // Use default input device.
ptr::null_mut(), // No input parameters.
ptr::null_mut(), // Use default output device.
&mut output_params,
4096, // TODO: Get latency by get_min_latency instead ?
- Some(noop_data_callback),
+ data_callback,
None, // No state callback.
ptr::null_mut(), // No user data pointer.
operation,
);
}
-fn test_default_duplex_stream_operation<F>(name: &'static str, operation: F)
+fn test_default_output_stream_operation_with_callback<F>(
+ name: &'static str,
+ data_callback: ffi::cubeb_data_callback,
+ operation: F,
+) where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ test_ops_context_operation("context: default output stream operation", |context_ptr| {
+ test_default_output_stream_operation_on_context_with_callback(
+ name,
+ context_ptr,
+ data_callback,
+ operation,
+ );
+ });
+}
+
+fn test_default_output_stream_operation<F>(name: &'static str, operation: F)
where
F: FnOnce(*mut ffi::cubeb_stream),
{
+ test_default_output_stream_operation_with_callback(name, Some(noop_data_callback), operation);
+}
+
+fn test_default_duplex_stream_operation_on_context_with_callback<F>(
+ name: &'static str,
+ context_ptr: *mut ffi::cubeb,
+ data_callback: ffi::cubeb_data_callback,
+ operation: F,
+) where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
// Make sure the parameters meet the requirements of AudioUnitContext::stream_init
// (in the comments).
let mut input_params = ffi::cubeb_stream_params::default();
@@ -720,24 +754,53 @@ where
output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
- test_ops_stream_operation(
+ test_ops_stream_operation_on_context(
name,
+ context_ptr,
ptr::null_mut(), // Use default input device.
&mut input_params,
ptr::null_mut(), // Use default output device.
&mut output_params,
4096, // TODO: Get latency by get_min_latency instead ?
- Some(noop_data_callback),
+ data_callback,
None, // No state callback.
ptr::null_mut(), // No user data pointer.
operation,
);
}
-fn test_stereo_input_duplex_stream_operation<F>(name: &'static str, operation: F)
+fn test_default_duplex_stream_operation_with_callback<F>(
+ name: &'static str,
+ data_callback: ffi::cubeb_data_callback,
+ operation: F,
+) where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ test_ops_context_operation("context: default duplex stream operation", |context_ptr| {
+ test_default_duplex_stream_operation_on_context_with_callback(
+ name,
+ context_ptr,
+ data_callback,
+ operation,
+ );
+ });
+}
+
+fn test_default_duplex_stream_operation<F>(name: &'static str, operation: F)
where
F: FnOnce(*mut ffi::cubeb_stream),
{
+ test_default_duplex_stream_operation_with_callback(name, Some(noop_data_callback), operation);
+}
+
+fn test_stereo_input_duplex_stream_operation_on_context_with_callback<F>(
+ name: &'static str,
+ context_ptr: *mut ffi::cubeb,
+ data_callback: ffi::cubeb_data_callback,
+ operation: F,
+) where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
let mut input_devices = get_devices_info_in_scope(Scope::Input);
input_devices.retain(|d| test_device_channels_in_scope(d.id, Scope::Input).unwrap_or(0) >= 2);
if input_devices.is_empty() {
@@ -759,24 +822,138 @@ where
output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
output_params.prefs = ffi::CUBEB_STREAM_PREF_NONE;
- test_ops_stream_operation(
+ test_ops_stream_operation_on_context(
name,
+ context_ptr,
input_devices[0].id as ffi::cubeb_devid,
&mut input_params,
ptr::null_mut(), // Use default output device.
&mut output_params,
4096, // TODO: Get latency by get_min_latency instead ?
+ data_callback,
+ None, // No state callback.
+ ptr::null_mut(), // No user data pointer.
+ operation,
+ );
+}
+
+fn test_stereo_input_duplex_stream_operation_with_callback<F>(
+ name: &'static str,
+ data_callback: ffi::cubeb_data_callback,
+ operation: F,
+) where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ test_ops_context_operation(
+ "context: stereo input duplex stream operation",
+ |context_ptr| {
+ test_stereo_input_duplex_stream_operation_on_context_with_callback(
+ name,
+ context_ptr,
+ data_callback,
+ operation,
+ );
+ },
+ );
+}
+
+fn test_stereo_input_duplex_stream_operation<F>(name: &'static str, operation: F)
+where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ test_stereo_input_duplex_stream_operation_with_callback(
+ name,
Some(noop_data_callback),
+ operation,
+ );
+}
+
+fn test_default_input_voice_stream_operation_on_context_with_callback<F>(
+ name: &'static str,
+ context_ptr: *mut ffi::cubeb,
+ data_callback: ffi::cubeb_data_callback,
+ operation: F,
+) where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ // Make sure the parameters meet the requirements of AudioUnitContext::stream_init
+ // (in the comments).
+ let mut input_params = ffi::cubeb_stream_params::default();
+ input_params.format = ffi::CUBEB_SAMPLE_FLOAT32NE;
+ input_params.rate = 44100;
+ input_params.channels = 1;
+ input_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
+ input_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE;
+
+ test_ops_stream_operation_on_context(
+ name,
+ context_ptr,
+ ptr::null_mut(), // Use default input device.
+ &mut input_params,
+ ptr::null_mut(), // Use default output device.
+ ptr::null_mut(), // No output parameters.
+ 4096, // TODO: Get latency by get_min_latency instead ?
+ data_callback,
None, // No state callback.
ptr::null_mut(), // No user data pointer.
operation,
);
}
-fn test_default_duplex_voice_stream_operation<F>(name: &'static str, operation: F)
+fn test_default_input_voice_stream_operation_on_context<F>(
+ name: &'static str,
+ context_ptr: *mut ffi::cubeb,
+ operation: F,
+) where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ test_default_input_voice_stream_operation_on_context_with_callback(
+ name,
+ context_ptr,
+ Some(noop_data_callback),
+ operation,
+ );
+}
+
+fn test_default_input_voice_stream_operation_with_callback<F>(
+ name: &'static str,
+ data_callback: ffi::cubeb_data_callback,
+ operation: F,
+) where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ test_ops_context_operation(
+ "context: default input voice stream operation",
+ |context_ptr| {
+ test_default_input_voice_stream_operation_on_context_with_callback(
+ name,
+ context_ptr,
+ data_callback,
+ operation,
+ );
+ },
+ );
+}
+
+fn test_default_input_voice_stream_operation<F>(name: &'static str, operation: F)
where
F: FnOnce(*mut ffi::cubeb_stream),
{
+ test_default_input_voice_stream_operation_with_callback(
+ name,
+ Some(noop_data_callback),
+ operation,
+ );
+}
+
+fn test_default_duplex_voice_stream_operation_on_context_with_callback<F>(
+ name: &'static str,
+ context_ptr: *mut ffi::cubeb,
+ data_callback: ffi::cubeb_data_callback,
+ operation: F,
+) where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
// Make sure the parameters meet the requirements of AudioUnitContext::stream_init
// (in the comments).
let mut input_params = ffi::cubeb_stream_params::default();
@@ -793,20 +970,64 @@ where
output_params.layout = ffi::CUBEB_LAYOUT_UNDEFINED;
output_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE;
- test_ops_stream_operation(
+ test_ops_stream_operation_on_context(
name,
+ context_ptr,
ptr::null_mut(), // Use default input device.
&mut input_params,
ptr::null_mut(), // Use default output device.
&mut output_params,
4096, // TODO: Get latency by get_min_latency instead ?
- Some(noop_data_callback),
+ data_callback,
None, // No state callback.
ptr::null_mut(), // No user data pointer.
operation,
);
}
+fn test_default_duplex_voice_stream_operation_on_context<F>(
+ name: &'static str,
+ context_ptr: *mut ffi::cubeb,
+ operation: F,
+) where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ test_default_duplex_voice_stream_operation_on_context_with_callback(
+ name,
+ context_ptr,
+ Some(noop_data_callback),
+ operation,
+ );
+}
+
+fn test_default_duplex_voice_stream_operation_with_callback<F>(
+ name: &'static str,
+ data_callback: ffi::cubeb_data_callback,
+ operation: F,
+) where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ test_ops_context_operation("context: duplex voice stream operation", |context_ptr| {
+ test_default_duplex_voice_stream_operation_on_context_with_callback(
+ name,
+ context_ptr,
+ data_callback,
+ operation,
+ );
+ });
+}
+
+fn test_default_duplex_voice_stream_operation<F>(name: &'static str, operation: F)
+where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ test_default_duplex_voice_stream_operation_with_callback(
+ name,
+ Some(noop_data_callback),
+ operation,
+ );
+}
+
fn test_stereo_input_duplex_voice_stream_operation<F>(name: &'static str, operation: F)
where
F: FnOnce(*mut ffi::cubeb_stream),
@@ -866,6 +1087,18 @@ fn test_ops_stream_stop() {
}
#[test]
+fn test_ops_stream_drain() {
+ test_default_output_stream_operation_with_callback(
+ "stream: drain",
+ Some(draining_data_callback),
+ |stream| {
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ thread::sleep(Duration::from_millis(10));
+ },
+ );
+}
+
+#[test]
fn test_ops_stream_position() {
test_default_output_stream_operation("stream: position", |stream| {
let mut position = u64::max_value();
@@ -987,17 +1220,72 @@ fn test_ops_stereo_input_duplex_stream_stop() {
}
#[test]
-fn test_ops_duplex_voice_stream_init_and_destroy() {
- test_default_duplex_voice_stream_operation(
- "duplex voice stream: init and destroy",
- |_stream| {},
+fn test_ops_stereo_input_duplex_stream_drain() {
+ test_stereo_input_duplex_stream_operation_with_callback(
+ "stereo-input duplex stream: drain",
+ Some(draining_data_callback),
+ |stream| {
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ thread::sleep(Duration::from_millis(10));
+ },
);
}
#[test]
+fn test_ops_input_voice_stream_init_and_destroy() {
+ test_default_input_voice_stream_operation("input voice stream: init and destroy", |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert_eq!(
+ stm.core_stream_data.using_voice_processing_unit(),
+ macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
+ );
+ });
+}
+
+#[test]
+fn test_ops_input_voice_stream_start() {
+ test_default_input_voice_stream_operation("input voice stream: start", |stream| {
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert_eq!(
+ stm.core_stream_data.using_voice_processing_unit(),
+ macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
+ );
+ });
+}
+
+#[test]
+fn test_ops_input_voice_stream_stop() {
+ test_default_input_voice_stream_operation("input voice stream: stop", |stream| {
+ assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert_eq!(
+ stm.core_stream_data.using_voice_processing_unit(),
+ macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
+ );
+ });
+}
+
+#[test]
+fn test_ops_duplex_voice_stream_init_and_destroy() {
+ test_default_duplex_voice_stream_operation("duplex voice stream: init and destroy", |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert_eq!(
+ stm.core_stream_data.using_voice_processing_unit(),
+ macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
+ );
+ });
+}
+
+#[test]
fn test_ops_duplex_voice_stream_start() {
test_default_duplex_voice_stream_operation("duplex voice stream: start", |stream| {
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert_eq!(
+ stm.core_stream_data.using_voice_processing_unit(),
+ macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
+ );
});
}
@@ -1005,21 +1293,334 @@ fn test_ops_duplex_voice_stream_start() {
fn test_ops_duplex_voice_stream_stop() {
test_default_duplex_voice_stream_operation("duplex voice stream: stop", |stream| {
assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert_eq!(
+ stm.core_stream_data.using_voice_processing_unit(),
+ macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
+ );
+ });
+}
+
+#[test]
+fn test_ops_duplex_voice_stream_drain() {
+ test_default_duplex_voice_stream_operation_with_callback(
+ "duplex voice stream: drain",
+ Some(draining_data_callback),
+ |stream| {
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert_eq!(
+ stm.core_stream_data.using_voice_processing_unit(),
+ macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
+ );
+ thread::sleep(Duration::from_millis(10));
+ },
+ );
+}
+
+#[test]
+#[ignore]
+fn test_ops_timing_sensitive_multiple_voice_stream_init_and_destroy() {
+ if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
+ // We disable VPIO on Monterey.
+ return;
+ }
+ let start = Instant::now();
+ let mut t1 = start;
+ let mut t2 = start;
+ let mut t3 = start;
+ let mut t4 = start;
+ let mut t5 = start;
+ let mut t6 = start;
+ let mut t7 = start;
+ let mut t8 = start;
+ let mut t9 = start;
+ let mut t10 = start;
+ test_ops_context_operation("multiple duplex voice streams", |context_ptr| {
+ // First stream uses vpio, creates the shared vpio unit.
+ test_default_duplex_voice_stream_operation_on_context(
+ "multiple voice streams: stream 1, duplex",
+ context_ptr,
+ |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
+
+ // Two concurrent vpio streams are supported.
+ test_default_input_voice_stream_operation_on_context(
+ "multiple voice streams: stream 2, input-only",
+ context_ptr,
+ |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
+
+ // Three concurrent vpio streams are supported.
+ test_default_duplex_voice_stream_operation_on_context(
+ "multiple voice streams: stream 3, duplex",
+ context_ptr,
+ |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
+ },
+ );
+ },
+ );
+ },
+ );
+ t1 = Instant::now();
+ // Fourth stream uses vpio, allows reuse of one already created.
+ test_default_duplex_voice_stream_operation_on_context(
+ "multiple voice streams: stream 4, duplex",
+ context_ptr,
+ |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
+ t2 = Instant::now();
+
+ // Fifth stream uses vpio, allows reuse of one already created.
+ test_default_duplex_voice_stream_operation_on_context(
+ "multiple voice streams: stream 5, duplex",
+ context_ptr,
+ |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
+ t3 = Instant::now();
+
+ // Sixth stream uses vpio, allows reuse of one already created.
+ test_default_input_voice_stream_operation_on_context(
+ "multiple voice streams: stream 6, input-only",
+ context_ptr,
+ |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
+ t4 = Instant::now();
+
+ // Seventh stream uses vpio, but is created anew.
+ test_default_input_voice_stream_operation_on_context(
+ "multiple voice streams: stream 7, input-only",
+ context_ptr,
+ |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
+ t5 = Instant::now();
+ },
+ );
+ t6 = Instant::now();
+ },
+ );
+ t7 = Instant::now();
+ },
+ );
+ t8 = Instant::now();
+ },
+ );
+ t9 = Instant::now();
+ });
+ t10 = Instant::now();
+
+ let reuse_vpio_1 = t2 - t1;
+ let reuse_vpio_2 = t3 - t2;
+ let reuse_vpio_3 = t4 - t3;
+ let create_standalone_vpio = t5 - t4;
+ assert!(
+ create_standalone_vpio > reuse_vpio_1 * 2,
+ "Failed create_standalone_vpio={}s > reuse_vpio_1={}s * 2",
+ create_standalone_vpio.as_secs_f32(),
+ reuse_vpio_1.as_secs_f32()
+ );
+ assert!(
+ create_standalone_vpio > reuse_vpio_2 * 2,
+ "Failed create_standalone_vpio={}s > reuse_vpio_2={}s * 2",
+ create_standalone_vpio.as_secs_f32(),
+ reuse_vpio_2.as_secs_f32()
+ );
+ assert!(
+ create_standalone_vpio > reuse_vpio_3 * 2,
+ "Failed create_standalone_vpio={}s > reuse_vpio_3={}s * 2",
+ create_standalone_vpio.as_secs_f32(),
+ reuse_vpio_3.as_secs_f32()
+ );
+
+ let recycle_vpio_1 = t6 - t5;
+ let recycle_vpio_2 = t7 - t6;
+ let recycle_vpio_3 = t8 - t7;
+ let recycle_vpio_4 = t9 - t8;
+ let dispose_vpios = t10 - t9;
+ assert!(
+ dispose_vpios > recycle_vpio_1 * 2,
+ "Failed dispose_vpios={}s > recycle_vpio_1 ={}s * 2",
+ dispose_vpios.as_secs_f32(),
+ recycle_vpio_1.as_secs_f32()
+ );
+ assert!(
+ dispose_vpios > recycle_vpio_2 * 2,
+ "Failed dispose_vpios={}s > recycle_vpio_2 ={}s * 2",
+ dispose_vpios.as_secs_f32(),
+ recycle_vpio_2.as_secs_f32()
+ );
+ assert!(
+ dispose_vpios > recycle_vpio_3 * 2,
+ "Failed dispose_vpios={}s > recycle_vpio_3 ={}s * 2",
+ dispose_vpios.as_secs_f32(),
+ recycle_vpio_3.as_secs_f32()
+ );
+ assert!(
+ dispose_vpios > recycle_vpio_4 * 2,
+ "Failed dispose_vpios={}s > recycle_vpio_4 ={}s * 2",
+ dispose_vpios.as_secs_f32(),
+ recycle_vpio_4.as_secs_f32()
+ );
+}
+
+#[test]
+#[ignore]
+fn test_ops_timing_sensitive_multiple_duplex_voice_stream_start() {
+ if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
+ // We disable VPIO on Monterey.
+ return;
+ }
+ test_ops_context_operation("multiple duplex voice streams", |context_ptr| {
+ let start = Instant::now();
+ // First stream uses vpio, creates the shared vpio unit.
+ test_default_duplex_voice_stream_operation_on_context(
+ "multiple duplex voice streams: stream 1",
+ context_ptr,
+ |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ },
+ );
+ let d1 = start.elapsed();
+ // Second stream uses vpio, allows reuse of the one already created.
+ test_default_duplex_voice_stream_operation_on_context(
+ "multiple duplex voice streams: stream 2",
+ context_ptr,
+ |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
+ assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ },
+ );
+ let d2 = start.elapsed() - d1;
+ // d1 being significantly longer than d2 is proof we reuse vpio.
+ assert!(
+ d1 > d2 * 2,
+ "Failed d1={}s > d2={}s * s",
+ d1.as_secs_f32(),
+ d2.as_secs_f32()
+ );
+ });
+}
+
+#[test]
+#[ignore]
+fn test_ops_timing_sensitive_multiple_duplex_voice_stream_params() {
+ if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
+ // We disable VPIO on Monterey.
+ return;
+ }
+ test_ops_context_operation("multiple duplex voice streams with params", |context_ptr| {
+ let start = Instant::now();
+ // First stream uses vpio, creates the shared vpio unit.
+ test_default_duplex_voice_stream_operation_on_context(
+ "multiple duplex voice streams: stream 1",
+ context_ptr,
+ |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
+ assert_eq!(
+ unsafe {
+ OPS.stream_set_input_processing_params.unwrap()(
+ stream,
+ ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
+ | ffi::CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION,
+ )
+ },
+ ffi::CUBEB_OK
+ );
+ assert_eq!(
+ unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
+ ffi::CUBEB_OK
+ );
+ },
+ );
+ let d1 = start.elapsed();
+ // Second stream uses vpio, allows reuse of the one already created.
+ test_default_duplex_voice_stream_operation_on_context(
+ "multiple duplex voice streams: stream 2",
+ context_ptr,
+ |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
+ let queue = stm.queue.clone();
+ // Test that input processing params does not carry over when reusing vpio.
+ let mut bypass: u32 = 0;
+ let r = queue
+ .run_sync(|| {
+ audio_unit_get_property(
+ stm.core_stream_data.input_unit,
+ kAUVoiceIOProperty_BypassVoiceProcessing,
+ kAudioUnitScope_Global,
+ AU_IN_BUS,
+ &mut bypass,
+ &mut mem::size_of::<u32>(),
+ )
+ })
+ .unwrap();
+ assert_eq!(r, NO_ERR);
+ assert_eq!(bypass, 1);
+
+ // Test that input mute state does not carry over when reusing vpio.
+ let mut mute: u32 = 0;
+ let r = queue
+ .run_sync(|| {
+ audio_unit_get_property(
+ stm.core_stream_data.input_unit,
+ kAUVoiceIOProperty_MuteOutput,
+ kAudioUnitScope_Global,
+ AU_IN_BUS,
+ &mut mute,
+ &mut mem::size_of::<u32>(),
+ )
+ })
+ .unwrap();
+ assert_eq!(r, NO_ERR);
+ assert_eq!(mute, 0);
+ },
+ );
+ let d2 = start.elapsed() - d1;
+ // d1 being significantly longer than d2 is proof we reuse vpio.
+ assert!(
+ d1 > d2 * 2,
+ "Failed d1={}s > d2={}s * 2",
+ d1.as_secs_f32(),
+ d2.as_secs_f32()
+ );
});
}
#[test]
fn test_ops_duplex_voice_stream_set_input_mute() {
+ if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
+ // We disable VPIO on Monterey.
+ return;
+ }
test_default_duplex_voice_stream_operation("duplex voice stream: mute", |stream| {
assert_eq!(
unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
ffi::CUBEB_OK
);
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
});
}
#[test]
fn test_ops_duplex_voice_stream_set_input_mute_before_start() {
+ if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
+ // We disable VPIO on Monterey.
+ return;
+ }
test_default_duplex_voice_stream_operation(
"duplex voice stream: mute before start",
|stream| {
@@ -1028,12 +1629,18 @@ fn test_ops_duplex_voice_stream_set_input_mute_before_start() {
ffi::CUBEB_OK
);
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
},
);
}
#[test]
fn test_ops_duplex_voice_stream_set_input_mute_before_start_with_reinit() {
+ if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
+ // We disable VPIO on Monterey.
+ return;
+ }
test_default_duplex_voice_stream_operation(
"duplex voice stream: mute before start with reinit",
|stream| {
@@ -1045,6 +1652,7 @@ fn test_ops_duplex_voice_stream_set_input_mute_before_start_with_reinit() {
// Hacky cast, but testing this here was simplest for now.
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
stm.reinit_async();
let queue = stm.queue.clone();
let mut mute_after_reinit = false;
@@ -1068,17 +1676,27 @@ fn test_ops_duplex_voice_stream_set_input_mute_before_start_with_reinit() {
#[test]
fn test_ops_duplex_voice_stream_set_input_mute_after_start() {
+ if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
+ // We disable VPIO on Monterey.
+ return;
+ }
test_default_duplex_voice_stream_operation("duplex voice stream: mute after start", |stream| {
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
assert_eq!(
unsafe { OPS.stream_set_input_mute.unwrap()(stream, 1) },
ffi::CUBEB_OK
);
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
});
}
#[test]
fn test_ops_duplex_voice_stream_set_input_processing_params() {
+ if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
+ // We disable VPIO on Monterey.
+ return;
+ }
test_default_duplex_voice_stream_operation("duplex voice stream: processing", |stream| {
let params: ffi::cubeb_input_processing_params =
ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
@@ -1088,11 +1706,17 @@ fn test_ops_duplex_voice_stream_set_input_processing_params() {
unsafe { OPS.stream_set_input_processing_params.unwrap()(stream, params) },
ffi::CUBEB_OK
);
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
});
}
#[test]
fn test_ops_duplex_voice_stream_set_input_processing_params_before_start() {
+ if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
+ // We disable VPIO on Monterey.
+ return;
+ }
test_default_duplex_voice_stream_operation(
"duplex voice stream: processing before start",
|stream| {
@@ -1105,12 +1729,18 @@ fn test_ops_duplex_voice_stream_set_input_processing_params_before_start() {
ffi::CUBEB_OK
);
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
},
);
}
#[test]
fn test_ops_duplex_voice_stream_set_input_processing_params_before_start_with_reinit() {
+ if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
+ // We disable VPIO on Monterey.
+ return;
+ }
test_default_duplex_voice_stream_operation(
"duplex voice stream: processing before start with reinit",
|stream| {
@@ -1126,6 +1756,7 @@ fn test_ops_duplex_voice_stream_set_input_processing_params_before_start_with_re
// Hacky cast, but testing this here was simplest for now.
let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
stm.reinit_async();
let queue = stm.queue.clone();
let mut params_after_reinit: ffi::cubeb_input_processing_params =
@@ -1170,9 +1801,15 @@ fn test_ops_duplex_voice_stream_set_input_processing_params_before_start_with_re
#[test]
fn test_ops_duplex_voice_stream_set_input_processing_params_after_start() {
+ if macos_kernel_major_version().unwrap() == MACOS_KERNEL_MAJOR_VERSION_MONTEREY {
+ // We disable VPIO on Monterey.
+ return;
+ }
test_default_duplex_voice_stream_operation(
"duplex voice stream: processing after start",
|stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert!(stm.core_stream_data.using_voice_processing_unit());
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
let params: ffi::cubeb_input_processing_params =
ffi::CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION
@@ -1190,7 +1827,13 @@ fn test_ops_duplex_voice_stream_set_input_processing_params_after_start() {
fn test_ops_stereo_input_duplex_voice_stream_init_and_destroy() {
test_stereo_input_duplex_voice_stream_operation(
"stereo-input duplex voice stream: init and destroy",
- |_stream| {},
+ |stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert_eq!(
+ stm.core_stream_data.using_voice_processing_unit(),
+ macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
+ );
+ },
);
}
@@ -1199,6 +1842,11 @@ fn test_ops_stereo_input_duplex_voice_stream_start() {
test_stereo_input_duplex_voice_stream_operation(
"stereo-input duplex voice stream: start",
|stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert_eq!(
+ stm.core_stream_data.using_voice_processing_unit(),
+ macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
+ );
assert_eq!(unsafe { OPS.stream_start.unwrap()(stream) }, ffi::CUBEB_OK);
},
);
@@ -1209,6 +1857,11 @@ fn test_ops_stereo_input_duplex_voice_stream_stop() {
test_stereo_input_duplex_voice_stream_operation(
"stereo-input duplex voice stream: stop",
|stream| {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ assert_eq!(
+ stm.core_stream_data.using_voice_processing_unit(),
+ macos_kernel_major_version().unwrap() != MACOS_KERNEL_MAJOR_VERSION_MONTEREY
+ );
assert_eq!(unsafe { OPS.stream_stop.unwrap()(stream) }, ffi::CUBEB_OK);
},
);
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/manual.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/manual.rs
index b2b2241cc9..3bf96c65c6 100644
--- a/third_party/rust/cubeb-coreaudio/src/backend/tests/manual.rs
+++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/manual.rs
@@ -4,7 +4,6 @@ use super::utils::{
};
use super::*;
use std::io;
-use std::sync::atomic::AtomicBool;
#[ignore]
#[test]
@@ -153,25 +152,84 @@ fn test_device_collection_change() {
let _ = std::io::stdin().read_line(&mut input);
}
+struct StreamData {
+ stream_ptr: *mut ffi::cubeb_stream,
+ enable_loopback: AtomicBool,
+}
+
+impl StreamData {
+ fn new() -> Self {
+ Self {
+ stream_ptr: ptr::null_mut(),
+ enable_loopback: AtomicBool::new(false),
+ }
+ }
+}
+
+struct StreamsData {
+ streams: Vec<StreamData>,
+ current_idx: Option<usize>,
+}
+
+impl StreamsData {
+ fn new() -> Self {
+ Self {
+ streams: Vec::new(),
+ current_idx: None,
+ }
+ }
+
+ fn len(&self) -> usize {
+ self.streams.len()
+ }
+
+ fn current_mut(&mut self) -> &mut StreamData {
+ &mut self.streams[self.current_idx.unwrap()]
+ }
+
+ fn current(&self) -> &StreamData {
+ &self.streams[self.current_idx.unwrap()]
+ }
+
+ fn select(&mut self, idx: usize) {
+ assert!(idx < self.len());
+ self.current_idx = Some(idx);
+ }
+
+ fn push(&mut self, stream: StreamData) {
+ self.streams.push(stream)
+ }
+}
+
#[ignore]
#[test]
fn test_stream_tester() {
test_ops_context_operation("context: stream tester", |context_ptr| {
- let mut stream_ptr: *mut ffi::cubeb_stream = ptr::null_mut();
- let enable_loopback = AtomicBool::new(false);
+ let mut input_prefs = StreamPrefs::NONE;
+ let mut output_prefs = StreamPrefs::NONE;
+ let mut streams = StreamsData::new();
loop {
println!(
- "commands:\n\
+ "Current stream: {} (of {}). Commands:\n\
\t'q': quit\n\
+ \t'b': change current stream\n\
+ \t'i': set input stream prefs to be used for creating streams\n\
+ \t'o': set output stream prefs to be used for creating streams\n\
\t'c': create a stream\n\
- \t'd': destroy a stream\n\
- \t's': start the created stream\n\
- \t't': stop the created stream\n\
+ Commands on the current stream:\n\
+ \t'd': destroy\n\
+ \t's': start\n\
+ \t't': stop\n\
\t'r': register a device changed callback\n\
\t'l': set loopback (DUPLEX-only)\n\
\t'v': set volume\n\
\t'm': set input mute\n\
- \t'p': set input processing"
+ \t'p': set input processing",
+ streams
+ .current_idx
+ .map(|i| format!("{}", i + 1 as usize))
+ .unwrap_or(String::from("N/A")),
+ streams.len(),
);
let mut command = String::new();
@@ -181,66 +239,130 @@ fn test_stream_tester() {
match command.as_str() {
"q" => {
println!("Quit.");
- destroy_stream(&mut stream_ptr);
+ for mut stream in streams.streams {
+ if !stream.stream_ptr.is_null() {
+ destroy_stream(&mut stream);
+ }
+ }
break;
}
- "c" => create_stream(&mut stream_ptr, context_ptr, &enable_loopback),
- "d" => destroy_stream(&mut stream_ptr),
- "s" => start_stream(stream_ptr),
- "t" => stop_stream(stream_ptr),
- "r" => register_device_change_callback(stream_ptr),
- "l" => set_loopback(stream_ptr, &enable_loopback),
- "v" => set_volume(stream_ptr),
- "m" => set_input_mute(stream_ptr),
- "p" => set_input_processing(stream_ptr),
- x => println!("Unknown command: {}", x),
+ "i" => set_prefs(&mut input_prefs),
+ "o" => set_prefs(&mut output_prefs),
+ "c" => create_stream(context_ptr, &mut streams, input_prefs, output_prefs),
+ _ if streams.current_idx.is_none() => {
+ println!("There are no streams! Create a stream first.")
+ }
+ cmd => match cmd {
+ "b" => select_stream(&mut streams),
+ "d" => destroy_stream(streams.current_mut()),
+ "s" => start_stream(streams.current()),
+ "t" => stop_stream(streams.current()),
+ "r" => register_device_change_callback(streams.current()),
+ "l" => set_loopback(streams.current()),
+ "v" => set_volume(streams.current()),
+ "m" => set_input_mute(streams.current()),
+ "p" => set_input_processing(streams.current()),
+ x => println!("Unknown command: {}", x),
+ },
}
}
});
- fn start_stream(stream_ptr: *mut ffi::cubeb_stream) {
- if stream_ptr.is_null() {
+ fn set_prefs(prefs: &mut StreamPrefs) {
+ let mut done = false;
+ while !done {
+ println!(
+ "Current prefs: {:?}\nSelect action:\n\
+ \t1) Set None\n\
+ \t2) Toggle Loopback\n\
+ \t3) Toggle Disable Device Switching\n\
+ \t4) Toggle Voice\n\
+ \t5) Set All\n\
+ \t0) Done",
+ prefs
+ );
+ let mut input = String::new();
+ let _ = io::stdin().read_line(&mut input);
+ assert_eq!(input.pop().unwrap(), '\n');
+ match input.as_str() {
+ "1" => *prefs = StreamPrefs::NONE,
+ "2" => prefs.toggle(StreamPrefs::LOOPBACK),
+ "3" => prefs.toggle(StreamPrefs::DISABLE_DEVICE_SWITCHING),
+ "4" => prefs.toggle(StreamPrefs::VOICE),
+ "5" => *prefs = StreamPrefs::all(),
+ "0" => done = true,
+ _ => println!("Invalid action. Select again.\n"),
+ }
+ }
+ }
+
+ fn select_stream(streams: &mut StreamsData) {
+ let num_streams = streams.len();
+ let current_idx = streams.current_idx.unwrap();
+ println!(
+ "Current stream is {}. Select stream 1 to {} on which to apply commands:",
+ current_idx + 1 as usize,
+ num_streams
+ );
+ let mut selection: Option<usize> = None;
+ while selection.is_none() {
+ let mut input = String::new();
+ let _ = io::stdin().read_line(&mut input);
+ assert_eq!(input.pop().unwrap(), '\n');
+ selection = match input.parse::<usize>() {
+ Ok(i) if (1..=num_streams).contains((&i).into()) => Some(i),
+ _ => {
+ println!("Invalid stream. Select again.\n");
+ None
+ }
+ }
+ }
+ streams.select(selection.unwrap() - 1)
+ }
+
+ fn start_stream(stream: &StreamData) {
+ if stream.stream_ptr.is_null() {
println!("No stream can start.");
return;
}
assert_eq!(
- unsafe { OPS.stream_start.unwrap()(stream_ptr) },
+ unsafe { OPS.stream_start.unwrap()(stream.stream_ptr) },
ffi::CUBEB_OK
);
- println!("Stream {:p} started.", stream_ptr);
+ println!("Stream {:p} started.", stream.stream_ptr);
}
- fn stop_stream(stream_ptr: *mut ffi::cubeb_stream) {
- if stream_ptr.is_null() {
+ fn stop_stream(stream: &StreamData) {
+ if stream.stream_ptr.is_null() {
println!("No stream can stop.");
return;
}
assert_eq!(
- unsafe { OPS.stream_stop.unwrap()(stream_ptr) },
+ unsafe { OPS.stream_stop.unwrap()(stream.stream_ptr) },
ffi::CUBEB_OK
);
- println!("Stream {:p} stopped.", stream_ptr);
+ println!("Stream {:p} stopped.", stream.stream_ptr);
}
- fn set_volume(stream_ptr: *mut ffi::cubeb_stream) {
- if stream_ptr.is_null() {
+ fn set_volume(stream: &StreamData) {
+ if stream.stream_ptr.is_null() {
println!("No stream can set volume.");
return;
}
const VOL: f32 = 0.5;
assert_eq!(
- unsafe { OPS.stream_set_volume.unwrap()(stream_ptr, VOL) },
+ unsafe { OPS.stream_set_volume.unwrap()(stream.stream_ptr, VOL) },
ffi::CUBEB_OK
);
- println!("Set stream {:p} volume to {}", stream_ptr, VOL);
+ println!("Set stream {:p} volume to {}", stream.stream_ptr, VOL);
}
- fn set_loopback(stream_ptr: *mut ffi::cubeb_stream, enable_loopback: &AtomicBool) {
- if stream_ptr.is_null() {
+ fn set_loopback(stream: &StreamData) {
+ if stream.stream_ptr.is_null() {
println!("No stream can set loopback.");
return;
}
- let stm = unsafe { &mut *(stream_ptr as *mut AudioUnitStream) };
+ let stm = unsafe { &mut *(stream.stream_ptr as *mut AudioUnitStream) };
if !stm.core_stream_data.has_input() || !stm.core_stream_data.has_output() {
println!("Duplex stream needed to set loopback");
return;
@@ -261,20 +383,20 @@ fn test_stream_tester() {
}
}
let loopback = loopback.unwrap();
- enable_loopback.store(loopback, Ordering::SeqCst);
+ stream.enable_loopback.store(loopback, Ordering::SeqCst);
println!(
"Loopback {} for stream {:p}",
if loopback { "enabled" } else { "disabled" },
- stream_ptr
+ stream.stream_ptr
);
}
- fn set_input_mute(stream_ptr: *mut ffi::cubeb_stream) {
- if stream_ptr.is_null() {
+ fn set_input_mute(stream: &StreamData) {
+ if stream.stream_ptr.is_null() {
println!("No stream can set input mute.");
return;
}
- let stm = unsafe { &mut *(stream_ptr as *mut AudioUnitStream) };
+ let stm = unsafe { &mut *(stream.stream_ptr as *mut AudioUnitStream) };
if !stm.core_stream_data.has_input() {
println!("Input stream needed to set loopback");
return;
@@ -295,7 +417,7 @@ fn test_stream_tester() {
}
}
let mute = mute.unwrap();
- let res = unsafe { OPS.stream_set_input_mute.unwrap()(stream_ptr, mute.into()) };
+ let res = unsafe { OPS.stream_set_input_mute.unwrap()(stream.stream_ptr, mute.into()) };
println!(
"{} set stream {:p} input {}",
if res == ffi::CUBEB_OK {
@@ -303,17 +425,17 @@ fn test_stream_tester() {
} else {
"Failed to"
},
- stream_ptr,
+ stream.stream_ptr,
if mute { "mute" } else { "unmute" }
);
}
- fn set_input_processing(stream_ptr: *mut ffi::cubeb_stream) {
- if stream_ptr.is_null() {
+ fn set_input_processing(stream: &StreamData) {
+ if stream.stream_ptr.is_null() {
println!("No stream can set input processing.");
return;
}
- let stm = unsafe { &mut *(stream_ptr as *mut AudioUnitStream) };
+ let stm = unsafe { &mut *(stream.stream_ptr as *mut AudioUnitStream) };
if !stm.core_stream_data.using_voice_processing_unit() {
println!("Duplex stream with voice processing needed to set input processing params");
return;
@@ -338,6 +460,23 @@ fn test_stream_tester() {
params.set(InputProcessingParams::ECHO_CANCELLATION, true);
params.set(InputProcessingParams::NOISE_SUPPRESSION, true);
}
+ let mut agc = u32::from(false);
+ let mut size: usize = mem::size_of::<u32>();
+ assert_eq!(
+ audio_unit_get_property(
+ stm.core_stream_data.input_unit,
+ kAUVoiceIOProperty_VoiceProcessingEnableAGC,
+ kAudioUnitScope_Global,
+ AU_IN_BUS,
+ &mut agc,
+ &mut size,
+ ),
+ NO_ERR
+ );
+ assert_eq!(size, mem::size_of::<u32>());
+ if agc == 1 {
+ params.set(InputProcessingParams::AUTOMATIC_GAIN_CONTROL, true);
+ }
}
let mut done = false;
while !done {
@@ -367,8 +506,9 @@ fn test_stream_tester() {
_ => println!("Invalid action. Select again.\n"),
}
}
- let res =
- unsafe { OPS.stream_set_input_processing_params.unwrap()(stream_ptr, params.bits()) };
+ let res = unsafe {
+ OPS.stream_set_input_processing_params.unwrap()(stream.stream_ptr, params.bits())
+ };
println!(
"{} set stream {:p} input processing params to {:?}",
if res == ffi::CUBEB_OK {
@@ -376,48 +516,62 @@ fn test_stream_tester() {
} else {
"Failed to"
},
- stream_ptr,
+ stream.stream_ptr,
params,
);
}
- fn register_device_change_callback(stream_ptr: *mut ffi::cubeb_stream) {
+ fn register_device_change_callback(stream: &StreamData) {
extern "C" fn callback(user_ptr: *mut c_void) {
println!("user pointer @ {:p}", user_ptr);
assert!(user_ptr.is_null());
}
- if stream_ptr.is_null() {
+ if stream.stream_ptr.is_null() {
println!("No stream for registering the callback.");
return;
}
assert_eq!(
unsafe {
- OPS.stream_register_device_changed_callback.unwrap()(stream_ptr, Some(callback))
+ OPS.stream_register_device_changed_callback.unwrap()(
+ stream.stream_ptr,
+ Some(callback),
+ )
},
ffi::CUBEB_OK
);
- println!("Stream {:p} now has a device change callback.", stream_ptr);
+ println!(
+ "Stream {:p} now has a device change callback.",
+ stream.stream_ptr
+ );
}
- fn destroy_stream(stream_ptr: &mut *mut ffi::cubeb_stream) {
- if stream_ptr.is_null() {
+ fn destroy_stream(stream: &mut StreamData) {
+ if stream.stream_ptr.is_null() {
println!("No need to destroy stream.");
return;
}
unsafe {
- OPS.stream_destroy.unwrap()(*stream_ptr);
+ OPS.stream_destroy.unwrap()((*stream).stream_ptr);
}
- println!("Stream {:p} destroyed.", *stream_ptr);
- *stream_ptr = ptr::null_mut();
+ println!("Stream {:p} destroyed.", stream.stream_ptr);
+ stream.stream_ptr = ptr::null_mut();
}
fn create_stream(
- stream_ptr: &mut *mut ffi::cubeb_stream,
context_ptr: *mut ffi::cubeb,
- enable_loopback: &AtomicBool,
+ streams: &mut StreamsData,
+ input_prefs: StreamPrefs,
+ output_prefs: StreamPrefs,
) {
- if !stream_ptr.is_null() {
+ if streams.len() == 0 || !streams.current().stream_ptr.is_null() {
+ println!("Allocating stream {}.", streams.len() + 1);
+ streams.push(StreamData::new());
+ streams.select(streams.len() - 1);
+ }
+
+ let stream = streams.current_mut();
+ if !stream.stream_ptr.is_null() {
println!("Stream has been created.");
return;
}
@@ -486,8 +640,8 @@ fn test_stream_tester() {
}
};
- let mut input_params = get_dummy_stream_params(Scope::Input);
- let mut output_params = get_dummy_stream_params(Scope::Output);
+ let mut input_params = get_dummy_stream_params(Scope::Input, input_prefs);
+ let mut output_params = get_dummy_stream_params(Scope::Output, output_prefs);
let (input_device, input_stream_params) = if stream_type.contains(StreamType::INPUT) {
(
@@ -519,7 +673,7 @@ fn test_stream_tester() {
unsafe {
OPS.stream_init.unwrap()(
context_ptr,
- stream_ptr,
+ &mut stream.stream_ptr,
stream_name.as_ptr(),
input_device as ffi::cubeb_devid,
input_stream_params,
@@ -528,13 +682,13 @@ fn test_stream_tester() {
4096, // latency
Some(data_callback),
Some(state_callback),
- enable_loopback as *const AtomicBool as *mut c_void, // user pointer
+ &stream.enable_loopback as *const AtomicBool as *mut c_void, // user pointer
)
},
ffi::CUBEB_OK
);
- assert!(!stream_ptr.is_null());
- println!("Stream {:p} created.", *stream_ptr);
+ assert!(!stream.stream_ptr.is_null());
+ println!("Stream {:p} created.", stream.stream_ptr);
extern "C" fn state_callback(
stream: *mut ffi::cubeb_stream,
@@ -592,14 +746,14 @@ fn test_stream_tester() {
nframes
}
- fn get_dummy_stream_params(scope: Scope) -> ffi::cubeb_stream_params {
+ fn get_dummy_stream_params(scope: Scope, prefs: StreamPrefs) -> ffi::cubeb_stream_params {
// The stream format for input and output must be same.
const STREAM_FORMAT: u32 = ffi::CUBEB_SAMPLE_FLOAT32NE;
// Make sure the parameters meet the requirements of AudioUnitContext::stream_init
// (in the comments).
let mut stream_params = ffi::cubeb_stream_params::default();
- stream_params.prefs = ffi::CUBEB_STREAM_PREF_VOICE;
+ stream_params.prefs = prefs.bits();
let (format, rate, channels, layout) = match scope {
Scope::Input => (STREAM_FORMAT, 48000, 1, ffi::CUBEB_LAYOUT_MONO),
Scope::Output => (STREAM_FORMAT, 44100, 2, ffi::CUBEB_LAYOUT_STEREO),
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs
index 16063d0011..be5edb1919 100644
--- a/third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs
+++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/parallel.rs
@@ -537,14 +537,16 @@ fn test_set_buffer_frame_size_in_parallel_in_scope(scope: Scope) {
units.push(test_get_default_audiounit(scope.clone()).unwrap());
let unit_value = units.last().unwrap().get_inner() as usize;
join_handles.push(thread::spawn(move || {
- let status = audio_unit_set_property(
- unit_value as AudioUnit,
- kAudioDevicePropertyBufferFrameSize,
- unit_scope,
- unit_element,
- &latency_frames,
- mem::size_of::<u32>(),
- );
+ let status = run_serially(|| {
+ audio_unit_set_property(
+ unit_value as AudioUnit,
+ kAudioDevicePropertyBufferFrameSize,
+ unit_scope,
+ unit_element,
+ &latency_frames,
+ mem::size_of::<u32>(),
+ )
+ });
(latency_frames, status)
}));
}
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs
index 42cb9ee997..5f54286153 100644
--- a/third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs
+++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/tone.rs
@@ -1,6 +1,6 @@
use super::utils::{test_get_default_device, test_ops_stream_operation, Scope};
use super::*;
-use std::sync::atomic::{AtomicI64, Ordering};
+use std::sync::atomic::AtomicI64;
#[test]
fn test_dial_tone() {
@@ -202,7 +202,7 @@ fn test_dial_tone() {
for data in buffer.iter_mut() {
let t1 = (2.0 * PI * 350.0 * (closure.phase) as f32 / SAMPLE_FREQUENCY as f32).sin();
let t2 = (2.0 * PI * 440.0 * (closure.phase) as f32 / SAMPLE_FREQUENCY as f32).sin();
- *data = f32_to_i16_sample(0.5 * (t1 + t2));
+ *data = f32_to_i16_sample(0.45 * (t1 + t2));
closure.phase += 1;
}
diff --git a/third_party/rust/cubeb-coreaudio/src/backend/tests/utils.rs b/third_party/rust/cubeb-coreaudio/src/backend/tests/utils.rs
index ef07aeeeb4..6fb6d38fb3 100644
--- a/third_party/rust/cubeb-coreaudio/src/backend/tests/utils.rs
+++ b/third_party/rust/cubeb-coreaudio/src/backend/tests/utils.rs
@@ -25,6 +25,29 @@ pub extern "C" fn noop_data_callback(
nframes
}
+pub extern "C" fn draining_data_callback(
+ stream: *mut ffi::cubeb_stream,
+ _user_ptr: *mut c_void,
+ _input_buffer: *const c_void,
+ output_buffer: *mut c_void,
+ nframes: i64,
+) -> i64 {
+ assert!(!stream.is_null());
+
+ // Feed silence data to output buffer
+ if !output_buffer.is_null() {
+ let stm = unsafe { &mut *(stream as *mut AudioUnitStream) };
+ let channels = stm.core_stream_data.output_stream_params.channels();
+ let samples = nframes as usize * channels as usize;
+ let sample_size = cubeb_sample_size(stm.core_stream_data.output_stream_params.format());
+ unsafe {
+ ptr::write_bytes(output_buffer, 0, samples * sample_size);
+ }
+ }
+
+ nframes - 1
+}
+
#[derive(Clone, Debug, PartialEq)]
pub enum Scope {
Input,
@@ -47,31 +70,35 @@ pub enum PropertyScope {
}
pub fn test_get_default_device(scope: Scope) -> Option<AudioObjectID> {
- let address = AudioObjectPropertyAddress {
- mSelector: match scope {
- Scope::Input => kAudioHardwarePropertyDefaultInputDevice,
- Scope::Output => kAudioHardwarePropertyDefaultOutputDevice,
- },
- mScope: kAudioObjectPropertyScopeGlobal,
- mElement: kAudioObjectPropertyElementMaster,
- };
+ debug_assert_not_running_serially();
+ run_serially_forward_panics(|| {
+ let address = AudioObjectPropertyAddress {
+ mSelector: match scope {
+ Scope::Input => kAudioHardwarePropertyDefaultInputDevice,
+ Scope::Output => kAudioHardwarePropertyDefaultOutputDevice,
+ },
+ mScope: kAudioObjectPropertyScopeGlobal,
+ mElement: kAudioObjectPropertyElementMaster,
+ };
- let mut devid: AudioObjectID = kAudioObjectUnknown;
- let mut size = mem::size_of::<AudioObjectID>();
- let status = unsafe {
- AudioObjectGetPropertyData(
- kAudioObjectSystemObject,
- &address,
- 0,
- ptr::null(),
- &mut size as *mut usize as *mut UInt32,
- &mut devid as *mut AudioObjectID as *mut c_void,
- )
- };
- if status != NO_ERR || devid == kAudioObjectUnknown {
- return None;
- }
- Some(devid)
+ let mut devid: AudioObjectID = kAudioObjectUnknown;
+ let mut size = mem::size_of::<AudioObjectID>();
+ let status = unsafe {
+ AudioObjectGetPropertyData(
+ kAudioObjectSystemObject,
+ &address,
+ 0,
+ ptr::null(),
+ &mut size as *mut usize as *mut UInt32,
+ &mut devid as *mut AudioObjectID as *mut c_void,
+ )
+ };
+
+ if status != NO_ERR || devid == kAudioObjectUnknown {
+ return None;
+ }
+ Some(devid)
+ })
}
// TODO: Create a GetProperty trait and add a default implementation for it, then implement it
@@ -99,16 +126,17 @@ impl TestAudioUnit {
impl Drop for TestAudioUnit {
fn drop(&mut self) {
- unsafe {
+ run_serially_forward_panics(|| unsafe {
AudioUnitUninitialize(self.0);
AudioComponentInstanceDispose(self.0);
- }
+ });
}
}
// TODO: 1. Return Result with custom errors.
// 2. Allow to create a in-out unit.
pub fn test_get_default_audiounit(scope: Scope) -> Option<TestAudioUnit> {
+ debug_assert_not_running_serially();
let device = test_get_default_device(scope.clone());
let unit = test_create_audiounit(ComponentSubType::HALOutput);
if device.is_none() || unit.is_none() {
@@ -133,7 +161,7 @@ pub fn test_get_default_audiounit(scope: Scope) -> Option<TestAudioUnit> {
}
}
- let status = unsafe {
+ let status = run_serially(|| unsafe {
AudioUnitSetProperty(
unit.get_inner(),
kAudioOutputUnitProperty_CurrentDevice,
@@ -142,7 +170,7 @@ pub fn test_get_default_audiounit(scope: Scope) -> Option<TestAudioUnit> {
&device as *const AudioObjectID as *const c_void,
mem::size_of::<AudioObjectID>() as u32,
)
- };
+ });
if status == NO_ERR {
Some(unit)
} else {
@@ -159,6 +187,7 @@ pub enum ComponentSubType {
// Surprisingly the AudioUnit can be created even when there is no any device on the platform,
// no matter its subtype is HALOutput or DefaultOutput.
pub fn test_create_audiounit(unit_type: ComponentSubType) -> Option<TestAudioUnit> {
+ debug_assert_not_running_serially();
let desc = AudioComponentDescription {
componentType: kAudioUnitType_Output,
componentSubType: match unit_type {
@@ -169,12 +198,12 @@ pub fn test_create_audiounit(unit_type: ComponentSubType) -> Option<TestAudioUni
componentFlags: 0,
componentFlagsMask: 0,
};
- let comp = unsafe { AudioComponentFindNext(ptr::null_mut(), &desc) };
+ let comp = run_serially(|| unsafe { AudioComponentFindNext(ptr::null_mut(), &desc) });
if comp.is_null() {
return None;
}
let mut unit: AudioUnit = ptr::null_mut();
- let status = unsafe { AudioComponentInstanceNew(comp, &mut unit) };
+ let status = run_serially(|| unsafe { AudioComponentInstanceNew(comp, &mut unit) });
// TODO: Is unit possible to be null when no error returns ?
if status != NO_ERR || unit.is_null() {
None
@@ -188,13 +217,14 @@ fn test_enable_audiounit_in_scope(
scope: Scope,
enable: bool,
) -> std::result::Result<(), OSStatus> {
+ debug_assert_not_running_serially();
assert!(!unit.is_null());
let (scope, element) = match scope {
Scope::Input => (kAudioUnitScope_Input, AU_IN_BUS),
Scope::Output => (kAudioUnitScope_Output, AU_OUT_BUS),
};
let on_off: u32 = if enable { 1 } else { 0 };
- let status = unsafe {
+ let status = run_serially(|| unsafe {
AudioUnitSetProperty(
unit,
kAudioOutputUnitProperty_EnableIO,
@@ -203,7 +233,7 @@ fn test_enable_audiounit_in_scope(
&on_off as *const u32 as *const c_void,
mem::size_of::<u32>() as u32,
)
- };
+ });
if status == NO_ERR {
Ok(())
} else {
@@ -216,72 +246,80 @@ pub enum DeviceFilter {
IncludeAll,
}
pub fn test_get_all_devices(filter: DeviceFilter) -> Vec<AudioObjectID> {
- let mut devices = Vec::new();
- let address = AudioObjectPropertyAddress {
- mSelector: kAudioHardwarePropertyDevices,
- mScope: kAudioObjectPropertyScopeGlobal,
- mElement: kAudioObjectPropertyElementMaster,
- };
- let mut size: usize = 0;
- let status = unsafe {
- AudioObjectGetPropertyDataSize(
- kAudioObjectSystemObject,
- &address,
- 0,
- ptr::null(),
- &mut size as *mut usize as *mut u32,
- )
- };
- // size will be 0 if there is no device at all.
- if status != NO_ERR || size == 0 {
- return devices;
- }
- assert_eq!(size % mem::size_of::<AudioObjectID>(), 0);
- let elements = size / mem::size_of::<AudioObjectID>();
- devices.resize(elements, kAudioObjectUnknown);
- let status = unsafe {
- AudioObjectGetPropertyData(
- kAudioObjectSystemObject,
- &address,
- 0,
- ptr::null(),
- &mut size as *mut usize as *mut u32,
- devices.as_mut_ptr() as *mut c_void,
- )
- };
- if status != NO_ERR {
- devices.clear();
- return devices;
- }
- for device in devices.iter() {
- assert_ne!(*device, kAudioObjectUnknown);
- }
+ debug_assert_not_running_serially();
+ // To avoid races, the devices getter and the device name filtering have
+ // to run in the same serial task. If not, a device may exist when the
+ // getter runs but not when getting its uid.
+ run_serially_forward_panics(|| {
+ let mut devices = Vec::new();
+ let address = AudioObjectPropertyAddress {
+ mSelector: kAudioHardwarePropertyDevices,
+ mScope: kAudioObjectPropertyScopeGlobal,
+ mElement: kAudioObjectPropertyElementMaster,
+ };
+ let mut size: usize = 0;
+ let status = unsafe {
+ AudioObjectGetPropertyDataSize(
+ kAudioObjectSystemObject,
+ &address,
+ 0,
+ ptr::null(),
+ &mut size as *mut usize as *mut u32,
+ )
+ };
+ // size will be 0 if there is no device at all.
+ if status != NO_ERR || size == 0 {
+ return devices;
+ }
+ assert_eq!(size % mem::size_of::<AudioObjectID>(), 0);
+ let elements = size / mem::size_of::<AudioObjectID>();
+ devices.resize(elements, kAudioObjectUnknown);
+ let status = unsafe {
+ AudioObjectGetPropertyData(
+ kAudioObjectSystemObject,
+ &address,
+ 0,
+ ptr::null(),
+ &mut size as *mut usize as *mut u32,
+ devices.as_mut_ptr() as *mut c_void,
+ )
+ };
+ if status != NO_ERR {
+ devices.clear();
+ return devices;
+ }
+ for device in devices.iter() {
+ assert_ne!(*device, kAudioObjectUnknown);
+ }
- match filter {
- DeviceFilter::ExcludeCubebAggregateAndVPIO => {
- devices.retain(|&device| {
- if let Ok(uid) = get_device_global_uid(device) {
- let uid = uid.into_string();
- !uid.contains(PRIVATE_AGGREGATE_DEVICE_NAME)
- && !uid.contains(VOICEPROCESSING_AGGREGATE_DEVICE_NAME)
- } else {
- true
- }
- });
+ match filter {
+ DeviceFilter::ExcludeCubebAggregateAndVPIO => {
+ devices.retain(|&device| {
+ if let Ok(uid) = get_device_global_uid(device) {
+ let uid = uid.into_string();
+ !uid.contains(PRIVATE_AGGREGATE_DEVICE_NAME)
+ && !uid.contains(VOICEPROCESSING_AGGREGATE_DEVICE_NAME)
+ } else {
+ true
+ }
+ });
+ }
+ _ => {}
}
- _ => {}
- }
- devices
+ devices
+ })
}
pub fn test_get_devices_in_scope(scope: Scope) -> Vec<AudioObjectID> {
+ debug_assert_not_running_serially();
let mut devices = test_get_all_devices(DeviceFilter::ExcludeCubebAggregateAndVPIO);
devices.retain(|device| test_device_in_scope(*device, scope.clone()));
devices
}
pub fn get_devices_info_in_scope(scope: Scope) -> Vec<TestDeviceInfo> {
+ debug_assert_not_running_serially();
fn print_info(info: &TestDeviceInfo) {
println!("{:>4}: {}\n\tuid: {}", info.id, info.label, info.uid);
}
@@ -337,8 +375,9 @@ pub fn test_device_channels_in_scope(
id: AudioObjectID,
scope: Scope,
) -> std::result::Result<u32, OSStatus> {
+ debug_assert_not_running_serially();
let address = AudioObjectPropertyAddress {
- mSelector: kAudioDevicePropertyStreamConfiguration,
+ mSelector: kAudioDevicePropertyStreams,
mScope: match scope {
Scope::Input => kAudioDevicePropertyScopeInput,
Scope::Output => kAudioDevicePropertyScopeOutput,
@@ -346,7 +385,7 @@ pub fn test_device_channels_in_scope(
mElement: kAudioObjectPropertyElementMaster,
};
let mut size: usize = 0;
- let status = unsafe {
+ let status = run_serially(|| unsafe {
AudioObjectGetPropertyDataSize(
id,
&address,
@@ -354,49 +393,90 @@ pub fn test_device_channels_in_scope(
ptr::null(),
&mut size as *mut usize as *mut u32,
)
- };
+ });
if status != NO_ERR {
return Err(status);
}
if size == 0 {
return Ok(0);
}
- let byte_len = size / mem::size_of::<u8>();
- let mut bytes = vec![0u8; byte_len];
- let status = unsafe {
+ let mut stream_list = vec![0, (size / mem::size_of::<AudioObjectID>()) as u32];
+ let status = run_serially(|| unsafe {
AudioObjectGetPropertyData(
id,
&address,
0,
ptr::null(),
&mut size as *mut usize as *mut u32,
- bytes.as_mut_ptr() as *mut c_void,
+ stream_list.as_mut_ptr() as *mut c_void,
)
- };
+ });
if status != NO_ERR {
return Err(status);
}
- let buf_list = unsafe { &*(bytes.as_mut_ptr() as *mut AudioBufferList) };
- let buf_len = buf_list.mNumberBuffers as usize;
- if buf_len == 0 {
- return Ok(0);
- }
- let buf_ptr = buf_list.mBuffers.as_ptr() as *const AudioBuffer;
- let buffers = unsafe { slice::from_raw_parts(buf_ptr, buf_len) };
- let mut channels: u32 = 0;
- for buffer in buffers {
- channels += buffer.mNumberChannels;
- }
+ let channels = stream_list
+ .iter()
+ .filter(|s: &&AudioObjectID| {
+ if scope != Scope::Input {
+ return true;
+ }
+ let address = AudioObjectPropertyAddress {
+ mSelector: kAudioStreamPropertyTerminalType,
+ mScope: kAudioObjectPropertyScopeGlobal,
+ mElement: kAudioObjectPropertyElementMaster,
+ };
+ let mut ttype: u32 = 0;
+ let status = unsafe {
+ AudioObjectGetPropertyData(
+ **s,
+ &address,
+ 0,
+ ptr::null(),
+ &mut mem::size_of::<u32>() as *mut usize as *mut u32,
+ &mut ttype as *mut u32 as *mut c_void,
+ )
+ };
+ if status != NO_ERR {
+ return false;
+ }
+ ttype == kAudioStreamTerminalTypeMicrophone
+ || (INPUT_MICROPHONE..OUTPUT_UNDEFINED).contains(&ttype)
+ })
+ .map(|s: &AudioObjectID| {
+ let address = AudioObjectPropertyAddress {
+ mSelector: kAudioStreamPropertyVirtualFormat,
+ mScope: kAudioObjectPropertyScopeGlobal,
+ mElement: kAudioObjectPropertyElementMaster,
+ };
+ let mut format = AudioStreamBasicDescription::default();
+ let status = unsafe {
+ AudioObjectGetPropertyData(
+ *s,
+ &address,
+ 0,
+ ptr::null(),
+ &mut mem::size_of::<AudioStreamBasicDescription>() as *mut usize as *mut u32,
+ &mut format as *mut AudioStreamBasicDescription as *mut c_void,
+ )
+ };
+ if status != NO_ERR {
+ return 0;
+ }
+ format.mChannelsPerFrame
+ })
+ .sum();
Ok(channels)
}
pub fn test_device_in_scope(id: AudioObjectID, scope: Scope) -> bool {
+ debug_assert_not_running_serially();
let channels = test_device_channels_in_scope(id, scope);
channels.is_ok() && channels.unwrap() > 0
}
pub fn test_get_all_onwed_devices(id: AudioDeviceID) -> Vec<AudioObjectID> {
assert_ne!(id, kAudioObjectUnknown);
+ debug_assert_running_serially();
let address = AudioObjectPropertyAddress {
mSelector: kAudioObjectPropertyOwnedObjects,
@@ -409,45 +489,46 @@ pub fn test_get_all_onwed_devices(id: AudioDeviceID) -> Vec<AudioObjectID> {
let qualifier_data = &class_id;
let mut size: usize = 0;
- unsafe {
- assert_eq!(
+ assert_eq!(
+ unsafe {
AudioObjectGetPropertyDataSize(
id,
&address,
qualifier_data_size as u32,
qualifier_data as *const u32 as *const c_void,
- &mut size as *mut usize as *mut u32
- ),
- NO_ERR
- );
- }
+ &mut size as *mut usize as *mut u32,
+ )
+ },
+ NO_ERR
+ );
assert_ne!(size, 0);
let elements = size / mem::size_of::<AudioObjectID>();
let mut devices: Vec<AudioObjectID> = allocate_array(elements);
- unsafe {
- assert_eq!(
+ assert_eq!(
+ unsafe {
AudioObjectGetPropertyData(
id,
&address,
qualifier_data_size as u32,
qualifier_data as *const u32 as *const c_void,
&mut size as *mut usize as *mut u32,
- devices.as_mut_ptr() as *mut c_void
- ),
- NO_ERR
- );
- }
+ devices.as_mut_ptr() as *mut c_void,
+ )
+ },
+ NO_ERR
+ );
devices
}
pub fn test_get_master_device(id: AudioObjectID) -> String {
assert_ne!(id, kAudioObjectUnknown);
+ debug_assert_running_serially();
let address = AudioObjectPropertyAddress {
- mSelector: kAudioAggregateDevicePropertyMasterSubDevice,
+ mSelector: kAudioAggregateDevicePropertyMainSubDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
@@ -465,6 +546,7 @@ pub fn test_get_master_device(id: AudioObjectID) -> String {
}
pub fn test_get_drift_compensations(id: AudioObjectID) -> std::result::Result<u32, OSStatus> {
+ debug_assert_running_serially();
let address = AudioObjectPropertyAddress {
mSelector: kAudioSubDevicePropertyDriftCompensation,
mScope: kAudioObjectPropertyScopeGlobal,
@@ -491,6 +573,7 @@ pub fn test_get_drift_compensations(id: AudioObjectID) -> std::result::Result<u3
pub fn test_audiounit_scope_is_enabled(unit: AudioUnit, scope: Scope) -> bool {
assert!(!unit.is_null());
+ debug_assert_not_running_serially();
let mut has_io: UInt32 = 0;
let (scope, element) = match scope {
Scope::Input => (kAudioUnitScope_Input, AU_IN_BUS),
@@ -498,14 +581,14 @@ pub fn test_audiounit_scope_is_enabled(unit: AudioUnit, scope: Scope) -> bool {
};
let mut size = mem::size_of::<UInt32>();
assert_eq!(
- audio_unit_get_property(
+ run_serially(|| audio_unit_get_property(
unit,
kAudioOutputUnitProperty_HasIO,
scope,
element,
&mut has_io,
&mut size
- ),
+ )),
NO_ERR
);
has_io != 0
@@ -516,6 +599,7 @@ pub fn test_audiounit_get_buffer_frame_size(
scope: Scope,
prop_scope: PropertyScope,
) -> std::result::Result<u32, OSStatus> {
+ debug_assert_not_running_serially();
let element = match scope {
Scope::Input => AU_IN_BUS,
Scope::Output => AU_OUT_BUS,
@@ -526,7 +610,7 @@ pub fn test_audiounit_get_buffer_frame_size(
};
let mut buffer_frames: u32 = 0;
let mut size = mem::size_of::<u32>();
- let status = unsafe {
+ let status = run_serially(|| unsafe {
AudioUnitGetProperty(
unit,
kAudioDevicePropertyBufferFrameSize,
@@ -535,7 +619,7 @@ pub fn test_audiounit_get_buffer_frame_size(
&mut buffer_frames as *mut u32 as *mut c_void,
&mut size as *mut usize as *mut u32,
)
- };
+ });
if status == NO_ERR {
Ok(buffer_frames)
} else {
@@ -554,6 +638,7 @@ pub fn test_set_default_device(
device: AudioObjectID,
scope: Scope,
) -> std::result::Result<AudioObjectID, OSStatus> {
+ debug_assert_not_running_serially();
assert!(test_device_in_scope(device, scope.clone()));
let default = test_get_default_device(scope.clone()).unwrap();
if default == device {
@@ -570,7 +655,7 @@ pub fn test_set_default_device(
mElement: kAudioObjectPropertyElementMaster,
};
let size = mem::size_of::<AudioObjectID>();
- let status = unsafe {
+ let status = run_serially(|| unsafe {
AudioObjectSetPropertyData(
kAudioObjectSystemObject,
&address,
@@ -579,7 +664,7 @@ pub fn test_set_default_device(
size as u32,
&device as *const AudioObjectID as *const c_void,
)
- };
+ });
let new_default = test_get_default_device(scope.clone()).unwrap();
if new_default == default {
Err(-1)
@@ -644,6 +729,7 @@ pub fn test_create_device_change_listener<F>(scope: Scope, listener: F) -> TestP
where
F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,
{
+ debug_assert_running_serially();
let address = AudioObjectPropertyAddress {
mSelector: match scope {
Scope::Input => kAudioHardwarePropertyDefaultInputDevice,
@@ -652,6 +738,7 @@ where
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster,
};
+
TestPropertyListener::new(kAudioObjectSystemObject, address, listener)
}
@@ -677,6 +764,7 @@ where
}
pub fn start(&self) -> std::result::Result<(), OSStatus> {
+ debug_assert_running_serially();
let status = unsafe {
AudioObjectAddPropertyListener(
self.device,
@@ -693,6 +781,7 @@ where
}
pub fn stop(&self) -> std::result::Result<(), OSStatus> {
+ debug_assert_running_serially();
let status = unsafe {
AudioObjectRemovePropertyListener(
self.device,
@@ -726,7 +815,7 @@ where
F: Fn(&[AudioObjectPropertyAddress]) -> OSStatus,
{
fn drop(&mut self) {
- self.stop();
+ run_serially(|| self.stop());
}
}
@@ -767,6 +856,7 @@ impl TestDevicePlugger {
}
fn destroy_aggregate_device(&mut self) -> std::result::Result<(), OSStatus> {
+ debug_assert_not_running_serially();
assert_ne!(self.plugin_id, kAudioObjectUnknown);
assert_ne!(self.device_id, kAudioObjectUnknown);
@@ -777,7 +867,7 @@ impl TestDevicePlugger {
};
let mut size: usize = 0;
- let status = unsafe {
+ let status = run_serially(|| unsafe {
AudioObjectGetPropertyDataSize(
self.plugin_id,
&address,
@@ -785,13 +875,13 @@ impl TestDevicePlugger {
ptr::null(),
&mut size as *mut usize as *mut u32,
)
- };
+ });
if status != NO_ERR {
return Err(status);
}
assert_ne!(size, 0);
- let status = unsafe {
+ let status = run_serially(|| unsafe {
// This call can simulate removing a device.
AudioObjectGetPropertyData(
self.plugin_id,
@@ -801,7 +891,7 @@ impl TestDevicePlugger {
&mut size as *mut usize as *mut u32,
&mut self.device_id as *mut AudioDeviceID as *mut c_void,
)
- };
+ });
if status == NO_ERR {
self.device_id = kAudioObjectUnknown;
Ok(())
@@ -811,6 +901,7 @@ impl TestDevicePlugger {
}
fn create_aggregate_device(&self) -> std::result::Result<AudioObjectID, OSStatus> {
+ debug_assert_not_running_serially();
use std::time::{SystemTime, UNIX_EPOCH};
const TEST_AGGREGATE_DEVICE_NAME: &str = "TestAggregateDevice";
@@ -830,7 +921,7 @@ impl TestDevicePlugger {
};
let mut size: usize = 0;
- let status = unsafe {
+ let status = run_serially(|| unsafe {
AudioObjectGetPropertyDataSize(
self.plugin_id,
&address,
@@ -838,7 +929,7 @@ impl TestDevicePlugger {
ptr::null(),
&mut size as *mut usize as *mut u32,
)
- };
+ });
if status != NO_ERR {
return Err(status);
}
@@ -915,14 +1006,18 @@ impl TestDevicePlugger {
CFRelease(sub_devices as *const c_void);
// This call can simulate adding a device.
- let status = AudioObjectGetPropertyData(
- self.plugin_id,
- &address,
- mem::size_of_val(&device_dict) as u32,
- &device_dict as *const CFMutableDictionaryRef as *const c_void,
- &mut size as *mut usize as *mut u32,
- &mut device_id as *mut AudioDeviceID as *mut c_void,
- );
+ let status = {
+ run_serially(|| {
+ AudioObjectGetPropertyData(
+ self.plugin_id,
+ &address,
+ mem::size_of_val(&device_dict) as u32,
+ &device_dict as *const CFMutableDictionaryRef as *const c_void,
+ &mut size as *mut usize as *mut u32,
+ &mut device_id as *mut AudioDeviceID as *mut c_void,
+ )
+ })
+ };
CFRelease(device_dict as *const c_void);
status
};
@@ -935,6 +1030,7 @@ impl TestDevicePlugger {
}
fn get_system_plugin_id() -> std::result::Result<AudioObjectID, OSStatus> {
+ debug_assert_not_running_serially();
let address = AudioObjectPropertyAddress {
mSelector: kAudioHardwarePropertyPlugInForBundleID,
mScope: kAudioObjectPropertyScopeGlobal,
@@ -942,7 +1038,7 @@ impl TestDevicePlugger {
};
let mut size: usize = 0;
- let status = unsafe {
+ let status = run_serially(|| unsafe {
AudioObjectGetPropertyDataSize(
kAudioObjectSystemObject,
&address,
@@ -950,7 +1046,7 @@ impl TestDevicePlugger {
ptr::null(),
&mut size as *mut usize as *mut u32,
)
- };
+ });
if status != NO_ERR {
return Err(status);
}
@@ -967,14 +1063,16 @@ impl TestDevicePlugger {
assert_eq!(size, mem::size_of_val(&translation_value));
let status = unsafe {
- let status = AudioObjectGetPropertyData(
- kAudioObjectSystemObject,
- &address,
- 0,
- ptr::null(),
- &mut size as *mut usize as *mut u32,
- &mut translation_value as *mut AudioValueTranslation as *mut c_void,
- );
+ let status = run_serially(|| {
+ AudioObjectGetPropertyData(
+ kAudioObjectSystemObject,
+ &address,
+ 0,
+ ptr::null(),
+ &mut size as *mut usize as *mut u32,
+ &mut translation_value as *mut AudioValueTranslation as *mut c_void,
+ )
+ });
CFRelease(in_bundle_ref as *const c_void);
status
};
@@ -991,10 +1089,11 @@ impl TestDevicePlugger {
// them into the array, if the device is an aggregate device. See the code in
// AggregateDevice::get_sub_devices and audiounit_set_aggregate_sub_device_list.
fn get_sub_devices(scope: Scope) -> Option<CFArrayRef> {
+ debug_assert_not_running_serially();
let device = test_get_default_device(scope);
device?;
let device = device.unwrap();
- let uid = get_device_global_uid(device);
+ let uid = run_serially(|| get_device_global_uid(device));
if uid.is_err() {
return None;
}
@@ -1033,6 +1132,7 @@ pub fn test_ops_context_operation<F>(name: &'static str, operation: F)
where
F: FnOnce(*mut ffi::cubeb),
{
+ debug_assert_not_running_serially();
let name_c_string = CString::new(name).expect("Failed to create context name");
let mut context = ptr::null_mut::<ffi::cubeb>();
assert_eq!(
@@ -1047,8 +1147,9 @@ where
// The in-out stream initializeed with different device will create an aggregate_device and
// result in firing device-collection-changed callbacks. Run in-out streams with tests
// capturing device-collection-changed callbacks may cause troubles.
-pub fn test_ops_stream_operation<F>(
+pub fn test_ops_stream_operation_on_context<F>(
name: &'static str,
+ context_ptr: *mut ffi::cubeb,
input_device: ffi::cubeb_devid,
input_stream_params: *mut ffi::cubeb_stream_params,
output_device: ffi::cubeb_devid,
@@ -1061,43 +1162,72 @@ pub fn test_ops_stream_operation<F>(
) where
F: FnOnce(*mut ffi::cubeb_stream),
{
- test_ops_context_operation("context: stream operation", |context_ptr| {
- // Do nothing if there is no input/output device to perform input/output tests.
- if !input_stream_params.is_null() && test_get_default_device(Scope::Input).is_none() {
- println!("No input device to perform input tests for \"{}\".", name);
- return;
- }
+ // Do nothing if there is no input/output device to perform input/output tests.
+ if !input_stream_params.is_null() && test_get_default_device(Scope::Input).is_none() {
+ println!("No input device to perform input tests for \"{}\".", name);
+ return;
+ }
- if !output_stream_params.is_null() && test_get_default_device(Scope::Output).is_none() {
- println!("No output device to perform output tests for \"{}\".", name);
- return;
- }
+ if !output_stream_params.is_null() && test_get_default_device(Scope::Output).is_none() {
+ println!("No output device to perform output tests for \"{}\".", name);
+ return;
+ }
- let mut stream: *mut ffi::cubeb_stream = ptr::null_mut();
- let stream_name = CString::new(name).expect("Failed to create stream name");
- assert_eq!(
- unsafe {
- OPS.stream_init.unwrap()(
- context_ptr,
- &mut stream,
- stream_name.as_ptr(),
- input_device,
- input_stream_params,
- output_device,
- output_stream_params,
- latency_frames,
- data_callback,
- state_callback,
- user_ptr,
- )
- },
- ffi::CUBEB_OK
- );
- assert!(!stream.is_null());
- operation(stream);
+ let mut stream: *mut ffi::cubeb_stream = ptr::null_mut();
+ let stream_name = CString::new(name).expect("Failed to create stream name");
+ assert_eq!(
unsafe {
- OPS.stream_destroy.unwrap()(stream);
- }
+ OPS.stream_init.unwrap()(
+ context_ptr,
+ &mut stream,
+ stream_name.as_ptr(),
+ input_device,
+ input_stream_params,
+ output_device,
+ output_stream_params,
+ latency_frames,
+ data_callback,
+ state_callback,
+ user_ptr,
+ )
+ },
+ ffi::CUBEB_OK
+ );
+ assert!(!stream.is_null());
+ operation(stream);
+ unsafe {
+ OPS.stream_destroy.unwrap()(stream);
+ }
+}
+
+pub fn test_ops_stream_operation<F>(
+ name: &'static str,
+ input_device: ffi::cubeb_devid,
+ input_stream_params: *mut ffi::cubeb_stream_params,
+ output_device: ffi::cubeb_devid,
+ output_stream_params: *mut ffi::cubeb_stream_params,
+ latency_frames: u32,
+ data_callback: ffi::cubeb_data_callback,
+ state_callback: ffi::cubeb_state_callback,
+ user_ptr: *mut c_void,
+ operation: F,
+) where
+ F: FnOnce(*mut ffi::cubeb_stream),
+{
+ test_ops_context_operation("context: stream operation", |context_ptr| {
+ test_ops_stream_operation_on_context(
+ name,
+ context_ptr,
+ input_device,
+ input_stream_params,
+ output_device,
+ output_stream_params,
+ latency_frames,
+ data_callback,
+ state_callback,
+ user_ptr,
+ operation,
+ );
});
}
@@ -1136,7 +1266,7 @@ fn test_get_raw_stream<F>(
user_ptr,
data_callback,
state_callback,
- global_latency_frames.unwrap(),
+ global_latency_frames,
);
stream.core_stream_data = CoreStreamData::new(&stream, None, None);
@@ -1154,6 +1284,7 @@ pub fn test_get_stream_with_default_data_callback_by_type<F>(
) where
F: FnOnce(&mut AudioUnitStream),
{
+ debug_assert_not_running_serially();
let mut input_params = get_dummy_stream_params(Scope::Input);
let mut output_params = get_dummy_stream_params(Scope::Output);
diff --git a/third_party/rust/d3d12/.cargo-checksum.json b/third_party/rust/d3d12/.cargo-checksum.json
index 8b8a944bf0..8b27c96861 100644
--- a/third_party/rust/d3d12/.cargo-checksum.json
+++ b/third_party/rust/d3d12/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"CHANGELOG.md":"45fa76b0e5bc51721887147000e9e78a5934cb04d1ad628e501ef2082763d353","Cargo.toml":"1108efc3deca25c270c3f8631abca07e7e696aace92d324ef8b83503cb2edbc3","README.md":"76cee3209f773a62535de6c9724b53f158406359f35b4d48b17ac3747b6c102e","src/com.rs":"cfd6556a7abf38cba57559038f9f2cf86274418448fb2745436c251a99575e05","src/command_allocator.rs":"ef01059a661749470f3772d188fe0fab0f002e1d154facdab4b9b2932f4b2d93","src/command_list.rs":"8723f3b755b721e0dbb234bd604956c1b7922a2368231197495daa3fa6548e63","src/debug.rs":"aa33b98f7c3e71cba75fc42c6ca9af72d96b45122422c16e48525e24590c57bf","src/descriptor.rs":"fea0b820de1566b54d17d8d0c67e6f5a2126eda19526397eb710ff7d6db9db9e","src/device.rs":"c1dd479aabd22bced0d407523d60629ad1da439fb47ad89fe7b48bae1c4b23e5","src/dxgi.rs":"1516186845b91bf3df813a29b4a0e00a85ca5649fb7a2755da43fba984c41a42","src/heap.rs":"dae2380684896c97e97ed022929f79ce2cc4f5418a3ec34883086f7c88f423d0","src/lib.rs":"612e2f471b84502d219da3fb86ee13f3cbd6faf17d77407bab6c84e51ec424d0","src/pso.rs":"ff819c321536695e34a3be9a6051cf3e57765049a4a2035db6ab27add5a7978a","src/query.rs":"ff61a2b76a108afc1f082724bb9b07ac8b52afbe97356e0fcf6df0ff7e53e07d","src/queue.rs":"bd32813d0b8a3bedf3223b69ade9f9c799a138a9e27d970f86435d9ce32d1557","src/resource.rs":"8989cdb7c3ee0687c826047f39f85148459d9219754f20a970bf8aaa09b96e27","src/sync.rs":"5c287fb7498242a397eb1f08887be9cff9b48dc7cb13af5792cce5f7182b55f8"},"package":null} \ No newline at end of file
+{"files":{"CHANGELOG.md":"45fa76b0e5bc51721887147000e9e78a5934cb04d1ad628e501ef2082763d353","Cargo.toml":"a3135c67216ba021525ebc8d18dc3de5d779f1a1ddde5f25f4439acabd45824a","README.md":"76cee3209f773a62535de6c9724b53f158406359f35b4d48b17ac3747b6c102e","src/com.rs":"cfd6556a7abf38cba57559038f9f2cf86274418448fb2745436c251a99575e05","src/command_allocator.rs":"ef01059a661749470f3772d188fe0fab0f002e1d154facdab4b9b2932f4b2d93","src/command_list.rs":"8723f3b755b721e0dbb234bd604956c1b7922a2368231197495daa3fa6548e63","src/debug.rs":"aa33b98f7c3e71cba75fc42c6ca9af72d96b45122422c16e48525e24590c57bf","src/descriptor.rs":"fea0b820de1566b54d17d8d0c67e6f5a2126eda19526397eb710ff7d6db9db9e","src/device.rs":"c1dd479aabd22bced0d407523d60629ad1da439fb47ad89fe7b48bae1c4b23e5","src/dxgi.rs":"1516186845b91bf3df813a29b4a0e00a85ca5649fb7a2755da43fba984c41a42","src/heap.rs":"dae2380684896c97e97ed022929f79ce2cc4f5418a3ec34883086f7c88f423d0","src/lib.rs":"612e2f471b84502d219da3fb86ee13f3cbd6faf17d77407bab6c84e51ec424d0","src/pso.rs":"ff819c321536695e34a3be9a6051cf3e57765049a4a2035db6ab27add5a7978a","src/query.rs":"ff61a2b76a108afc1f082724bb9b07ac8b52afbe97356e0fcf6df0ff7e53e07d","src/queue.rs":"bd32813d0b8a3bedf3223b69ade9f9c799a138a9e27d970f86435d9ce32d1557","src/resource.rs":"8989cdb7c3ee0687c826047f39f85148459d9219754f20a970bf8aaa09b96e27","src/sync.rs":"5c287fb7498242a397eb1f08887be9cff9b48dc7cb13af5792cce5f7182b55f8"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/d3d12/Cargo.toml b/third_party/rust/d3d12/Cargo.toml
index 88aedd92f7..1425e10b80 100644
--- a/third_party/rust/d3d12/Cargo.toml
+++ b/third_party/rust/d3d12/Cargo.toml
@@ -40,7 +40,7 @@ implicit-link = []
bitflags = "2"
[target."cfg(windows)".dependencies.libloading]
-version = ">=0.7,<0.9"
+version = ">=0.7, <0.9"
optional = true
[target."cfg(windows)".dependencies.winapi]
diff --git a/third_party/rust/embed-manifest/.cargo-checksum.json b/third_party/rust/embed-manifest/.cargo-checksum.json
new file mode 100644
index 0000000000..bb64810ff2
--- /dev/null
+++ b/third_party/rust/embed-manifest/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"a1cbcc712504ce6658dc209cab886c724654e6e0ef7b26315fd86d27e04a6e09","Cargo.toml":"4f091eabd120a0c1beefc14b92728e312f0c4f57ec8d85e2b3c5008e1ab01fd0","LICENSE":"d041a5a86caaf9cc7720f3637d689252083a664d9ccd946a36d1c7c708d204cb","README.md":"8cf8a7b20cfb7fa43c4ee9585bf92ea6c5a5c4ca3ef700974edb02025d5146c8","rustfmt.toml":"84f2508ad6e506e71f2005a75ef20ba32eedb3bd6547af9ea04596517be883bb","src/embed/coff.rs":"55ba01eea1a5e16ef7ae4438f5cc51ec60b6eca45f5cabd706ee5b82da4f010b","src/embed/error.rs":"aecb4928e70b02b784557352608f6d4fb9b88b44ae3297a33969a0f2219c416c","src/embed/mod.rs":"a3f02a410c7b681f87ecfed415c97c4252569dee231b864c2b12e5d80ed4065e","src/embed/test.rs":"969dc4e2faf9ef9e9164b0e1ad01082a0a5e6ef5eb963526cb207a0ec380d809","src/lib.rs":"2786336ef4e787b0e3ccec9ab02043ae064f50df73a63ffe8c374217adfb353a","src/manifest/mod.rs":"c82936859d2ef795836972e08323c9698cc0513ba125cfed43414cd05d27e0b1","src/manifest/test.rs":"f4ab58ed4fc99b087ba30ab595026e980573aad980b8fa6f59a5b748404012ff","src/manifest/xml.rs":"1bce12120e17a49da43eabbd1b04f712b3f6ece7fcbca9186319e301890e20c5","testdata/sample.exe.manifest":"01e80ef76de2b628d452c7e80022654b9e0c8aee72ec64ee522c7083d835f4df"},"package":"41cd446c890d6bed1d8b53acef5f240069ebef91d6fae7c5f52efe61fe8b5eae"} \ No newline at end of file
diff --git a/third_party/rust/embed-manifest/CHANGELOG.md b/third_party/rust/embed-manifest/CHANGELOG.md
new file mode 100644
index 0000000000..8bc510bbf0
--- /dev/null
+++ b/third_party/rust/embed-manifest/CHANGELOG.md
@@ -0,0 +1,59 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [1.4.0] - 2023-06-22
+### Added
+- Use `empty_manifest()` to start from a manifest with no default values.
+ Fixes [issue #6](https://gitlab.com/careyevans/embed-manifest/-/issues/6).
+### Fixed
+- Generate an object file with a single `.rsrc` section on GNU targets.
+ This lets it replace the default manifest from MinGW build tools.
+ Fixes [issue #5](https://gitlab.com/careyevans/embed-manifest/-/issues/5).
+
+## [1.3.1] - 2022-08-07
+### Added
+- Format the code with Rustfmt.
+- Assume `gnullvm` target environment should work like `gnu`.
+- Add Windows 11 22H2 SDK version for maxversiontested.
+### Changed
+- Update `object` dependency and simplify unit tests.
+
+## [1.3.0] - 2022-05-01
+### Changed
+- Use our own code again to generate COFF object files for GNU targets, but with
+ better knowledge of how such files are structured, reducing dependencies and
+ compile time.
+- Link directly to the COFF object file instead of an archive file with one member.
+### Fixed
+- Make the custom `Error` type public.
+
+## [1.2.1] - 2022-04-18
+### Added
+- Add checks for Windows builds to the documentation, for programs that
+ should still build for non-Windows targets.
+
+## [1.2.0] - 2022-04-17
+### Added
+- Generate the manifest XML from Rust code rather than requiring the developer
+ to supply a correct manifest file.
+
+## [1.1.0] - 2022-03-24
+### Changed
+- Use [Gimli Object](https://crates.io/crates/object) crate to build COFF
+ objects containing resources for GNU targets, removing a lot of magic numbers
+ and generating output more like LLVM `windres`.
+
+## [1.0.0] - 2021-12-18
+### Added
+- Initial version.
+
+[1.0.0]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.0.0
+[1.1.0]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.1.0
+[1.2.0]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.2.0
+[1.2.1]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.2.1
+[1.3.0]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.3.0
+[1.3.1]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.3.1
+[1.4.0]: https://gitlab.com/careyevans/embed-manifest/-/releases/v1.4.0
diff --git a/third_party/rust/embed-manifest/Cargo.toml b/third_party/rust/embed-manifest/Cargo.toml
new file mode 100644
index 0000000000..aeedb3598d
--- /dev/null
+++ b/third_party/rust/embed-manifest/Cargo.toml
@@ -0,0 +1,38 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.56"
+name = "embed-manifest"
+version = "1.4.0"
+authors = ["Carey Evans <carey@carey.geek.nz>"]
+description = "Build script library to easily embed a Windows manifest."
+readme = "README.md"
+keywords = [
+ "build",
+ "manifest",
+ "windows",
+]
+categories = ["development-tools::build-utils"]
+license = "MIT"
+repository = "https://gitlab.com/careyevans/embed-manifest"
+
+[dev-dependencies.object]
+version = "0.31.1"
+features = [
+ "read_core",
+ "coff",
+]
+default-features = false
+
+[dev-dependencies.tempfile]
+version = "3.5.0"
diff --git a/third_party/rust/embed-manifest/LICENSE b/third_party/rust/embed-manifest/LICENSE
new file mode 100644
index 0000000000..e724c68d3c
--- /dev/null
+++ b/third_party/rust/embed-manifest/LICENSE
@@ -0,0 +1,23 @@
+MIT License
+
+Copyright (c) 2021, 2022 Carey Evans
+
+All rights reserved.
+
+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/third_party/rust/embed-manifest/README.md b/third_party/rust/embed-manifest/README.md
new file mode 100644
index 0000000000..7752af0579
--- /dev/null
+++ b/third_party/rust/embed-manifest/README.md
@@ -0,0 +1,62 @@
+Windows Manifest Embedding for Rust
+===================================
+
+The `embed-manifest` crate provides a straightforward way to embed
+a Windows manifest in an executable, whatever the build environment,
+without dependencies on external tools from LLVM or MinGW.
+
+If you need to embed more resources than just a manifest, you may
+find the [winres](https://crates.io/crates/winres) or
+[embed-resource](https://crates.io/crates/embed-resource)
+crates more suitable. They have additional dependencies and setup
+requirements that may make them a little more difficult to use, though.
+
+
+Why use it?
+-----------
+
+The Rust compiler doesn’t add a manifest to a Windows executable, which
+means that it runs with a few compatibility options and settings that
+make it look like the program is running on an older version of Windows.
+By adding an application manifest using this crate, even when cross-compiling:
+
+- 32-bit programs with names that look like installers don’t try to run
+ as an administrator.
+- 32-bit programs aren’t shown a virtualised view of the filesystem and
+ registry.
+- Many non-Unicode APIs accept UTF-8 strings, the same as Rust uses.
+- The program sees the real Windows version, not Windows Vista.
+- Built-in message boxes and other UI elements can display without blurring
+ on high-DPI monitors.
+- Other features like long path names in APIs can be enabled.
+
+
+Usage
+-----
+
+To embed a default manifest, include this code in a `build.rs` build
+script:
+
+```rust
+use embed_manifest::{embed_manifest, new_manifest};
+
+fn main() {
+ if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
+ embed_manifest(new_manifest("Contoso.Sample"))
+ .expect("unable to embed manifest file");
+ }
+ println!("cargo:rerun-if-changed=build.rs");
+}
+```
+
+See the [crate documentation](https://docs.rs/embed-manifest) for
+information about how to customise the embedded manifest.
+
+
+License
+-------
+
+For the avoidance of doubt, while this crate itself is licensed to
+you under the MIT License, this does not affect the copyright status
+and licensing of your own code when this is used from a Cargo build
+script.
diff --git a/third_party/rust/embed-manifest/rustfmt.toml b/third_party/rust/embed-manifest/rustfmt.toml
new file mode 100644
index 0000000000..3aa392f175
--- /dev/null
+++ b/third_party/rust/embed-manifest/rustfmt.toml
@@ -0,0 +1,2 @@
+max_width = 132
+newline_style = "Unix"
diff --git a/third_party/rust/embed-manifest/src/embed/coff.rs b/third_party/rust/embed-manifest/src/embed/coff.rs
new file mode 100644
index 0000000000..bf751c3bf5
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/embed/coff.rs
@@ -0,0 +1,192 @@
+//! Just as much COFF object file support as is needed to write a resource data segment
+//! for GNU Windows targets. Inspired by the `write::Object` code from the `object` crate.
+//!
+//! Integers are converted from u64 to u32 and added without checking because the manifest
+//! data cannot get anywhere close to overflowing unless the supplied application name or
+//! number of dependencies was extremely long. If this code was used more generally or if
+//! the input was less trustworthy then more checked conversions and checked arithmetic
+//! would be needed.
+
+use std::io::{self, Seek, SeekFrom, Write};
+use std::time::SystemTime;
+
+#[derive(Debug, Clone, Copy)]
+pub enum MachineType {
+ I386,
+ X86_64,
+ Aarch64,
+}
+
+impl MachineType {
+ pub fn machine(&self) -> u16 {
+ match self {
+ Self::I386 => 0x014c,
+ Self::X86_64 => 0x8664,
+ Self::Aarch64 => 0xaa64,
+ }
+ }
+
+ pub fn relocation_type(&self) -> u16 {
+ match self {
+ Self::I386 => 7,
+ Self::X86_64 => 3,
+ Self::Aarch64 => 2,
+ }
+ }
+}
+
+pub struct CoffWriter<W> {
+ /// wrapped writer or buffer
+ writer: W,
+ /// machine type for file header
+ machine_type: MachineType,
+ // size in bytes of resource section data
+ size_of_raw_data: u32,
+ // number of relocations at the end of the section
+ number_of_relocations: u16,
+}
+
+impl<W: Write + Seek> CoffWriter<W> {
+ /// Create a new instance wrapping a writer.
+ pub fn new(mut writer: W, machine_type: MachineType) -> io::Result<Self> {
+ // Add space for file header and section table.
+ writer.write_all(&[0; 60])?;
+ Ok(Self {
+ writer,
+ machine_type,
+ size_of_raw_data: 0,
+ number_of_relocations: 0,
+ })
+ }
+
+ /// Add data to a section and return its offset within the section.
+ pub fn add_data(&mut self, data: &[u8]) -> io::Result<u32> {
+ let start = self.size_of_raw_data;
+ self.writer.write_all(data)?;
+ self.size_of_raw_data = start + data.len() as u32;
+ Ok(start)
+ }
+
+ // Pad the resource data to a multiple of `n` bytes.
+ pub fn align_to(&mut self, n: u32) -> io::Result<()> {
+ let offset = self.size_of_raw_data % n;
+ if offset != 0 {
+ let padding = n - offset;
+ for _ in 0..padding {
+ self.writer.write_all(&[0])?;
+ }
+ self.size_of_raw_data += padding;
+ }
+ Ok(())
+ }
+
+ /// Write a relocation for a symbol at the end of the section.
+ pub fn add_relocation(&mut self, address: u32) -> io::Result<()> {
+ self.number_of_relocations += 1;
+ self.writer.write_all(&address.to_le_bytes())?;
+ self.writer.write_all(&[0, 0, 0, 0])?;
+ self.writer.write_all(&self.machine_type.relocation_type().to_le_bytes())
+ }
+
+ /// Write the object and section headers and write the symbol table.
+ pub fn finish(mut self) -> io::Result<W> {
+ // Get the timestamp for the header. `as` is correct here, as the low 32 bits
+ // should be used.
+ let timestamp = SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .map_or(0, |d| d.as_secs() as u32);
+
+ // Copy file location of the symbol table.
+ let pointer_to_symbol_table = self.writer.stream_position()? as u32;
+
+ // Write the symbols and auxiliary data for the section.
+ self.writer.write_all(b".rsrc\0\0\0")?; // name
+ self.writer.write_all(&[0, 0, 0, 0])?; // address
+ self.writer.write_all(&[1, 0])?; // section number (1-based)
+ self.writer.write_all(&[0, 0, 3, 1])?; // type = 0, class = static, aux symbols = 1
+ self.writer.write_all(&self.size_of_raw_data.to_le_bytes())?;
+ self.writer.write_all(&self.number_of_relocations.to_le_bytes())?;
+ self.writer.write_all(&[0; 12])?;
+
+ // Write the empty string table.
+ self.writer.write_all(&[0; 4])?;
+
+ // Write the object header.
+ let end_of_file = self.writer.seek(SeekFrom::Start(0))?;
+ self.writer.write_all(&self.machine_type.machine().to_le_bytes())?;
+ self.writer.write_all(&[1, 0])?; // number of sections
+ self.writer.write_all(&timestamp.to_le_bytes())?;
+ self.writer.write_all(&pointer_to_symbol_table.to_le_bytes())?;
+ self.writer.write_all(&[2, 0, 0, 0])?; // number of symbol table entries
+ self.writer.write_all(&[0; 4])?; // optional header size = 0, characteristics = 0
+
+ // Write the section header.
+ self.writer.write_all(b".rsrc\0\0\0")?;
+ self.writer.write_all(&[0; 8])?; // virtual size = 0 and virtual address = 0
+ self.writer.write_all(&self.size_of_raw_data.to_le_bytes())?;
+ self.writer.write_all(&[60, 0, 0, 0])?; // pointer to raw data
+ self.writer.write_all(&(self.size_of_raw_data + 60).to_le_bytes())?; // pointer to relocations
+ self.writer.write_all(&[0; 4])?; // pointer to line numbers
+ self.writer.write_all(&self.number_of_relocations.to_le_bytes())?;
+ self.writer.write_all(&[0; 2])?; // number of line numbers
+ self.writer.write_all(&[0x40, 0, 0x30, 0xc0])?; // characteristics
+
+ // Return the inner writer and dispose of this object.
+ self.writer.seek(SeekFrom::Start(end_of_file))?;
+ Ok(self.writer)
+ }
+}
+
+/// Returns the bytes for a resource directory table.
+///
+/// Most of the fields are set to zero, including the timestamp, to aid
+/// with making builds reproducible.
+///
+/// ```c
+/// typedef struct {
+/// DWORD Characteristics,
+/// DWORD TimeDateStamp,
+/// WORD MajorVersion,
+/// WORD MinorVersion,
+/// WORD NumberOfNamedEntries,
+/// WORD NumberOfIdEntries
+/// } IMAGE_RESOURCE_DIRECTORY;
+/// ```
+pub fn resource_directory_table(number_of_id_entries: u16) -> [u8; 16] {
+ let mut table = [0; 16];
+ table[14..16].copy_from_slice(&number_of_id_entries.to_le_bytes());
+ table
+}
+
+/// Returns the bytes for a resource directory entry for an ID.
+///
+/// ```c
+/// typedef struct {
+/// DWORD Name,
+/// DWORD OffsetToData
+/// } IMAGE_RESOURCE_DIRECTORY_ENTRY;
+/// ```
+pub fn resource_directory_id_entry(id: u32, offset: u32, subdirectory: bool) -> [u8; 8] {
+ let mut entry = [0; 8];
+ entry[0..4].copy_from_slice(&id.to_le_bytes());
+ let flag: u32 = if subdirectory { 0x80000000 } else { 0 };
+ entry[4..8].copy_from_slice(&((offset & 0x7fffffff) | flag).to_le_bytes());
+ entry
+}
+
+/// Returns the bytes for a resource data entry.
+///
+/// ```c
+/// typedef struct {
+/// DWORD OffsetToData,
+/// DWORD Size,
+/// DWORD CodePage,
+/// DWORD Reserved
+/// } IMAGE_RESOURCE_DATA_ENTRY;
+/// ```
+pub fn resource_data_entry(rva: u32, size: u32) -> [u8; 16] {
+ let mut entry = [0; 16];
+ entry[0..4].copy_from_slice(&rva.to_le_bytes());
+ entry[4..8].copy_from_slice(&size.to_le_bytes());
+ entry
+}
diff --git a/third_party/rust/embed-manifest/src/embed/error.rs b/third_party/rust/embed-manifest/src/embed/error.rs
new file mode 100644
index 0000000000..365d0525ed
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/embed/error.rs
@@ -0,0 +1,57 @@
+//! Error handling for application manifest embedding.
+
+use std::fmt::{self, Display, Formatter};
+use std::io::{self, ErrorKind};
+
+/// The error type which is returned when application manifest embedding fails.
+#[derive(Debug)]
+pub struct Error {
+ repr: Repr,
+}
+
+#[derive(Debug)]
+enum Repr {
+ IoError(io::Error),
+ UnknownTarget,
+}
+
+impl Error {
+ pub(crate) fn unknown_target() -> Error {
+ Error {
+ repr: Repr::UnknownTarget,
+ }
+ }
+}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ match self.repr {
+ Repr::IoError(ref e) => write!(f, "I/O error: {}", e),
+ Repr::UnknownTarget => f.write_str("unknown target"),
+ }
+ }
+}
+
+impl std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self.repr {
+ Repr::IoError(ref e) => Some(e),
+ _ => None,
+ }
+ }
+}
+
+impl From<io::Error> for Error {
+ fn from(e: io::Error) -> Self {
+ Error { repr: Repr::IoError(e) }
+ }
+}
+
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ match e.repr {
+ Repr::IoError(ioe) => ioe,
+ _ => io::Error::new(ErrorKind::Other, e),
+ }
+ }
+}
diff --git a/third_party/rust/embed-manifest/src/embed/mod.rs b/third_party/rust/embed-manifest/src/embed/mod.rs
new file mode 100644
index 0000000000..b054878ac6
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/embed/mod.rs
@@ -0,0 +1,139 @@
+use std::env;
+use std::fs::{self, File};
+use std::io::{self, stdout, BufWriter, Cursor, Write};
+use std::path::{Path, PathBuf};
+
+use crate::manifest::ManifestBuilder;
+
+use self::coff::{resource_data_entry, resource_directory_id_entry, resource_directory_table, CoffWriter, MachineType};
+use self::error::Error;
+
+mod coff;
+pub mod error;
+
+#[cfg(test)]
+mod test;
+
+/// Embeds the manifest described by `manifest` by converting it to XML,
+/// then saving it to a file and passing the correct options to the linker
+/// on MSVC targets, or by building a static library and instructing Cargo
+/// to link the executable against it on GNU targets.
+pub fn embed_manifest(manifest: ManifestBuilder) -> Result<(), Error> {
+ let out_dir = get_out_dir()?;
+ let target = get_target()?;
+ if matches!(target.os, TargetOs::WindowsMsvc) {
+ let manifest_file = out_dir.join("manifest.xml");
+ write!(BufWriter::new(File::create(&manifest_file)?), "{}", manifest)?;
+ link_manifest_msvc(&manifest_file, &mut stdout().lock())
+ } else {
+ let manifest_data = manifest.to_string();
+ link_manifest_gnu(manifest_data.as_bytes(), &out_dir, target.arch, &mut stdout().lock())
+ }
+}
+
+/// Directly embeds the manifest in the provided `file` by passing the correct
+/// options to the linker on MSVC targets, or by building a static library
+/// and instructing Cargo to link the executable against it on GNU targets.
+pub fn embed_manifest_file<P: AsRef<Path>>(file: P) -> Result<(), io::Error> {
+ let out_dir = get_out_dir()?;
+ let target = get_target()?;
+ if matches!(target.os, TargetOs::WindowsMsvc) {
+ Ok(link_manifest_msvc(file.as_ref(), &mut stdout().lock())?)
+ } else {
+ let manifest = fs::read(file.as_ref())?;
+ Ok(link_manifest_gnu(&manifest, &out_dir, target.arch, &mut stdout().lock())?)
+ }
+}
+
+fn get_out_dir() -> Result<PathBuf, io::Error> {
+ match env::var_os("OUT_DIR") {
+ Some(dir) => Ok(PathBuf::from(dir)),
+ None => env::current_dir(),
+ }
+}
+
+enum TargetOs {
+ WindowsGnu,
+ WindowsMsvc,
+}
+
+struct Target {
+ arch: MachineType,
+ os: TargetOs,
+}
+
+fn get_target() -> Result<Target, Error> {
+ match env::var("TARGET") {
+ Ok(target) => parse_target(&target),
+ _ => Err(Error::unknown_target()),
+ }
+}
+
+fn parse_target(target: &str) -> Result<Target, Error> {
+ let mut iter = target.splitn(3, '-');
+ let arch = match iter.next() {
+ Some("i686") => MachineType::I386,
+ Some("aarch64") => MachineType::Aarch64,
+ Some("x86_64") => MachineType::X86_64,
+ _ => return Err(Error::unknown_target()),
+ };
+ if iter.next() != Some("pc") {
+ return Err(Error::unknown_target());
+ }
+ let os = match iter.next() {
+ Some("windows-gnu") => TargetOs::WindowsGnu,
+ Some("windows-gnullvm") => TargetOs::WindowsGnu,
+ Some("windows-msvc") => TargetOs::WindowsMsvc,
+ _ => return Err(Error::unknown_target()),
+ };
+ Ok(Target { arch, os })
+}
+
+fn link_manifest_msvc<W: Write>(manifest_path: &Path, out: &mut W) -> Result<(), Error> {
+ writeln!(out, "cargo:rustc-link-arg-bins=/MANIFEST:EMBED")?;
+ writeln!(
+ out,
+ "cargo:rustc-link-arg-bins=/MANIFESTINPUT:{}",
+ manifest_path.canonicalize()?.display()
+ )?;
+ writeln!(out, "cargo:rustc-link-arg-bins=/MANIFESTUAC:NO")?;
+ Ok(())
+}
+
+fn link_manifest_gnu<W: Write>(manifest: &[u8], out_dir: &Path, arch: MachineType, out: &mut W) -> Result<(), Error> {
+ // Generate a COFF object file containing the manifest in a .rsrc section.
+ let object_data = create_object_file(manifest, arch)?;
+ let path = out_dir.join("embed-manifest.o");
+ fs::create_dir_all(out_dir)?;
+ fs::write(&path, object_data)?;
+
+ // Link the manifest with the executable.
+ writeln!(out, "cargo:rustc-link-arg-bins={}", path.display())?;
+ Ok(())
+}
+
+fn create_object_file(manifest: &[u8], arch: MachineType) -> Result<Vec<u8>, io::Error> {
+ // Start object file with .rsrc section.
+ let mut obj = CoffWriter::new(Cursor::new(Vec::with_capacity(4096)), arch)?;
+
+ // Create resource directories for type ID 24, name ID 1, language ID 1033.
+ obj.add_data(&resource_directory_table(1))?;
+ obj.add_data(&resource_directory_id_entry(24, 24, true))?;
+ obj.add_data(&resource_directory_table(1))?;
+ obj.add_data(&resource_directory_id_entry(1, 48, true))?;
+ obj.add_data(&resource_directory_table(1))?;
+ obj.add_data(&resource_directory_id_entry(1033, 72, false))?;
+
+ // Add resource data entry with relocated address.
+ let address = obj.add_data(&resource_data_entry(88, manifest.len() as u32))?;
+
+ // Add the manifest data.
+ obj.add_data(manifest)?;
+ obj.align_to(8)?;
+
+ // Write manifest data relocation at the end of the section.
+ obj.add_relocation(address)?;
+
+ // Finish writing file and return the populated object data.
+ Ok(obj.finish()?.into_inner())
+}
diff --git a/third_party/rust/embed-manifest/src/embed/test.rs b/third_party/rust/embed-manifest/src/embed/test.rs
new file mode 100644
index 0000000000..c464c6044c
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/embed/test.rs
@@ -0,0 +1,173 @@
+use std::fs;
+use std::path::{Path, PathBuf};
+
+use object::coff::CoffFile;
+use object::pe::{ImageResourceDataEntry, ImageResourceDirectory, ImageResourceDirectoryEntry, IMAGE_RESOURCE_DATA_IS_DIRECTORY};
+use object::{
+ pod, Architecture, LittleEndian, Object, ObjectSection, ObjectSymbol, RelocationEncoding, RelocationKind, SectionKind,
+};
+use tempfile::{tempdir, TempDir};
+
+use crate::embed::coff::MachineType;
+use crate::embed::{create_object_file, link_manifest_gnu, link_manifest_msvc, TargetOs};
+use crate::new_manifest;
+
+#[test]
+fn create_obj() {
+ let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsGnu);
+ let data = fs::read(&res.object_file()).unwrap();
+ let obj = CoffFile::parse(&data[..]).unwrap();
+ assert_eq!(obj.architecture(), Architecture::X86_64);
+ let expected_manifest = fs::read(&sample_manifest_path()).unwrap();
+ check_object_file(obj, &expected_manifest);
+}
+
+#[test]
+fn link_lib_gnu() {
+ let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsGnu);
+ assert!(res.object_file().exists());
+ let object_option = format!("cargo:rustc-link-arg-bins={}", res.object_file().display());
+ assert_eq!(res.lines(), &[object_option.as_str()]);
+}
+
+#[test]
+fn link_file_msvc() {
+ let res = do_embed_file(MachineType::X86_64, TargetOs::WindowsMsvc);
+ assert!(!res.object_file().exists());
+ let mut input_option = String::from("cargo:rustc-link-arg-bins=/MANIFESTINPUT:");
+ input_option.push_str(res.manifest_path.canonicalize().unwrap().to_str().unwrap());
+ assert_eq!(
+ res.lines(),
+ &[
+ "cargo:rustc-link-arg-bins=/MANIFEST:EMBED",
+ input_option.as_str(),
+ "cargo:rustc-link-arg-bins=/MANIFESTUAC:NO"
+ ]
+ );
+}
+
+struct EmbedResult {
+ manifest_path: PathBuf,
+ out_dir: TempDir,
+ output: String,
+}
+
+impl EmbedResult {
+ fn object_file(&self) -> PathBuf {
+ self.out_dir.path().join("embed-manifest.o")
+ }
+
+ fn lines(&self) -> Vec<&str> {
+ self.output.lines().collect()
+ }
+}
+
+fn sample_manifest_path() -> PathBuf {
+ Path::new(env!("CARGO_MANIFEST_DIR")).join("testdata/sample.exe.manifest")
+}
+
+fn do_embed_file(arch: MachineType, os: TargetOs) -> EmbedResult {
+ let manifest_path = sample_manifest_path();
+ let out_dir = tempdir().unwrap();
+ let mut buf: Vec<u8> = Vec::new();
+ if matches!(os, TargetOs::WindowsMsvc) {
+ link_manifest_msvc(&manifest_path, &mut buf).unwrap();
+ } else {
+ link_manifest_gnu(&fs::read(&manifest_path).unwrap(), out_dir.path(), arch, &mut buf).unwrap();
+ }
+ EmbedResult {
+ manifest_path,
+ out_dir,
+ output: String::from_utf8(buf).unwrap(),
+ }
+}
+
+#[test]
+fn object_file_x86() {
+ let manifest = new_manifest("Test.X86").to_string().into_bytes();
+ let file = create_object_file(&manifest, MachineType::I386).unwrap();
+ let obj = CoffFile::parse(&file[..]).unwrap();
+ assert_eq!(obj.architecture(), Architecture::I386);
+ check_object_file(obj, &manifest);
+}
+
+#[test]
+fn object_file_x86_64() {
+ let manifest = new_manifest("Test.X86_64").to_string().into_bytes();
+ let file = create_object_file(&manifest, MachineType::X86_64).unwrap();
+ let obj = CoffFile::parse(&file[..]).unwrap();
+ assert_eq!(obj.architecture(), Architecture::X86_64);
+ check_object_file(obj, &manifest);
+}
+
+#[test]
+fn object_file_aarch64() {
+ let manifest = new_manifest("Test.AARCH64").to_string().into_bytes();
+ let file = create_object_file(&manifest, MachineType::Aarch64).unwrap();
+ let obj = CoffFile::parse(&file[..]).unwrap();
+ assert_eq!(obj.architecture(), Architecture::Aarch64);
+ check_object_file(obj, &manifest);
+}
+
+fn check_object_file(obj: CoffFile, expected_manifest: &[u8]) {
+ // There should be one sections `.rsrc`.
+ assert_eq!(
+ obj.sections().map(|s| s.name().unwrap().to_string()).collect::<Vec<_>>(),
+ &[".rsrc"]
+ );
+
+ // There should be one section symbol.
+ assert_eq!(
+ obj.symbols().map(|s| s.name().unwrap().to_string()).collect::<Vec<_>>(),
+ &[".rsrc"]
+ );
+
+ // The resource section must be a data section.
+ let rsrc = obj.section_by_name(".rsrc").unwrap();
+ assert_eq!(rsrc.address(), 0);
+ assert_eq!(rsrc.kind(), SectionKind::Data);
+
+ // The data RVA in the resource data entry must be relocatable.
+ let (addr, reloc) = rsrc.relocations().next().unwrap();
+ assert_eq!(reloc.kind(), RelocationKind::ImageOffset);
+ assert_eq!(reloc.encoding(), RelocationEncoding::Generic);
+ assert_eq!(addr, 0x48); // size of the directory table, three directories, and no strings
+ assert_eq!(reloc.addend(), 0);
+
+ // The resource directory contains one manifest resource type subdirectory.
+ let data = rsrc.data().unwrap();
+ let (dir, rest) = pod::from_bytes::<ImageResourceDirectory>(data).unwrap();
+ assert_eq!(0, dir.number_of_named_entries.get(LittleEndian));
+ assert_eq!(1, dir.number_of_id_entries.get(LittleEndian));
+ let (entries, _) = pod::slice_from_bytes::<ImageResourceDirectoryEntry>(rest, 1).unwrap();
+ assert_eq!(24, entries[0].name_or_id.get(LittleEndian));
+ let offset = entries[0].offset_to_data_or_directory.get(LittleEndian);
+ assert_eq!(IMAGE_RESOURCE_DATA_IS_DIRECTORY, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
+ let offset = (offset & !IMAGE_RESOURCE_DATA_IS_DIRECTORY) as usize;
+
+ // The manifest subdirectory contains one image (not DLL) manifest subdirectory.
+ let (dir, rest) = pod::from_bytes::<ImageResourceDirectory>(&data[offset..]).unwrap();
+ assert_eq!(0, dir.number_of_named_entries.get(LittleEndian));
+ assert_eq!(1, dir.number_of_id_entries.get(LittleEndian));
+ let (entries, _) = pod::slice_from_bytes::<ImageResourceDirectoryEntry>(rest, 1).unwrap();
+ assert_eq!(1, entries[0].name_or_id.get(LittleEndian));
+ let offset = entries[0].offset_to_data_or_directory.get(LittleEndian);
+ assert_eq!(IMAGE_RESOURCE_DATA_IS_DIRECTORY, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
+ let offset = (offset & !IMAGE_RESOURCE_DATA_IS_DIRECTORY) as usize;
+
+ // The image manifest subdirectory contains one US English manifest data entry.
+ let (dir, rest) = pod::from_bytes::<ImageResourceDirectory>(&data[offset..]).unwrap();
+ assert_eq!(0, dir.number_of_named_entries.get(LittleEndian));
+ assert_eq!(1, dir.number_of_id_entries.get(LittleEndian));
+ let (entries, _) = pod::slice_from_bytes::<ImageResourceDirectoryEntry>(rest, 1).unwrap();
+ assert_eq!(0x0409, entries[0].name_or_id.get(LittleEndian));
+ let offset = entries[0].offset_to_data_or_directory.get(LittleEndian);
+ assert_eq!(0, offset & IMAGE_RESOURCE_DATA_IS_DIRECTORY);
+ let offset = offset as usize;
+
+ // The manifest data matches what was added.
+ let (entry, resource_data) = pod::from_bytes::<ImageResourceDataEntry>(&data[offset..]).unwrap();
+ let end = entry.size.get(LittleEndian) as usize;
+ let manifest = &resource_data[..end];
+ assert_eq!(manifest, expected_manifest);
+}
diff --git a/third_party/rust/embed-manifest/src/lib.rs b/third_party/rust/embed-manifest/src/lib.rs
new file mode 100644
index 0000000000..6c60cdf541
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/lib.rs
@@ -0,0 +1,134 @@
+//! The `embed-manifest` crate provides a straightforward way to embed
+//! a Windows manifest in an executable, whatever the build environment
+//! and even when cross-compiling, without dependencies on external
+//! tools from LLVM or MinGW.
+//!
+//! This should be called from a [build script][1], as shown below.
+//!
+//! [1]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
+//!
+//! On MSVC targets, the manifest file is embedded in the executable by
+//! instructing Cargo to pass `/MANIFEST` options to `LINK.EXE`. This
+//! requires Cargo from Rust 1.56.
+//!
+//! On GNU targets, the manifest file is added as a resource in a COFF
+//! object file, and Cargo is instructed to link this file into the
+//! executable, also using functionality from Rust 1.56.
+//!
+//! # Usage
+//!
+//! This crate should be added to the `[build-dependencies]` section in
+//! your executable’s `Cargo.toml`:
+//!
+//! ```toml
+//! [build-dependencies]
+//! embed-manifest = "1.3.1"
+//! ```
+//!
+//! In the same directory, create a `build.rs` file to call this crate’s
+//! code when building for Windows, and to only run once:
+//!
+//! ```
+//! use embed_manifest::{embed_manifest, new_manifest};
+//!
+//! fn main() {
+//! # let tempdir = tempfile::tempdir().unwrap();
+//! # std::env::set_var("OUT_DIR", tempdir.path());
+//! # std::env::set_var("TARGET", "x86_64-pc-windows-gnu");
+//! # std::env::set_var("CARGO_CFG_WINDOWS", "");
+//! if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
+//! embed_manifest(new_manifest("Contoso.Sample")).expect("unable to embed manifest file");
+//! }
+//! println!("cargo:rerun-if-changed=build.rs");
+//! }
+//! ```
+//!
+//! To customise the application manifest, use the methods on it to change things like
+//! enabling the segment heap:
+//!
+//! ```
+//! use embed_manifest::{embed_manifest, new_manifest, manifest::HeapType};
+//!
+//! fn main() {
+//! # let tempdir = tempfile::tempdir().unwrap();
+//! # std::env::set_var("OUT_DIR", tempdir.path());
+//! # std::env::set_var("TARGET", "x86_64-pc-windows-gnu");
+//! # std::env::set_var("CARGO_CFG_WINDOWS", "");
+//! if std::env::var_os("CARGO_CFG_WINDOWS").is_some() {
+//! embed_manifest(new_manifest("Contoso.Sample").heap_type(HeapType::SegmentHeap))
+//! .expect("unable to embed manifest file");
+//! }
+//! println!("cargo:rerun-if-changed=build.rs");
+//! }
+//! ```
+//!
+//! or making it always use legacy single-byte API encoding and only declaring compatibility
+//! up to Windows 8.1, without checking whether this is a Windows build:
+//!
+//! ```
+//! use embed_manifest::{embed_manifest, new_manifest};
+//! use embed_manifest::manifest::{ActiveCodePage::Legacy, SupportedOS::*};
+//!
+//! fn main() {
+//! # let tempdir = tempfile::tempdir().unwrap();
+//! # std::env::set_var("OUT_DIR", tempdir.path());
+//! # std::env::set_var("TARGET", "x86_64-pc-windows-gnu");
+//! let manifest = new_manifest("Contoso.Sample")
+//! .active_code_page(Legacy)
+//! .supported_os(Windows7..=Windows81);
+//! embed_manifest(manifest).expect("unable to embed manifest file");
+//! println!("cargo:rerun-if-changed=build.rs");
+//! }
+//! ```
+
+#![allow(clippy::needless_doctest_main)]
+
+pub use embed::error::Error;
+pub use embed::{embed_manifest, embed_manifest_file};
+
+use crate::manifest::ManifestBuilder;
+
+mod embed;
+pub mod manifest;
+
+/// Creates a new [`ManifestBuilder`] with sensible defaults, allowing customisation
+/// before the Windows application manifest XML is generated.
+///
+/// The initial values used by the manifest are:
+/// - Version number from the `CARGO_PKG_VERSION_MAJOR`, `CARGO_PKG_VERSION_MINOR` and
+/// `CARGO_PKG_VERSION_PATCH` environment variables. This can then be changed with
+/// [`version()`][ManifestBuilder::version].
+/// - A dependency on version 6 of the Common Controls so that message boxes and dialogs
+/// will use the latest design, and have the best available support for high DPI displays.
+/// This can be removed with
+/// [`remove_dependency`][ManifestBuilder::remove_dependency].
+/// - [Compatible with Windows from 7 to 11][ManifestBuilder::supported_os],
+/// matching the Rust compiler [tier 1 targets][tier1].
+/// - A “[maximum version tested][ManifestBuilder::max_version_tested]” of Windows 10
+/// version 1903.
+/// - An [active code page][ManifestBuilder::active_code_page] of UTF-8, so that
+/// single-byte Windows APIs will generally interpret Rust strings correctly, starting
+/// from Windows 10 version 1903.
+/// - [Version 2 of per-monitor high DPI awareness][manifest::DpiAwareness::PerMonitorV2Only],
+/// so that user interface elements can be scaled correctly by the application and
+/// Common Controls from Windows 10 version 1703.
+/// - [Long path awareness][ManifestBuilder::long_path_aware] from Windows 10 version
+/// 1607 [when separately enabled][longpaths].
+/// - [Printer driver isolation][ManifestBuilder::printer_driver_isolation] enabled
+/// to improve reliability and security.
+/// - An [execution level][ManifestBuilder::requested_execution_level] of “as invoker”
+/// so that the UAC elevation dialog will never be displayed, regardless of the name
+/// of the program, and [UAC Virtualisation][uac] is disabled in 32-bit programs.
+///
+/// [tier1]: https://doc.rust-lang.org/nightly/rustc/platform-support.html
+/// [longpaths]: https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later
+/// [uac]: https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works#virtualization
+pub fn new_manifest(name: &str) -> ManifestBuilder {
+ ManifestBuilder::new(name)
+}
+
+/// Creates a new [`ManifestBuilder`] without any settings, allowing creation of
+/// a manifest with only desired content.
+pub fn empty_manifest() -> ManifestBuilder {
+ ManifestBuilder::empty()
+}
diff --git a/third_party/rust/embed-manifest/src/manifest/mod.rs b/third_party/rust/embed-manifest/src/manifest/mod.rs
new file mode 100644
index 0000000000..716a3559f8
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/manifest/mod.rs
@@ -0,0 +1,882 @@
+//! A builder for Windows application manifest XML files.
+//!
+//! This module allows the construction of application manifests from code with the
+//! [`ManifestBuilder`], for use from a Cargo build script. Once configured, the builder
+//! should be passed to [`embed_manifest()`][crate::embed_manifest] to generate the
+//! correct instructions for Cargo. For any other use, the builder will output the XML
+//! when formatted for [`Display`], or with [`to_string()`][ToString]. For more
+//! information about the different elements of an application manifest, see
+//! [Application Manifests][1] in the Microsoft Windows App Development documentation.
+//!
+//! [1]: https://docs.microsoft.com/en-us/windows/win32/sbscs/application-manifests
+//!
+//! To generate the manifest XML separately, the XML can be output with `write!` or
+//! copied to a string with [`to_string()`][ToString]. To produce the manifest XML with
+//! extra whitespace for formatting, format it with the ‘alternate’ flag:
+//!
+//! ```
+//! # use embed_manifest::new_manifest;
+//! let builder = new_manifest("Company.OrgUnit.Program");
+//! assert_eq!(format!("{:#}", builder), r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+//! <assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
+//! <assemblyIdentity name="Company.OrgUnit.Program" type="win32" version="1.4.0.0"/>
+//! <dependency>
+//! <dependentAssembly>
+//! <assemblyIdentity language="*" name="Microsoft.Windows.Common-Controls" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" type="win32" version="6.0.0.0"/>
+//! </dependentAssembly>
+//! </dependency>
+//! <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+//! <application>
+//! <maxversiontested Id="10.0.18362.1"/>
+//! <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+//! <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+//! <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+//! <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+//! </application>
+//! </compatibility>
+//! <asmv3:application>
+//! <asmv3:windowsSettings>
+//! <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
+//! <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2</dpiAwareness>
+//! <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+//! <printerDriverIsolation xmlns="http://schemas.microsoft.com/SMI/2011/WindowsSettings">true</printerDriverIsolation>
+//! </asmv3:windowsSettings>
+//! </asmv3:application>
+//! <asmv3:trustInfo>
+//! <asmv3:security>
+//! <asmv3:requestedPrivileges>
+//! <asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+//! </asmv3:requestedPrivileges>
+//! </asmv3:security>
+//! </asmv3:trustInfo>
+//! </assembly>"#.replace("\n", "\r\n"))
+//! ```
+
+use std::fmt::{Display, Formatter};
+use std::ops::RangeBounds;
+use std::{env, fmt};
+
+use crate::manifest::xml::XmlFormatter;
+
+mod xml;
+
+#[cfg(test)]
+mod test;
+
+/// An opaque container to describe the Windows application manifest for the
+/// executable. A new instance with reasonable defaults is created with
+/// [`new_manifest()`][crate::new_manifest].
+#[derive(Debug)]
+pub struct ManifestBuilder {
+ identity: Option<AssemblyIdentity>,
+ dependent_assemblies: Vec<AssemblyIdentity>,
+ compatibility: ApplicationCompatibility,
+ windows_settings: WindowsSettings,
+ requested_execution_level: Option<RequestedExecutionLevel>,
+}
+
+impl ManifestBuilder {
+ pub(crate) fn new(name: &str) -> Self {
+ ManifestBuilder {
+ identity: Some(AssemblyIdentity::application(name)),
+ dependent_assemblies: vec![AssemblyIdentity::new(
+ "Microsoft.Windows.Common-Controls",
+ [6, 0, 0, 0],
+ 0x6595b64144ccf1df,
+ )],
+ compatibility: ApplicationCompatibility {
+ max_version_tested: Some(MaxVersionTested::Windows10Version1903),
+ supported_os: vec![
+ SupportedOS::Windows7,
+ SupportedOS::Windows8,
+ SupportedOS::Windows81,
+ SupportedOS::Windows10,
+ ],
+ },
+ windows_settings: WindowsSettings::new(),
+ requested_execution_level: Some(RequestedExecutionLevel {
+ level: ExecutionLevel::AsInvoker,
+ ui_access: false,
+ }),
+ }
+ }
+
+ pub(crate) fn empty() -> Self {
+ ManifestBuilder {
+ identity: None,
+ dependent_assemblies: Vec::new(),
+ compatibility: ApplicationCompatibility {
+ max_version_tested: None,
+ supported_os: Vec::new(),
+ },
+ windows_settings: WindowsSettings::empty(),
+ requested_execution_level: None,
+ }
+ }
+
+ // Set the dot-separated [application name][identity] in the manifest.
+ //
+ // [identity]: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#assemblyIdentity
+ pub fn name(mut self, name: &str) -> Self {
+ match self.identity {
+ Some(ref mut identity) => identity.name = name.to_string(),
+ None => self.identity = Some(AssemblyIdentity::application_version(name, 0, 0, 0, 0)),
+ }
+ self
+ }
+
+ /// Set the four-part application version number in the manifest.
+ pub fn version(mut self, major: u16, minor: u16, build: u16, revision: u16) -> Self {
+ match self.identity {
+ Some(ref mut identity) => identity.version = Version(major, minor, build, revision),
+ None => {
+ self.identity = Some(AssemblyIdentity::application_version("", major, minor, build, revision));
+ }
+ }
+ self
+ }
+
+ /// Add a dependency on a specific version of a side-by-side assembly
+ /// to the application manifest. For more information on side-by-side
+ /// assemblies, see [Using Side-by-side Assemblies][sxs].
+ ///
+ /// [sxs]: https://docs.microsoft.com/en-us/windows/win32/sbscs/using-side-by-side-assemblies
+ pub fn dependency(mut self, identity: AssemblyIdentity) -> Self {
+ self.dependent_assemblies.push(identity);
+ self
+ }
+
+ /// Remove a dependency on a side-by-side assembly. This can be used to
+ /// remove the default dependency on Common Controls version 6:
+ ///
+ /// ```
+ /// # use embed_manifest::new_manifest;
+ /// new_manifest("Company.OrgUnit.Program")
+ /// .remove_dependency("Microsoft.Windows.Common-Controls")
+ /// # ;
+ /// ```
+ pub fn remove_dependency(mut self, name: &str) -> Self {
+ self.dependent_assemblies.retain(|d| d.name != name);
+ self
+ }
+
+ /// Set the “maximum version tested” based on a Windows SDK version.
+ /// This compatibility setting enables the use of XAML Islands, as described in
+ /// [Host a standard WinRT XAML control in a C++ desktop (Win32) app][xaml].
+ ///
+ /// [xaml]: https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/host-standard-control-with-xaml-islands-cpp
+ pub fn max_version_tested(mut self, version: MaxVersionTested) -> Self {
+ self.compatibility.max_version_tested = Some(version);
+ self
+ }
+
+ /// Remove the “maximum version tested” from the application compatibility.
+ pub fn remove_max_version_tested(mut self) -> Self {
+ self.compatibility.max_version_tested = None;
+ self
+ }
+
+ /// Set the range of supported versions of Windows for application compatibility.
+ /// The default value declares compatibility with every version from
+ /// [Windows 7][SupportedOS::Windows7] to [Windows 10 and 11][SupportedOS::Windows10].
+ pub fn supported_os<R: RangeBounds<SupportedOS>>(mut self, os_range: R) -> Self {
+ use SupportedOS::*;
+
+ self.compatibility.supported_os.clear();
+ for os in [WindowsVista, Windows7, Windows8, Windows81, Windows10] {
+ if os_range.contains(&os) {
+ self.compatibility.supported_os.push(os);
+ }
+ }
+ self
+ }
+
+ /// Set the code page used for single-byte Windows API, starting from Windows 10
+ /// version 1903. The default setting of [UTF-8][`ActiveCodePage::Utf8`] makes Rust
+ /// strings work directly with APIs like `MessageBoxA`.
+ pub fn active_code_page(mut self, code_page: ActiveCodePage) -> Self {
+ self.windows_settings.active_code_page = code_page;
+ self
+ }
+
+ /// Configures how Windows should display this program on monitors where the
+ /// graphics need scaling, whether by the application drawing its user
+ /// interface at different sizes or by the Windows system rendering the graphics
+ /// to a bitmap then resizing that for display.
+ pub fn dpi_awareness(mut self, setting: DpiAwareness) -> Self {
+ self.windows_settings.dpi_awareness = setting;
+ self
+ }
+
+ /// Attempts to scale GDI primitives by the per-monitor scaling values,
+ /// from Windows 10 version 1703. It seems to be best to leave this disabled.
+ pub fn gdi_scaling(mut self, setting: Setting) -> Self {
+ self.windows_settings.gdi_scaling = setting.enabled();
+ self
+ }
+
+ /// Select the memory allocator use by the standard heap allocation APIs,
+ /// including the default Rust allocator. Selecting a different algorithm
+ /// may make performance and memory use better or worse, so any changes
+ /// should be carefully tested.
+ pub fn heap_type(mut self, setting: HeapType) -> Self {
+ self.windows_settings.heap_type = setting;
+ self
+ }
+
+ /// Enable paths longer than 260 characters with some wide-character Win32 APIs,
+ /// when also enabled in the Windows registry. For more details, see
+ /// [Maximum Path Length Limitation][1] in the Windows App Development
+ /// documentation.
+ ///
+ /// As of Rust 1.58, the [Rust standard library bypasses this limitation][2] itself
+ /// by using Unicode paths beginning with `\\?\`.
+ ///
+ /// [1]: https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
+ /// [2]: https://github.com/rust-lang/rust/pull/89174
+ pub fn long_path_aware(mut self, setting: Setting) -> Self {
+ self.windows_settings.long_path_aware = setting.enabled();
+ self
+ }
+
+ /// Enable printer driver isolation for the application, improving security and
+ /// stability when printing by loading the printer driver in a separate
+ /// application. This is poorly documented, but is described in a blog post,
+ /// “[Application-level Printer Driver Isolation][post]”.
+ ///
+ /// [post]: https://peteronprogramming.wordpress.com/2018/01/22/application-level-printer-driver-isolation/
+ pub fn printer_driver_isolation(mut self, setting: Setting) -> Self {
+ self.windows_settings.printer_driver_isolation = setting.enabled();
+ self
+ }
+
+ /// Configure whether the application should receive mouse wheel scroll events
+ /// with a minimum delta of 1, 40 or 120, as described in
+ /// [Windows precision touchpad devices][touchpad].
+ ///
+ /// [touchpad]: https://docs.microsoft.com/en-us/windows/win32/w8cookbook/windows-precision-touchpad-devices
+ pub fn scrolling_awareness(mut self, setting: ScrollingAwareness) -> Self {
+ self.windows_settings.scrolling_awareness = setting;
+ self
+ }
+
+ /// Allows the application to disable the filtering that normally
+ /// removes UWP windows from the results of the `EnumWindows` API.
+ pub fn window_filtering(mut self, setting: Setting) -> Self {
+ self.windows_settings.disable_window_filtering = setting.disabled();
+ self
+ }
+
+ /// Selects the authorities to execute the program with, rather than
+ /// [guessing based on a filename][installer] like `setup.exe`,
+ /// `update.exe` or `patch.exe`.
+ ///
+ /// [installer]: https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works#installer-detection-technology
+ pub fn requested_execution_level(mut self, level: ExecutionLevel) -> Self {
+ match self.requested_execution_level {
+ Some(ref mut requested_execution_level) => requested_execution_level.level = level,
+ None => self.requested_execution_level = Some(RequestedExecutionLevel { level, ui_access: false }),
+ }
+ self
+ }
+
+ /// Allows the application to access the user interface of applications
+ /// running with elevated permissions when this program does not, typically
+ /// for accessibility. The program must additionally be correctly signed
+ /// and installed in a trusted location like the Program Files directory.
+ pub fn ui_access(mut self, access: bool) -> Self {
+ match self.requested_execution_level {
+ Some(ref mut requested_execution_level) => requested_execution_level.ui_access = access,
+ None => {
+ self.requested_execution_level = Some(RequestedExecutionLevel {
+ level: ExecutionLevel::AsInvoker,
+ ui_access: access,
+ })
+ }
+ }
+ self
+ }
+}
+
+impl Display for ManifestBuilder {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ let mut w = XmlFormatter::new(f);
+ w.start_document()?;
+ let mut attrs = vec![("xmlns", "urn:schemas-microsoft-com:asm.v1")];
+ if !self.windows_settings.is_empty() || self.requested_execution_level.is_some() {
+ attrs.push(("xmlns:asmv3", "urn:schemas-microsoft-com:asm.v3"));
+ }
+ attrs.push(("manifestVersion", "1.0"));
+ w.start_element("assembly", &attrs)?;
+ if let Some(ref identity) = self.identity {
+ identity.xml_to(&mut w)?;
+ }
+ if !self.dependent_assemblies.is_empty() {
+ w.element("dependency", &[], |w| {
+ for d in self.dependent_assemblies.as_slice() {
+ w.element("dependentAssembly", &[], |w| d.xml_to(w))?;
+ }
+ Ok(())
+ })?;
+ }
+ if !self.compatibility.is_empty() {
+ self.compatibility.xml_to(&mut w)?;
+ }
+ if !self.windows_settings.is_empty() {
+ self.windows_settings.xml_to(&mut w)?;
+ }
+ if let Some(ref requested_execution_level) = self.requested_execution_level {
+ requested_execution_level.xml_to(&mut w)?;
+ }
+ w.end_element("assembly")
+ }
+}
+
+/// Identity of a side-by-side assembly dependency for the application.
+#[derive(Debug)]
+pub struct AssemblyIdentity {
+ r#type: AssemblyType,
+ name: String,
+ language: Option<String>,
+ processor_architecture: Option<AssemblyProcessorArchitecture>,
+ version: Version,
+ public_key_token: Option<PublicKeyToken>,
+}
+
+impl AssemblyIdentity {
+ fn application(name: &str) -> AssemblyIdentity {
+ let major = env::var("CARGO_PKG_VERSION_MAJOR").map_or(0, |s| s.parse().unwrap_or(0));
+ let minor = env::var("CARGO_PKG_VERSION_MINOR").map_or(0, |s| s.parse().unwrap_or(0));
+ let patch = env::var("CARGO_PKG_VERSION_PATCH").map_or(0, |s| s.parse().unwrap_or(0));
+ AssemblyIdentity {
+ r#type: AssemblyType::Win32,
+ name: name.to_string(),
+ language: None,
+ processor_architecture: None,
+ version: Version(major, minor, patch, 0),
+ public_key_token: None,
+ }
+ }
+
+ fn application_version(name: &str, major: u16, minor: u16, build: u16, revision: u16) -> AssemblyIdentity {
+ AssemblyIdentity {
+ r#type: AssemblyType::Win32,
+ name: name.to_string(),
+ language: None,
+ processor_architecture: None,
+ version: Version(major, minor, build, revision),
+ public_key_token: None,
+ }
+ }
+
+ /// Creates a new value for a [manifest dependency][ManifestBuilder::dependency],
+ /// with the `version` as an array of numbers like `[1, 0, 0, 0]` for 1.0.0.0,
+ /// and the public key token as a 64-bit number like `0x6595b64144ccf1df`.
+ pub fn new(name: &str, version: [u16; 4], public_key_token: u64) -> AssemblyIdentity {
+ AssemblyIdentity {
+ r#type: AssemblyType::Win32,
+ name: name.to_string(),
+ language: Some("*".to_string()),
+ processor_architecture: Some(AssemblyProcessorArchitecture::All),
+ version: Version(version[0], version[1], version[2], version[3]),
+ public_key_token: Some(PublicKeyToken(public_key_token)),
+ }
+ }
+
+ /// Change the language from `"*"` to the language code in `language`.
+ pub fn language(mut self, language: &str) -> Self {
+ self.language = Some(language.to_string());
+ self
+ }
+
+ /// Change the processor architecture from `"*"` to the architecture in `arch`.
+ pub fn processor_architecture(mut self, arch: AssemblyProcessorArchitecture) -> Self {
+ self.processor_architecture = Some(arch);
+ self
+ }
+
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ let version = self.version.to_string();
+ let public_key_token = self.public_key_token.as_ref().map(|token| token.to_string());
+
+ let mut attrs: Vec<(&str, &str)> = Vec::with_capacity(6);
+ if let Some(ref language) = self.language {
+ attrs.push(("language", language));
+ }
+ attrs.push(("name", &self.name));
+ if let Some(ref arch) = self.processor_architecture {
+ attrs.push(("processorArchitecture", arch.as_str()))
+ }
+ if let Some(ref token) = public_key_token {
+ attrs.push(("publicKeyToken", token));
+ }
+ attrs.push(("type", self.r#type.as_str()));
+ attrs.push(("version", &version));
+ w.empty_element("assemblyIdentity", &attrs)
+ }
+}
+
+#[derive(Debug)]
+struct Version(u16, u16, u16, u16);
+
+impl fmt::Display for Version {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{}.{}.{}.{}", self.0, self.1, self.2, self.3)
+ }
+}
+
+#[derive(Debug)]
+struct PublicKeyToken(u64);
+
+impl fmt::Display for PublicKeyToken {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(f, "{:016x}", self.0)
+ }
+}
+
+/// Processor architecture for an assembly identity.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum AssemblyProcessorArchitecture {
+ /// Any processor architecture, as `"*"`.
+ All,
+ /// 32-bit x86 processors, as `"x86"`.
+ X86,
+ /// 64-bit x64 processors, as `"x64"`.
+ Amd64,
+ /// 32-bit ARM processors, as `"arm"`.
+ Arm,
+ /// 64-bit ARM processors, as `"arm64"`.
+ Arm64,
+}
+
+impl AssemblyProcessorArchitecture {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::All => "*",
+ Self::X86 => "x86",
+ Self::Amd64 => "amd64",
+ Self::Arm => "arm",
+ Self::Arm64 => "arm64",
+ }
+ }
+}
+
+impl fmt::Display for AssemblyProcessorArchitecture {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(self.as_str())
+ }
+}
+
+#[derive(Debug)]
+#[non_exhaustive]
+enum AssemblyType {
+ Win32,
+}
+
+impl AssemblyType {
+ fn as_str(&self) -> &'static str {
+ "win32"
+ }
+}
+
+#[derive(Debug)]
+struct ApplicationCompatibility {
+ max_version_tested: Option<MaxVersionTested>,
+ supported_os: Vec<SupportedOS>,
+}
+
+impl ApplicationCompatibility {
+ fn is_empty(&self) -> bool {
+ self.supported_os.is_empty()
+ }
+
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ w.element(
+ "compatibility",
+ &[("xmlns", "urn:schemas-microsoft-com:compatibility.v1")],
+ |w| {
+ w.element("application", &[], |w| {
+ if self.supported_os.contains(&SupportedOS::Windows10) {
+ if let Some(ref version) = self.max_version_tested {
+ w.empty_element("maxversiontested", &[("Id", version.as_str())])?;
+ }
+ }
+ for os in self.supported_os.iter() {
+ w.empty_element("supportedOS", &[("Id", os.as_str())])?
+ }
+ Ok(())
+ })
+ },
+ )
+ }
+}
+
+/// Windows build versions for [`max_version_tested()`][ManifestBuilder::max_version_tested]
+/// from the [Windows SDK archive](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/).
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum MaxVersionTested {
+ /// Windows 10 version 1903, with build version 10.0.18362.0.
+ Windows10Version1903,
+ /// Windows 10 version 2004, with build version 10.0.19041.0.
+ Windows10Version2004,
+ /// Windows 10 version 2104, with build version 10.0.20348.0.
+ Windows10Version2104,
+ /// Windows 11, with build version 10.0.22000.194.
+ Windows11,
+ /// Windows 11 version 22H2, with build version 10.0.22621.1.
+ Windows11Version22H2,
+}
+
+impl MaxVersionTested {
+ /// Return the Windows version as a string.
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::Windows10Version1903 => "10.0.18362.1",
+ Self::Windows10Version2004 => "10.0.19041.0",
+ Self::Windows10Version2104 => "10.0.20348.0",
+ Self::Windows11 => "10.0.22000.194",
+ Self::Windows11Version22H2 => "10.0.22621.1",
+ }
+ }
+}
+
+impl Display for MaxVersionTested {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(self.as_str())
+ }
+}
+
+/// Operating system versions for Windows compatibility.
+#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum SupportedOS {
+ /// Windows Vista and Windows Server 2008.
+ WindowsVista,
+ /// Windows 7 and Windows Server 2008 R2.
+ Windows7,
+ /// Windows 8 and Windows Server 2012.
+ Windows8,
+ /// Windows 8.1 and Windows Server 2012 R2.
+ Windows81,
+ /// Windows 10 and 11, and Windows Server 2016, 2019 and 2022.
+ Windows10,
+}
+
+impl SupportedOS {
+ /// Returns the GUID string for the Windows version.
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::WindowsVista => "{e2011457-1546-43c5-a5fe-008deee3d3f0}",
+ Self::Windows7 => "{35138b9a-5d96-4fbd-8e2d-a2440225f93a}",
+ Self::Windows8 => "{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}",
+ Self::Windows81 => "{1f676c76-80e1-4239-95bb-83d0f6d0da78}",
+ Self::Windows10 => "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}",
+ }
+ }
+}
+
+impl Display for SupportedOS {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(self.as_str())
+ }
+}
+
+static WS2005: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2005/WindowsSettings");
+static WS2011: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2011/WindowsSettings");
+static WS2013: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2013/WindowsSettings");
+static WS2016: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2016/WindowsSettings");
+static WS2017: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2017/WindowsSettings");
+static WS2019: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2019/WindowsSettings");
+static WS2020: (&str, &str) = ("xmlns", "http://schemas.microsoft.com/SMI/2020/WindowsSettings");
+
+#[derive(Debug)]
+struct WindowsSettings {
+ active_code_page: ActiveCodePage,
+ disable_window_filtering: bool,
+ dpi_awareness: DpiAwareness,
+ gdi_scaling: bool,
+ heap_type: HeapType,
+ long_path_aware: bool,
+ printer_driver_isolation: bool,
+ scrolling_awareness: ScrollingAwareness,
+}
+
+impl WindowsSettings {
+ fn new() -> Self {
+ Self {
+ active_code_page: ActiveCodePage::Utf8,
+ disable_window_filtering: false,
+ dpi_awareness: DpiAwareness::PerMonitorV2Only,
+ gdi_scaling: false,
+ heap_type: HeapType::LowFragmentationHeap,
+ long_path_aware: true,
+ printer_driver_isolation: true,
+ scrolling_awareness: ScrollingAwareness::UltraHighResolution,
+ }
+ }
+
+ fn empty() -> Self {
+ Self {
+ active_code_page: ActiveCodePage::System,
+ disable_window_filtering: false,
+ dpi_awareness: DpiAwareness::UnawareByDefault,
+ gdi_scaling: false,
+ heap_type: HeapType::LowFragmentationHeap,
+ long_path_aware: false,
+ printer_driver_isolation: false,
+ scrolling_awareness: ScrollingAwareness::UltraHighResolution,
+ }
+ }
+
+ fn is_empty(&self) -> bool {
+ matches!(
+ self,
+ Self {
+ active_code_page: ActiveCodePage::System,
+ disable_window_filtering: false,
+ dpi_awareness: DpiAwareness::UnawareByDefault,
+ gdi_scaling: false,
+ heap_type: HeapType::LowFragmentationHeap,
+ long_path_aware: false,
+ printer_driver_isolation: false,
+ scrolling_awareness: ScrollingAwareness::UltraHighResolution,
+ }
+ )
+ }
+
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ w.element("asmv3:application", &[], |w| {
+ w.element("asmv3:windowsSettings", &[], |w| {
+ self.active_code_page.xml_to(w)?;
+ if self.disable_window_filtering {
+ w.element("disableWindowFiltering", &[WS2011], |w| w.text("true"))?;
+ }
+ self.dpi_awareness.xml_to(w)?;
+ if self.gdi_scaling {
+ w.element("gdiScaling", &[WS2017], |w| w.text("true"))?;
+ }
+ if matches!(self.heap_type, HeapType::SegmentHeap) {
+ w.element("heapType", &[WS2020], |w| w.text("SegmentHeap"))?;
+ }
+ if self.long_path_aware {
+ w.element("longPathAware", &[WS2016], |w| w.text("true"))?;
+ }
+ if self.printer_driver_isolation {
+ w.element("printerDriverIsolation", &[WS2011], |w| w.text("true"))?;
+ }
+ self.scrolling_awareness.xml_to(w)
+ })
+ })
+ }
+}
+
+/// Configure whether a Windows setting is enabled or disabled, avoiding confusion
+/// over which of these options `true` and `false` represent.
+#[derive(Debug)]
+pub enum Setting {
+ Disabled = 0,
+ Enabled = 1,
+}
+
+impl Setting {
+ /// Returns `true` if the setting should be disabled.
+ fn disabled(&self) -> bool {
+ matches!(self, Setting::Disabled)
+ }
+
+ /// Returns `true` if the setting should be enabled.
+ fn enabled(&self) -> bool {
+ matches!(self, Setting::Enabled)
+ }
+}
+
+/// The code page used by single-byte APIs in the program.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum ActiveCodePage {
+ /// Use the code page from the configured system locale, or UTF-8 if “Use Unicode UTF-8
+ /// for worldwide language support” is configured.
+ System,
+ /// Use UTF-8 from Windows 10 version 1903 and on Windows 11.
+ Utf8,
+ /// Use the code page from the configured system locale, even when “Use Unicode UTF-8
+ /// for worldwide language support” is configured.
+ Legacy,
+ /// Use the code page from the configured system locale on Windows 10, or from this
+ /// locale on Windows 11.
+ Locale(String),
+}
+
+impl ActiveCodePage {
+ pub fn as_str(&self) -> &str {
+ match self {
+ Self::System => "",
+ Self::Utf8 => "UTF-8",
+ Self::Legacy => "Legacy",
+ Self::Locale(s) => s,
+ }
+ }
+
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ match self {
+ Self::System => Ok(()),
+ _ => w.element("activeCodePage", &[WS2019], |w| w.text(self.as_str())),
+ }
+ }
+}
+
+impl Display for ActiveCodePage {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(self.as_str())
+ }
+}
+
+/// Options for how Windows will handle drawing on monitors when the graphics
+/// need scaling to display at the correct size.
+///
+/// See [High DPI Desktop Application Development on Windows][dpi] for more details
+/// about the impact of these options.
+///
+/// [dpi]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum DpiAwareness {
+ /// DPI awareness is not configured, so Windows will scale the application unless
+ /// changed via the `SetProcessDpiAware` or `SetProcessDpiAwareness` API.
+ UnawareByDefault,
+ /// DPI awareness is not configured, with Windows 8.1, 10 and 11 not able
+ /// to change the setting via API.
+ Unaware,
+ /// The program uses the system DPI, or the DPI of the monitor they start on if
+ /// “Fix scaling for apps” is enabled. If the DPI does not match the current
+ /// monitor then Windows will scale the application.
+ System,
+ /// The program uses the system DPI on Windows Vista, 7 and 8, and version 1 of
+ /// per-monitor DPI awareness on Windows 8.1, 10 and 11. Using version 1 of the
+ /// API is not recommended.
+ PerMonitor,
+ /// The program uses the system DPI on Windows Vista, 7 and 8, version 1 of
+ /// per-monitor DPI awareness on Windows 8.1 and Windows 10 version 1507 and 1511,
+ /// and version 2 of per-monitor DPI awareness from Windows 10 version 1607.
+ PerMonitorV2,
+ /// The program uses the system DPI on Windows Vista, 7, 8, 8.1 and Windows 10
+ /// version 1507 and 1511, and version 2 of per-monitor DPI awareness from
+ /// Windows 10 version 1607.
+ PerMonitorV2Only,
+}
+
+impl DpiAwareness {
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ let settings = match self {
+ Self::UnawareByDefault => (None, None),
+ Self::Unaware => (Some("false"), None),
+ Self::System => (Some("true"), None),
+ Self::PerMonitor => (Some("true/pm"), None),
+ Self::PerMonitorV2 => (Some("true/pm"), Some("permonitorv2,permonitor")),
+ Self::PerMonitorV2Only => (None, Some("permonitorv2")),
+ };
+ if let Some(dpi_aware) = settings.0 {
+ w.element("dpiAware", &[WS2005], |w| w.text(dpi_aware))?;
+ }
+ if let Some(dpi_awareness) = settings.1 {
+ w.element("dpiAwareness", &[WS2016], |w| w.text(dpi_awareness))?;
+ }
+ Ok(())
+ }
+}
+
+/// The heap type for the default memory allocator.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum HeapType {
+ /// The default heap type since Windows Vista.
+ LowFragmentationHeap,
+ /// The modern segment heap, which may reduce total memory allocation in some programs.
+ /// This is supported since Windows 10 version 2004. See
+ /// [Improving Memory Usage in Microsoft Edge][edge].
+ ///
+ /// [edge]: https://blogs.windows.com/msedgedev/2020/06/17/improving-memory-usage/
+ SegmentHeap,
+}
+
+/// Whether the application supports scroll wheel events with a minimum delta
+/// of 1, 40 or 120.
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum ScrollingAwareness {
+ /// The application can only handle scroll wheel events with the original delta of 120.
+ LowResolution,
+ /// The application can handle high precision scroll wheel events with a delta of 40.
+ HighResolution,
+ /// The application can handle ultra high precision scroll wheel events with a delta as low as 1.
+ UltraHighResolution,
+}
+
+impl ScrollingAwareness {
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ match self {
+ Self::LowResolution => w.element("ultraHighResolutionScrollingAware", &[WS2013], |w| w.text("false")),
+ Self::HighResolution => w.element("highResolutionScrollingAware", &[WS2013], |w| w.text("true")),
+ Self::UltraHighResolution => Ok(()),
+ }
+ }
+}
+
+#[derive(Debug)]
+struct RequestedExecutionLevel {
+ level: ExecutionLevel,
+ ui_access: bool,
+}
+
+impl RequestedExecutionLevel {
+ fn xml_to(&self, w: &mut XmlFormatter) -> fmt::Result {
+ w.element("asmv3:trustInfo", &[], |w| {
+ w.element("asmv3:security", &[], |w| {
+ w.element("asmv3:requestedPrivileges", &[], |w| {
+ w.empty_element(
+ "asmv3:requestedExecutionLevel",
+ &[
+ ("level", self.level.as_str()),
+ ("uiAccess", if self.ui_access { "true" } else { "false" }),
+ ],
+ )
+ })
+ })
+ })
+ }
+}
+
+/// The requested execution level for the application when started.
+///
+/// The behaviour of each option is described in
+/// [Designing UAC Applications for Windows Vista Step 6: Create and Embed an Application Manifest][step6].
+///
+/// [step6]: https://msdn.microsoft.com/en-us/library/bb756929.aspx
+#[derive(Debug)]
+pub enum ExecutionLevel {
+ /// The application will always run with the same authorities as the program invoking it.
+ AsInvoker,
+ /// The program will run without special authorities for a standard user, but will try to
+ /// run with administrator authority if the user is an administrator. This is rarely used.
+ HighestAvailable,
+ /// The application will run as an administrator, prompting for elevation if necessary.
+ RequireAdministrator,
+}
+
+impl ExecutionLevel {
+ pub fn as_str(&self) -> &'static str {
+ match self {
+ Self::AsInvoker => "asInvoker",
+ Self::HighestAvailable => "highestAvailable",
+ Self::RequireAdministrator => "requireAdministrator",
+ }
+ }
+}
+
+impl Display for ExecutionLevel {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.pad(self.as_str())
+ }
+}
diff --git a/third_party/rust/embed-manifest/src/manifest/test.rs b/third_party/rust/embed-manifest/src/manifest/test.rs
new file mode 100644
index 0000000000..9290f7661d
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/manifest/test.rs
@@ -0,0 +1,117 @@
+use super::{ExecutionLevel, SupportedOS};
+use crate::{empty_manifest, new_manifest};
+
+fn enc(s: &str) -> String {
+ let mut buf = String::with_capacity(1024);
+ buf.push('\u{feff}');
+ for l in s.lines() {
+ buf.push_str(l);
+ buf.push_str("\r\n");
+ }
+ buf.truncate(buf.len() - 2);
+ buf
+}
+
+fn encp(s: &str) -> String {
+ s.replace("\n", "\r\n")
+}
+
+#[test]
+fn empty_manifest_canonical() {
+ let builder = empty_manifest();
+ assert_eq!(
+ format!("{}", builder),
+ enc(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"></assembly>"#)
+ );
+}
+
+#[test]
+fn empty_manifest_pretty() {
+ let builder = empty_manifest();
+ assert_eq!(
+ format!("{:#}", builder),
+ encp(
+ r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"/>"#
+ )
+ );
+}
+
+#[test]
+fn only_execution_level() {
+ let builder = empty_manifest().requested_execution_level(ExecutionLevel::AsInvoker);
+ assert_eq!(
+ format!("{:#}", builder),
+ encp(
+ r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
+ <asmv3:trustInfo>
+ <asmv3:security>
+ <asmv3:requestedPrivileges>
+ <asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </asmv3:requestedPrivileges>
+ </asmv3:security>
+ </asmv3:trustInfo>
+</assembly>"#
+ )
+ );
+}
+
+#[test]
+fn only_windows10() {
+ let builder = empty_manifest().supported_os(SupportedOS::Windows10..);
+ assert_eq!(
+ format!("{:#}", builder),
+ encp(
+ r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ </application>
+ </compatibility>
+</assembly>"#
+ )
+ );
+}
+
+#[test]
+fn no_comctl32_6() {
+ let builder = new_manifest("Company.OrgUnit.Program")
+ .version(1, 0, 0, 2)
+ .remove_dependency("Microsoft.Windows.Common-Controls");
+ assert_eq!(
+ format!("{:#}", builder),
+ encp(
+ r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
+ <assemblyIdentity name="Company.OrgUnit.Program" type="win32" version="1.0.0.2"/>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <maxversiontested Id="10.0.18362.1"/>
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ </application>
+ </compatibility>
+ <asmv3:application>
+ <asmv3:windowsSettings>
+ <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2</dpiAwareness>
+ <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+ <printerDriverIsolation xmlns="http://schemas.microsoft.com/SMI/2011/WindowsSettings">true</printerDriverIsolation>
+ </asmv3:windowsSettings>
+ </asmv3:application>
+ <asmv3:trustInfo>
+ <asmv3:security>
+ <asmv3:requestedPrivileges>
+ <asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </asmv3:requestedPrivileges>
+ </asmv3:security>
+ </asmv3:trustInfo>
+</assembly>"#
+ )
+ );
+}
diff --git a/third_party/rust/embed-manifest/src/manifest/xml.rs b/third_party/rust/embed-manifest/src/manifest/xml.rs
new file mode 100644
index 0000000000..a247f42a61
--- /dev/null
+++ b/third_party/rust/embed-manifest/src/manifest/xml.rs
@@ -0,0 +1,140 @@
+use std::fmt::{self, Display, Formatter, Write};
+
+/// Simple but still over-engineered XML generator for a [`Formatter`], for generating
+/// Windows Manifest XML. This can easily generate invalid XML.
+///
+/// When used correctly, this should generate the same output as MT’s `-canonicalize`
+/// option.
+pub struct XmlFormatter<'a, 'f> {
+ f: &'f mut Formatter<'a>,
+ state: State,
+ depth: usize,
+}
+
+#[derive(Eq, PartialEq)]
+enum State {
+ Init,
+ StartTag,
+ Text,
+}
+
+impl<'a, 'f> XmlFormatter<'a, 'f> {
+ pub fn new(f: &'f mut Formatter<'a>) -> Self {
+ Self {
+ f,
+ state: State::Init,
+ depth: 0,
+ }
+ }
+
+ fn pretty(&mut self) -> fmt::Result {
+ if self.f.alternate() {
+ self.f.write_str("\r\n")?;
+ for _ in 0..self.depth {
+ self.f.write_str(" ")?;
+ }
+ }
+ Ok(())
+ }
+
+ pub fn start_document(&mut self) -> fmt::Result {
+ if !self.f.alternate() {
+ self.f.write_char('\u{FEFF}')?;
+ }
+ self.f
+ .write_str("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n")
+ }
+
+ pub fn element<F: FnOnce(&mut Self) -> fmt::Result>(&mut self, name: &str, attrs: &[(&str, &str)], f: F) -> fmt::Result {
+ self.start_element(name, attrs)?;
+ f(self)?;
+ self.end_element(name)
+ }
+
+ pub fn empty_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> fmt::Result {
+ self.start_element(name, attrs)?;
+ self.end_element(name)
+ }
+
+ pub fn start_element(&mut self, name: &str, attrs: &[(&str, &str)]) -> fmt::Result {
+ if self.state == State::StartTag {
+ self.f.write_char('>')?;
+ }
+ if self.depth != 0 {
+ self.pretty()?;
+ }
+ write!(self.f, "<{}", name)?;
+ for (name, value) in attrs {
+ write!(self.f, " {}=\"{}\"", name, Xml(value))?;
+ }
+ self.depth += 1;
+ self.state = State::StartTag;
+ Ok(())
+ }
+
+ pub fn end_element(&mut self, name: &str) -> fmt::Result {
+ self.depth -= 1;
+ match self.state {
+ State::Init => {
+ self.pretty()?;
+ write!(self.f, "</{}>", name)
+ }
+ State::Text => {
+ self.state = State::Init;
+ write!(self.f, "</{}>", name)
+ }
+ State::StartTag => {
+ self.state = State::Init;
+ if self.f.alternate() {
+ self.f.write_str("/>")
+ } else {
+ write!(self.f, "></{}>", name)
+ }
+ }
+ }
+ }
+
+ pub fn text(&mut self, s: &str) -> fmt::Result {
+ if self.state == State::StartTag {
+ self.state = State::Text;
+ self.f.write_char('>')?;
+ }
+ Xml(s).fmt(self.f)
+ }
+}
+
+/// Temporary wrapper for outputting a string with XML attribute encoding.
+/// This does not do anything with the control characters which are not
+/// valid in XML, encoded or not.
+struct Xml<'a>(&'a str);
+
+impl<'a> Display for Xml<'a> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ // Process the string in blocks separated by special characters, so that the parts that
+ // don't need encoding can be written all at once, not character by character, and with
+ // no checks for whether string slices are aligned on character boundaries.
+ for s in self.0.split_inclusive(&['<', '&', '>', '"', '\r'][..]) {
+ // Check whether the last character in the substring needs encoding. This will be
+ // `None` at the end of the input string.
+ let mut iter = s.chars();
+ let ch = match iter.next_back() {
+ Some('<') => Some("&lt;"),
+ Some('&') => Some("&amp;"),
+ Some('>') => Some("&gt;"),
+ Some('"') => Some("&quot;"),
+ Some('\r') => Some("&#13;"),
+ _ => None,
+ };
+ // Write the substring except the last character, then the encoded character;
+ // or the entire substring if it is not terminated by a special character.
+ match ch {
+ Some(enc) => {
+ f.write_str(iter.as_str())?;
+ f.write_str(enc)?;
+ }
+ None => f.write_str(s)?,
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/third_party/rust/embed-manifest/testdata/sample.exe.manifest b/third_party/rust/embed-manifest/testdata/sample.exe.manifest
new file mode 100644
index 0000000000..b7e12866f0
--- /dev/null
+++ b/third_party/rust/embed-manifest/testdata/sample.exe.manifest
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
+ <assemblyIdentity name="Sample.Test" type="win32" version="1.0.0.0"/>
+ <asmv3:trustInfo>
+ <asmv3:security>
+ <asmv3:requestedPrivileges>
+ <asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </asmv3:requestedPrivileges>
+ </asmv3:security>
+ </asmv3:trustInfo>
+</assembly> \ No newline at end of file
diff --git a/third_party/rust/error-support/.cargo-checksum.json b/third_party/rust/error-support/.cargo-checksum.json
index 475bff7665..e81ad51911 100644
--- a/third_party/rust/error-support/.cargo-checksum.json
+++ b/third_party/rust/error-support/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"49ef90bd388b59229db34b35fe06eb769183431c88b5712e6e9992851aef605d","README.md":"8030b4a314b1be31ba018ac12c3b586bb736db5307c3c395f2857fffe0130322","build.rs":"c8d3c38c1208eea36224662b284d8daf3e7ad1b07d22d750524f3da1cc66ccca","src/errorsupport.udl":"e793034d01a2608298528051757f38405e006ee1abc4cf65dc6f18c53590ace8","src/handling.rs":"6e0568b18d426531cb2ae9967c8dd0d51ece5a065f68b15eeb308b995edaa167","src/lib.rs":"96ae3cc2c1077ae45442ace6b5b5311b86267d0b9067f3ff58396af30ccbbc07","src/macros.rs":"0d03f82fab20c96a182f941baf3fcf2a286b00fea871ee7fd8e339abc14f9522","src/redact.rs":"c9a4df1a87be68b15d583587bda941d4c60a1d0449e2d43ff99f3611a290a863","src/reporting.rs":"38efd24d86ba8facfb181cb27e8b698d2831db0afab85691ffda034a4dc68dfa","uniffi.toml":"644fe81c12fe3c01ee81e017ca3c00d0e611f014b7eade51aadaf208179a3450"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"bd2f0908b3576a3ad9a416ecb0e4f8441a48a95036cf0439a65e37d836178142","README.md":"8030b4a314b1be31ba018ac12c3b586bb736db5307c3c395f2857fffe0130322","build.rs":"c8d3c38c1208eea36224662b284d8daf3e7ad1b07d22d750524f3da1cc66ccca","src/errorsupport.udl":"e793034d01a2608298528051757f38405e006ee1abc4cf65dc6f18c53590ace8","src/handling.rs":"6e0568b18d426531cb2ae9967c8dd0d51ece5a065f68b15eeb308b995edaa167","src/lib.rs":"96ae3cc2c1077ae45442ace6b5b5311b86267d0b9067f3ff58396af30ccbbc07","src/macros.rs":"0d03f82fab20c96a182f941baf3fcf2a286b00fea871ee7fd8e339abc14f9522","src/redact.rs":"c9a4df1a87be68b15d583587bda941d4c60a1d0449e2d43ff99f3611a290a863","src/reporting.rs":"38efd24d86ba8facfb181cb27e8b698d2831db0afab85691ffda034a4dc68dfa","uniffi.toml":"644fe81c12fe3c01ee81e017ca3c00d0e611f014b7eade51aadaf208179a3450"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/error-support/Cargo.toml b/third_party/rust/error-support/Cargo.toml
index e4c39618a6..074ad4a32d 100644
--- a/third_party/rust/error-support/Cargo.toml
+++ b/third_party/rust/error-support/Cargo.toml
@@ -21,7 +21,7 @@ license = "MPL-2.0"
[dependencies]
log = "0.4"
-uniffi = "0.25.2"
+uniffi = "0.27.1"
[dependencies.backtrace]
version = "0.3"
@@ -37,5 +37,5 @@ version = "1.4"
version = ">=0.11,<=0.12"
[build-dependencies.uniffi]
-version = "0.25.2"
+version = "0.27.1"
features = ["build"]
diff --git a/third_party/rust/glean-core/.cargo-checksum.json b/third_party/rust/glean-core/.cargo-checksum.json
index 54674fc768..663b3141c3 100644
--- a/third_party/rust/glean-core/.cargo-checksum.json
+++ b/third_party/rust/glean-core/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"2dde200f0e0e4e523634f8c2c8c1c2ca75af83163ac7b0ba8f62f3096fd0c97d","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"026495898699b54608eb4ec16074ffafc57920d80ccb59961c501a1ea28c9985","build.rs":"4857bea99c6b8c08db8818efa9d3738716f52d3acb68159323957ae52892a3eb","src/common_metric_data.rs":"72051c4349885d4a94fc41bb4edda88d31551f2f9ebcdb4e868a98161bc76233","src/core/mod.rs":"8f5e98a108ec5d1849402af1de90b5f53ba839240743c2c5283a49a4045e1293","src/core_metrics.rs":"a877e42e0f8b932adb52a5681ad76fd977808cb48c7eeb29b1e4bbe804f1ea96","src/coverage.rs":"49613fd310bd24d779472720975fbe6c97ec370a95eb55f10afa43f67539c942","src/database/mod.rs":"3917bad7773696a43ab58e7860d5a8f1d63dca7c27920343aa8786acc5a586cd","src/debug.rs":"90158cc5d488ba67b60d06647e54e59a1d7bdeb906087e4fe4cfab4373c1cc6c","src/dispatcher/global.rs":"f69cd81a90a37c306d4e0ce8177ea5a3ae2ffda5b431ae46b9a22c9e38891271","src/dispatcher/mod.rs":"440a331a7beeaa6e9824c2fd4306c09ce2a115a358d5beb830dba4d69aec3563","src/error.rs":"b93c7d3e243b21bb9eafc95f17860aba1a942b2f2b0a7f43307690f05fece516","src/error_recording.rs":"d7858647808d81173558e12de88f3fbe4e589969e3bd246bfb388f90f8ff3814","src/event_database/mod.rs":"9d4d3e4b075dc585c00317304401b2c9115f39db8fdbd9f1c93d3fc6fd350fd5","src/fd_logger.rs":"0c9def6fa53db1a2ab93c85795f8a7df57797bcfd3978146923e151752e291a6","src/glean.udl":"24d9e431f95d79dc4254feff68f19a4ea4e6e76c33b110e10c5e5dbd5bc64ff2","src/glean_metrics.rs":"9414fb1453d19f6832df33e4c6ef7383d62203e47026bf5bc9552b083101ddd1","src/histogram/exponential.rs":"58bb1770bae45770d92995515d328eb50a7e78726224f779446ae7d1632a6a3e","src/histogram/functional.rs":"1a63a305b48bcef7bc38136b40d916df4bb8f098dc602514ada54a9b091f6951","src/histogram/linear.rs":"4342a1733175d7f97b2b41adb18100537c206100c9fccb5bd13bd782c9cb3c9a","src/histogram/mod.rs":"eeb7aff80806ab76cdce101dc08887b5552f8b4bdf64683f64f767e0f06a889d","src/internal_metrics.rs":"263779535963a804c8c7fa6f8e284ac8ec7f415ceeadbb6a8f913a1e7073ae18","src/internal_pings.rs":"7267166a8e357053526c545cf62bb502a7b6f07aed1de48d43041228d8835366","src/lib.rs":"367ea21f9d3f1c808b258011821d8505cd47d29eff8e8e6d938623e6e9997b73","src/lib_unit_tests.rs":"46897c6bb4003c5e00152d7b55c00d3176b5bffb28d8669a3fb0d10e5233e3a5","src/metrics/boolean.rs":"2b9ef57e3582c9bd8b2cca8ab94c962a4871ecc00e837b913c9b0349ba9dff08","src/metrics/counter.rs":"b4a52a8167fb0edd6354f952525e59f3eadb4261de3483374f03c94449d30b92","src/metrics/custom_distribution.rs":"e1f2edfefb67da4bf369bab3d3047f4ff6539a1fea0eee81c78d96626e5b4bb0","src/metrics/datetime.rs":"e4405762fc71718299fa1b208e3d5fda654bd1b82fe908c884c284e3530de2ec","src/metrics/denominator.rs":"95e8442f90bad97f80fc74b146782f215344b52c5f3825ae0a8baffdc001a714","src/metrics/event.rs":"7281d8b63f34758a47abd7ae3956f44701d1fd48433ccba7a4302526a9912255","src/metrics/experiment.rs":"5f9278cca4e133eb8df33bbfe36d1fe0ef3eade8c09f1b46db3c4d0790515412","src/metrics/labeled.rs":"8d6e76a07064d132cd617c7901f2bc11ff6ba31e3483ba3b96354a4a3736b58d","src/metrics/memory_distribution.rs":"7f6ca51acb470df277ff14427c0e7bb07d921c0a0087d0cc56aebe038d198ccc","src/metrics/memory_unit.rs":"d7a678e5242febd021283b30c0099a9e62729944816a3f17d2d91e2808bc0570","src/metrics/metrics_enabled_config.rs":"87fed12219c756ecf1e5c8cd6a21f26999b6bbcf3ffc1b5467b0a58ca5ad35d8","src/metrics/mod.rs":"8f8958b8cedfe01df6c97ec26b63f14fd7516f9de7ba62984062db96b5708720","src/metrics/numerator.rs":"937dfd583b797ac798a525cedca95c5a36262356760a89670d8113983c263154","src/metrics/object.rs":"89ce5190ed681b26b74a06a4ecaf9f96c36f96be1276f1fdb40f4406648e08c1","src/metrics/ping.rs":"4ccdf0ae2ac6f3e5a352334797d2805f1a3d932e92f08447285dd9bec4e7d724","src/metrics/quantity.rs":"aa13a8f8cf8e5e0281668fbbafc2998411df2a499479423558fd91b9bd7f8702","src/metrics/rate.rs":"603cc45c149c7a27c93b6a80146bf43f8ce70d9655f905bb5be6bc2c15bcb22b","src/metrics/recorded_experiment.rs":"33958abee79d8b55dec4cb5d20742640423713010f76314075cefde18b5c118a","src/metrics/string.rs":"2418632c492463970c3eca533d5318f519698bb361d73dd8781db108d7d1fbd8","src/metrics/string_list.rs":"ed53a095184c3e8224d0511809b5d7601ba3166505a39b0570f24ebeb0a5b97c","src/metrics/text.rs":"5c994a282b16b9dde6d6dc4922475457a72c82f64248778811b84db70ed4c116","src/metrics/time_unit.rs":"b7578010c6270a45b30342b59189a862b2ede9dd24e9afae3e90fa6b970b3d24","src/metrics/timespan.rs":"b0fda3a45597c8306a0d1928dcf0837538859e66ebd9db113ebb6efbea721d4c","src/metrics/timing_distribution.rs":"5da04272dd8b44502ffd0b60b12c84239a7fe359a51754b6c0cd96388a4e8a3c","src/metrics/url.rs":"f6b27a60d13a1268f0115c5d292c9b16b6bc370055961368cb2648283b7140a0","src/metrics/uuid.rs":"cacffd95ab30ed327ec2fa5feaf1359e667706746401f1e2c1195ad9553c4b54","src/ping/mod.rs":"fcadd52d2d536c9ace01f8a3812c3fb3c39b8094915db1b3656839fb87f771b5","src/scheduler.rs":"129863e31205404a3d1708627a62583324c347d143f976216f769893ec541ea0","src/storage/mod.rs":"04dc1a94be1d59097cd87b14386952a6ec8b9115bc06397ae389a323f6f55dcc","src/system.rs":"e3d1b54e1d39cafe6f4dc7ff5021b08c879733f909951b0e1332b3efa9ed97bd","src/traits/boolean.rs":"be0e130f8043215705becc956d45b126c340568f1b24a396c0af9b4334a41ced","src/traits/counter.rs":"c686d26e131d854cd7a7df83c900ca7c17a03c663a30cf58ab48c7259476ce85","src/traits/custom_distribution.rs":"0bd1d425e4c059cca6af2dfb13c78e5e4c6c07fb46c7e31489ad0c5959854833","src/traits/datetime.rs":"636ac1456b1b042e38cf5ae6193c5b232ea0b80df62f583a2097891baef9641b","src/traits/event.rs":"3f48aa336854141784d121f7fa9e283f6ff708a9214f9c0aade3a68cc38dda99","src/traits/labeled.rs":"c633c68e70a44e73f8aff88aaab1029c0faded3cad08d822590ed8838f24b4fd","src/traits/memory_distribution.rs":"55bb8f45e948319fbba9d28a50d8742da134b066a42e480887db7c7e435f4096","src/traits/mod.rs":"d14b69d0946848c1f92cc8977cbc3fc9338ff1b53b7acc31ea0fe2f1122beecb","src/traits/numerator.rs":"6e4f236bdc448f1bde7a8c249dcd086204c2c69990d3f444e746290929226ed3","src/traits/object.rs":"c03bad670ec7affbc578247f9e1904e898c1870b9bf25750c5094113f995623f","src/traits/ping.rs":"8831c106c03afeb458b0b028fa1ce61f056ebf8e82bc0a171a1bff255d920748","src/traits/quantity.rs":"6ffe25c913bef4315573d747308c182de740b2a4e02ba22cd21d0c33ba521f31","src/traits/rate.rs":"f000790440e0f389f0b160526a9a9a266e58d1405915ae56ac550f482858222c","src/traits/string.rs":"0c3c88382ff2e8eba89c7cfe129c4b84e31140af717819533c14919541ad790c","src/traits/string_list.rs":"14e56b62c2c2be1dd8013f12001f235b084abd2a0d5aa2f7932843877af49ac0","src/traits/text.rs":"8af7d3a0c87cfd8c6d33d6ad47532b431055bbdd395f9110da5630222c23cf93","src/traits/timespan.rs":"52be325a9c061916f34c5b638a07a93b4a14aa89fe365783103d2e06b998f547","src/traits/timing_distribution.rs":"00ebdef647a7a208c01d13ba7b3996750e36de98d1f63859b609c80c8df25b6f","src/traits/url.rs":"c27f7add23214ff051078b65b88120b620560d2841a1056c7214d5237e86b9e4","src/traits/uuid.rs":"81322e71c7e847bacaf827a2cd58f6193bdc208355524207f7f38db039da6aa8","src/upload/directory.rs":"6359220db9d85ee0f3931ca518f95ffb2020c1c03bd632f17ed5c16ddd00343b","src/upload/mod.rs":"a388563d5e2940c5c28b48fc7b67ca507512efccae95fd1c2f04b15ec21aa08c","src/upload/policy.rs":"c250957a37783e74af8002cd80ba06ef9780a389fb0f61b8b665b79688f0a360","src/upload/request.rs":"0b7e215f61499a681d1cebc9cf4a0efbaae2f543a5d44e5db40cbe61ed90549e","src/upload/result.rs":"7efbbe50e8d36beb3f23e7bfd172d22e1c003472d2dd8055b06f6050c36437c5","src/util.rs":"ee7500434d9758a320dd410f18d7e18da956591e19d2555db87eef9623e4b916","tests/boolean.rs":"76d6014ff108cb6514d9bceb1b2b14749a55b09921f4595a5e30f1bd3546e9f0","tests/common/mod.rs":"c1d980a9cff0b64f452ebbe43f24d70aa685b80b48db08fc4338a60466b07a5e","tests/counter.rs":"3663a3f5ec5c0bd2b758a9920cd20cc619a12566b445e4421ec7c98232bf5a32","tests/custom_distribution.rs":"41c593a0b4561e21f29d1a5b948de964a866253c58ca76ffefebe370fca150e0","tests/datetime.rs":"ec3c9760e70bb2cbc61ab23281c891bc1ec493c5c545466c29fd13e4f05c2c96","tests/event.rs":"67291cbcc4d1cba56ada6ba733fb1dc4c6327680059e8d7637add2ae45cd344b","tests/labeled.rs":"e9ea6dba17059d68114efce0c23373be9ceed922bf5e638a2158a6422c75a1c1","tests/memory_distribution.rs":"a5a7aa955e60823ea29a6f4bc96c61e41f1e41f08958aa4854668cf8fe04cde6","tests/object.rs":"8c35676e04f6ccf54a28764700915e753fc0355bfa5d7804d72caba66fd564cd","tests/ping.rs":"eb9f6be1aba21acc5dc670622bf622976718a706df1cc2095efa56a8e8b3fe1a","tests/ping_maker.rs":"b267ecf7c714ff27512424b743da0ea4f05a87755c1b96355bfca3e173e3f62e","tests/quantity.rs":"55e7dca346fd1d27f0974b78ca3fb12427cb5da2ee637afc08a54f360f947361","tests/rate.rs":"1de571b9f0ee9a9006cbc8a31f91352d3ff1190b50840f0f668b470a7cd2a3a5","tests/storage.rs":"f0c8312bd789d7bda502cd45f35fef6b8591652bd194d07da4d81935ebe69b48","tests/string.rs":"7ece988a4b8efe6932ccb90bfe2f3c8aaea983777e99d7de6028bf6a29459ee6","tests/string_list.rs":"77188a2b90663c3f8dac5da89a6cb6b1d16a9f8c66ccd032d02966dfd14a3486","tests/text.rs":"1d43f6b90a43124311cacf0a6ee16f9e1e9263bcd11fee8b996d6efd81633638","tests/timespan.rs":"d50d75c7d75da3a878d67331cb0df8ae5e6a099ffab474361f71a408e02528d7","tests/timing_distribution.rs":"20860a7baccdcee6aed40c9cc8202b94f3b2e61164fbaf8f2af96b0f404a895a","tests/uuid.rs":"052ad26a6927c56272219340211cf4a059d200f14287b482fe8621d7bce3cc54","uniffi.toml":"6ddc98b686b0925a81abd9d1c769e5c98ac29771b210a1c535931a46dec9a8e3"},"package":"ed9acc46fd38c5c995a0537e76364496addace660839dc279079e5957e3c1093"} \ No newline at end of file
+{"files":{"Cargo.toml":"c590a29d01f2ccad65fdbed80578177ae3c02522d6c6c60eef9644d71f04a0e3","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"026495898699b54608eb4ec16074ffafc57920d80ccb59961c501a1ea28c9985","build.rs":"4857bea99c6b8c08db8818efa9d3738716f52d3acb68159323957ae52892a3eb","src/common_metric_data.rs":"864990a1e5770d5d5fdebcd2c36b58c3442334030fb60f53811395b56baac94b","src/core/mod.rs":"9880520967e9da0b475d280c17cd70debf9a1d15912018cbba775e5fde0ff588","src/core_metrics.rs":"a877e42e0f8b932adb52a5681ad76fd977808cb48c7eeb29b1e4bbe804f1ea96","src/coverage.rs":"49613fd310bd24d779472720975fbe6c97ec370a95eb55f10afa43f67539c942","src/database/mod.rs":"b3684bb6a11e0aa2a51306a53feddbc89bc21879d4930d5e9995869950af3413","src/debug.rs":"d0dfc0932a0953bbbe029f723bf2613c8d691f34b017e858030c46b02a46a17d","src/dispatcher/global.rs":"f69cd81a90a37c306d4e0ce8177ea5a3ae2ffda5b431ae46b9a22c9e38891271","src/dispatcher/mod.rs":"391310269947452d7e0de24c848c183110c60149d75e345ba6d5d146f222dace","src/error.rs":"b93c7d3e243b21bb9eafc95f17860aba1a942b2f2b0a7f43307690f05fece516","src/error_recording.rs":"1aba34e9d3c741755055f5b76415114b25b146b0aa90049c3457cfe12066deda","src/event_database/mod.rs":"78633293e1f3c9e9d51705615a7a4b603d7f85567bfdc2b0bad35ccda6a12d44","src/fd_logger.rs":"0c9def6fa53db1a2ab93c85795f8a7df57797bcfd3978146923e151752e291a6","src/glean.udl":"0fcf72a8e3304d98e896dd3dfd9787208776c2b21b59f1c241029978ee37a925","src/glean_metrics.rs":"9414fb1453d19f6832df33e4c6ef7383d62203e47026bf5bc9552b083101ddd1","src/histogram/exponential.rs":"58bb1770bae45770d92995515d328eb50a7e78726224f779446ae7d1632a6a3e","src/histogram/functional.rs":"1a63a305b48bcef7bc38136b40d916df4bb8f098dc602514ada54a9b091f6951","src/histogram/linear.rs":"4342a1733175d7f97b2b41adb18100537c206100c9fccb5bd13bd782c9cb3c9a","src/histogram/mod.rs":"bbb9535a633b5a85b6b11c6e4eed3314ab797950355a9bb8ccf3a22000f1e093","src/internal_metrics.rs":"263779535963a804c8c7fa6f8e284ac8ec7f415ceeadbb6a8f913a1e7073ae18","src/internal_pings.rs":"0e3b8ce673cf92bd085fd4f07aa43876c97472dbd921a2d0dc0f10c9fe6b1c6b","src/lib.rs":"fb50a72a7221358c5daa1a8c0e59c92d5d358adc10b1ba9b831d2f07c07d90e2","src/lib_unit_tests.rs":"76d1997f7608b735cc4e905cfa94f79dd71a4a2ed1eccaa89d3d72ccd8d348e2","src/metrics/boolean.rs":"2b9ef57e3582c9bd8b2cca8ab94c962a4871ecc00e837b913c9b0349ba9dff08","src/metrics/counter.rs":"b4a52a8167fb0edd6354f952525e59f3eadb4261de3483374f03c94449d30b92","src/metrics/custom_distribution.rs":"e1f2edfefb67da4bf369bab3d3047f4ff6539a1fea0eee81c78d96626e5b4bb0","src/metrics/datetime.rs":"e4405762fc71718299fa1b208e3d5fda654bd1b82fe908c884c284e3530de2ec","src/metrics/denominator.rs":"95e8442f90bad97f80fc74b146782f215344b52c5f3825ae0a8baffdc001a714","src/metrics/event.rs":"cd52e200d313e2e6f31707419d4a7fe1cab34916ee145f8136440d6da34aaad4","src/metrics/experiment.rs":"5f9278cca4e133eb8df33bbfe36d1fe0ef3eade8c09f1b46db3c4d0790515412","src/metrics/labeled.rs":"8d6e76a07064d132cd617c7901f2bc11ff6ba31e3483ba3b96354a4a3736b58d","src/metrics/memory_distribution.rs":"7f6ca51acb470df277ff14427c0e7bb07d921c0a0087d0cc56aebe038d198ccc","src/metrics/memory_unit.rs":"ee32e020cb303dd631457374048a3ed53a2e7cbacc29c54d17d836fb15507538","src/metrics/metrics_enabled_config.rs":"c45f2cd48b36f8706e0e1d402d6fc375f5bab50f7d0840e0fbbbeacb6f2732af","src/metrics/mod.rs":"8f8958b8cedfe01df6c97ec26b63f14fd7516f9de7ba62984062db96b5708720","src/metrics/numerator.rs":"937dfd583b797ac798a525cedca95c5a36262356760a89670d8113983c263154","src/metrics/object.rs":"89ce5190ed681b26b74a06a4ecaf9f96c36f96be1276f1fdb40f4406648e08c1","src/metrics/ping.rs":"86dc577422075c759edb998acbd890c239569d72b30a994e7777d6d0f7676c5a","src/metrics/quantity.rs":"aa13a8f8cf8e5e0281668fbbafc2998411df2a499479423558fd91b9bd7f8702","src/metrics/rate.rs":"603cc45c149c7a27c93b6a80146bf43f8ce70d9655f905bb5be6bc2c15bcb22b","src/metrics/recorded_experiment.rs":"33958abee79d8b55dec4cb5d20742640423713010f76314075cefde18b5c118a","src/metrics/string.rs":"0906b4d5ec1ec10b7a56fd6eb39dc30500531658df2c8bc3f55c9579e15c88db","src/metrics/string_list.rs":"ed53a095184c3e8224d0511809b5d7601ba3166505a39b0570f24ebeb0a5b97c","src/metrics/text.rs":"757f6919124d74e0512faa5bb9751a729b6bbc63ebe4d16ca81e9087f5595eaf","src/metrics/time_unit.rs":"4704703e19e799933aec3f39e3d3a125058756d7c7ba04f8729885c7843df447","src/metrics/timespan.rs":"1ad5233c7522cab70b4c095fb24cace66ace9741731f97bc001ede071f10d1ef","src/metrics/timing_distribution.rs":"261f971d012e80e93180caea69da549498597d47771264c9bb0667a9573f47ed","src/metrics/url.rs":"589ae1f8047367ad8c19b57a48ca8130d5f36cf3ce5954124150f0eb89c620ea","src/metrics/uuid.rs":"cacffd95ab30ed327ec2fa5feaf1359e667706746401f1e2c1195ad9553c4b54","src/ping/mod.rs":"fcadd52d2d536c9ace01f8a3812c3fb3c39b8094915db1b3656839fb87f771b5","src/scheduler.rs":"129863e31205404a3d1708627a62583324c347d143f976216f769893ec541ea0","src/storage/mod.rs":"91f02556f113799e0d88d732ab342bda443f43461369e8b41c424c074d742591","src/system.rs":"e3d1b54e1d39cafe6f4dc7ff5021b08c879733f909951b0e1332b3efa9ed97bd","src/traits/boolean.rs":"be0e130f8043215705becc956d45b126c340568f1b24a396c0af9b4334a41ced","src/traits/counter.rs":"c686d26e131d854cd7a7df83c900ca7c17a03c663a30cf58ab48c7259476ce85","src/traits/custom_distribution.rs":"0bd1d425e4c059cca6af2dfb13c78e5e4c6c07fb46c7e31489ad0c5959854833","src/traits/datetime.rs":"636ac1456b1b042e38cf5ae6193c5b232ea0b80df62f583a2097891baef9641b","src/traits/event.rs":"a02235aae630aba7a45a3166b756927252b397af3ecdfab7236931e62725ac49","src/traits/labeled.rs":"c633c68e70a44e73f8aff88aaab1029c0faded3cad08d822590ed8838f24b4fd","src/traits/memory_distribution.rs":"55bb8f45e948319fbba9d28a50d8742da134b066a42e480887db7c7e435f4096","src/traits/mod.rs":"d14b69d0946848c1f92cc8977cbc3fc9338ff1b53b7acc31ea0fe2f1122beecb","src/traits/numerator.rs":"6e4f236bdc448f1bde7a8c249dcd086204c2c69990d3f444e746290929226ed3","src/traits/object.rs":"c03bad670ec7affbc578247f9e1904e898c1870b9bf25750c5094113f995623f","src/traits/ping.rs":"8831c106c03afeb458b0b028fa1ce61f056ebf8e82bc0a171a1bff255d920748","src/traits/quantity.rs":"6ffe25c913bef4315573d747308c182de740b2a4e02ba22cd21d0c33ba521f31","src/traits/rate.rs":"f000790440e0f389f0b160526a9a9a266e58d1405915ae56ac550f482858222c","src/traits/string.rs":"0c3c88382ff2e8eba89c7cfe129c4b84e31140af717819533c14919541ad790c","src/traits/string_list.rs":"14e56b62c2c2be1dd8013f12001f235b084abd2a0d5aa2f7932843877af49ac0","src/traits/text.rs":"8af7d3a0c87cfd8c6d33d6ad47532b431055bbdd395f9110da5630222c23cf93","src/traits/timespan.rs":"52be325a9c061916f34c5b638a07a93b4a14aa89fe365783103d2e06b998f547","src/traits/timing_distribution.rs":"00ebdef647a7a208c01d13ba7b3996750e36de98d1f63859b609c80c8df25b6f","src/traits/url.rs":"c27f7add23214ff051078b65b88120b620560d2841a1056c7214d5237e86b9e4","src/traits/uuid.rs":"81322e71c7e847bacaf827a2cd58f6193bdc208355524207f7f38db039da6aa8","src/upload/directory.rs":"e42c62f27ace5c6504cc7703a4c1d9ffd0e6ac7c4fba7d7dee231430fb67f8f8","src/upload/mod.rs":"6151a6d3b4fccb3df7ef03207e2f77bf34dbf04b3b705e2af55dd02a731f99f8","src/upload/policy.rs":"c250957a37783e74af8002cd80ba06ef9780a389fb0f61b8b665b79688f0a360","src/upload/request.rs":"5891364d4254aafdb43751f476b0b908b681544793ac98802fe103de321ec326","src/upload/result.rs":"7efbbe50e8d36beb3f23e7bfd172d22e1c003472d2dd8055b06f6050c36437c5","src/util.rs":"ee7500434d9758a320dd410f18d7e18da956591e19d2555db87eef9623e4b916","tests/boolean.rs":"76d6014ff108cb6514d9bceb1b2b14749a55b09921f4595a5e30f1bd3546e9f0","tests/common/mod.rs":"c5bf5a9f3660ae1a1c1dbb659ab6be60438c58bc7c459f2f96dca467d05d4ab3","tests/counter.rs":"3663a3f5ec5c0bd2b758a9920cd20cc619a12566b445e4421ec7c98232bf5a32","tests/custom_distribution.rs":"41c593a0b4561e21f29d1a5b948de964a866253c58ca76ffefebe370fca150e0","tests/datetime.rs":"ec3c9760e70bb2cbc61ab23281c891bc1ec493c5c545466c29fd13e4f05c2c96","tests/event.rs":"0fbec0e8929c99603b79c62a1f57f8cabe614451fdafb6eb9d47f22116303245","tests/labeled.rs":"e9ea6dba17059d68114efce0c23373be9ceed922bf5e638a2158a6422c75a1c1","tests/memory_distribution.rs":"a5a7aa955e60823ea29a6f4bc96c61e41f1e41f08958aa4854668cf8fe04cde6","tests/object.rs":"8c35676e04f6ccf54a28764700915e753fc0355bfa5d7804d72caba66fd564cd","tests/ping.rs":"eb9f6be1aba21acc5dc670622bf622976718a706df1cc2095efa56a8e8b3fe1a","tests/ping_maker.rs":"7ad1f76a1eda2dabf0422fff74d9c2c1a39b9d1d315a4dbe6057dff44efcfae0","tests/quantity.rs":"55e7dca346fd1d27f0974b78ca3fb12427cb5da2ee637afc08a54f360f947361","tests/rate.rs":"1de571b9f0ee9a9006cbc8a31f91352d3ff1190b50840f0f668b470a7cd2a3a5","tests/storage.rs":"f0c8312bd789d7bda502cd45f35fef6b8591652bd194d07da4d81935ebe69b48","tests/string.rs":"7ece988a4b8efe6932ccb90bfe2f3c8aaea983777e99d7de6028bf6a29459ee6","tests/string_list.rs":"77188a2b90663c3f8dac5da89a6cb6b1d16a9f8c66ccd032d02966dfd14a3486","tests/text.rs":"1d43f6b90a43124311cacf0a6ee16f9e1e9263bcd11fee8b996d6efd81633638","tests/timespan.rs":"d50d75c7d75da3a878d67331cb0df8ae5e6a099ffab474361f71a408e02528d7","tests/timing_distribution.rs":"20860a7baccdcee6aed40c9cc8202b94f3b2e61164fbaf8f2af96b0f404a895a","tests/uuid.rs":"052ad26a6927c56272219340211cf4a059d200f14287b482fe8621d7bce3cc54","uniffi.toml":"6ddc98b686b0925a81abd9d1c769e5c98ac29771b210a1c535931a46dec9a8e3"},"package":"ea06a592b1395e0a16a5f4d6872f009ca7c98acc5127a8119088f1b435b5aaae"} \ No newline at end of file
diff --git a/third_party/rust/glean-core/Cargo.toml b/third_party/rust/glean-core/Cargo.toml
index 9d33444fbd..932b16a4a7 100644
--- a/third_party/rust/glean-core/Cargo.toml
+++ b/third_party/rust/glean-core/Cargo.toml
@@ -13,7 +13,7 @@
edition = "2021"
rust-version = "1.66"
name = "glean-core"
-version = "58.1.0"
+version = "59.0.0"
authors = [
"Jan-Erik Rediger <jrediger@mozilla.com>",
"The Glean Team <glean-team@mozilla.com>",
@@ -80,7 +80,7 @@ version = "1.0.4"
version = "0.1.40"
[dependencies.uniffi]
-version = "0.25.2"
+version = "0.27.0"
default-features = false
[dependencies.uuid]
@@ -105,7 +105,7 @@ version = "0.4"
version = "3.8.0"
[build-dependencies.uniffi]
-version = "0.25.2"
+version = "0.27.0"
features = ["build"]
default-features = false
diff --git a/third_party/rust/glean-core/src/common_metric_data.rs b/third_party/rust/glean-core/src/common_metric_data.rs
index 033cbe1472..9bda9bb462 100644
--- a/third_party/rust/glean-core/src/common_metric_data.rs
+++ b/third_party/rust/glean-core/src/common_metric_data.rs
@@ -2,7 +2,6 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
-use std::convert::TryFrom;
use std::sync::atomic::{AtomicU8, Ordering};
use crate::error::{Error, ErrorKind};
diff --git a/third_party/rust/glean-core/src/core/mod.rs b/third_party/rust/glean-core/src/core/mod.rs
index 30f9a34f11..f69f0c3868 100644
--- a/third_party/rust/glean-core/src/core/mod.rs
+++ b/third_party/rust/glean-core/src/core/mod.rs
@@ -120,6 +120,7 @@ where
/// rate_limit: None,
/// enable_event_timestamps: true,
/// experimentation_id: None,
+/// enable_internal_pings: true,
/// };
/// let mut glean = Glean::new(cfg).unwrap();
/// let ping = PingType::new("sample", true, false, true, true, vec![]);
@@ -208,7 +209,7 @@ impl Glean {
core_metrics: CoreMetrics::new(),
additional_metrics: AdditionalMetrics::new(),
database_metrics: DatabaseMetrics::new(),
- internal_pings: InternalPings::new(),
+ internal_pings: InternalPings::new(cfg.enable_internal_pings),
upload_manager,
data_path: PathBuf::from(&cfg.data_path),
application_id,
@@ -288,7 +289,9 @@ impl Glean {
}
// We set this only for non-subprocess situations.
- glean.schedule_metrics_pings = cfg.use_core_mps;
+ // If internal pings are disabled, we don't set up the MPS either,
+ // it wouldn't send any data anyway.
+ glean.schedule_metrics_pings = cfg.enable_internal_pings && cfg.use_core_mps;
// We only scan the pendings pings directories **after** dealing with the upload state.
// If upload is disabled, we delete all pending pings files
@@ -305,6 +308,7 @@ impl Glean {
data_path: &str,
application_id: &str,
upload_enabled: bool,
+ enable_internal_pings: bool,
) -> Self {
let cfg = InternalConfiguration {
data_path: data_path.into(),
@@ -320,6 +324,7 @@ impl Glean {
rate_limit: None,
enable_event_timestamps: true,
experimentation_id: None,
+ enable_internal_pings,
};
let mut glean = Self::new(cfg).unwrap();
diff --git a/third_party/rust/glean-core/src/database/mod.rs b/third_party/rust/glean-core/src/database/mod.rs
index af473c98d9..0dbf0220bc 100644
--- a/third_party/rust/glean-core/src/database/mod.rs
+++ b/third_party/rust/glean-core/src/database/mod.rs
@@ -824,7 +824,6 @@ mod test {
use super::*;
use crate::tests::new_glean;
use std::collections::HashMap;
- use std::path::Path;
use tempfile::tempdir;
#[test]
diff --git a/third_party/rust/glean-core/src/debug.rs b/third_party/rust/glean-core/src/debug.rs
index a572a02b8f..88f807bd88 100644
--- a/third_party/rust/glean-core/src/debug.rs
+++ b/third_party/rust/glean-core/src/debug.rs
@@ -240,7 +240,6 @@ fn validate_source_tags(tags: &Vec<String>) -> bool {
#[cfg(test)]
mod test {
use super::*;
- use std::env;
#[test]
fn debug_option_is_correctly_loaded_from_env() {
diff --git a/third_party/rust/glean-core/src/dispatcher/mod.rs b/third_party/rust/glean-core/src/dispatcher/mod.rs
index 48efa4ef96..ead58fb867 100644
--- a/third_party/rust/glean-core/src/dispatcher/mod.rs
+++ b/third_party/rust/glean-core/src/dispatcher/mod.rs
@@ -360,9 +360,8 @@ impl Dispatcher {
#[cfg(test)]
mod test {
use super::*;
- use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
- use std::sync::{Arc, Mutex};
- use std::{thread, time::Duration};
+ use std::sync::atomic::AtomicU8;
+ use std::sync::Mutex;
fn enable_test_logging() {
// When testing we want all logs to go to stdout/stderr by default,
diff --git a/third_party/rust/glean-core/src/error_recording.rs b/third_party/rust/glean-core/src/error_recording.rs
index aaf850d019..fa828242f1 100644
--- a/third_party/rust/glean-core/src/error_recording.rs
+++ b/third_party/rust/glean-core/src/error_recording.rs
@@ -12,7 +12,6 @@
//! but are not actually used directly, since the `send_in_pings` value needs to match the pings of the metric that is erroring (plus the "metrics" ping),
//! not some constant value that we could define in `metrics.yaml`.
-use std::convert::TryFrom;
use std::fmt::Display;
use crate::common_metric_data::CommonMetricDataInternal;
diff --git a/third_party/rust/glean-core/src/event_database/mod.rs b/third_party/rust/glean-core/src/event_database/mod.rs
index d83e56fbec..50b2488a4c 100644
--- a/third_party/rust/glean-core/src/event_database/mod.rs
+++ b/third_party/rust/glean-core/src/event_database/mod.rs
@@ -4,7 +4,6 @@
use std::cmp::Ordering;
use std::collections::HashMap;
-use std::convert::TryFrom;
use std::fs;
use std::fs::{create_dir_all, File, OpenOptions};
use std::io::BufRead;
@@ -638,8 +637,8 @@ impl EventDatabase {
#[cfg(test)]
mod test {
use super::*;
+ use crate::test_get_num_recorded_errors;
use crate::tests::new_glean;
- use crate::{test_get_num_recorded_errors, CommonMetricData};
use chrono::{TimeZone, Timelike};
#[test]
diff --git a/third_party/rust/glean-core/src/glean.udl b/third_party/rust/glean-core/src/glean.udl
index e68a57ea4c..dc71fea594 100644
--- a/third_party/rust/glean-core/src/glean.udl
+++ b/third_party/rust/glean-core/src/glean.udl
@@ -90,6 +90,7 @@ dictionary InternalConfiguration {
PingRateLimit? rate_limit;
boolean enable_event_timestamps;
string? experimentation_id;
+ boolean enable_internal_pings;
};
// How to specify the rate pings may be uploaded before they are throttled.
diff --git a/third_party/rust/glean-core/src/histogram/mod.rs b/third_party/rust/glean-core/src/histogram/mod.rs
index 282b02e0ab..6e2880dffa 100644
--- a/third_party/rust/glean-core/src/histogram/mod.rs
+++ b/third_party/rust/glean-core/src/histogram/mod.rs
@@ -5,7 +5,6 @@
//! A simple histogram implementation for exponential histograms.
use std::collections::HashMap;
-use std::convert::TryFrom;
use serde::{Deserialize, Serialize};
diff --git a/third_party/rust/glean-core/src/internal_pings.rs b/third_party/rust/glean-core/src/internal_pings.rs
index 07c3849006..1cf32feb60 100644
--- a/third_party/rust/glean-core/src/internal_pings.rs
+++ b/third_party/rust/glean-core/src/internal_pings.rs
@@ -19,9 +19,9 @@ pub struct InternalPings {
}
impl InternalPings {
- pub fn new() -> InternalPings {
+ pub fn new(enabled: bool) -> InternalPings {
InternalPings {
- baseline: PingType::new(
+ baseline: PingType::new_internal(
"baseline",
true,
true,
@@ -32,8 +32,9 @@ impl InternalPings {
"dirty_startup".to_string(),
"inactive".to_string(),
],
+ enabled,
),
- metrics: PingType::new(
+ metrics: PingType::new_internal(
"metrics",
true,
false,
@@ -46,8 +47,9 @@ impl InternalPings {
"tomorrow".to_string(),
"upgrade".to_string(),
],
+ enabled,
),
- events: PingType::new(
+ events: PingType::new_internal(
"events",
true,
false,
@@ -58,6 +60,7 @@ impl InternalPings {
"inactive".to_string(),
"max_capacity".to_string(),
],
+ enabled,
),
deletion_request: PingType::new(
"deletion-request",
diff --git a/third_party/rust/glean-core/src/lib.rs b/third_party/rust/glean-core/src/lib.rs
index b7f9d73beb..af68fde264 100644
--- a/third_party/rust/glean-core/src/lib.rs
+++ b/third_party/rust/glean-core/src/lib.rs
@@ -17,7 +17,6 @@
use std::borrow::Cow;
use std::collections::HashMap;
-use std::convert::TryFrom;
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
@@ -25,7 +24,7 @@ use std::thread;
use std::time::Duration;
use crossbeam_channel::unbounded;
-use log::{self, LevelFilter};
+use log::LevelFilter;
use once_cell::sync::{Lazy, OnceCell};
use uuid::Uuid;
@@ -136,6 +135,8 @@ pub struct InternalConfiguration {
/// be noted that this has an underlying StringMetric and so should conform to the limitations that
/// StringMetric places on length, etc.
pub experimentation_id: Option<String>,
+ /// Whether to enable internal pings. Default: true
+ pub enable_internal_pings: bool,
}
/// How to specify the rate at which pings may be uploaded before they are throttled.
diff --git a/third_party/rust/glean-core/src/lib_unit_tests.rs b/third_party/rust/glean-core/src/lib_unit_tests.rs
index cb1e4129d8..14d3b98417 100644
--- a/third_party/rust/glean-core/src/lib_unit_tests.rs
+++ b/third_party/rust/glean-core/src/lib_unit_tests.rs
@@ -6,12 +6,10 @@
// the lib.rs file.
use std::collections::HashSet;
-use std::iter::FromIterator;
use serde_json::json;
use super::*;
-use crate::metrics::{StringMetric, TimeUnit, TimespanMetric, TimingDistributionMetric};
const GLOBAL_APPLICATION_ID: &str = "org.mozilla.glean.test.app";
pub fn new_glean(tempdir: Option<tempfile::TempDir>) -> (Glean, tempfile::TempDir) {
@@ -21,7 +19,7 @@ pub fn new_glean(tempdir: Option<tempfile::TempDir>) -> (Glean, tempfile::TempDi
None => tempfile::tempdir().unwrap(),
};
let tmpname = dir.path().display().to_string();
- let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true);
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, true);
(glean, dir)
}
@@ -41,7 +39,7 @@ fn path_is_constructed_from_data() {
fn experiment_id_and_branch_get_truncated_if_too_long() {
let t = tempfile::tempdir().unwrap();
let name = t.path().display().to_string();
- let glean = Glean::with_options(&name, "org.mozilla.glean.tests", true);
+ let glean = Glean::with_options(&name, "org.mozilla.glean.tests", true, true);
// Generate long strings for the used ids.
let very_long_id = "test-experiment-id".repeat(10);
@@ -82,7 +80,7 @@ fn experiment_id_and_branch_get_truncated_if_too_long() {
fn limits_on_experiments_extras_are_applied_correctly() {
let t = tempfile::tempdir().unwrap();
let name = t.path().display().to_string();
- let glean = Glean::with_options(&name, "org.mozilla.glean.tests", true);
+ let glean = Glean::with_options(&name, "org.mozilla.glean.tests", true, true);
let experiment_id = "test-experiment_id".to_string();
let branch_id = "test-branch-id".to_string();
@@ -138,7 +136,7 @@ fn limits_on_experiments_extras_are_applied_correctly() {
fn experiments_status_is_correctly_toggled() {
let t = tempfile::tempdir().unwrap();
let name = t.path().display().to_string();
- let glean = Glean::with_options(&name, "org.mozilla.glean.tests", true);
+ let glean = Glean::with_options(&name, "org.mozilla.glean.tests", true, true);
// Define the experiment's data.
let experiment_id: String = "test-toggle-experiment".into();
@@ -199,6 +197,7 @@ fn experimentation_id_is_set_correctly() {
rate_limit: None,
enable_event_timestamps: true,
experimentation_id: Some(experimentation_id.to_string()),
+ enable_internal_pings: true,
})
.unwrap();
@@ -219,7 +218,7 @@ fn client_id_and_first_run_date_must_be_regenerated() {
let dir = tempfile::tempdir().unwrap();
let tmpname = dir.path().display().to_string();
{
- let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true);
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, true);
glean.data_store.as_ref().unwrap().clear_all();
@@ -236,7 +235,7 @@ fn client_id_and_first_run_date_must_be_regenerated() {
}
{
- let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true);
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, true);
assert!(glean
.core_metrics
.client_id
@@ -339,7 +338,7 @@ fn client_id_is_managed_correctly_when_toggling_uploading() {
fn client_id_is_set_to_known_value_when_uploading_disabled_at_start() {
let dir = tempfile::tempdir().unwrap();
let tmpname = dir.path().display().to_string();
- let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, false);
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, false, true);
assert_eq!(
*KNOWN_CLIENT_ID,
@@ -355,7 +354,7 @@ fn client_id_is_set_to_known_value_when_uploading_disabled_at_start() {
fn client_id_is_set_to_random_value_when_uploading_enabled_at_start() {
let dir = tempfile::tempdir().unwrap();
let tmpname = dir.path().display().to_string();
- let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true);
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, true);
let current_client_id = glean
.core_metrics
@@ -369,7 +368,7 @@ fn client_id_is_set_to_random_value_when_uploading_enabled_at_start() {
fn enabling_when_already_enabled_is_a_noop() {
let dir = tempfile::tempdir().unwrap();
let tmpname = dir.path().display().to_string();
- let mut glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true);
+ let mut glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, true);
assert!(!glean.set_upload_enabled(true));
}
@@ -378,7 +377,7 @@ fn enabling_when_already_enabled_is_a_noop() {
fn disabling_when_already_disabled_is_a_noop() {
let dir = tempfile::tempdir().unwrap();
let tmpname = dir.path().display().to_string();
- let mut glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, false);
+ let mut glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, false, true);
assert!(!glean.set_upload_enabled(false));
}
@@ -601,14 +600,14 @@ fn test_first_run() {
let dir = tempfile::tempdir().unwrap();
let tmpname = dir.path().display().to_string();
{
- let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true);
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, true);
// Check that this is indeed the first run.
assert!(glean.is_first_run());
}
{
// Other runs must be not marked as "first run".
- let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true);
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, true);
assert!(!glean.is_first_run());
}
}
@@ -618,7 +617,7 @@ fn test_dirty_bit() {
let dir = tempfile::tempdir().unwrap();
let tmpname = dir.path().display().to_string();
{
- let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true);
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, true);
// The dirty flag must not be set the first time Glean runs.
assert!(!glean.is_dirty_flag_set());
@@ -630,7 +629,7 @@ fn test_dirty_bit() {
{
// Check that next time Glean runs, it correctly picks up the "dirty flag".
// It is expected to be 'true'.
- let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true);
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, true);
assert!(glean.is_dirty_flag_set());
// Set the dirty flag to false.
@@ -641,7 +640,7 @@ fn test_dirty_bit() {
{
// Check that next time Glean runs, it correctly picks up the "dirty flag".
// It is expected to be 'false'.
- let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true);
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, true);
assert!(!glean.is_dirty_flag_set());
}
}
@@ -1065,7 +1064,7 @@ fn test_empty_application_id() {
let dir = tempfile::tempdir().unwrap();
let tmpname = dir.path().display().to_string();
- let glean = Glean::with_options(&tmpname, "", true);
+ let glean = Glean::with_options(&tmpname, "", true, true);
// Check that this is indeed the first run.
assert!(glean.is_first_run());
}
@@ -1080,7 +1079,7 @@ fn records_database_file_size() {
let tmpname = dir.path().display().to_string();
// Initialize Glean once to ensure we create the database and did not error.
- let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true);
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, true);
let database_size = &glean.database_metrics.size;
let data = database_size.get_value(&glean, "metrics");
@@ -1089,7 +1088,7 @@ fn records_database_file_size() {
drop(glean);
// Initialize Glean again to record file size.
- let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true);
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, true);
let database_size = &glean.database_metrics.size;
let data = database_size.get_value(&glean, "metrics");
@@ -1161,3 +1160,46 @@ fn test_activity_api() {
// Check that we set everything we needed for the 'inactive' status.
assert!(!glean.is_dirty_flag_set());
}
+
+#[test]
+fn disabled_pings_are_not_submitted() {
+ let _ = env_logger::builder().is_test(true).try_init();
+
+ let dir = tempfile::tempdir().unwrap();
+ let (mut glean, _t) = new_glean(Some(dir));
+
+ let ping = PingType::new_internal("custom-disabled", true, false, true, true, vec![], false);
+ glean.register_ping_type(&ping);
+
+ // We need to store a metric as an empty ping is not stored.
+ let counter = CounterMetric::new(CommonMetricData {
+ name: "counter".into(),
+ category: "local".into(),
+ send_in_pings: vec!["custom-disabled".into()],
+ ..Default::default()
+ });
+ counter.add_sync(&glean, 1);
+
+ assert!(!ping.submit_sync(&glean, None));
+}
+
+#[test]
+fn internal_pings_can_be_disabled() {
+ let _ = env_logger::builder().is_test(true).try_init();
+
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().display().to_string();
+ let glean = Glean::with_options(&tmpname, GLOBAL_APPLICATION_ID, true, false);
+
+ // We need to store a metric as an empty ping is not stored.
+ let counter = CounterMetric::new(CommonMetricData {
+ name: "counter".into(),
+ category: "local".into(),
+ send_in_pings: vec!["baseline".into()],
+ ..Default::default()
+ });
+ counter.add_sync(&glean, 1);
+
+ let submitted = glean.internal_pings.baseline.submit_sync(&glean, None);
+ assert!(!submitted);
+}
diff --git a/third_party/rust/glean-core/src/metrics/event.rs b/third_party/rust/glean-core/src/metrics/event.rs
index c7aefd9cd6..74f90f4867 100644
--- a/third_party/rust/glean-core/src/metrics/event.rs
+++ b/third_party/rust/glean-core/src/metrics/event.rs
@@ -175,9 +175,21 @@ impl EventMetric {
.into()
.unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
- glean
+ let events = glean
.event_storage()
- .test_get_value(&self.meta, queried_ping_name)
+ .test_get_value(&self.meta, queried_ping_name);
+
+ events.map(|mut evts| {
+ for ev in &mut evts {
+ let Some(extra) = &mut ev.extra else { continue };
+ extra.remove("glean_timestamp");
+ if extra.is_empty() {
+ ev.extra = None;
+ }
+ }
+
+ evts
+ })
}
/// **Test-only API (exported for FFI purposes).**
diff --git a/third_party/rust/glean-core/src/metrics/memory_unit.rs b/third_party/rust/glean-core/src/metrics/memory_unit.rs
index ce51b975fa..19006a594e 100644
--- a/third_party/rust/glean-core/src/metrics/memory_unit.rs
+++ b/third_party/rust/glean-core/src/metrics/memory_unit.rs
@@ -2,8 +2,6 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
-use std::convert::TryFrom;
-
use serde::{Deserialize, Serialize};
use crate::error::{Error, ErrorKind};
diff --git a/third_party/rust/glean-core/src/metrics/metrics_enabled_config.rs b/third_party/rust/glean-core/src/metrics/metrics_enabled_config.rs
index 26d0deff31..b36cbc150a 100644
--- a/third_party/rust/glean-core/src/metrics/metrics_enabled_config.rs
+++ b/third_party/rust/glean-core/src/metrics/metrics_enabled_config.rs
@@ -2,7 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
-use std::{collections::HashMap, convert::TryFrom};
+use std::collections::HashMap;
use serde::{Deserialize, Serialize};
diff --git a/third_party/rust/glean-core/src/metrics/ping.rs b/third_party/rust/glean-core/src/metrics/ping.rs
index e60284b1e2..5defab7a71 100644
--- a/third_party/rust/glean-core/src/metrics/ping.rs
+++ b/third_party/rust/glean-core/src/metrics/ping.rs
@@ -31,6 +31,11 @@ struct InnerPing {
pub include_info_sections: bool,
/// The "reason" codes that this ping can send
pub reason_codes: Vec<String>,
+
+ /// Whether this ping is enabled.
+ /// Note: Data for disabled pings is still recorded.
+ /// It will not be cleared out on submit.
+ enabled: bool,
}
impl fmt::Debug for PingType {
@@ -68,6 +73,26 @@ impl PingType {
include_info_sections: bool,
reason_codes: Vec<String>,
) -> Self {
+ Self::new_internal(
+ name,
+ include_client_id,
+ send_if_empty,
+ precise_timestamps,
+ include_info_sections,
+ reason_codes,
+ true,
+ )
+ }
+
+ pub(crate) fn new_internal<A: Into<String>>(
+ name: A,
+ include_client_id: bool,
+ send_if_empty: bool,
+ precise_timestamps: bool,
+ include_info_sections: bool,
+ reason_codes: Vec<String>,
+ enabled: bool,
+ ) -> Self {
let this = Self(Arc::new(InnerPing {
name: name.into(),
include_client_id,
@@ -75,6 +100,7 @@ impl PingType {
precise_timestamps,
include_info_sections,
reason_codes,
+ enabled,
}));
// Register this ping.
@@ -140,6 +166,11 @@ impl PingType {
/// Whether the ping was succesfully assembled and queued.
#[doc(hidden)]
pub fn submit_sync(&self, glean: &Glean, reason: Option<&str>) -> bool {
+ if !self.0.enabled {
+ log::info!("Ping disabled: not submitting '{}' ping.", self.0.name);
+ return false;
+ }
+
if !glean.is_upload_enabled() {
log::info!("Glean disabled: not submitting any pings.");
return false;
diff --git a/third_party/rust/glean-core/src/metrics/string.rs b/third_party/rust/glean-core/src/metrics/string.rs
index 4aa30a8d7e..a56ffab648 100644
--- a/third_party/rust/glean-core/src/metrics/string.rs
+++ b/third_party/rust/glean-core/src/metrics/string.rs
@@ -149,10 +149,8 @@ impl StringMetric {
#[cfg(test)]
mod test {
use super::*;
- use crate::test_get_num_recorded_errors;
use crate::tests::new_glean;
use crate::util::truncate_string_at_boundary;
- use crate::ErrorType;
use crate::Lifetime;
#[test]
diff --git a/third_party/rust/glean-core/src/metrics/text.rs b/third_party/rust/glean-core/src/metrics/text.rs
index baa8e88d75..35f803c728 100644
--- a/third_party/rust/glean-core/src/metrics/text.rs
+++ b/third_party/rust/glean-core/src/metrics/text.rs
@@ -153,10 +153,8 @@ impl TextMetric {
#[cfg(test)]
mod test {
use super::*;
- use crate::test_get_num_recorded_errors;
use crate::tests::new_glean;
use crate::util::truncate_string_at_boundary;
- use crate::ErrorType;
use crate::Lifetime;
#[test]
diff --git a/third_party/rust/glean-core/src/metrics/time_unit.rs b/third_party/rust/glean-core/src/metrics/time_unit.rs
index 6d61a8a242..6c68d5dff0 100644
--- a/third_party/rust/glean-core/src/metrics/time_unit.rs
+++ b/third_party/rust/glean-core/src/metrics/time_unit.rs
@@ -2,7 +2,6 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
-use std::convert::TryFrom;
use std::time::Duration;
use serde::{Deserialize, Serialize};
diff --git a/third_party/rust/glean-core/src/metrics/timespan.rs b/third_party/rust/glean-core/src/metrics/timespan.rs
index ee63fb52f8..d72492a590 100644
--- a/third_party/rust/glean-core/src/metrics/timespan.rs
+++ b/third_party/rust/glean-core/src/metrics/timespan.rs
@@ -2,7 +2,6 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
-use std::convert::TryInto;
use std::sync::{Arc, RwLock};
use std::time::Duration;
diff --git a/third_party/rust/glean-core/src/metrics/timing_distribution.rs b/third_party/rust/glean-core/src/metrics/timing_distribution.rs
index 3293be9518..776935afea 100644
--- a/third_party/rust/glean-core/src/metrics/timing_distribution.rs
+++ b/third_party/rust/glean-core/src/metrics/timing_distribution.rs
@@ -96,7 +96,7 @@ impl TimingDistributionMetric {
Self {
meta: Arc::new(meta.into()),
time_unit,
- next_id: Arc::new(AtomicUsize::new(0)),
+ next_id: Arc::new(AtomicUsize::new(1)),
start_times: Arc::new(Mutex::new(Default::default())),
}
}
diff --git a/third_party/rust/glean-core/src/metrics/url.rs b/third_party/rust/glean-core/src/metrics/url.rs
index 48b3f9e7ae..0fd5712eeb 100644
--- a/third_party/rust/glean-core/src/metrics/url.rs
+++ b/third_party/rust/glean-core/src/metrics/url.rs
@@ -168,9 +168,7 @@ impl UrlMetric {
#[cfg(test)]
mod test {
use super::*;
- use crate::test_get_num_recorded_errors;
use crate::tests::new_glean;
- use crate::ErrorType;
use crate::Lifetime;
#[test]
diff --git a/third_party/rust/glean-core/src/storage/mod.rs b/third_party/rust/glean-core/src/storage/mod.rs
index 67cb9a1552..a4225e21ed 100644
--- a/third_party/rust/glean-core/src/storage/mod.rs
+++ b/third_party/rust/glean-core/src/storage/mod.rs
@@ -235,7 +235,7 @@ mod test {
fn test_experiments_json_serialization() {
let t = tempfile::tempdir().unwrap();
let name = t.path().display().to_string();
- let glean = Glean::with_options(&name, "org.mozilla.glean", true);
+ let glean = Glean::with_options(&name, "org.mozilla.glean", true, true);
let extra: HashMap<String, String> = [("test-key".into(), "test-value".into())]
.iter()
@@ -264,7 +264,7 @@ mod test {
fn test_experiments_json_serialization_empty() {
let t = tempfile::tempdir().unwrap();
let name = t.path().display().to_string();
- let glean = Glean::with_options(&name, "org.mozilla.glean", true);
+ let glean = Glean::with_options(&name, "org.mozilla.glean", true, true);
let metric = ExperimentMetric::new(&glean, "some-experiment".to_string());
diff --git a/third_party/rust/glean-core/src/traits/event.rs b/third_party/rust/glean-core/src/traits/event.rs
index aa84699b30..ba8c0e5609 100644
--- a/third_party/rust/glean-core/src/traits/event.rs
+++ b/third_party/rust/glean-core/src/traits/event.rs
@@ -3,7 +3,6 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
use std::collections::HashMap;
-use std::convert::TryFrom;
use std::hash::Hash;
use crate::event_database::RecordedEvent;
diff --git a/third_party/rust/glean-core/src/upload/directory.rs b/third_party/rust/glean-core/src/upload/directory.rs
index 706550fe6c..91a4d061d1 100644
--- a/third_party/rust/glean-core/src/upload/directory.rs
+++ b/third_party/rust/glean-core/src/upload/directory.rs
@@ -317,8 +317,6 @@ impl PingDirectoryManager {
#[cfg(test)]
mod test {
- use std::fs::File;
-
use super::*;
use crate::metrics::PingType;
use crate::tests::new_glean;
diff --git a/third_party/rust/glean-core/src/upload/mod.rs b/third_party/rust/glean-core/src/upload/mod.rs
index e51a9d9508..f217137f00 100644
--- a/third_party/rust/glean-core/src/upload/mod.rs
+++ b/third_party/rust/glean-core/src/upload/mod.rs
@@ -14,7 +14,6 @@
use std::collections::HashMap;
use std::collections::VecDeque;
-use std::convert::TryInto;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, RwLock, RwLockWriteGuard};
@@ -856,9 +855,6 @@ pub fn chunked_log_info(_path: &str, payload: &str) {
#[cfg(test)]
mod test {
- use std::thread;
- use std::time::Duration;
-
use uuid::Uuid;
use super::*;
diff --git a/third_party/rust/glean-core/src/upload/request.rs b/third_party/rust/glean-core/src/upload/request.rs
index b4ac6eba97..6f3b0c0e5c 100644
--- a/third_party/rust/glean-core/src/upload/request.rs
+++ b/third_party/rust/glean-core/src/upload/request.rs
@@ -8,7 +8,7 @@ use std::collections::HashMap;
use chrono::prelude::{DateTime, Utc};
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
-use serde_json::{self, Value as JsonValue};
+use serde_json::Value as JsonValue;
use std::io::prelude::*;
use crate::error::{ErrorKind, Result};
diff --git a/third_party/rust/glean-core/tests/common/mod.rs b/third_party/rust/glean-core/tests/common/mod.rs
index ebc4f14045..cbc6201d02 100644
--- a/third_party/rust/glean-core/tests/common/mod.rs
+++ b/third_party/rust/glean-core/tests/common/mod.rs
@@ -63,6 +63,7 @@ pub fn new_glean(tempdir: Option<tempfile::TempDir>) -> (Glean, tempfile::TempDi
rate_limit: None,
enable_event_timestamps: false,
experimentation_id: None,
+ enable_internal_pings: true,
};
let glean = Glean::new(cfg).unwrap();
diff --git a/third_party/rust/glean-core/tests/event.rs b/third_party/rust/glean-core/tests/event.rs
index c83e225ca2..48120956d7 100644
--- a/third_party/rust/glean-core/tests/event.rs
+++ b/third_party/rust/glean-core/tests/event.rs
@@ -481,6 +481,7 @@ fn with_event_timestamps() {
rate_limit: None,
enable_event_timestamps: true,
experimentation_id: None, // Enabling event timestamps
+ enable_internal_pings: true,
};
let glean = Glean::new(cfg).unwrap();
diff --git a/third_party/rust/glean-core/tests/ping_maker.rs b/third_party/rust/glean-core/tests/ping_maker.rs
index bc3aac6311..f716dc4692 100644
--- a/third_party/rust/glean-core/tests/ping_maker.rs
+++ b/third_party/rust/glean-core/tests/ping_maker.rs
@@ -91,6 +91,7 @@ fn test_metrics_must_report_experimentation_id() {
rate_limit: None,
enable_event_timestamps: true,
experimentation_id: Some("test-experimentation-id".to_string()),
+ enable_internal_pings: true,
})
.unwrap();
let ping_maker = PingMaker::new();
@@ -143,6 +144,7 @@ fn experimentation_id_is_removed_if_send_if_empty_is_false() {
rate_limit: None,
enable_event_timestamps: true,
experimentation_id: Some("test-experimentation-id".to_string()),
+ enable_internal_pings: true,
})
.unwrap();
let ping_maker = PingMaker::new();
diff --git a/third_party/rust/glean/.cargo-checksum.json b/third_party/rust/glean/.cargo-checksum.json
index f624e73c99..7cb5c7390c 100644
--- a/third_party/rust/glean/.cargo-checksum.json
+++ b/third_party/rust/glean/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"29b8551de6fff2f0fa3a821eb933f71a2a326b3ce3d37c25bcef3001f9146dfb","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"5627cc81e6187ab6c2b4dff061af16d559edcab64ba786bac39daa69c703c595","src/common_test.rs":"454df3d99eef045270e813946f921f56c39b16c18a5fadedc32829c3d44129cf","src/configuration.rs":"82b3a7933d913e1e2a4f328a76621db2d2e618d209d9785086d64c5c78c2a2d6","src/core_metrics.rs":"fef8fb4e5fa57c179836c6eb2cf59278fe3b8b036dbe57b0ff02971b4acd822f","src/lib.rs":"aa9c81fc6dc19ca1cb4bede25d554377a5d717fb3b246967edb1be12a395ce61","src/net/http_uploader.rs":"01ad5bd91384411a12c74434cd1c5cd585078cb34faba4615c70bdb669a9bccb","src/net/mod.rs":"f47b96bb878f1a6c771cedbaeaeefb270bc87fb1d1bbbed1b282dddca16216ed","src/private/event.rs":"d7c70c02648584c19c73af89e5180d3c6153c911f2c6830f7d1599b18d6150eb","src/private/mod.rs":"3565eb569d2b96f938f130abe0fc3ee3f55e7e03fd6501e309d3ef6af72ef6ee","src/private/object.rs":"3f70363a196aea46cc163af025a53e48c117c6208babc4bce772bb4c337cced8","src/private/ping.rs":"a6262a3453c77cbf30766c19b535a1bf66a37b2a316e8f87baee03025255c33e","src/system.rs":"6eae5b41c15eba9cad6dbd116abe3519ee3e1fe034e79bdd692b029829a8c384","src/test.rs":"6388b9e8bf96e0fb56ad71b7a5b5630d209ae62f1a65c62e878cbc1757ddd585","tests/common/mod.rs":"08fb9483d9b6ed9fe873b4395245166ae8a15263be750c7a8e298c41d9604745","tests/init_fails.rs":"906bbf0faa613976623e0cf782bd86545b49d76afaab182af7634690b747ebf7","tests/never_init.rs":"19bad996e22f7d6958cc1a650528530aa7d1aeb4a8ab42229a90bbc0315c8ed1","tests/no_time_to_init.rs":"06c81148c27d383cb708c0c80a2e806024c9955337d7adfba8c53aaeade9be67","tests/overflowing_preinit.rs":"7ad4b2274dd9240b53430859a4eb1d2597cf508a5a678333f3d3abbadd2ed4a7","tests/persist_ping_lifetime.rs":"81415dc1d74743f02269f0d0dfa524003147056853f080276972e64a0b761d3c","tests/persist_ping_lifetime_nopanic.rs":"18379d3ffbf4a2c8c684c04ff7a0660b86dfbbb447db2d24dfed6073cb7ddf8f","tests/schema.rs":"9d24028cab4dc60fe3c4d7a0bafbff0815cbc0249fa3e23625d42c3b4fa71734","tests/simple.rs":"1b8b227249ae9d3cc281db07ed779bc75252c7849b1c48b4ac3d765228d65b20","tests/test-shutdown-blocking.sh":"9b16a01c190c7062474dd92182298a3d9a27928c8fa990340fdd798e6cdb7ab2","tests/test-thread-crashing.sh":"ff1bc8e5d7e4ba3a10d0d38bef222db8bfba469e7d30e45b1053d177a4084f09","tests/upload_timing.rs":"3024b7999a0c23f2c3d7e59725b5455522e4e9fdf63e3265b93fea4cec18725f"},"package":"f58388f10d013e2d12bb58e6e76983ede120789956fe827913a3d2560c66d44d"} \ No newline at end of file
+{"files":{"Cargo.toml":"af0535de86b60e3e08cadcdb9e61ce4a699c168608d7e9e2ebb92d949e7f31ef","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"5627cc81e6187ab6c2b4dff061af16d559edcab64ba786bac39daa69c703c595","src/common_test.rs":"de47b53dcca37985c0a2b8c02daecbf32309aa54f5a4dd9290719c2c1fd0fa55","src/configuration.rs":"27075b12236021c54d0c99427bcbd417933ca02545275604d3c13f32ca25af13","src/core_metrics.rs":"fef8fb4e5fa57c179836c6eb2cf59278fe3b8b036dbe57b0ff02971b4acd822f","src/lib.rs":"d4010f265de330081467673df05bbd45efbdfeef28823f7dc11a903b11fb8976","src/net/http_uploader.rs":"01ad5bd91384411a12c74434cd1c5cd585078cb34faba4615c70bdb669a9bccb","src/net/mod.rs":"f47b96bb878f1a6c771cedbaeaeefb270bc87fb1d1bbbed1b282dddca16216ed","src/private/event.rs":"d7c70c02648584c19c73af89e5180d3c6153c911f2c6830f7d1599b18d6150eb","src/private/mod.rs":"3565eb569d2b96f938f130abe0fc3ee3f55e7e03fd6501e309d3ef6af72ef6ee","src/private/object.rs":"3f70363a196aea46cc163af025a53e48c117c6208babc4bce772bb4c337cced8","src/private/ping.rs":"a6262a3453c77cbf30766c19b535a1bf66a37b2a316e8f87baee03025255c33e","src/system.rs":"6eae5b41c15eba9cad6dbd116abe3519ee3e1fe034e79bdd692b029829a8c384","src/test.rs":"6388b9e8bf96e0fb56ad71b7a5b5630d209ae62f1a65c62e878cbc1757ddd585","tests/common/mod.rs":"08fb9483d9b6ed9fe873b4395245166ae8a15263be750c7a8e298c41d9604745","tests/init_fails.rs":"906bbf0faa613976623e0cf782bd86545b49d76afaab182af7634690b747ebf7","tests/never_init.rs":"19bad996e22f7d6958cc1a650528530aa7d1aeb4a8ab42229a90bbc0315c8ed1","tests/no_time_to_init.rs":"06c81148c27d383cb708c0c80a2e806024c9955337d7adfba8c53aaeade9be67","tests/overflowing_preinit.rs":"7ad4b2274dd9240b53430859a4eb1d2597cf508a5a678333f3d3abbadd2ed4a7","tests/persist_ping_lifetime.rs":"81415dc1d74743f02269f0d0dfa524003147056853f080276972e64a0b761d3c","tests/persist_ping_lifetime_nopanic.rs":"18379d3ffbf4a2c8c684c04ff7a0660b86dfbbb447db2d24dfed6073cb7ddf8f","tests/schema.rs":"9615eded31a2582c8f04c729d551c0c81a57029ba62a19184221c2e1cd39baf0","tests/simple.rs":"1b8b227249ae9d3cc281db07ed779bc75252c7849b1c48b4ac3d765228d65b20","tests/test-shutdown-blocking.sh":"9b16a01c190c7062474dd92182298a3d9a27928c8fa990340fdd798e6cdb7ab2","tests/test-thread-crashing.sh":"ff1bc8e5d7e4ba3a10d0d38bef222db8bfba469e7d30e45b1053d177a4084f09","tests/upload_timing.rs":"3024b7999a0c23f2c3d7e59725b5455522e4e9fdf63e3265b93fea4cec18725f"},"package":"0ceede8fb9c90ba1b77fb8290d3ae7b62bfcb422ad1d6e46bae1c8af3f22f12d"} \ No newline at end of file
diff --git a/third_party/rust/glean/Cargo.toml b/third_party/rust/glean/Cargo.toml
index bc25a08940..edcc84d5d6 100644
--- a/third_party/rust/glean/Cargo.toml
+++ b/third_party/rust/glean/Cargo.toml
@@ -13,7 +13,7 @@
edition = "2021"
rust-version = "1.66"
name = "glean"
-version = "58.1.0"
+version = "59.0.0"
authors = [
"Jan-Erik Rediger <jrediger@mozilla.com>",
"The Glean Team <glean-team@mozilla.com>",
@@ -35,7 +35,7 @@ license = "MPL-2.0"
repository = "https://github.com/mozilla/glean"
[dependencies.glean-core]
-version = "58.1.0"
+version = "59.0.0"
[dependencies.inherent]
version = "1"
diff --git a/third_party/rust/glean/src/common_test.rs b/third_party/rust/glean/src/common_test.rs
index e3c80da5f2..fdb7cfadbf 100644
--- a/third_party/rust/glean/src/common_test.rs
+++ b/third_party/rust/glean/src/common_test.rs
@@ -42,7 +42,6 @@ pub(crate) fn new_glean(
Some(c) => c,
None => ConfigurationBuilder::new(true, tmpname, GLOBAL_APPLICATION_ID)
.with_server_endpoint("invalid-test-host")
- .with_event_timestamps(false)
.build(),
};
diff --git a/third_party/rust/glean/src/configuration.rs b/third_party/rust/glean/src/configuration.rs
index ca0a39c3f1..462bedaa7a 100644
--- a/third_party/rust/glean/src/configuration.rs
+++ b/third_party/rust/glean/src/configuration.rs
@@ -46,6 +46,8 @@ pub struct Configuration {
/// be noted that this has an underlying StringMetric and so should conform to the limitations that
/// StringMetric places on length, etc.
pub experimentation_id: Option<String>,
+ /// Whether to enable internal pings. Default: true
+ pub enable_internal_pings: bool,
}
/// Configuration builder.
@@ -92,6 +94,8 @@ pub struct Builder {
/// be noted that this has an underlying StringMetric and so should conform to the limitations that
/// StringMetric places on length, etc.
pub experimentation_id: Option<String>,
+ /// Whether to enable internal pings. Default: true
+ pub enable_internal_pings: bool,
}
impl Builder {
@@ -115,6 +119,7 @@ impl Builder {
rate_limit: None,
enable_event_timestamps: true,
experimentation_id: None,
+ enable_internal_pings: true,
}
}
@@ -134,6 +139,7 @@ impl Builder {
rate_limit: self.rate_limit,
enable_event_timestamps: self.enable_event_timestamps,
experimentation_id: self.experimentation_id,
+ enable_internal_pings: self.enable_internal_pings,
}
}
@@ -184,4 +190,10 @@ impl Builder {
self.experimentation_id = Some(value);
self
}
+
+ /// Set whether to enable internal pings.
+ pub fn with_internal_pings(mut self, value: bool) -> Self {
+ self.enable_internal_pings = value;
+ self
+ }
}
diff --git a/third_party/rust/glean/src/lib.rs b/third_party/rust/glean/src/lib.rs
index 5c5b945a95..81899d42ee 100644
--- a/third_party/rust/glean/src/lib.rs
+++ b/third_party/rust/glean/src/lib.rs
@@ -122,6 +122,7 @@ fn initialize_internal(cfg: Configuration, client_info: ClientInfoMetrics) -> Op
rate_limit: cfg.rate_limit,
enable_event_timestamps: cfg.enable_event_timestamps,
experimentation_id: cfg.experimentation_id,
+ enable_internal_pings: cfg.enable_internal_pings,
};
glean_core::glean_initialize(core_cfg, client_info.into(), callbacks);
diff --git a/third_party/rust/glean/tests/schema.rs b/third_party/rust/glean/tests/schema.rs
index 01a2108b3c..59132cd82a 100644
--- a/third_party/rust/glean/tests/schema.rs
+++ b/third_party/rust/glean/tests/schema.rs
@@ -6,8 +6,7 @@ use std::collections::HashMap;
use std::io::Read;
use flate2::read::GzDecoder;
-use glean_core::TextMetric;
-use jsonschema_valid::{self, schemas::Draft};
+use jsonschema_valid::schemas::Draft;
use serde_json::Value;
use glean::net::{PingUploadRequest, UploadResult};
diff --git a/third_party/rust/goblin/.cargo-checksum.json b/third_party/rust/goblin/.cargo-checksum.json
index 4eff08bdd6..619a6b6211 100644
--- a/third_party/rust/goblin/.cargo-checksum.json
+++ b/third_party/rust/goblin/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"CHANGELOG.md":"ade9f25d4bd1545f2ff2661d6a1301fe228cf2551a9cb27fcaa17c8119b73c8b","Cargo.toml":"09b271ef4ee3491cb1f6309cef8b60471b960c057c6e57fc90ed579adcc57453","LICENSE":"036bf6b6d6fd6dd1abda2ff6cdb672a63bdf32c468048720072910f2268a965f","README.md":"c09b08f3d5e7e33c4a8fd647708d313ee2ba98b165a1d077fb90f280dcb4da31","src/archive/mod.rs":"ae739638d7267011bedf51712516d3485171d8f2df2ab6746a0d942d86efd6a6","src/elf/compression_header.rs":"2eb5fdda9177c1c897310d86714967de019b39c6e23b1f3a890dd3a659be0acc","src/elf/constants_header.rs":"f2ede290ecacf60b1719e9994aa45612bf0f7baf63806a293d4530a674e5861a","src/elf/constants_relocation.rs":"a010071cd2a25ab71e0c7181eb1d9f417daa2d1ec25a09c74bd12ad944892225","src/elf/dynamic.rs":"c26e75311f2da9e34dc4c0a2120dfcc20df88a41d67c52b9bf703258de018fd8","src/elf/gnu_hash.rs":"7a9fcaf6cb38167d20527364bdf9bc2379c44dede5d7666275a1eb20dc665179","src/elf/header.rs":"3391a1fa9b8e3923f7ce74caff0668d8ddb5b34767bf3da309ff497fd81c34c7","src/elf/mod.rs":"2ee0faa0917deb5e90ca60e9c852434745a4c7f553e609e9603a57b7d55b739f","src/elf/note.rs":"bf5e45e2697f7700d5adbb52f890ea4c63b70b7077ca0e7c751420bb92923529","src/elf/program_header.rs":"4c322eb124c4e2bdeec4915067d2bb11fe9e7fba1811dc351a3f7581df121da0","src/elf/reloc.rs":"8b29162055b2846342b49e5e9e0a1482786fb92b4787bb9eb1c6d04f38b94e87","src/elf/section_header.rs":"f55f4d263f618bd1dec76ff0483f3b2dc3791c8e5c5c2b6ff296a5bc26001666","src/elf/sym.rs":"045c01107f4e100d6827cb819b82a28ea10c0d9bc00a1cdddb04a0865f1162ec","src/elf/symver.rs":"3f899201f64a702653d44288f860003e7acd75e38111d36479af823ed92b1341","src/error.rs":"af620a5692bca070dc727d49cdbb566a533bfb97724ca68932ae7fec7dc05cf6","src/lib.rs":"465eb53b540dfd142d204984ee7280130542d7f83d6c53691299d773f7394faf","src/mach/bind_opcodes.rs":"1dcacfb853d05c2c7e6dbb4509ee705a8ea645db0d334991a2293fef92eee851","src/mach/constants.rs":"c2a2381a0b9c3047d37582465e8965d995dca414d0da21fb7bcc6b8334e49eb6","src/mach/exports.rs":"d22122744673a3ce5f54b2b4b20bfa47d17378e64d3dda2858dd13add74ed3dc","src/mach/fat.rs":"45a3228aaa1ab8b77f322dd4924b7383f1357e226ffc079846d67c0268389ea7","src/mach/header.rs":"006619188f51fa43051dc04aa4b2ecd5f89136cf05cb6a7b23a228228008e6ae","src/mach/imports.rs":"2153269dfff32e23d72f76a82d658be06bd79b7e35d79b7e17115e4eb24b13d5","src/mach/load_command.rs":"0a689e774ae96212666165909c026037f22a3c4e3645250b9bae60c957d50ca4","src/mach/mod.rs":"53ad219fd2265a5689ab38d5031722268eab6bbb649c75756e74295df4b611b7","src/mach/relocation.rs":"11b0b76ed7d997c87e396100515f931fe84473c228bed0e980fbab311530070a","src/mach/segment.rs":"0dc29bf42b25f60c7258bc8b757f6a862e846582dd6d2e70737933ad6334a0e4","src/mach/symbols.rs":"d2505fa8d65ea267abfcb6a9fc4d1acd47d5605aa6775935757e2fa8e92af507","src/pe/authenticode.rs":"c3df9266c4f0a865e0da4b10fa1494eca083953fc4ded0b707b547a7d4ef296a","src/pe/certificate_table.rs":"75ab5dce6bc0c28d3687a5c119c0fa0d00e4796c8959a32d9d208f2369273c50","src/pe/characteristic.rs":"6f810a6e5646b922cf7e3ca6d314677a4e1e1ad5695278c2b1b527a05f4299f3","src/pe/data_directories.rs":"d4e156f0c5b509860ceb3c7d42e1621e6c2143b90fc412806b3cefab1acc577a","src/pe/debug.rs":"3811c616a9b6d6b54e15348bb369b794bb89532e04fe19eca91b745d7c51a553","src/pe/exception.rs":"de2c9c07812ecd315c8400fc8fdcadc6a44d7a8be96e69a3f4ccf14ef8cf8426","src/pe/export.rs":"c98f5ce0b1b18bb87f06d1d41dbf70f443d65ecb1624cb23a1ef6c5f93a892e1","src/pe/header.rs":"f02a4beddc00ddd6624df7defc42991ceb507360b5aa1003cf33332c1c89a743","src/pe/import.rs":"855276e46c01ccd7631104e4d1265592e36c9468aadcacc937a40c29d94aabe3","src/pe/mod.rs":"ec958ee9a717672dec7b56d9d7d33e444c37eb781f299a920a60eb7fa39ef7a1","src/pe/optional_header.rs":"4fd94187fb343756817f23ccc58ec035a1b462b69457c706d9e2f11225d0cb1c","src/pe/options.rs":"b38f4e87f13ae381712621786f89e931452b2b4857a7bb6f140c4c21a63aa652","src/pe/relocation.rs":"c479b80bb1d6910f2168505dda4f2d8925b7edc34bed4e25d069546f88f52bb3","src/pe/section_table.rs":"d7144c7be3242d7aa653d22dca1cf15f7110f79a946a15cbe6ecf531e0cacb19","src/pe/symbol.rs":"9a65226c93c4499e21d094ceb838d58db706951580a1c43dfb36b95dbaff70f0","src/pe/utils.rs":"88e1cd9114c5d4ad58a09c39b312689de20ddd7382654ec660b00424f5c3129c","src/strtab.rs":"6d122084cf5d5244b2bd734b1d6d2c018116cc537ffc0c81d042d5b8815d7782","tests/bins/elf/gnu_hash/README.md":"52581e2ea7067a55bd8aedf4079200fb76448573ae9ffef7d886b9556e980db9"},"package":"f27c1b4369c2cd341b5de549380158b105a04c331be5db9110eef7b6d2742134"} \ No newline at end of file
+{"files":{"CHANGELOG.md":"2d45bc2d0db50fd4416e2123f8b98c7288935b3be7985bdd115ecbd236acea41","Cargo.toml":"0d8dade295950e9f63574e7a74390ddec56c039cb44d2507df7e6ff832b49a0d","LICENSE":"036bf6b6d6fd6dd1abda2ff6cdb672a63bdf32c468048720072910f2268a965f","README.md":"302466b411dc5bc705cdf563b928c14755342ab6f2dff371be064446fa0aa0a9","src/archive/mod.rs":"ae739638d7267011bedf51712516d3485171d8f2df2ab6746a0d942d86efd6a6","src/elf/compression_header.rs":"2eb5fdda9177c1c897310d86714967de019b39c6e23b1f3a890dd3a659be0acc","src/elf/constants_header.rs":"f2ede290ecacf60b1719e9994aa45612bf0f7baf63806a293d4530a674e5861a","src/elf/constants_relocation.rs":"a010071cd2a25ab71e0c7181eb1d9f417daa2d1ec25a09c74bd12ad944892225","src/elf/dynamic.rs":"c26e75311f2da9e34dc4c0a2120dfcc20df88a41d67c52b9bf703258de018fd8","src/elf/gnu_hash.rs":"7a9fcaf6cb38167d20527364bdf9bc2379c44dede5d7666275a1eb20dc665179","src/elf/header.rs":"3391a1fa9b8e3923f7ce74caff0668d8ddb5b34767bf3da309ff497fd81c34c7","src/elf/mod.rs":"2ee0faa0917deb5e90ca60e9c852434745a4c7f553e609e9603a57b7d55b739f","src/elf/note.rs":"bf5e45e2697f7700d5adbb52f890ea4c63b70b7077ca0e7c751420bb92923529","src/elf/program_header.rs":"4c322eb124c4e2bdeec4915067d2bb11fe9e7fba1811dc351a3f7581df121da0","src/elf/reloc.rs":"a5d21f9d1ddae8e730e852fcaf1cd2dd194e35fbac8f86fb8fd9033a03bdc66d","src/elf/section_header.rs":"f55f4d263f618bd1dec76ff0483f3b2dc3791c8e5c5c2b6ff296a5bc26001666","src/elf/sym.rs":"045c01107f4e100d6827cb819b82a28ea10c0d9bc00a1cdddb04a0865f1162ec","src/elf/symver.rs":"3f899201f64a702653d44288f860003e7acd75e38111d36479af823ed92b1341","src/error.rs":"a1bb56d82db52ac627e55b163f489f06a78c939a8ccfdec210b4f726d6ed6e9d","src/lib.rs":"f29832bdf7d7f7d9e34f65704afea2710d578df60cc171dd179b5ce889faaf12","src/mach/bind_opcodes.rs":"1dcacfb853d05c2c7e6dbb4509ee705a8ea645db0d334991a2293fef92eee851","src/mach/constants.rs":"c2a2381a0b9c3047d37582465e8965d995dca414d0da21fb7bcc6b8334e49eb6","src/mach/exports.rs":"d22122744673a3ce5f54b2b4b20bfa47d17378e64d3dda2858dd13add74ed3dc","src/mach/fat.rs":"45a3228aaa1ab8b77f322dd4924b7383f1357e226ffc079846d67c0268389ea7","src/mach/header.rs":"006619188f51fa43051dc04aa4b2ecd5f89136cf05cb6a7b23a228228008e6ae","src/mach/imports.rs":"2153269dfff32e23d72f76a82d658be06bd79b7e35d79b7e17115e4eb24b13d5","src/mach/load_command.rs":"42e6f0973092185db233230e71e9312bbac7c2e1090bb6d713804020319dfa33","src/mach/mod.rs":"53ad219fd2265a5689ab38d5031722268eab6bbb649c75756e74295df4b611b7","src/mach/relocation.rs":"11b0b76ed7d997c87e396100515f931fe84473c228bed0e980fbab311530070a","src/mach/segment.rs":"0dc29bf42b25f60c7258bc8b757f6a862e846582dd6d2e70737933ad6334a0e4","src/mach/symbols.rs":"d2505fa8d65ea267abfcb6a9fc4d1acd47d5605aa6775935757e2fa8e92af507","src/pe/authenticode.rs":"ad9c77e42392b49114cf8ce2839111f3231dcfe21cbb8e402ee14e568f5ae657","src/pe/certificate_table.rs":"f6c31ba518d9fc4b6e12d2f24d6c9d58b21b341a1f189cbcf2aae0ae51304ad3","src/pe/characteristic.rs":"6f810a6e5646b922cf7e3ca6d314677a4e1e1ad5695278c2b1b527a05f4299f3","src/pe/data_directories.rs":"d0352ccc03e0ab2935235e91b391cc55828406087f026f90ec11ca5906fd8c8c","src/pe/debug.rs":"3811c616a9b6d6b54e15348bb369b794bb89532e04fe19eca91b745d7c51a553","src/pe/exception.rs":"de2c9c07812ecd315c8400fc8fdcadc6a44d7a8be96e69a3f4ccf14ef8cf8426","src/pe/export.rs":"c98f5ce0b1b18bb87f06d1d41dbf70f443d65ecb1624cb23a1ef6c5f93a892e1","src/pe/header.rs":"879c2ddc8318ab37b4577ac34241fa039d106e0e530dab07edfc9b4e13b08356","src/pe/import.rs":"855276e46c01ccd7631104e4d1265592e36c9468aadcacc937a40c29d94aabe3","src/pe/mod.rs":"ffaeca313ea2fb31c41eb0ede0ef28fede2276b0bb7d81dfc08b4ead6289600d","src/pe/optional_header.rs":"4048151649a7fe3f8f2d7bb67e784bae889eeb1651bf924f9fbe92400b809217","src/pe/options.rs":"457877197f768c331437297d787dc718b1053b813e3a1dd9b968133fb1540d44","src/pe/relocation.rs":"c479b80bb1d6910f2168505dda4f2d8925b7edc34bed4e25d069546f88f52bb3","src/pe/section_table.rs":"e4b1a2f78c2336aaa0355b5ef102dbe29138c4fa1ba29ed3f379aad1fc64bdff","src/pe/symbol.rs":"1a5fb5bec5727752a6506682ed2ab57829ea810f21f951932a0107861ec0e092","src/pe/utils.rs":"e6da9979ba5f2ae7d1274eef8230cdc4dd90c90a79c7bb9438f8b8ff0aef74be","src/strtab.rs":"dcbd0592c7f032980d112a5f752c175fe8dd257a948892e1f060d25ab52328f5","tests/bins/elf/gnu_hash/README.md":"52581e2ea7067a55bd8aedf4079200fb76448573ae9ffef7d886b9556e980db9"},"package":"bb07a4ffed2093b118a525b1d8f5204ae274faed5604537caf7135d0f18d9887"} \ No newline at end of file
diff --git a/third_party/rust/goblin/CHANGELOG.md b/third_party/rust/goblin/CHANGELOG.md
index 393105f351..71c79c7b49 100644
--- a/third_party/rust/goblin/CHANGELOG.md
+++ b/third_party/rust/goblin/CHANGELOG.md
@@ -3,9 +3,32 @@ All notable changes to this project will be documented in this file.
Before 1.0, this project does not adhere to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
-Goblin is now 0.7, which means we will try our best to ease breaking changes. Tracking issue is here: https://github.com/m4b/goblin/issues/97
+Goblin is now 0.8, which means we will try our best to ease breaking changes. Tracking issue is here: https://github.com/m4b/goblin/issues/97
-## [0.7.0] - unreleased
+## [0.8.0] - 2023-12-31 - Happy New Years!
+### Breaking
+msrv: bumped to 1.63.0 since scroll bumped as well
+pe: new field added to parse options: https://github.com/m4b/goblin/pull/377
+pe: attribute certs now non-exhaustive: https://github.com/m4b/goblin/pull/378
+goblin: hint and object enum is now non-exhaustive
+pe: write support introduced some breaking changes, e.g., data directories array adds a tuple of usize and data directory,
+ DosHeader has all the fields filled out, Header struct has a dos_stub field added,
+ symbols and strings fields is made optional in Coff struct, see: https://github.com/m4b/goblin/pull/361
+### Fixed
+elf: fix documentation, thanks @crzysdrs: https://github.com/m4b/goblin/pull/374
+pe: attribute certificates non-exhaustive, thanks @RaitoBezarius: https://github.com/m4b/goblin/pull/378
+pe: fix authenticode parsing, thanks @baloo: https://github.com/m4b/goblin/pull/383
+### Added
+strtab: len method added to return number of bytes of the strtab
+pe: absolutely epic pe write support PR, thanks @RaitoBezarius and @Baloo: https://github.com/m4b/goblin/pull/361
+pe: add coff object file support, thanks @vadimcn, https://github.com/m4b/goblin/pull/379
+pe: allow toggling parsing of attribute certs, thanks @suttonbradley: https://github.com/m4b/goblin/pull/377
+mach: add new mach-o constants, thanks @keith: https://github.com/m4b/goblin/pull/372
+
+## [0.7.1] - 2023-6-11
+### MSRV bump from log
+
+## [0.7.0] - 2023-6-11
### Breaking
mach: Implement `LC_NOTE`, (breakage=load commands are marked non-exhaustive), thanks @messense: https://github.com/m4b/goblin/pull/342
### Fixed
diff --git a/third_party/rust/goblin/Cargo.toml b/third_party/rust/goblin/Cargo.toml
index 7c586a3e0d..f2d6acc389 100644
--- a/third_party/rust/goblin/Cargo.toml
+++ b/third_party/rust/goblin/Cargo.toml
@@ -11,9 +11,9 @@
[package]
edition = "2021"
-rust-version = "1.60.0"
+rust-version = "1.63.0"
name = "goblin"
-version = "0.7.1"
+version = "0.8.0"
authors = [
"m4b <m4b.github.io@gmail.com>",
"seu <seu@panopticon.re>",
@@ -44,6 +44,7 @@ categories = [
]
license = "MIT"
repository = "https://github.com/m4b/goblin"
+resolver = "2"
[dependencies.log]
version = "0.4"
@@ -54,9 +55,12 @@ default-features = false
version = "0.2.3"
[dependencies.scroll]
-version = "0.11"
+version = "0.12"
default_features = false
+[dev-dependencies.stderrlog]
+version = "0.5.4"
+
[features]
alloc = [
"scroll/derive",
diff --git a/third_party/rust/goblin/README.md b/third_party/rust/goblin/README.md
index 76c2260457..ee9462d2ff 100644
--- a/third_party/rust/goblin/README.md
+++ b/third_party/rust/goblin/README.md
@@ -20,13 +20,13 @@ https://docs.rs/goblin/
### Usage
-Goblin requires `rustc` 1.60.0 (Rust 2021 edition).
+Goblin requires `rustc` 1.63.0 (Rust 2021 edition).
Add to your `Cargo.toml`
```toml
[dependencies]
-goblin = "0.7"
+goblin = "0.8"
```
### Features
@@ -190,6 +190,7 @@ In lexicographic order:
[@baloo]: https://github.com/baloo
[@burjui]: https://github.com/burjui
[@connorkuehl]: https://github.com/connorkuehl
+[@crzysdrs]: https://github.com/crzysdrs
[@dancrossnyc]: https://github.com/dancrossnyc
[@dureuill]: https://github.com/dureuill
[@Evian-Zhang]: https://github.com/Evian-Zhang
@@ -238,6 +239,7 @@ In lexicographic order:
[@sanxiyn]: https://github.com/sanxiyn
[@skdltmxn]: https://github.com/skdltmxn
[@sollyucko]: https://github.com/sollyucko
+[@suttonbradley]: https://github.com/suttonbradley
[@Swatinem]: https://github.com/Swatinem
[@SweetVishnya]: https://github.com/SweetVishnya
[@SquareMan]: https://github.com/SquareMan
@@ -248,6 +250,7 @@ In lexicographic order:
[@Tiwalun]: https://github.com/Tiwalun
[@track-5]: https://github.com/track-5
[@tux3]: https://github.com/tux3
+[@vadimcn]: https://github.com/vadimcn
[@wickerwacka]: https://github.com/wickerwaka
[@willglynn]: https://github.com/willglynn
[@woodruffw]: https://github.com/woodruffw
diff --git a/third_party/rust/goblin/src/elf/reloc.rs b/third_party/rust/goblin/src/elf/reloc.rs
index eeb3e1a2ac..d8a1df9d6a 100644
--- a/third_party/rust/goblin/src/elf/reloc.rs
+++ b/third_party/rust/goblin/src/elf/reloc.rs
@@ -51,7 +51,7 @@
//! | `R_X86_64_GOTPC32` | 26 | 32 | GOT + A - P |
//! | `R_X86_64_SIZE32` | 32 | 32 | Z + A |
//! | `R_X86_64_SIZE64` | 33 | 64 | Z + A |
-//! | `R_X86_64_GOTPC32_TLSDESC` 34 | 32 | |
+//! | `R_X86_64_GOTPC32_TLSDESC`| 34 | 32 | |
//! | `R_X86_64_TLSDESC_CALL` | 35 | NONE | |
//! | `R_X86_64_TLSDESC` | 36 | 64 × 2 | |
//! | `R_X86_64_IRELATIVE` | 37 | 64 | indirect (B + A) |
diff --git a/third_party/rust/goblin/src/error.rs b/third_party/rust/goblin/src/error.rs
index e2dd517e15..0ec4e1e0c9 100644
--- a/third_party/rust/goblin/src/error.rs
+++ b/third_party/rust/goblin/src/error.rs
@@ -3,6 +3,7 @@
use alloc::string::String;
use core::fmt;
+use core::num::TryFromIntError;
use core::result;
#[cfg(feature = "std")]
use std::{error, io};
@@ -42,6 +43,12 @@ impl From<io::Error> for Error {
}
}
+impl From<TryFromIntError> for Error {
+ fn from(err: TryFromIntError) -> Error {
+ Error::Malformed(format!("Integer do not fit: {err}"))
+ }
+}
+
impl From<scroll::Error> for Error {
fn from(err: scroll::Error) -> Error {
Error::Scroll(err)
diff --git a/third_party/rust/goblin/src/lib.rs b/third_party/rust/goblin/src/lib.rs
index 25ab841322..ec77f93d5c 100644
--- a/third_party/rust/goblin/src/lib.rs
+++ b/third_party/rust/goblin/src/lib.rs
@@ -42,13 +42,17 @@
//! Object::PE(pe) => {
//! println!("pe: {:#?}", &pe);
//! },
+//! Object::COFF(coff) => {
+//! println!("coff: {:#?}", &coff);
+//! },
//! Object::Mach(mach) => {
//! println!("mach: {:#?}", &mach);
//! },
//! Object::Archive(archive) => {
//! println!("archive: {:#?}", &archive);
//! },
-//! Object::Unknown(magic) => { println!("unknown magic: {:#x}", magic) }
+//! Object::Unknown(magic) => { println!("unknown magic: {:#x}", magic) },
+//! _ => { }
//! }
//! }
//! }
@@ -218,12 +222,14 @@ pub struct HintData {
}
#[derive(Debug)]
+#[non_exhaustive]
/// A hint at the underlying binary format for 16 bytes of arbitrary data
pub enum Hint {
Elf(HintData),
Mach(HintData),
MachFat(usize),
PE,
+ COFF,
Archive,
Unknown(u64),
}
@@ -253,10 +259,14 @@ if_everything! {
Ok(Hint::Elf(HintData { is_lsb, is_64 }))
} else if &bytes[0..archive::SIZEOF_MAGIC] == archive::MAGIC {
Ok(Hint::Archive)
- } else if (&bytes[0..2]).pread_with::<u16>(0, LE)? == pe::header::DOS_MAGIC {
- Ok(Hint::PE)
} else {
- mach::peek_bytes(bytes)
+ match *&bytes[0..2].pread_with::<u16>(0, LE)? {
+ pe::header::DOS_MAGIC => Ok(Hint::PE),
+ pe::header::COFF_MACHINE_X86 |
+ pe::header::COFF_MACHINE_X86_64 |
+ pe::header::COFF_MACHINE_ARM64 => Ok(Hint::COFF),
+ _ => mach::peek_bytes(bytes)
+ }
}
}
@@ -273,12 +283,15 @@ if_everything! {
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
+ #[non_exhaustive]
/// A parseable object that goblin understands
pub enum Object<'a> {
/// An ELF32/ELF64!
Elf(elf::Elf<'a>),
/// A PE32/PE32+!
PE(pe::PE<'a>),
+ /// A COFF
+ COFF(pe::Coff<'a>),
/// A 32/64-bit Mach-o binary _OR_ it is a multi-architecture binary container!
Mach(mach::Mach<'a>),
/// A Unix archive
@@ -296,7 +309,8 @@ if_everything! {
Hint::Mach(_) | Hint::MachFat(_) => Ok(Object::Mach(mach::Mach::parse(bytes)?)),
Hint::Archive => Ok(Object::Archive(archive::Archive::parse(bytes)?)),
Hint::PE => Ok(Object::PE(pe::PE::parse(bytes)?)),
- Hint::Unknown(magic) => Ok(Object::Unknown(magic))
+ Hint::COFF => Ok(Object::COFF(pe::Coff::parse(bytes)?)),
+ Hint::Unknown(magic) => Ok(Object::Unknown(magic)),
}
} else {
Err(error::Error::Malformed(format!("Object is too small.")))
diff --git a/third_party/rust/goblin/src/mach/load_command.rs b/third_party/rust/goblin/src/mach/load_command.rs
index 34a44e2bae..305c3b823f 100644
--- a/third_party/rust/goblin/src/mach/load_command.rs
+++ b/third_party/rust/goblin/src/mach/load_command.rs
@@ -1343,9 +1343,12 @@ pub const PLATFORM_IOSSIMULATOR: u32 = 7;
pub const PLATFORM_TVOSSIMULATOR: u32 = 8;
pub const PLATFORM_WATCHOSSIMULATOR: u32 = 9;
pub const PLATFORM_DRIVERKIT: u32 = 10;
+pub const PLATFORM_VISIONOS: u32 = 11;
+pub const PLATFORM_VISIONOSSIMULATOR: u32 = 12;
pub const TOOL_CLANG: u32 = 1;
pub const TOOL_SWIFT: u32 = 2;
pub const TOOL_LD: u32 = 3;
+pub const TOOL_LLD: u32 = 4;
pub fn cmd_to_str(cmd: u32) -> &'static str {
match cmd {
diff --git a/third_party/rust/goblin/src/pe/authenticode.rs b/third_party/rust/goblin/src/pe/authenticode.rs
index e16b7997cd..0f738e6888 100644
--- a/third_party/rust/goblin/src/pe/authenticode.rs
+++ b/third_party/rust/goblin/src/pe/authenticode.rs
@@ -8,9 +8,13 @@
// - data directory entry for certtable
// - certtable
+use alloc::collections::VecDeque;
use core::ops::Range;
+use log::debug;
-use super::PE;
+use super::{section_table::SectionTable, PE};
+
+static PADDING: [u8; 7] = [0; 7];
impl PE<'_> {
/// [`authenticode_ranges`] returns the various ranges of the binary that are relevant for
@@ -19,6 +23,7 @@ impl PE<'_> {
ExcludedSectionsIter {
pe: self,
state: IterState::default(),
+ sections: VecDeque::default(),
}
}
}
@@ -29,19 +34,22 @@ impl PE<'_> {
pub(super) struct ExcludedSections {
checksum: Range<usize>,
datadir_entry_certtable: Range<usize>,
- certtable: Option<Range<usize>>,
+ certificate_table_size: usize,
+ end_image_header: usize,
}
impl ExcludedSections {
pub(super) fn new(
checksum: Range<usize>,
datadir_entry_certtable: Range<usize>,
- certtable: Option<Range<usize>>,
+ certificate_table_size: usize,
+ end_image_header: usize,
) -> Self {
Self {
checksum,
datadir_entry_certtable,
- certtable,
+ certificate_table_size,
+ end_image_header,
}
}
}
@@ -49,14 +57,26 @@ impl ExcludedSections {
pub struct ExcludedSectionsIter<'s> {
pe: &'s PE<'s>,
state: IterState,
+ sections: VecDeque<SectionTable>,
}
#[derive(Debug, PartialEq)]
enum IterState {
Initial,
- DatadirEntry(usize),
- CertTable(usize),
- Final(usize),
+ ChecksumEnd(usize),
+ CertificateTableEnd(usize),
+ HeaderEnd {
+ end_image_header: usize,
+ sum_of_bytes_hashed: usize,
+ },
+ Sections {
+ tail: usize,
+ sum_of_bytes_hashed: usize,
+ },
+ Final {
+ sum_of_bytes_hashed: usize,
+ },
+ Padding(usize),
Done,
}
@@ -76,24 +96,166 @@ impl<'s> Iterator for ExcludedSectionsIter<'s> {
loop {
match self.state {
IterState::Initial => {
- self.state = IterState::DatadirEntry(sections.checksum.end);
- return Some(&bytes[..sections.checksum.start]);
+ // 3. Hash the image header from its base to immediately before the start of the
+ // checksum address, as specified in Optional Header Windows-Specific Fields.
+ let out = Some(&bytes[..sections.checksum.start]);
+ debug!("hashing {:#x} {:#x}", 0, sections.checksum.start);
+
+ // 4. Skip over the checksum, which is a 4-byte field.
+ debug_assert_eq!(sections.checksum.end - sections.checksum.start, 4);
+ self.state = IterState::ChecksumEnd(sections.checksum.end);
+
+ return out;
+ }
+ IterState::ChecksumEnd(checksum_end) => {
+ // 5. Hash everything from the end of the checksum field to immediately before the start
+ // of the Certificate Table entry, as specified in Optional Header Data Directories.
+ let out =
+ Some(&bytes[checksum_end..sections.datadir_entry_certtable.start]);
+ debug!(
+ "hashing {checksum_end:#x} {:#x}",
+ sections.datadir_entry_certtable.start
+ );
+
+ // 6. Get the Attribute Certificate Table address and size from the Certificate Table entry.
+ // For details, see section 5.7 of the PE/COFF specification.
+ // 7. Exclude the Certificate Table entry from the calculation
+ self.state =
+ IterState::CertificateTableEnd(sections.datadir_entry_certtable.end);
+
+ return out;
+ }
+ IterState::CertificateTableEnd(start) => {
+ // 7. Exclude the Certificate Table entry from the calculation and hash everything from
+ // the end of the Certificate Table entry to the end of image header, including
+ // Section Table (headers). The Certificate Table entry is 8 bytes long, as specified
+ // in Optional Header Data Directories.
+ let end_image_header = sections.end_image_header;
+ let buf = Some(&bytes[start..end_image_header]);
+ debug!("hashing {start:#x} {:#x}", end_image_header - start);
+
+ // 8. Create a counter called SUM_OF_BYTES_HASHED, which is not part of the signature.
+ // Set this counter to the SizeOfHeaders field, as specified in
+ // Optional Header Windows-Specific Field.
+ let sum_of_bytes_hashed = end_image_header;
+
+ self.state = IterState::HeaderEnd {
+ end_image_header,
+ sum_of_bytes_hashed,
+ };
+
+ return buf;
}
- IterState::DatadirEntry(start) => {
- self.state = IterState::CertTable(sections.datadir_entry_certtable.end);
- return Some(&bytes[start..sections.datadir_entry_certtable.start]);
+ IterState::HeaderEnd {
+ end_image_header,
+ sum_of_bytes_hashed,
+ } => {
+ // 9. Build a temporary table of pointers to all of the section headers in the
+ // image. The NumberOfSections field of COFF File Header indicates how big
+ // the table should be. Do not include any section headers in the table whose
+ // SizeOfRawData field is zero.
+
+ // Implementation detail:
+ // We require allocation here because the section table has a variable size and
+ // needs to be sorted.
+ let mut sections: VecDeque<SectionTable> = self
+ .pe
+ .sections
+ .iter()
+ .filter(|section| section.size_of_raw_data != 0)
+ .cloned()
+ .collect();
+
+ // 10. Using the PointerToRawData field (offset 20) in the referenced SectionHeader
+ // structure as a key, arrange the table's elements in ascending order. In
+ // other words, sort the section headers in ascending order according to the
+ // disk-file offset of the sections.
+ sections
+ .make_contiguous()
+ .sort_by_key(|section| section.pointer_to_raw_data);
+
+ self.sections = sections;
+
+ self.state = IterState::Sections {
+ tail: end_image_header,
+ sum_of_bytes_hashed,
+ };
}
- IterState::CertTable(start) => {
- if let Some(certtable) = sections.certtable.as_ref() {
- self.state = IterState::Final(certtable.end);
- return Some(&bytes[start..certtable.start]);
+ IterState::Sections {
+ mut tail,
+ mut sum_of_bytes_hashed,
+ } => {
+ // 11. Walk through the sorted table, load the corresponding section into memory,
+ // and hash the entire section. Use the SizeOfRawData field in the SectionHeader
+ // structure to determine the amount of data to hash.
+ if let Some(section) = self.sections.pop_front() {
+ let start = section.pointer_to_raw_data as usize;
+ let end = start + section.size_of_raw_data as usize;
+ tail = end;
+
+ // 12. Add the section’s SizeOfRawData value to SUM_OF_BYTES_HASHED.
+ sum_of_bytes_hashed += section.size_of_raw_data as usize;
+
+ debug!("hashing {start:#x} {:#x}", end - start);
+ let buf = &bytes[start..end];
+
+ // 13. Repeat steps 11 and 12 for all of the sections in the sorted table.
+ self.state = IterState::Sections {
+ tail,
+ sum_of_bytes_hashed,
+ };
+
+ return Some(buf);
} else {
- self.state = IterState::Final(start)
+ self.state = IterState::Final {
+ sum_of_bytes_hashed,
+ };
+ }
+ }
+ IterState::Final {
+ sum_of_bytes_hashed,
+ } => {
+ // 14. Create a value called FILE_SIZE, which is not part of the signature.
+ // Set this value to the image’s file size, acquired from the underlying
+ // file system. If FILE_SIZE is greater than SUM_OF_BYTES_HASHED, the
+ // file contains extra data that must be added to the hash. This data
+ // begins at the SUM_OF_BYTES_HASHED file offset, and its length is:
+ // (File Size) - ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED)
+ //
+ // Note: The size of Attribute Certificate Table is specified in the second
+ // ULONG value in the Certificate Table entry (32 bit: offset 132,
+ // 64 bit: offset 148) in Optional Header Data Directories.
+ let file_size = bytes.len();
+
+ // If FILE_SIZE is not a multiple of 8 bytes, the data added to the hash must
+ // be appended with zero padding of length (8 – (FILE_SIZE % 8)) bytes
+ let pad_size = (8 - file_size % 8) % 8;
+ self.state = IterState::Padding(pad_size);
+
+ if file_size > sum_of_bytes_hashed {
+ let extra_data_start = sum_of_bytes_hashed;
+ let len =
+ file_size - sections.certificate_table_size - sum_of_bytes_hashed;
+
+ debug!("hashing {extra_data_start:#x} {len:#x}",);
+ let buf = &bytes[extra_data_start..extra_data_start + len];
+
+ return Some(buf);
}
}
- IterState::Final(start) => {
+ IterState::Padding(pad_size) => {
self.state = IterState::Done;
- return Some(&bytes[start..]);
+
+ if pad_size != 0 {
+ debug!("hashing {pad_size:#x}");
+
+ // NOTE (safety): pad size will be at most 7, and PADDING has a size of 7
+ // pad_size is computed ~10 lines above.
+ debug_assert!(pad_size <= 7);
+ debug_assert_eq!(PADDING.len(), 7);
+
+ return Some(&PADDING[..pad_size]);
+ }
}
IterState::Done => return None,
}
diff --git a/third_party/rust/goblin/src/pe/certificate_table.rs b/third_party/rust/goblin/src/pe/certificate_table.rs
index 353a6c70ce..be45ce48dc 100644
--- a/third_party/rust/goblin/src/pe/certificate_table.rs
+++ b/third_party/rust/goblin/src/pe/certificate_table.rs
@@ -3,12 +3,15 @@
/// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-attribute-certificate-table-image-only
/// https://learn.microsoft.com/en-us/windows/win32/api/wintrust/ns-wintrust-win_certificate
use crate::error;
-use scroll::Pread;
+use scroll::{ctx, Pread, Pwrite};
use alloc::string::ToString;
use alloc::vec::Vec;
+use super::utils::pad;
+
#[repr(u16)]
+#[non_exhaustive]
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum AttributeCertificateRevision {
/// WIN_CERT_REVISION_1_0
@@ -38,7 +41,7 @@ impl TryFrom<u16> for AttributeCertificateRevision {
}
#[repr(u16)]
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Copy, Clone)]
pub enum AttributeCertificateType {
/// WIN_CERT_TYPE_X509
X509 = 0x0001,
@@ -127,7 +130,28 @@ impl<'a> AttributeCertificate<'a> {
}
}
+impl<'a> ctx::TryIntoCtx<scroll::Endian> for &AttributeCertificate<'a> {
+ type Error = error::Error;
+
+ /// Writes an aligned attribute certificate in the buffer.
+ fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> {
+ let offset = &mut 0;
+ bytes.gwrite_with(self.length, offset, ctx)?;
+ bytes.gwrite_with(self.revision as u16, offset, ctx)?;
+ bytes.gwrite_with(self.certificate_type as u16, offset, ctx)?;
+ // Extend by zero the buffer until it is aligned on a quadword (16 bytes).
+ let maybe_certificate_padding = pad(self.certificate.len(), Some(16usize));
+ bytes.gwrite(self.certificate, offset)?;
+ if let Some(cert_padding) = maybe_certificate_padding {
+ bytes.gwrite(&cert_padding[..], offset)?;
+ }
+
+ Ok(*offset)
+ }
+}
+
pub type CertificateDirectoryTable<'a> = Vec<AttributeCertificate<'a>>;
+
pub(crate) fn enumerate_certificates(
bytes: &[u8],
table_virtual_address: u32,
diff --git a/third_party/rust/goblin/src/pe/data_directories.rs b/third_party/rust/goblin/src/pe/data_directories.rs
index 265e4e27f7..e65db5953d 100644
--- a/third_party/rust/goblin/src/pe/data_directories.rs
+++ b/third_party/rust/goblin/src/pe/data_directories.rs
@@ -1,5 +1,8 @@
use crate::error;
-use scroll::{Pread, Pwrite, SizeWith};
+use scroll::{
+ ctx::{self},
+ Pread, Pwrite, SizeWith,
+};
#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone, Default, Pread, Pwrite, SizeWith)]
@@ -13,14 +16,86 @@ const NUM_DATA_DIRECTORIES: usize = 16;
impl DataDirectory {
pub fn parse(bytes: &[u8], offset: &mut usize) -> error::Result<Self> {
- let dd = bytes.gread_with(offset, scroll::LE)?;
- Ok(dd)
+ Ok(bytes.gread_with(offset, scroll::LE)?)
+ }
+}
+
+#[derive(Debug, PartialEq, Copy, Clone)]
+pub enum DataDirectoryType {
+ ExportTable,
+ ImportTable,
+ ResourceTable,
+ ExceptionTable,
+ CertificateTable,
+ BaseRelocationTable,
+ DebugTable,
+ Architecture,
+ GlobalPtr,
+ TlsTable,
+ LoadConfigTable,
+ BoundImportTable,
+ ImportAddressTable,
+ DelayImportDescriptor,
+ ClrRuntimeHeader,
+}
+
+impl TryFrom<usize> for DataDirectoryType {
+ type Error = error::Error;
+ fn try_from(value: usize) -> Result<Self, Self::Error> {
+ Ok(match value {
+ 0 => Self::ExportTable,
+ 1 => Self::ImportTable,
+ 2 => Self::ResourceTable,
+ 3 => Self::ExceptionTable,
+ 4 => Self::CertificateTable,
+ 5 => Self::BaseRelocationTable,
+ 6 => Self::DebugTable,
+ 7 => Self::Architecture,
+ 8 => Self::GlobalPtr,
+ 9 => Self::TlsTable,
+ 10 => Self::LoadConfigTable,
+ 11 => Self::BoundImportTable,
+ 12 => Self::ImportAddressTable,
+ 13 => Self::DelayImportDescriptor,
+ 14 => Self::ClrRuntimeHeader,
+ _ => {
+ return Err(error::Error::Malformed(
+ "Wrong data directory index number".into(),
+ ))
+ }
+ })
}
}
#[derive(Debug, PartialEq, Copy, Clone, Default)]
pub struct DataDirectories {
- pub data_directories: [Option<DataDirectory>; NUM_DATA_DIRECTORIES],
+ pub data_directories: [Option<(usize, DataDirectory)>; NUM_DATA_DIRECTORIES],
+}
+
+impl ctx::TryIntoCtx<scroll::Endian> for DataDirectories {
+ type Error = error::Error;
+
+ fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> {
+ let offset = &mut 0;
+ for opt_dd in self.data_directories {
+ if let Some((dd_offset, dd)) = opt_dd {
+ bytes.pwrite_with(dd, dd_offset, ctx)?;
+ *offset += dd_offset;
+ } else {
+ bytes.gwrite(&[0; SIZEOF_DATA_DIRECTORY][..], offset)?;
+ }
+ }
+ Ok(NUM_DATA_DIRECTORIES * SIZEOF_DATA_DIRECTORY)
+ }
+}
+
+macro_rules! build_dd_getter {
+ ($dd_name:tt, $index:tt) => {
+ pub fn $dd_name(&self) -> Option<&DataDirectory> {
+ let idx = $index;
+ self.data_directories[idx].as_ref().map(|(_, dd)| dd)
+ }
+ };
}
impl DataDirectories {
@@ -37,70 +112,42 @@ impl DataDirectories {
let dd = if dd.virtual_address == 0 && dd.size == 0 {
None
} else {
- Some(dd)
+ Some((*offset, dd))
};
*dir = dd;
}
Ok(DataDirectories { data_directories })
}
- pub fn get_export_table(&self) -> &Option<DataDirectory> {
- let idx = 0;
- &self.data_directories[idx]
- }
- pub fn get_import_table(&self) -> &Option<DataDirectory> {
- let idx = 1;
- &self.data_directories[idx]
- }
- pub fn get_resource_table(&self) -> &Option<DataDirectory> {
- let idx = 2;
- &self.data_directories[idx]
- }
- pub fn get_exception_table(&self) -> &Option<DataDirectory> {
- let idx = 3;
- &self.data_directories[idx]
- }
- pub fn get_certificate_table(&self) -> &Option<DataDirectory> {
- let idx = 4;
- &self.data_directories[idx]
- }
- pub fn get_base_relocation_table(&self) -> &Option<DataDirectory> {
- let idx = 5;
- &self.data_directories[idx]
- }
- pub fn get_debug_table(&self) -> &Option<DataDirectory> {
- let idx = 6;
- &self.data_directories[idx]
- }
- pub fn get_architecture(&self) -> &Option<DataDirectory> {
- let idx = 7;
- &self.data_directories[idx]
- }
- pub fn get_global_ptr(&self) -> &Option<DataDirectory> {
- let idx = 8;
- &self.data_directories[idx]
- }
- pub fn get_tls_table(&self) -> &Option<DataDirectory> {
- let idx = 9;
- &self.data_directories[idx]
- }
- pub fn get_load_config_table(&self) -> &Option<DataDirectory> {
- let idx = 10;
- &self.data_directories[idx]
- }
- pub fn get_bound_import_table(&self) -> &Option<DataDirectory> {
- let idx = 11;
- &self.data_directories[idx]
- }
- pub fn get_import_address_table(&self) -> &Option<DataDirectory> {
- let idx = 12;
- &self.data_directories[idx]
- }
- pub fn get_delay_import_descriptor(&self) -> &Option<DataDirectory> {
- let idx = 13;
- &self.data_directories[idx]
- }
- pub fn get_clr_runtime_header(&self) -> &Option<DataDirectory> {
- let idx = 14;
- &self.data_directories[idx]
+
+ build_dd_getter!(get_export_table, 0);
+ build_dd_getter!(get_import_table, 1);
+ build_dd_getter!(get_resource_table, 2);
+ build_dd_getter!(get_exception_table, 3);
+ build_dd_getter!(get_certificate_table, 4);
+ build_dd_getter!(get_base_relocation_table, 5);
+ build_dd_getter!(get_debug_table, 6);
+ build_dd_getter!(get_architecture, 7);
+ build_dd_getter!(get_global_ptr, 8);
+ build_dd_getter!(get_tls_table, 9);
+ build_dd_getter!(get_load_config_table, 10);
+ build_dd_getter!(get_bound_import_table, 11);
+ build_dd_getter!(get_import_address_table, 12);
+ build_dd_getter!(get_delay_import_descriptor, 13);
+ build_dd_getter!(get_clr_runtime_header, 14);
+
+ pub fn dirs(&self) -> impl Iterator<Item = (DataDirectoryType, DataDirectory)> {
+ self.data_directories
+ .into_iter()
+ .enumerate()
+ // (Index, Option<DD>) -> Option<(Index, DD)> -> (DDT, DD)
+ .filter_map(|(i, o)|
+ // We should not have invalid indexes.
+ // Indeed: `data_directories: &[_; N]` where N is the number
+ // of data directories.
+ // The `TryFrom` trait for integers to DataDirectoryType
+ // takes into account the N possible data directories.
+ // Therefore, the unwrap can never fail as long as Rust guarantees
+ // on types are honored.
+ o.map(|(_, v)| (i.try_into().unwrap(), v)))
}
}
diff --git a/third_party/rust/goblin/src/pe/header.rs b/third_party/rust/goblin/src/pe/header.rs
index c1ded9dd9f..06e23c0b03 100644
--- a/third_party/rust/goblin/src/pe/header.rs
+++ b/third_party/rust/goblin/src/pe/header.rs
@@ -3,24 +3,60 @@ use crate::pe::{optional_header, section_table, symbol};
use crate::strtab;
use alloc::vec::Vec;
use log::debug;
-use scroll::{IOread, IOwrite, Pread, Pwrite, SizeWith};
+use scroll::{ctx, IOread, IOwrite, Pread, Pwrite, SizeWith};
/// DOS header present in all PE binaries
#[repr(C)]
-#[derive(Debug, PartialEq, Copy, Clone, Default)]
+#[derive(Debug, PartialEq, Copy, Clone, Default, Pwrite)]
pub struct DosHeader {
/// Magic number: 5a4d
pub signature: u16,
- /// Pointer to PE header, always at offset 0x3c
+ /// e_cblp
+ pub bytes_on_last_page: u16,
+ /// e_cp
+ pub pages_in_file: u16,
+ /// e_crlc
+ pub relocations: u16,
+ /// e_cparhdr
+ pub size_of_header_in_paragraphs: u16,
+ /// e_minalloc
+ pub minimum_extra_paragraphs_needed: u16,
+ /// e_maxalloc
+ pub maximum_extra_paragraphs_needed: u16,
+ /// e_ss
+ pub initial_relative_ss: u16,
+ /// e_sp
+ pub initial_sp: u16,
+ /// e_csum
+ pub checksum: u16,
+ /// e_ip
+ pub initial_ip: u16,
+ /// e_cs
+ pub initial_relative_cs: u16,
+ /// e_lfarlc
+ pub file_address_of_relocation_table: u16,
+ /// e_ovno
+ pub overlay_number: u16,
+ /// e_res[4]
+ pub reserved: [u16; 4],
+ /// e_oemid
+ pub oem_id: u16,
+ /// e_oeminfo
+ pub oem_info: u16,
+ /// e_res2[10]
+ pub reserved2: [u16; 10],
+ /// e_lfanew: pointer to PE header, always at offset 0x3c
pub pe_pointer: u32,
}
pub const DOS_MAGIC: u16 = 0x5a4d;
pub const PE_POINTER_OFFSET: u32 = 0x3c;
+pub const DOS_STUB_OFFSET: u32 = PE_POINTER_OFFSET + (core::mem::size_of::<u32>() as u32);
impl DosHeader {
pub fn parse(bytes: &[u8]) -> error::Result<Self> {
- let signature = bytes.pread_with(0, scroll::LE).map_err(|_| {
+ let mut offset = 0;
+ let signature = bytes.gread_with(&mut offset, scroll::LE).map_err(|_| {
error::Error::Malformed(format!("cannot parse DOS signature (offset {:#x})", 0))
})?;
if signature != DOS_MAGIC {
@@ -29,6 +65,33 @@ impl DosHeader {
signature
)));
}
+
+ let bytes_on_last_page = bytes.gread_with(&mut offset, scroll::LE)?;
+ let pages_in_file = bytes.gread_with(&mut offset, scroll::LE)?;
+ let relocations = bytes.gread_with(&mut offset, scroll::LE)?;
+ let size_of_header_in_paragraphs = bytes.gread_with(&mut offset, scroll::LE)?;
+ let minimum_extra_paragraphs_needed = bytes.gread_with(&mut offset, scroll::LE)?;
+ let maximum_extra_paragraphs_needed = bytes.gread_with(&mut offset, scroll::LE)?;
+ let initial_relative_ss = bytes.gread_with(&mut offset, scroll::LE)?;
+ let initial_sp = bytes.gread_with(&mut offset, scroll::LE)?;
+ let checksum = bytes.gread_with(&mut offset, scroll::LE)?;
+ let initial_ip = bytes.gread_with(&mut offset, scroll::LE)?;
+ let initial_relative_cs = bytes.gread_with(&mut offset, scroll::LE)?;
+ let file_address_of_relocation_table = bytes.gread_with(&mut offset, scroll::LE)?;
+ let overlay_number = bytes.gread_with(&mut offset, scroll::LE)?;
+ let reserved = [0x0; 4];
+ offset += core::mem::size_of_val(&reserved);
+ let oem_id = bytes.gread_with(&mut offset, scroll::LE)?;
+ let oem_info = bytes.gread_with(&mut offset, scroll::LE)?;
+ let reserved2 = [0x0; 10];
+ offset += core::mem::size_of_val(&reserved2);
+
+ debug_assert!(
+ offset == PE_POINTER_OFFSET as usize,
+ "expected offset ({:#x}) after reading DOS header to be at 0x3C",
+ offset
+ );
+
let pe_pointer = bytes
.pread_with(PE_POINTER_OFFSET as usize, scroll::LE)
.map_err(|_| {
@@ -37,6 +100,7 @@ impl DosHeader {
PE_POINTER_OFFSET
))
})?;
+
let pe_signature: u32 =
bytes
.pread_with(pe_pointer as usize, scroll::LE)
@@ -52,13 +116,48 @@ impl DosHeader {
pe_signature
)));
}
+
Ok(DosHeader {
signature,
+ bytes_on_last_page,
+ pages_in_file,
+ relocations,
+ size_of_header_in_paragraphs,
+ minimum_extra_paragraphs_needed,
+ maximum_extra_paragraphs_needed,
+ initial_relative_ss,
+ initial_sp,
+ checksum,
+ initial_ip,
+ initial_relative_cs,
+ file_address_of_relocation_table,
+ overlay_number,
+ reserved,
+ oem_id,
+ oem_info,
+ reserved2,
pe_pointer,
})
}
}
+#[repr(C)]
+#[derive(Debug, PartialEq, Copy, Clone, Pread, Pwrite)]
+/// The DOS stub program which should be executed in DOS mode
+pub struct DosStub(pub [u8; 0x40]);
+impl Default for DosStub {
+ fn default() -> Self {
+ // "This program cannot be run in DOS mode" error program
+ Self([
+ 0x0E, 0x1F, 0xBA, 0x0E, 0x00, 0xB4, 0x09, 0xCD, 0x21, 0xB8, 0x01, 0x4C, 0xCD, 0x21,
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20, 0x63,
+ 0x61, 0x6E, 0x6E, 0x6F, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6E, 0x20, 0x69,
+ 0x6E, 0x20, 0x44, 0x4F, 0x53, 0x20, 0x6D, 0x6F, 0x64, 0x65, 0x2E, 0x0D, 0x0D, 0x0A,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ])
+ }
+}
+
/// COFF Header
#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone, Default, Pread, Pwrite, IOread, IOwrite, SizeWith)]
@@ -163,14 +262,24 @@ impl CoffHeader {
}
/// Return the COFF symbol table.
- pub fn symbols<'a>(&self, bytes: &'a [u8]) -> error::Result<symbol::SymbolTable<'a>> {
+ pub fn symbols<'a>(&self, bytes: &'a [u8]) -> error::Result<Option<symbol::SymbolTable<'a>>> {
let offset = self.pointer_to_symbol_table as usize;
let number = self.number_of_symbol_table as usize;
- symbol::SymbolTable::parse(bytes, offset, number)
+ if offset == 0 {
+ Ok(None)
+ } else {
+ symbol::SymbolTable::parse(bytes, offset, number).map(Some)
+ }
}
/// Return the COFF string table.
- pub fn strings<'a>(&self, bytes: &'a [u8]) -> error::Result<strtab::Strtab<'a>> {
+ pub fn strings<'a>(&self, bytes: &'a [u8]) -> error::Result<Option<strtab::Strtab<'a>>> {
+ // > The file offset of the COFF symbol table, or zero if no COFF symbol table is present.
+ // > This value should be zero for an image because COFF debugging information is deprecated.
+ if self.pointer_to_symbol_table == 0 {
+ return Ok(None);
+ }
+
let mut offset = self.pointer_to_symbol_table as usize
+ symbol::SymbolTable::size(self.number_of_symbol_table as usize);
@@ -180,13 +289,15 @@ impl CoffHeader {
// The offset needs to be advanced in order to read the strings.
offset += length_field_size;
- Ok(strtab::Strtab::parse(bytes, offset, length, 0)?)
+ Ok(Some(strtab::Strtab::parse(bytes, offset, length, 0)?))
}
}
#[derive(Debug, PartialEq, Copy, Clone, Default)]
pub struct Header {
pub dos_header: DosHeader,
+ /// DOS program for legacy loaders
+ pub dos_stub: DosStub,
/// PE Magic: PE\0\0, little endian
pub signature: u32,
pub coff_header: CoffHeader,
@@ -196,6 +307,12 @@ pub struct Header {
impl Header {
pub fn parse(bytes: &[u8]) -> error::Result<Self> {
let dos_header = DosHeader::parse(&bytes)?;
+ let dos_stub = bytes.pread(DOS_STUB_OFFSET as usize).map_err(|_| {
+ error::Error::Malformed(format!(
+ "cannot parse DOS stub (offset {:#x})",
+ DOS_STUB_OFFSET
+ ))
+ })?;
let mut offset = dos_header.pe_pointer as usize;
let signature = bytes.gread_with(&mut offset, scroll::LE).map_err(|_| {
error::Error::Malformed(format!("cannot parse PE signature (offset {:#x})", offset))
@@ -208,6 +325,7 @@ impl Header {
};
Ok(Header {
dos_header,
+ dos_stub,
signature,
coff_header,
optional_header,
@@ -215,6 +333,22 @@ impl Header {
}
}
+impl ctx::TryIntoCtx<scroll::Endian> for Header {
+ type Error = error::Error;
+
+ fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> {
+ let offset = &mut 0;
+ bytes.gwrite_with(self.dos_header, offset, ctx)?;
+ bytes.gwrite_with(self.dos_stub, offset, ctx)?;
+ bytes.gwrite_with(self.signature, offset, scroll::LE)?;
+ bytes.gwrite_with(self.coff_header, offset, ctx)?;
+ if let Some(opt_header) = self.optional_header {
+ bytes.gwrite_with(opt_header, offset, ctx)?;
+ }
+ Ok(*offset)
+ }
+}
+
/// Convert machine to str representation
pub fn machine_to_str(machine: u16) -> &'static str {
match machine {
diff --git a/third_party/rust/goblin/src/pe/mod.rs b/third_party/rust/goblin/src/pe/mod.rs
index 5a7337630a..2336fddc57 100644
--- a/third_party/rust/goblin/src/pe/mod.rs
+++ b/third_party/rust/goblin/src/pe/mod.rs
@@ -3,7 +3,12 @@
// TODO: panics with unwrap on None for apisetschema.dll, fhuxgraphics.dll and some others
+use core::cmp::max;
+
+use alloc::borrow::Cow;
+use alloc::string::String;
use alloc::vec::Vec;
+use log::warn;
pub mod authenticode;
pub mod certificate_table;
@@ -23,8 +28,11 @@ pub mod utils;
use crate::container;
use crate::error;
+use crate::pe::utils::pad;
use crate::strtab;
+use scroll::{ctx, Pwrite};
+
use log::debug;
#[derive(Debug)]
@@ -140,7 +148,7 @@ impl<'a> PE<'a> {
entry, image_base, is_64
);
let file_alignment = optional_header.windows_fields.file_alignment;
- if let Some(export_table) = *optional_header.data_directories.get_export_table() {
+ if let Some(&export_table) = optional_header.data_directories.get_export_table() {
if let Ok(ed) = export::ExportData::parse_with_opts(
bytes,
export_table,
@@ -162,7 +170,7 @@ impl<'a> PE<'a> {
}
}
debug!("exports: {:#?}", exports);
- if let Some(import_table) = *optional_header.data_directories.get_import_table() {
+ if let Some(&import_table) = optional_header.data_directories.get_import_table() {
let id = if is_64 {
import::ImportData::parse_with_opts::<u64>(
bytes,
@@ -196,7 +204,7 @@ impl<'a> PE<'a> {
import_data = Some(id);
}
debug!("imports: {:#?}", imports);
- if let Some(debug_table) = *optional_header.data_directories.get_debug_table() {
+ if let Some(&debug_table) = optional_header.data_directories.get_debug_table() {
debug_data = Some(debug::DebugData::parse_with_opts(
bytes,
debug_table,
@@ -209,8 +217,8 @@ impl<'a> PE<'a> {
if header.coff_header.machine == header::COFF_MACHINE_X86_64 {
// currently only x86_64 is supported
debug!("exception data: {:#?}", exception_data);
- if let Some(exception_table) =
- *optional_header.data_directories.get_exception_table()
+ if let Some(&exception_table) =
+ optional_header.data_directories.get_exception_table()
{
exception_data = Some(exception::ExceptionData::parse_with_opts(
bytes,
@@ -222,26 +230,30 @@ impl<'a> PE<'a> {
}
}
- let certtable = if let Some(certificate_table) =
- *optional_header.data_directories.get_certificate_table()
- {
- certificates = certificate_table::enumerate_certificates(
- bytes,
- certificate_table.virtual_address,
- certificate_table.size,
- )?;
+ // Parse attribute certificates unless opted out of
+ let certificate_table_size = if opts.parse_attribute_certificates {
+ if let Some(&certificate_table) =
+ optional_header.data_directories.get_certificate_table()
+ {
+ certificates = certificate_table::enumerate_certificates(
+ bytes,
+ certificate_table.virtual_address,
+ certificate_table.size,
+ )?;
- let start = certificate_table.virtual_address as usize;
- let end = start + certificate_table.size as usize;
- Some(start..end)
+ certificate_table.size as usize
+ } else {
+ 0
+ }
} else {
- None
+ 0
};
authenticode_excluded_sections = Some(authenticode::ExcludedSections::new(
checksum,
datadir_entry_certtable,
- certtable,
+ certificate_table_size,
+ optional_header.windows_fields.size_of_headers as usize,
));
}
Ok(PE {
@@ -265,6 +277,192 @@ impl<'a> PE<'a> {
certificates,
})
}
+
+ pub fn write_sections(
+ &self,
+ bytes: &mut [u8],
+ offset: &mut usize,
+ file_alignment: Option<usize>,
+ ctx: scroll::Endian,
+ ) -> Result<usize, error::Error> {
+ // sections table and data
+ debug_assert!(
+ self.sections
+ .iter()
+ .flat_map(|section_a| {
+ self.sections
+ .iter()
+ .map(move |section_b| (section_a, section_b))
+ })
+ // given sections = (s_1, …, s_n)
+ // for all (s_i, s_j), i != j, verify that s_i does not overlap with s_j and vice versa.
+ .all(|(section_i, section_j)| section_i == section_j
+ || !section_i.overlaps_with(section_j)),
+ "Overlapping sections were found, this is not supported."
+ );
+
+ for section in &self.sections {
+ let section_data = section.data(&self.bytes)?.ok_or_else(|| {
+ error::Error::Malformed(format!(
+ "Section data `{}` is malformed",
+ section.name().unwrap_or("unknown name")
+ ))
+ })?;
+ let file_section_offset =
+ usize::try_from(section.pointer_to_raw_data).map_err(|_| {
+ error::Error::Malformed(format!(
+ "Section `{}`'s pointer to raw data does not fit in platform `usize`",
+ section.name().unwrap_or("unknown name")
+ ))
+ })?;
+ let vsize: usize = section.virtual_size.try_into()?;
+ let ondisk_size: usize = section.size_of_raw_data.try_into()?;
+ let section_name = String::from(section.name().unwrap_or("unknown name"));
+
+ let mut file_offset = file_section_offset;
+ // `file_section_offset` is a on-disk offset which can be anywhere in the file.
+ // Write section data first to avoid the final consumption.
+ match section_data {
+ Cow::Borrowed(borrowed) => bytes.gwrite(borrowed, &mut file_offset)?,
+ Cow::Owned(owned) => bytes.gwrite(owned.as_slice(), &mut file_offset)?,
+ };
+
+ // Section tables follows the header.
+ bytes.gwrite_with(section, offset, ctx)?;
+
+ // for size size_of_raw_data
+ // if < virtual_size, pad with 0
+ // Pad with zeros if necessary
+ if file_offset < vsize {
+ bytes.gwrite(vec![0u8; vsize - file_offset].as_slice(), &mut file_offset)?;
+ }
+
+ // Align on a boundary as per file alignement field.
+ if let Some(pad) = pad(file_offset - file_section_offset, file_alignment) {
+ debug!(
+ "aligning `{}` {:#x} -> {:#x} bytes'",
+ section_name,
+ file_offset - file_section_offset,
+ file_offset - file_section_offset + pad.len()
+ );
+ bytes.gwrite(pad.as_slice(), &mut file_offset)?;
+ }
+
+ let written_data_size = file_offset - file_section_offset;
+ if ondisk_size != written_data_size {
+ warn!("Original PE is inefficient or bug (on-disk data size in PE: {:#x}), we wrote {:#x} bytes",
+ ondisk_size,
+ written_data_size);
+ }
+ }
+
+ Ok(*offset)
+ }
+
+ pub fn write_certificates(
+ &self,
+ bytes: &mut [u8],
+ ctx: scroll::Endian,
+ ) -> Result<usize, error::Error> {
+ let opt_header = self
+ .header
+ .optional_header
+ .ok_or(error::Error::Malformed(format!(
+ "This PE binary has no optional header; it is required to write certificates"
+ )))?;
+ let mut max_offset = 0;
+
+ if let Some(certificate_directory) = opt_header.data_directories.get_certificate_table() {
+ let mut certificate_start = certificate_directory.virtual_address.try_into()?;
+ for certificate in &self.certificates {
+ bytes.gwrite_with(certificate, &mut certificate_start, ctx)?;
+ max_offset = max(certificate_start, max_offset);
+ }
+ }
+
+ Ok(max_offset)
+ }
+}
+
+impl<'a> ctx::TryIntoCtx<scroll::Endian> for PE<'a> {
+ type Error = error::Error;
+
+ fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> {
+ let mut offset = 0;
+ // We need to maintain a `max_offset` because
+ // we could be writing sections in the wrong order (i.e. not an increasing order for the
+ // pointer on raw disk)
+ // and there could be holes between sections.
+ // If we don't re-layout sections, we cannot fix that ourselves.
+ // Same can be said about the certificate table, there could be a hole between sections
+ // and the certificate data.
+ // To avoid those troubles, we maintain the max over all offsets we see so far.
+ let mut max_offset = 0;
+ let file_alignment: Option<usize> = match self.header.optional_header {
+ Some(opt_header) => {
+ debug_assert!(
+ opt_header.windows_fields.file_alignment.count_ones() == 1,
+ "file alignment should be a power of 2"
+ );
+ Some(opt_header.windows_fields.file_alignment.try_into()?)
+ }
+ _ => None,
+ };
+ bytes.gwrite_with(self.header, &mut offset, ctx)?;
+ max_offset = max(offset, max_offset);
+ self.write_sections(bytes, &mut offset, file_alignment, ctx)?;
+ // We want the section offset for which we have the highest pointer on disk.
+ // The next offset is reserved for debug tables (outside of sections) and/or certificate
+ // tables.
+ max_offset = max(
+ self.sections
+ .iter()
+ .max_by_key(|section| section.pointer_to_raw_data as usize)
+ .map(|section| (section.pointer_to_raw_data + section.size_of_raw_data) as usize)
+ .unwrap_or(offset),
+ max_offset,
+ );
+
+ // COFF Symbol Table
+ // Auxiliary Symbol Records
+ // COFF String Table
+ assert!(
+ self.header.coff_header.pointer_to_symbol_table == 0,
+ "Symbol tables in PE are deprecated and not supported to write"
+ );
+
+ // The following data directories are
+ // taken care inside a section:
+ // - export table (.edata)
+ // - import table (.idata)
+ // - bound import table
+ // - import address table
+ // - delay import tables
+ // - resource table (.rsrc)
+ // - exception table (.pdata)
+ // - base relocation table (.reloc)
+ // - debug table (.debug) <- this one is special, it can be outside of a
+ // section.
+ // - load config table
+ // - tls table (.tls)
+ // - architecture (reserved, 0 for now)
+ // - global ptr is a "empty" data directory (header-only)
+ // - clr runtime header (.cormeta is object-only)
+ //
+ // Nonetheless, we need to write the attribute certificate table one.
+ max_offset = max(max_offset, self.write_certificates(bytes, ctx)?);
+
+ // TODO: we would like to support debug table outside of a section.
+ // i.e. debug tables that are never mapped in memory
+ // See https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#debug-directory-image-only
+ // > The debug directory can be in a discardable .debug section (if one exists), or it can be included in any other section in the image file, or not be in a section at all.
+ // In case it's not in a section at all, we need to find a way
+ // to rewrite it again.
+ // and we need to respect the ordering between attribute certificates
+ // and debug table.
+
+ Ok(max_offset)
+ }
}
/// An analyzed COFF object
@@ -274,10 +472,12 @@ pub struct Coff<'a> {
pub header: header::CoffHeader,
/// A list of the sections in this COFF binary
pub sections: Vec<section_table::SectionTable>,
- /// The COFF symbol table.
- pub symbols: symbol::SymbolTable<'a>,
- /// The string table.
- pub strings: strtab::Strtab<'a>,
+ /// The COFF symbol table, they are not guaranteed to exist.
+ /// For an image, this is expected to be None as COFF debugging information
+ /// has been deprecated.
+ pub symbols: Option<symbol::SymbolTable<'a>>,
+ /// The string table, they don't exist if COFF symbol table does not exist.
+ pub strings: Option<strtab::Strtab<'a>>,
}
impl<'a> Coff<'a> {
@@ -414,7 +614,7 @@ mod tests {
#[test]
fn string_table_excludes_length() {
let coff = Coff::parse(&&COFF_FILE_SINGLE_STRING_IN_STRING_TABLE[..]).unwrap();
- let string_table = coff.strings.to_vec().unwrap();
+ let string_table = coff.strings.unwrap().to_vec().unwrap();
assert!(string_table == vec!["ExitProcess"]);
}
@@ -422,9 +622,10 @@ mod tests {
#[test]
fn symbol_name_excludes_length() {
let coff = Coff::parse(&COFF_FILE_SINGLE_STRING_IN_STRING_TABLE).unwrap();
- let strings = coff.strings;
+ let strings = coff.strings.unwrap();
let symbols = coff
.symbols
+ .unwrap()
.iter()
.filter(|(_, name, _)| name.is_none())
.map(|(_, _, sym)| sym.name(&strings).unwrap().to_owned())
diff --git a/third_party/rust/goblin/src/pe/optional_header.rs b/third_party/rust/goblin/src/pe/optional_header.rs
index b1d3192764..852f4dced0 100644
--- a/third_party/rust/goblin/src/pe/optional_header.rs
+++ b/third_party/rust/goblin/src/pe/optional_header.rs
@@ -71,6 +71,22 @@ impl From<StandardFields32> for StandardFields {
}
}
+impl From<StandardFields> for StandardFields32 {
+ fn from(fields: StandardFields) -> Self {
+ StandardFields32 {
+ magic: fields.magic,
+ major_linker_version: fields.major_linker_version,
+ minor_linker_version: fields.minor_linker_version,
+ size_of_code: fields.size_of_code as u32,
+ size_of_initialized_data: fields.size_of_initialized_data as u32,
+ size_of_uninitialized_data: fields.size_of_uninitialized_data as u32,
+ address_of_entry_point: fields.address_of_entry_point as u32,
+ base_of_code: fields.base_of_code as u32,
+ base_of_data: fields.base_of_data,
+ }
+ }
+}
+
impl From<StandardFields64> for StandardFields {
fn from(fields: StandardFields64) -> Self {
StandardFields {
@@ -87,6 +103,21 @@ impl From<StandardFields64> for StandardFields {
}
}
+impl From<StandardFields> for StandardFields64 {
+ fn from(fields: StandardFields) -> Self {
+ StandardFields64 {
+ magic: fields.magic,
+ major_linker_version: fields.major_linker_version,
+ minor_linker_version: fields.minor_linker_version,
+ size_of_code: fields.size_of_code as u32,
+ size_of_initialized_data: fields.size_of_initialized_data as u32,
+ size_of_uninitialized_data: fields.size_of_uninitialized_data as u32,
+ address_of_entry_point: fields.address_of_entry_point as u32,
+ base_of_code: fields.base_of_code as u32,
+ }
+ }
+}
+
/// Standard fields magic number for 32-bit binary
pub const MAGIC_32: u16 = 0x10b;
/// Standard fields magic number for 64-bit binary
@@ -208,6 +239,36 @@ impl From<WindowsFields32> for WindowsFields {
}
}
+impl TryFrom<WindowsFields64> for WindowsFields32 {
+ type Error = crate::error::Error;
+
+ fn try_from(value: WindowsFields64) -> Result<Self, Self::Error> {
+ Ok(WindowsFields32 {
+ image_base: value.image_base.try_into()?,
+ section_alignment: value.section_alignment,
+ file_alignment: value.file_alignment,
+ major_operating_system_version: value.major_operating_system_version,
+ minor_operating_system_version: value.minor_operating_system_version,
+ major_image_version: value.major_image_version,
+ minor_image_version: value.minor_image_version,
+ major_subsystem_version: value.major_subsystem_version,
+ minor_subsystem_version: value.minor_subsystem_version,
+ win32_version_value: value.win32_version_value,
+ size_of_image: value.size_of_image,
+ size_of_headers: value.size_of_headers,
+ check_sum: value.check_sum,
+ subsystem: value.subsystem,
+ dll_characteristics: value.dll_characteristics,
+ size_of_stack_reserve: value.size_of_stack_reserve.try_into()?,
+ size_of_stack_commit: value.size_of_stack_commit.try_into()?,
+ size_of_heap_reserve: value.size_of_heap_reserve.try_into()?,
+ size_of_heap_commit: value.size_of_heap_commit.try_into()?,
+ loader_flags: value.loader_flags,
+ number_of_rva_and_sizes: value.number_of_rva_and_sizes,
+ })
+ }
+}
+
// impl From<WindowsFields32> for WindowsFields {
// fn from(windows: WindowsFields32) -> Self {
// WindowsFields {
@@ -289,6 +350,28 @@ impl<'a> ctx::TryFromCtx<'a, Endian> for OptionalHeader {
}
}
+impl ctx::TryIntoCtx<scroll::Endian> for OptionalHeader {
+ type Error = error::Error;
+
+ fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> {
+ let offset = &mut 0;
+ match self.standard_fields.magic {
+ MAGIC_32 => {
+ bytes.gwrite_with::<StandardFields32>(self.standard_fields.into(), offset, ctx)?;
+ bytes.gwrite_with(WindowsFields32::try_from(self.windows_fields)?, offset, ctx)?;
+ bytes.gwrite_with(self.data_directories, offset, ctx)?;
+ }
+ MAGIC_64 => {
+ bytes.gwrite_with::<StandardFields64>(self.standard_fields.into(), offset, ctx)?;
+ bytes.gwrite_with(self.windows_fields, offset, ctx)?;
+ bytes.gwrite_with(self.data_directories, offset, ctx)?;
+ }
+ _ => panic!(),
+ }
+ Ok(*offset)
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/third_party/rust/goblin/src/pe/options.rs b/third_party/rust/goblin/src/pe/options.rs
index 5fea632f75..4461a4c7bd 100644
--- a/third_party/rust/goblin/src/pe/options.rs
+++ b/third_party/rust/goblin/src/pe/options.rs
@@ -3,11 +3,19 @@
pub struct ParseOptions {
/// Wether the parser should resolve rvas or not. Default: true
pub resolve_rva: bool,
+ /// Whether or not to parse attribute certificates.
+ /// Set to false for in-memory representation, as the [loader does not map this info into
+ /// memory](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#other-contents-of-the-file).
+ /// For on-disk representations, leave as true. Default: true
+ pub parse_attribute_certificates: bool,
}
impl ParseOptions {
/// Returns a parse options structure with default values
pub fn default() -> Self {
- ParseOptions { resolve_rva: true }
+ ParseOptions {
+ resolve_rva: true,
+ parse_attribute_certificates: true,
+ }
}
}
diff --git a/third_party/rust/goblin/src/pe/section_table.rs b/third_party/rust/goblin/src/pe/section_table.rs
index 4491827f16..f49c2b689c 100644
--- a/third_party/rust/goblin/src/pe/section_table.rs
+++ b/third_party/rust/goblin/src/pe/section_table.rs
@@ -1,6 +1,8 @@
use crate::error::{self, Error};
use crate::pe::relocation;
+use alloc::borrow::Cow;
use alloc::string::{String, ToString};
+use alloc::vec::Vec;
use scroll::{ctx, Pread, Pwrite};
#[repr(C)]
@@ -79,6 +81,32 @@ impl SectionTable {
Ok(table)
}
+ pub fn data<'a, 'b: 'a>(&'a self, pe_bytes: &'b [u8]) -> error::Result<Option<Cow<[u8]>>> {
+ let section_start: usize = self.pointer_to_raw_data.try_into().map_err(|_| {
+ Error::Malformed(format!("Virtual address cannot fit in platform `usize`"))
+ })?;
+
+ // assert!(self.virtual_size <= self.size_of_raw_data);
+ // if vsize > size_of_raw_data, the section is zero padded.
+ let section_end: usize = section_start
+ + usize::try_from(self.size_of_raw_data).map_err(|_| {
+ Error::Malformed(format!("Virtual size cannot fit in platform `usize`"))
+ })?;
+
+ let original_bytes = pe_bytes.get(section_start..section_end).map(Cow::Borrowed);
+
+ if original_bytes.is_some() && self.virtual_size > self.size_of_raw_data {
+ let mut bytes: Vec<u8> = Vec::new();
+ bytes.resize(self.size_of_raw_data.try_into()?, 0);
+ bytes.copy_from_slice(&original_bytes.unwrap());
+ bytes.resize(self.virtual_size.try_into()?, 0);
+
+ Ok(Some(Cow::Owned(bytes)))
+ } else {
+ Ok(original_bytes)
+ }
+ }
+
pub fn name_offset(&self) -> error::Result<Option<usize>> {
// Based on https://github.com/llvm-mirror/llvm/blob/af7b1832a03ab6486c42a40d21695b2c03b2d8a3/lib/Object/COFFObjectFile.cpp#L1054
if self.name[0] == b'/' {
@@ -163,6 +191,15 @@ impl SectionTable {
let number = self.number_of_relocations as usize;
relocation::Relocations::parse(bytes, offset, number)
}
+
+ /// Tests if `another_section` on-disk ranges will collide.
+ pub fn overlaps_with(&self, another_section: &SectionTable) -> bool {
+ let self_end = self.pointer_to_raw_data + self.size_of_raw_data;
+ let another_end = another_section.pointer_to_raw_data + another_section.size_of_raw_data;
+
+ !((self_end <= another_section.pointer_to_raw_data)
+ || (another_end <= self.pointer_to_raw_data))
+ }
}
impl ctx::SizeWith<scroll::Endian> for SectionTable {
@@ -171,7 +208,7 @@ impl ctx::SizeWith<scroll::Endian> for SectionTable {
}
}
-impl ctx::TryIntoCtx<scroll::Endian> for SectionTable {
+impl ctx::TryIntoCtx<scroll::Endian> for &SectionTable {
type Error = error::Error;
fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> {
let offset = &mut 0;
@@ -189,7 +226,7 @@ impl ctx::TryIntoCtx<scroll::Endian> for SectionTable {
}
}
-impl ctx::IntoCtx<scroll::Endian> for SectionTable {
+impl ctx::IntoCtx<scroll::Endian> for &SectionTable {
fn into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) {
bytes.pwrite_with(self, 0, ctx).unwrap();
}
diff --git a/third_party/rust/goblin/src/pe/symbol.rs b/third_party/rust/goblin/src/pe/symbol.rs
index b2ced808a1..e5aba25bec 100644
--- a/third_party/rust/goblin/src/pe/symbol.rs
+++ b/third_party/rust/goblin/src/pe/symbol.rs
@@ -412,6 +412,7 @@ pub struct AuxSectionDefinition {
}
/// A COFF symbol table.
+// TODO: #[derive(Pwrite)] produce unparseable tokens
pub struct SymbolTable<'a> {
symbols: &'a [u8],
}
@@ -483,6 +484,14 @@ impl<'a> SymbolTable<'a> {
}
}
+impl<'a> ctx::TryIntoCtx<scroll::Endian> for SymbolTable<'a> {
+ type Error = error::Error;
+
+ fn try_into_ctx(self, bytes: &mut [u8], _ctx: scroll::Endian) -> Result<usize, Self::Error> {
+ bytes.pwrite(self.symbols, 0).map_err(|err| err.into())
+ }
+}
+
impl<'a> Debug for SymbolTable<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("SymbolTable")
diff --git a/third_party/rust/goblin/src/pe/utils.rs b/third_party/rust/goblin/src/pe/utils.rs
index 07a320d57b..289ccc529b 100644
--- a/third_party/rust/goblin/src/pe/utils.rs
+++ b/third_party/rust/goblin/src/pe/utils.rs
@@ -1,5 +1,6 @@
use crate::error;
use alloc::string::ToString;
+use alloc::vec::Vec;
use scroll::Pread;
use super::options;
@@ -178,3 +179,18 @@ where
let result: T = bytes.pread_with(offset, scroll::LE)?;
Ok(result)
}
+
+pub(crate) fn pad(length: usize, alignment: Option<usize>) -> Option<Vec<u8>> {
+ match alignment {
+ Some(alignment) => {
+ let overhang = length % alignment;
+ if overhang != 0 {
+ let repeat = alignment - overhang;
+ Some(vec![0u8; repeat])
+ } else {
+ None
+ }
+ }
+ None => None,
+ }
+}
diff --git a/third_party/rust/goblin/src/strtab.rs b/third_party/rust/goblin/src/strtab.rs
index dc7b8080f0..487d8d4279 100644
--- a/third_party/rust/goblin/src/strtab.rs
+++ b/third_party/rust/goblin/src/strtab.rs
@@ -34,6 +34,11 @@ impl<'a> Strtab<'a> {
Self::from_slice_unparsed(bytes, 0, bytes.len(), delim)
}
+ /// Returns the length of this `Strtab` in bytes
+ pub fn len(&self) -> usize {
+ self.bytes.len()
+ }
+
/// Creates a `Strtab` directly without bounds check and without parsing it.
///
/// This is potentially unsafe and should only be used if `feature = "alloc"` is disabled.
diff --git a/third_party/rust/naga/.cargo-checksum.json b/third_party/rust/naga/.cargo-checksum.json
index 8ac93423af..8a1163971f 100644
--- a/third_party/rust/naga/.cargo-checksum.json
+++ b/third_party/rust/naga/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{".cargo/config.toml":"d7389d2a0c08ec72b79e83a3c76980903e3f9123625c32e69c798721193e2e74","CHANGELOG.md":"6b2c4d8dfd8c537811c33744703b4c03fa8aa15f5fab8f0e2be76f597cb7e273","Cargo.toml":"eab8de21e33a65dbcddbdfd97fca5b98d5cf684f288ac32cf6177a761e44a2e0","README.md":"daa4717a9952b52604bbc3a55af902b252adeacc779991317d8f301f07faa94b","benches/criterion.rs":"f45e38b26e1323e934d32623572ff5395a53fed06f760eb1e07b22ed07858a38","src/arena.rs":"33ed2ec7b36429b133ed2a7de6fb9735827f69ea8b6c2ce97f64746a24a5bf36","src/back/dot/mod.rs":"a40050a73ac00c8fa43dd0b45a84fca6959d28c8c99ab3046b01f33c02f8c8f4","src/back/glsl/features.rs":"3d12147d201aaed746a94741356458a435a1ff7cf30b66baf44ba0b8dfe4b0ca","src/back/glsl/keywords.rs":"1546facbaddf696602102f32e47db7afc875f8ca3fbccc2122e0bcc45e022b53","src/back/glsl/mod.rs":"9e8b34a09401744a2ad4deae4d4863bd0be1d7d5da6ca72a98ca80fe0e3dfde6","src/back/hlsl/conv.rs":"2d7a8e7753b8fb21659e582612eea82e42e353abd23df719de450074a4da731e","src/back/hlsl/help.rs":"06da97ea0d58e2b94823ca1dae67a8611be6d5d047649b1d83755acb4c110808","src/back/hlsl/keywords.rs":"a7164690a4da866e6bfb18ced20e32cc8c42dd7387e0e84addf0c2674f529cf5","src/back/hlsl/mod.rs":"2f5296c45a2147093cae17250321580e7f01c57f907e529d19521eccd0cd4147","src/back/hlsl/storage.rs":"2c2a0071cafe487a398e396dddc85bdb319b1a5d74c097d529078e247a904359","src/back/hlsl/writer.rs":"36f0410edf9c0a8295e4916ca0e7a4e98cd170fcd5ecf6826df6051bef003b9c","src/back/mod.rs":"b941caed50c086f49d25e76228d247ba6c2da6dbeea18d968c02dc68bb97f409","src/back/msl/keywords.rs":"e6a4ef77363f995de1f8079c0b8591497cbf9520c5d3b2d41c7e1f483e8abd24","src/back/msl/mod.rs":"15fdb90b8cd2b98273b22b9569fc322eb473fd135865eef82cc615d27320d779","src/back/msl/sampler.rs":"9b01d68669e12ff7123243284b85e1a9d2c4d49140bd74ca32dedc007cbf15af","src/back/msl/writer.rs":"27e8604a5d11391b91b328f420e93f7cf475364a783b3dc5ba8bb720f17d9d86","src/back/spv/block.rs":"e2326e10cc8ca64398636c1b27166b406611006ffc2388c20fca4a271d609afe","src/back/spv/helpers.rs":"a4e260130f39c7345decec40dadf1e94419c8f6d236ce7a53b5300aa72952a1b","src/back/spv/image.rs":"e4b982ce430e17881d6370191d849dbe6bb8f6d86f4896815eb1736e43b4e302","src/back/spv/index.rs":"26611dd50df5cfd214900e19415f5374dd301d3b7d3bfedbc5ec0f254328287a","src/back/spv/instructions.rs":"d0ced535fdec49323105a7d6ee40a8ed6b4966ac5f0f40b062f0eb11a531b106","src/back/spv/layout.rs":"e263de53cd2f9a03ad94b82b434ce636609bc1ed435a2d1132951663bfaa8ebd","src/back/spv/mod.rs":"31b0229f59b5784b57851fcf6325095add58af6de3afa85d518a4e266c4b99a9","src/back/spv/ray.rs":"a34bf6b26d873f7270caa45841d9ef291aca8d9732ecd086b14d8856038e1e41","src/back/spv/recyclable.rs":"114db0ea12774d6514f995d07295cb9a42631ab75165fc60980c10e9b5ecb832","src/back/spv/selection.rs":"81e404abfa0a977f7c1f76ccb37a78d13ccadbda229048dad53cc67687cc39db","src/back/spv/writer.rs":"e90f76d7de82429db5d375b679de5dd73f205e245c97622924271995db373d1e","src/back/wgsl/mod.rs":"2dd12bbea9ace835850192bb68c5760953da6bac6a636073d1eca19381c0c0b6","src/back/wgsl/writer.rs":"96795df390cffade8ca27baea88ab29592d7e425c4351a9e2591d4f4ecdc73f3","src/block.rs":"c69089e5bbb6de6ba24efb15b21d5d434fcabfbc4d48feae948d2a4da135aae7","src/compact/expressions.rs":"7a4c916282a5b484519ed29ab451c7b595d8dea73c83c5c2cf7efc6fbc648fda","src/compact/functions.rs":"174bd9167ecf6353afb8c36d365ba3f9b483233eb4bacf578e50183c7433aa15","src/compact/handle_set_map.rs":"817c5193352d5fd6a61a5c970daba23224e14a65aea15f8f1c8679c99f834ca2","src/compact/mod.rs":"f1a606e8732f3c5837ab40ba5569eb1687336ef412f7f4b6cc348dd52b8076b3","src/compact/statements.rs":"4df33ee9589300e769e75c674bdc30578e93704ec3eb2aabc7132121745b55c8","src/compact/types.rs":"18343f2ca2c123eea2531cffc1d54a7798797caccecaa1f9b8c4fd5dd6ca1a05","src/front/glsl/ast.rs":"a4615f0c52b0dc9fdb07f816b4534c1ca547c2d176008ca86d66f9e6874f227d","src/front/glsl/builtins.rs":"d35501d5b42b61c261da24436b82eafdf96371b1600d148648d90d041f736ae4","src/front/glsl/context.rs":"066203c24ff5bc6154aa671f4492b5e8dfede8b57ef886f093cc95470d66411b","src/front/glsl/error.rs":"cca4a3aa9de2808952ff68c183755df5fdf6a7cb81f170ba747795176c0342fd","src/front/glsl/functions.rs":"b420be6b54195e9cdabdf76bb854e3e1f3be6542c6c129656fd0b1bd900dcebd","src/front/glsl/lex.rs":"08736ae8beb955da5b0e6e3e0f45995a824995f7096d516a2910417e9c7afa32","src/front/glsl/mod.rs":"c6e81710ae94a52583ba6f2a80a505d6bcd6ea6552008b80b27539af48838df1","src/front/glsl/offset.rs":"9358602ca4f9ef21d5066d674dae757bf88fdf5c289c4360534354d13bd41dc0","src/front/glsl/parser.rs":"fe5291512db412b33b6c09d5b3dcf7c54ff6ec55b47f0a078dcc11695e78471d","src/front/glsl/parser/declarations.rs":"d637cc52e553910a2e97b70b3366c15aefbe737f413adb11c27efd184c1fbf9d","src/front/glsl/parser/expressions.rs":"520cfc9402d5fbd48e52ef1d36562c6b74794c09ec33ec1ebb10aa48d129b66f","src/front/glsl/parser/functions.rs":"75aedcea4133bc4aba06ef49b1697eac96cc28d191e9830689fc4a6c0c4856eb","src/front/glsl/parser/types.rs":"aeb97e1a5fb03205cd5630c29da59d81a376ce9a83a603b62b037e63ad948e88","src/front/glsl/parser_tests.rs":"bfd4dff2580f4369a57edbcace47d23e2666751ffc3ab55f8d7dfe01f1a66311","src/front/glsl/token.rs":"c25c489b152ee2d445ace3c2046473abe64d558b8d27fa08709110e58718b6ac","src/front/glsl/types.rs":"58c9cf3d570dff8cb68f2931faf5b18e875e510741bf035ec10b9ff6df27c5d8","src/front/glsl/variables.rs":"fb2a09e386b6e98ca9fb8fb744afac1e8b19d1b67c6ede5c474e3ba860d3d4cb","src/front/interpolator.rs":"9b6ca498d5fbd9bc1515510a04e303a00b324121d7285da3c955cfe18eb4224c","src/front/mod.rs":"77acd7fb71d004969d1ee69fc728647f03242762988786c4e15fadf8315600af","src/front/spv/convert.rs":"dccc6671e6a4a7f1139aecdf979faa3689609081af5fa2cbbd6a2e8c4128c004","src/front/spv/error.rs":"6438aac57cfcf5d3858dd7652ccda1967a3123c6374f1cab829092b00549f70f","src/front/spv/function.rs":"1acb7bdd34ecfe08c6f4b4d06c2a0ea74aaf9975352e8804e3e4fab90745132f","src/front/spv/image.rs":"5d55cfbf6752732a594114cd09a9a207216e1ee85d8f2c9bc4310217a55ea321","src/front/spv/mod.rs":"22d0de7c43c42279e788144ff806cadfe3a3ea7923d961d11740af22492c4087","src/front/spv/null.rs":"e1446d99d04c76a9c3bbd24dd9b20c4711ce8a918a9b403be6cccbde1175b3b4","src/front/type_gen.rs":"b4f1df23380e06c9fdad4140810ce96ab041dbb1d371a07045b4e0069aa8ba55","src/front/wgsl/error.rs":"e1efd61062a5eb5f7e0413dc05d17abdbe0679c08f2fbdb7478e2b6e8dd13b25","src/front/wgsl/index.rs":"2b9a4929a46bd822d3ed6f9a150e24d437e5bdca8293eb748aebe80ce7e74153","src/front/wgsl/lower/construction.rs":"92342e27f5bdeb598e178799b74aa610788549c19a49fe0ae8914916bfa3c7be","src/front/wgsl/lower/conversion.rs":"961b19bf8ddd4667c6caf854a1889f3d6477757f4125538c3e9ca7d730975dd7","src/front/wgsl/lower/mod.rs":"08eece7a5460e414e2f8398cec96f12a8b9f6a457270426d8e4f045b62290d1f","src/front/wgsl/mod.rs":"02b194a0a29ef7281f71b424564e18ada4a8b1a0d8c26ec40b6be195bd4c4904","src/front/wgsl/parse/ast.rs":"c7eaae40179f0889f2b142d3b31968cbfab6d3cfe02e425912c6da8dadac51df","src/front/wgsl/parse/conv.rs":"9b2a06b5fd577e1881b2212e1d675d3aefe4d1fee99a17b5f7b07c36913e8186","src/front/wgsl/parse/lexer.rs":"17db87d0017f8f9a80fa151b8545f04e1b40c4e5feef6197f4a117efa03488bf","src/front/wgsl/parse/mod.rs":"3b4895a2baf91c719b95f0afb6441ffac2036c2a9ff817e633882fd257afcc38","src/front/wgsl/parse/number.rs":"dafd3d8651cfa1389cb359d76d39bd689e54f8d5025aa23e06c6edd871369efd","src/front/wgsl/tests.rs":"7a0a083a5b66af8e7d4b1a02401b27f077eb72d07181b610693f35b11f107c6c","src/front/wgsl/to_wgsl.rs":"2e2e30d86b07f209b866e530d3a882803bf28b39ce379052561a749f628e8e28","src/keywords/mod.rs":"0138f3931f8af0b0a05174549d0fd2152945b027dc3febefc1bbd676581d2e45","src/keywords/wgsl.rs":"c648ac44241ad55c8c8bad3d8f1bab973d11ddb9c380dcca369b735ed3975309","src/lib.rs":"f2072172957699d4282f247c452d8d8f0a0da08b9d78b279ee010296669d28d8","src/proc/constant_evaluator.rs":"bea5d259dbc4d9f9dacf3717dcb17a0774a22f1b3e5251b7e5b6991330ed3057","src/proc/emitter.rs":"39ac886c651e2ad33c06a676a7e4826a0e93de0af660c01e8e4b1f7406742f88","src/proc/index.rs":"f4250f6944c2b631e8140979024e8deb86fa8d5352d8641ba954a388b2c0940e","src/proc/layouter.rs":"b3d061c87424f36981c902716f37ab7b72f2bb2d0c2d7e900c51149318ea1a0a","src/proc/mod.rs":"4be5dcb137147cd8182a291f90959c46f1681c2d2c7da9e63f702a5f84c8809d","src/proc/namer.rs":"7328fac41e40890c64c7ee2fa985a4395424f18b08d30f30ca2583fdabd2fd35","src/proc/terminator.rs":"13c59bf00f5b26171d971effc421091f5e00dedddd246c2daa44fe65aeda060a","src/proc/typifier.rs":"99de19270d01c12ec49d14323aa1d9b8774f1ee715804af7235deff70739ba3d","src/span.rs":"6560599f20b8bc2de746ee9fd6b05c32bb630af914fce8845d84fdc72f9a636c","src/valid/analyzer.rs":"8472b98f16a4a4a0fa7079197db25696f77ef3e1602a7cddea1930daebd27917","src/valid/compose.rs":"83e4c09c39f853cf085b83b87e48b3db571da619132960d3ec954ebdfb0a74f2","src/valid/expression.rs":"7a8d5f74677c627dee3e15d223e83453ea7f6567dc806fcdfeebd32081012779","src/valid/function.rs":"40754e51906b053becdd8813b189fe709b7693c08babd28b5d3f5c576475b171","src/valid/handles.rs":"0878915e67b16d7c41cf8245d9ab3b3f4a604e7d4e87527ea40e03efcbf1f74a","src/valid/interface.rs":"32ef8e4665106b5c71540833e17ee9cd1dde5a900c9b81f61e0b7b8192c4aaf2","src/valid/mod.rs":"3b2772c88561aeb4dc8f5ce0d8ed5169bcdf5f7db04a62aaf22d04c171cb4f35","src/valid/type.rs":"61357577fa2dffa9c7326504f5c1a5fe7c44afd5d6f439c2354b390c6783fc86"},"package":null} \ No newline at end of file
+{"files":{".cargo/config.toml":"d7389d2a0c08ec72b79e83a3c76980903e3f9123625c32e69c798721193e2e74","CHANGELOG.md":"6b2c4d8dfd8c537811c33744703b4c03fa8aa15f5fab8f0e2be76f597cb7e273","Cargo.toml":"dd06bf980497dcb0d35c2c33d845ba80cb7a2f78bd0e824e3d6be6b950b8088b","README.md":"468211bb7457683510ff862f31f27e3ba07514f851a4390570d0e9259cbeaf6f","benches/criterion.rs":"f45e38b26e1323e934d32623572ff5395a53fed06f760eb1e07b22ed07858a38","src/arena.rs":"33ed2ec7b36429b133ed2a7de6fb9735827f69ea8b6c2ce97f64746a24a5bf36","src/back/dot/mod.rs":"a40050a73ac00c8fa43dd0b45a84fca6959d28c8c99ab3046b01f33c02f8c8f4","src/back/glsl/features.rs":"3d12147d201aaed746a94741356458a435a1ff7cf30b66baf44ba0b8dfe4b0ca","src/back/glsl/keywords.rs":"1546facbaddf696602102f32e47db7afc875f8ca3fbccc2122e0bcc45e022b53","src/back/glsl/mod.rs":"9e8b34a09401744a2ad4deae4d4863bd0be1d7d5da6ca72a98ca80fe0e3dfde6","src/back/hlsl/conv.rs":"2d7a8e7753b8fb21659e582612eea82e42e353abd23df719de450074a4da731e","src/back/hlsl/help.rs":"06da97ea0d58e2b94823ca1dae67a8611be6d5d047649b1d83755acb4c110808","src/back/hlsl/keywords.rs":"a7164690a4da866e6bfb18ced20e32cc8c42dd7387e0e84addf0c2674f529cf5","src/back/hlsl/mod.rs":"2f5296c45a2147093cae17250321580e7f01c57f907e529d19521eccd0cd4147","src/back/hlsl/storage.rs":"2c2a0071cafe487a398e396dddc85bdb319b1a5d74c097d529078e247a904359","src/back/hlsl/writer.rs":"36f0410edf9c0a8295e4916ca0e7a4e98cd170fcd5ecf6826df6051bef003b9c","src/back/mod.rs":"6101d92bff1c5b7fe3e503188554dfdcce56d4eb610237e0126bf6d8c4e1a85d","src/back/msl/keywords.rs":"e6a4ef77363f995de1f8079c0b8591497cbf9520c5d3b2d41c7e1f483e8abd24","src/back/msl/mod.rs":"15fdb90b8cd2b98273b22b9569fc322eb473fd135865eef82cc615d27320d779","src/back/msl/sampler.rs":"9b01d68669e12ff7123243284b85e1a9d2c4d49140bd74ca32dedc007cbf15af","src/back/msl/writer.rs":"27e8604a5d11391b91b328f420e93f7cf475364a783b3dc5ba8bb720f17d9d86","src/back/spv/block.rs":"e2326e10cc8ca64398636c1b27166b406611006ffc2388c20fca4a271d609afe","src/back/spv/helpers.rs":"a4e260130f39c7345decec40dadf1e94419c8f6d236ce7a53b5300aa72952a1b","src/back/spv/image.rs":"e4b982ce430e17881d6370191d849dbe6bb8f6d86f4896815eb1736e43b4e302","src/back/spv/index.rs":"54bb90176be12a9a243099343e62b8dd35dd019d237d4d7dcdb9f0da7747df09","src/back/spv/instructions.rs":"d0ced535fdec49323105a7d6ee40a8ed6b4966ac5f0f40b062f0eb11a531b106","src/back/spv/layout.rs":"e263de53cd2f9a03ad94b82b434ce636609bc1ed435a2d1132951663bfaa8ebd","src/back/spv/mod.rs":"bd0589e0f6ce33b294f1f907dd5a1c03b46429b1b0d77986f8bb98ad317b11e3","src/back/spv/ray.rs":"a34bf6b26d873f7270caa45841d9ef291aca8d9732ecd086b14d8856038e1e41","src/back/spv/recyclable.rs":"114db0ea12774d6514f995d07295cb9a42631ab75165fc60980c10e9b5ecb832","src/back/spv/selection.rs":"81e404abfa0a977f7c1f76ccb37a78d13ccadbda229048dad53cc67687cc39db","src/back/spv/writer.rs":"87460df9918c1f104213394c0532e384ae282d132fdb7e09823eeb10c2392e3d","src/back/wgsl/mod.rs":"2dd12bbea9ace835850192bb68c5760953da6bac6a636073d1eca19381c0c0b6","src/back/wgsl/writer.rs":"96795df390cffade8ca27baea88ab29592d7e425c4351a9e2591d4f4ecdc73f3","src/block.rs":"c69089e5bbb6de6ba24efb15b21d5d434fcabfbc4d48feae948d2a4da135aae7","src/compact/expressions.rs":"7a4c916282a5b484519ed29ab451c7b595d8dea73c83c5c2cf7efc6fbc648fda","src/compact/functions.rs":"174bd9167ecf6353afb8c36d365ba3f9b483233eb4bacf578e50183c7433aa15","src/compact/handle_set_map.rs":"817c5193352d5fd6a61a5c970daba23224e14a65aea15f8f1c8679c99f834ca2","src/compact/mod.rs":"f1a606e8732f3c5837ab40ba5569eb1687336ef412f7f4b6cc348dd52b8076b3","src/compact/statements.rs":"4df33ee9589300e769e75c674bdc30578e93704ec3eb2aabc7132121745b55c8","src/compact/types.rs":"18343f2ca2c123eea2531cffc1d54a7798797caccecaa1f9b8c4fd5dd6ca1a05","src/front/glsl/ast.rs":"a4615f0c52b0dc9fdb07f816b4534c1ca547c2d176008ca86d66f9e6874f227d","src/front/glsl/builtins.rs":"d35501d5b42b61c261da24436b82eafdf96371b1600d148648d90d041f736ae4","src/front/glsl/context.rs":"066203c24ff5bc6154aa671f4492b5e8dfede8b57ef886f093cc95470d66411b","src/front/glsl/error.rs":"cca4a3aa9de2808952ff68c183755df5fdf6a7cb81f170ba747795176c0342fd","src/front/glsl/functions.rs":"b420be6b54195e9cdabdf76bb854e3e1f3be6542c6c129656fd0b1bd900dcebd","src/front/glsl/lex.rs":"08736ae8beb955da5b0e6e3e0f45995a824995f7096d516a2910417e9c7afa32","src/front/glsl/mod.rs":"c6e81710ae94a52583ba6f2a80a505d6bcd6ea6552008b80b27539af48838df1","src/front/glsl/offset.rs":"9358602ca4f9ef21d5066d674dae757bf88fdf5c289c4360534354d13bd41dc0","src/front/glsl/parser.rs":"fe5291512db412b33b6c09d5b3dcf7c54ff6ec55b47f0a078dcc11695e78471d","src/front/glsl/parser/declarations.rs":"d637cc52e553910a2e97b70b3366c15aefbe737f413adb11c27efd184c1fbf9d","src/front/glsl/parser/expressions.rs":"520cfc9402d5fbd48e52ef1d36562c6b74794c09ec33ec1ebb10aa48d129b66f","src/front/glsl/parser/functions.rs":"75aedcea4133bc4aba06ef49b1697eac96cc28d191e9830689fc4a6c0c4856eb","src/front/glsl/parser/types.rs":"aeb97e1a5fb03205cd5630c29da59d81a376ce9a83a603b62b037e63ad948e88","src/front/glsl/parser_tests.rs":"bfd4dff2580f4369a57edbcace47d23e2666751ffc3ab55f8d7dfe01f1a66311","src/front/glsl/token.rs":"c25c489b152ee2d445ace3c2046473abe64d558b8d27fa08709110e58718b6ac","src/front/glsl/types.rs":"58c9cf3d570dff8cb68f2931faf5b18e875e510741bf035ec10b9ff6df27c5d8","src/front/glsl/variables.rs":"bfed08368b56dc1f55fe487b240cf7b8e09443e508ea410cfac48df477591eee","src/front/interpolator.rs":"9b6ca498d5fbd9bc1515510a04e303a00b324121d7285da3c955cfe18eb4224c","src/front/mod.rs":"77acd7fb71d004969d1ee69fc728647f03242762988786c4e15fadf8315600af","src/front/spv/convert.rs":"dccc6671e6a4a7f1139aecdf979faa3689609081af5fa2cbbd6a2e8c4128c004","src/front/spv/error.rs":"6438aac57cfcf5d3858dd7652ccda1967a3123c6374f1cab829092b00549f70f","src/front/spv/function.rs":"1acb7bdd34ecfe08c6f4b4d06c2a0ea74aaf9975352e8804e3e4fab90745132f","src/front/spv/image.rs":"5d55cfbf6752732a594114cd09a9a207216e1ee85d8f2c9bc4310217a55ea321","src/front/spv/mod.rs":"22d0de7c43c42279e788144ff806cadfe3a3ea7923d961d11740af22492c4087","src/front/spv/null.rs":"e1446d99d04c76a9c3bbd24dd9b20c4711ce8a918a9b403be6cccbde1175b3b4","src/front/type_gen.rs":"b4f1df23380e06c9fdad4140810ce96ab041dbb1d371a07045b4e0069aa8ba55","src/front/wgsl/error.rs":"e1efd61062a5eb5f7e0413dc05d17abdbe0679c08f2fbdb7478e2b6e8dd13b25","src/front/wgsl/index.rs":"2b9a4929a46bd822d3ed6f9a150e24d437e5bdca8293eb748aebe80ce7e74153","src/front/wgsl/lower/construction.rs":"92342e27f5bdeb598e178799b74aa610788549c19a49fe0ae8914916bfa3c7be","src/front/wgsl/lower/conversion.rs":"961b19bf8ddd4667c6caf854a1889f3d6477757f4125538c3e9ca7d730975dd7","src/front/wgsl/lower/mod.rs":"08eece7a5460e414e2f8398cec96f12a8b9f6a457270426d8e4f045b62290d1f","src/front/wgsl/mod.rs":"02b194a0a29ef7281f71b424564e18ada4a8b1a0d8c26ec40b6be195bd4c4904","src/front/wgsl/parse/ast.rs":"c7eaae40179f0889f2b142d3b31968cbfab6d3cfe02e425912c6da8dadac51df","src/front/wgsl/parse/conv.rs":"9b2a06b5fd577e1881b2212e1d675d3aefe4d1fee99a17b5f7b07c36913e8186","src/front/wgsl/parse/lexer.rs":"17db87d0017f8f9a80fa151b8545f04e1b40c4e5feef6197f4a117efa03488bf","src/front/wgsl/parse/mod.rs":"3b4895a2baf91c719b95f0afb6441ffac2036c2a9ff817e633882fd257afcc38","src/front/wgsl/parse/number.rs":"dafd3d8651cfa1389cb359d76d39bd689e54f8d5025aa23e06c6edd871369efd","src/front/wgsl/tests.rs":"7a0a083a5b66af8e7d4b1a02401b27f077eb72d07181b610693f35b11f107c6c","src/front/wgsl/to_wgsl.rs":"2e2e30d86b07f209b866e530d3a882803bf28b39ce379052561a749f628e8e28","src/keywords/mod.rs":"0138f3931f8af0b0a05174549d0fd2152945b027dc3febefc1bbd676581d2e45","src/keywords/wgsl.rs":"c648ac44241ad55c8c8bad3d8f1bab973d11ddb9c380dcca369b735ed3975309","src/lib.rs":"f2072172957699d4282f247c452d8d8f0a0da08b9d78b279ee010296669d28d8","src/proc/constant_evaluator.rs":"bea5d259dbc4d9f9dacf3717dcb17a0774a22f1b3e5251b7e5b6991330ed3057","src/proc/emitter.rs":"39ac886c651e2ad33c06a676a7e4826a0e93de0af660c01e8e4b1f7406742f88","src/proc/index.rs":"f4250f6944c2b631e8140979024e8deb86fa8d5352d8641ba954a388b2c0940e","src/proc/layouter.rs":"b3d061c87424f36981c902716f37ab7b72f2bb2d0c2d7e900c51149318ea1a0a","src/proc/mod.rs":"4be5dcb137147cd8182a291f90959c46f1681c2d2c7da9e63f702a5f84c8809d","src/proc/namer.rs":"7328fac41e40890c64c7ee2fa985a4395424f18b08d30f30ca2583fdabd2fd35","src/proc/terminator.rs":"13c59bf00f5b26171d971effc421091f5e00dedddd246c2daa44fe65aeda060a","src/proc/typifier.rs":"99de19270d01c12ec49d14323aa1d9b8774f1ee715804af7235deff70739ba3d","src/span.rs":"47d92ea25ce7178ad903ac4f5acd0bc40ebd90de1e470e538a2aa3063d980890","src/valid/analyzer.rs":"65ed1487c5a169688714a7386046b30ec380eaf44e5500f366b9c9a308f59464","src/valid/compose.rs":"83e4c09c39f853cf085b83b87e48b3db571da619132960d3ec954ebdfb0a74f2","src/valid/expression.rs":"7a8d5f74677c627dee3e15d223e83453ea7f6567dc806fcdfeebd32081012779","src/valid/function.rs":"40754e51906b053becdd8813b189fe709b7693c08babd28b5d3f5c576475b171","src/valid/handles.rs":"0878915e67b16d7c41cf8245d9ab3b3f4a604e7d4e87527ea40e03efcbf1f74a","src/valid/interface.rs":"32ef8e4665106b5c71540833e17ee9cd1dde5a900c9b81f61e0b7b8192c4aaf2","src/valid/mod.rs":"3b2772c88561aeb4dc8f5ce0d8ed5169bcdf5f7db04a62aaf22d04c171cb4f35","src/valid/type.rs":"635a4f7b2681d2cc7645a5589640098efb3cdaf3c492d79924c9e80c806c7890"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/naga/Cargo.toml b/third_party/rust/naga/Cargo.toml
index dc2434e4b0..04375a9960 100644
--- a/third_party/rust/naga/Cargo.toml
+++ b/third_party/rust/naga/Cargo.toml
@@ -11,7 +11,7 @@
[package]
edition = "2021"
-rust-version = "1.70"
+rust-version = "1.74"
name = "naga"
version = "0.19.0"
authors = ["gfx-rs developers"]
@@ -97,7 +97,7 @@ optional = true
[dev-dependencies]
bincode = "1"
diff = "0.1"
-env_logger = "0.10"
+env_logger = "0.11"
ron = "0.8.0"
[dev-dependencies.hlsl-snapshots]
diff --git a/third_party/rust/naga/README.md b/third_party/rust/naga/README.md
index b7f352fc91..0e07d40496 100644
--- a/third_party/rust/naga/README.md
+++ b/third_party/rust/naga/README.md
@@ -4,7 +4,7 @@
[![Crates.io](https://img.shields.io/crates/v/naga.svg?label=naga)](https://crates.io/crates/naga)
[![Docs.rs](https://docs.rs/naga/badge.svg)](https://docs.rs/naga)
[![Build Status](https://github.com/gfx-rs/naga/workflows/pipeline/badge.svg)](https://github.com/gfx-rs/naga/actions)
-![MSRV](https://img.shields.io/badge/rustc-1.70+-blue.svg)
+![MSRV](https://img.shields.io/badge/rustc-1.74+-blue.svg)
[![codecov.io](https://codecov.io/gh/gfx-rs/naga/branch/master/graph/badge.svg?token=9VOKYO8BM2)](https://codecov.io/gh/gfx-rs/naga)
The shader translation library for the needs of [wgpu](https://github.com/gfx-rs/wgpu).
@@ -42,7 +42,7 @@ First, install `naga-cli` from crates.io or directly from GitHub.
cargo install naga-cli
# development version
-cargo install naga-cli --git https://github.com/gfx-rs/naga.git
+cargo install naga-cli --git https://github.com/gfx-rs/wgpu.git
```
Then, you can run `naga` command.
diff --git a/third_party/rust/naga/src/back/mod.rs b/third_party/rust/naga/src/back/mod.rs
index 8100b930e9..c8f091decb 100644
--- a/third_party/rust/naga/src/back/mod.rs
+++ b/third_party/rust/naga/src/back/mod.rs
@@ -16,14 +16,19 @@ pub mod spv;
#[cfg(feature = "wgsl-out")]
pub mod wgsl;
-const COMPONENTS: &[char] = &['x', 'y', 'z', 'w'];
-const INDENT: &str = " ";
-const BAKE_PREFIX: &str = "_e";
+/// Names of vector components.
+pub const COMPONENTS: &[char] = &['x', 'y', 'z', 'w'];
+/// Indent for backends.
+pub const INDENT: &str = " ";
+/// Prefix used for baking.
+pub const BAKE_PREFIX: &str = "_e";
-type NeedBakeExpressions = crate::FastHashSet<crate::Handle<crate::Expression>>;
+/// Expressions that need baking.
+pub type NeedBakeExpressions = crate::FastHashSet<crate::Handle<crate::Expression>>;
+/// Indentation level.
#[derive(Clone, Copy)]
-struct Level(usize);
+pub struct Level(pub usize);
impl Level {
const fn next(&self) -> Self {
@@ -52,7 +57,7 @@ impl std::fmt::Display for Level {
/// [`EntryPoint`]: crate::EntryPoint
/// [`Module`]: crate::Module
/// [`Module::entry_points`]: crate::Module::entry_points
-enum FunctionType {
+pub enum FunctionType {
/// A regular function.
Function(crate::Handle<crate::Function>),
/// An [`EntryPoint`], and its index in [`Module::entry_points`].
@@ -63,7 +68,8 @@ enum FunctionType {
}
impl FunctionType {
- fn is_compute_entry_point(&self, module: &crate::Module) -> bool {
+ /// Returns true if the function is an entry point for a compute shader.
+ pub fn is_compute_entry_point(&self, module: &crate::Module) -> bool {
match *self {
FunctionType::EntryPoint(index) => {
module.entry_points[index as usize].stage == crate::ShaderStage::Compute
@@ -74,19 +80,20 @@ impl FunctionType {
}
/// Helper structure that stores data needed when writing the function
-struct FunctionCtx<'a> {
+pub struct FunctionCtx<'a> {
/// The current function being written
- ty: FunctionType,
+ pub ty: FunctionType,
/// Analysis about the function
- info: &'a crate::valid::FunctionInfo,
+ pub info: &'a crate::valid::FunctionInfo,
/// The expression arena of the current function being written
- expressions: &'a crate::Arena<crate::Expression>,
+ pub expressions: &'a crate::Arena<crate::Expression>,
/// Map of expressions that have associated variable names
- named_expressions: &'a crate::NamedExpressions,
+ pub named_expressions: &'a crate::NamedExpressions,
}
impl FunctionCtx<'_> {
- fn resolve_type<'a>(
+ /// Helper method that resolves a type of a given expression.
+ pub fn resolve_type<'a>(
&'a self,
handle: crate::Handle<crate::Expression>,
types: &'a crate::UniqueArena<crate::Type>,
@@ -95,7 +102,10 @@ impl FunctionCtx<'_> {
}
/// Helper method that generates a [`NameKey`](crate::proc::NameKey) for a local in the current function
- const fn name_key(&self, local: crate::Handle<crate::LocalVariable>) -> crate::proc::NameKey {
+ pub const fn name_key(
+ &self,
+ local: crate::Handle<crate::LocalVariable>,
+ ) -> crate::proc::NameKey {
match self.ty {
FunctionType::Function(handle) => crate::proc::NameKey::FunctionLocal(handle, local),
FunctionType::EntryPoint(idx) => crate::proc::NameKey::EntryPointLocal(idx, local),
@@ -106,7 +116,7 @@ impl FunctionCtx<'_> {
///
/// # Panics
/// - If the function arguments are less or equal to `arg`
- const fn argument_key(&self, arg: u32) -> crate::proc::NameKey {
+ pub const fn argument_key(&self, arg: u32) -> crate::proc::NameKey {
match self.ty {
FunctionType::Function(handle) => crate::proc::NameKey::FunctionArgument(handle, arg),
FunctionType::EntryPoint(ep_index) => {
@@ -115,8 +125,8 @@ impl FunctionCtx<'_> {
}
}
- // Returns true if the given expression points to a fixed-function pipeline input.
- fn is_fixed_function_input(
+ /// Returns true if the given expression points to a fixed-function pipeline input.
+ pub fn is_fixed_function_input(
&self,
mut expression: crate::Handle<crate::Expression>,
module: &crate::Module,
@@ -162,7 +172,7 @@ impl crate::Expression {
/// See the [module-level documentation][emit] for details.
///
/// [emit]: index.html#expression-evaluation-time
- const fn bake_ref_count(&self) -> usize {
+ pub const fn bake_ref_count(&self) -> usize {
match *self {
// accesses are never cached, only loads are
crate::Expression::Access { .. } | crate::Expression::AccessIndex { .. } => usize::MAX,
@@ -181,9 +191,7 @@ impl crate::Expression {
}
/// Helper function that returns the string corresponding to the [`BinaryOperator`](crate::BinaryOperator)
-/// # Notes
-/// Used by `glsl-out`, `msl-out`, `wgsl-out`, `hlsl-out`.
-const fn binary_operation_str(op: crate::BinaryOperator) -> &'static str {
+pub const fn binary_operation_str(op: crate::BinaryOperator) -> &'static str {
use crate::BinaryOperator as Bo;
match op {
Bo::Add => "+",
@@ -208,8 +216,6 @@ const fn binary_operation_str(op: crate::BinaryOperator) -> &'static str {
}
/// Helper function that returns the string corresponding to the [`VectorSize`](crate::VectorSize)
-/// # Notes
-/// Used by `msl-out`, `wgsl-out`, `hlsl-out`.
const fn vector_size_str(size: crate::VectorSize) -> &'static str {
match size {
crate::VectorSize::Bi => "2",
@@ -219,7 +225,8 @@ const fn vector_size_str(size: crate::VectorSize) -> &'static str {
}
impl crate::TypeInner {
- const fn is_handle(&self) -> bool {
+ /// Returns true if this is a handle to a type rather than the type directly.
+ pub const fn is_handle(&self) -> bool {
match *self {
crate::TypeInner::Image { .. } | crate::TypeInner::Sampler { .. } => true,
_ => false,
@@ -266,8 +273,9 @@ bitflags::bitflags! {
}
}
+/// The intersection test to use for ray queries.
#[repr(u32)]
-enum RayIntersectionType {
+pub enum RayIntersectionType {
Triangle = 1,
BoundingBox = 4,
}
diff --git a/third_party/rust/naga/src/back/spv/index.rs b/third_party/rust/naga/src/back/spv/index.rs
index 92e0f88d9a..0effb568be 100644
--- a/third_party/rust/naga/src/back/spv/index.rs
+++ b/third_party/rust/naga/src/back/spv/index.rs
@@ -3,8 +3,9 @@ Bounds-checking for SPIR-V output.
*/
use super::{
- helpers::global_needs_wrapper, selection::Selection, Block, BlockContext, Error, IdGenerator,
- Instruction, Word,
+ helpers::{global_needs_wrapper, map_storage_class},
+ selection::Selection,
+ Block, BlockContext, Error, IdGenerator, Instruction, Word,
};
use crate::{arena::Handle, proc::BoundsCheckPolicy};
@@ -42,32 +43,113 @@ impl<'w> BlockContext<'w> {
array: Handle<crate::Expression>,
block: &mut Block,
) -> Result<Word, Error> {
- // Naga IR permits runtime-sized arrays as global variables or as the
- // final member of a struct that is a global variable. SPIR-V permits
- // only the latter, so this back end wraps bare runtime-sized arrays
- // in a made-up struct; see `helpers::global_needs_wrapper` and its uses.
- // This code must handle both cases.
- let (structure_id, last_member_index) = match self.ir_function.expressions[array] {
+ // Naga IR permits runtime-sized arrays as global variables, or as the
+ // final member of a struct that is a global variable, or one of these
+ // inside a buffer that is itself an element in a buffer bindings array.
+ // SPIR-V requires that runtime-sized arrays are wrapped in structs.
+ // See `helpers::global_needs_wrapper` and its uses.
+ let (opt_array_index_id, global_handle, opt_last_member_index) = match self
+ .ir_function
+ .expressions[array]
+ {
crate::Expression::AccessIndex { base, index } => {
match self.ir_function.expressions[base] {
- crate::Expression::GlobalVariable(handle) => (
- self.writer.global_variables[handle.index()].access_id,
- index,
- ),
- _ => return Err(Error::Validation("array length expression")),
+ // The global variable is an array of buffer bindings of structs,
+ // we are accessing one of them with a static index,
+ // and the last member of it.
+ crate::Expression::AccessIndex {
+ base: base_outer,
+ index: index_outer,
+ } => match self.ir_function.expressions[base_outer] {
+ crate::Expression::GlobalVariable(handle) => {
+ let index_id = self.get_index_constant(index_outer);
+ (Some(index_id), handle, Some(index))
+ }
+ _ => return Err(Error::Validation("array length expression case-1a")),
+ },
+ // The global variable is an array of buffer bindings of structs,
+ // we are accessing one of them with a dynamic index,
+ // and the last member of it.
+ crate::Expression::Access {
+ base: base_outer,
+ index: index_outer,
+ } => match self.ir_function.expressions[base_outer] {
+ crate::Expression::GlobalVariable(handle) => {
+ let index_id = self.cached[index_outer];
+ (Some(index_id), handle, Some(index))
+ }
+ _ => return Err(Error::Validation("array length expression case-1b")),
+ },
+ // The global variable is a buffer, and we are accessing the last member.
+ crate::Expression::GlobalVariable(handle) => {
+ let global = &self.ir_module.global_variables[handle];
+ match self.ir_module.types[global.ty].inner {
+ // The global variable is an array of buffer bindings of run-time arrays.
+ crate::TypeInner::BindingArray { .. } => (Some(index), handle, None),
+ // The global variable is a struct, and we are accessing the last member
+ _ => (None, handle, Some(index)),
+ }
+ }
+ _ => return Err(Error::Validation("array length expression case-1c")),
}
}
+ // The global variable is an array of buffer bindings of arrays.
+ crate::Expression::Access { base, index } => match self.ir_function.expressions[base] {
+ crate::Expression::GlobalVariable(handle) => {
+ let index_id = self.cached[index];
+ let global = &self.ir_module.global_variables[handle];
+ match self.ir_module.types[global.ty].inner {
+ crate::TypeInner::BindingArray { .. } => (Some(index_id), handle, None),
+ _ => return Err(Error::Validation("array length expression case-2a")),
+ }
+ }
+ _ => return Err(Error::Validation("array length expression case-2b")),
+ },
+ // The global variable is a run-time array.
crate::Expression::GlobalVariable(handle) => {
let global = &self.ir_module.global_variables[handle];
if !global_needs_wrapper(self.ir_module, global) {
- return Err(Error::Validation("array length expression"));
+ return Err(Error::Validation("array length expression case-3"));
}
-
- (self.writer.global_variables[handle.index()].var_id, 0)
+ (None, handle, None)
}
- _ => return Err(Error::Validation("array length expression")),
+ _ => return Err(Error::Validation("array length expression case-4")),
};
+ let gvar = self.writer.global_variables[global_handle.index()].clone();
+ let global = &self.ir_module.global_variables[global_handle];
+ let (last_member_index, gvar_id) = match opt_last_member_index {
+ Some(index) => (index, gvar.access_id),
+ None => {
+ if !global_needs_wrapper(self.ir_module, global) {
+ return Err(Error::Validation(
+ "pointer to a global that is not a wrapped array",
+ ));
+ }
+ (0, gvar.var_id)
+ }
+ };
+ let structure_id = match opt_array_index_id {
+ // We are indexing inside a binding array, generate the access op.
+ Some(index_id) => {
+ let element_type_id = match self.ir_module.types[global.ty].inner {
+ crate::TypeInner::BindingArray { base, size: _ } => {
+ let class = map_storage_class(global.space);
+ self.get_pointer_id(base, class)?
+ }
+ _ => return Err(Error::Validation("array length expression case-5")),
+ };
+ let structure_id = self.gen_id();
+ block.body.push(Instruction::access_chain(
+ element_type_id,
+ structure_id,
+ gvar_id,
+ &[index_id],
+ ));
+ structure_id
+ }
+ None => gvar_id,
+ };
let length_id = self.gen_id();
block.body.push(Instruction::array_length(
self.writer.get_uint_type_id(),
diff --git a/third_party/rust/naga/src/back/spv/mod.rs b/third_party/rust/naga/src/back/spv/mod.rs
index b7d57be0d4..eb29e3cd8b 100644
--- a/third_party/rust/naga/src/back/spv/mod.rs
+++ b/third_party/rust/naga/src/back/spv/mod.rs
@@ -576,6 +576,15 @@ impl BlockContext<'_> {
self.writer
.get_constant_scalar(crate::Literal::I32(scope as _))
}
+
+ fn get_pointer_id(
+ &mut self,
+ handle: Handle<crate::Type>,
+ class: spirv::StorageClass,
+ ) -> Result<Word, Error> {
+ self.writer
+ .get_pointer_id(&self.ir_module.types, handle, class)
+ }
}
#[derive(Clone, Copy, Default)]
diff --git a/third_party/rust/naga/src/back/spv/writer.rs b/third_party/rust/naga/src/back/spv/writer.rs
index de3220bbda..a5065e0623 100644
--- a/third_party/rust/naga/src/back/spv/writer.rs
+++ b/third_party/rust/naga/src/back/spv/writer.rs
@@ -565,36 +565,38 @@ impl Writer {
// Handle globals are pre-emitted and should be loaded automatically.
//
// Any that are binding arrays we skip as we cannot load the array, we must load the result after indexing.
- let is_binding_array = match ir_module.types[var.ty].inner {
- crate::TypeInner::BindingArray { .. } => true,
- _ => false,
- };
-
- if var.space == crate::AddressSpace::Handle && !is_binding_array {
- let var_type_id = self.get_type_id(LookupType::Handle(var.ty));
- let id = self.id_gen.next();
- prelude
- .body
- .push(Instruction::load(var_type_id, id, gv.var_id, None));
- gv.access_id = gv.var_id;
- gv.handle_id = id;
- } else if global_needs_wrapper(ir_module, var) {
- let class = map_storage_class(var.space);
- let pointer_type_id = self.get_pointer_id(&ir_module.types, var.ty, class)?;
- let index_id = self.get_index_constant(0);
-
- let id = self.id_gen.next();
- prelude.body.push(Instruction::access_chain(
- pointer_type_id,
- id,
- gv.var_id,
- &[index_id],
- ));
- gv.access_id = id;
- } else {
- // by default, the variable ID is accessed as is
- gv.access_id = gv.var_id;
- };
+ match ir_module.types[var.ty].inner {
+ crate::TypeInner::BindingArray { .. } => {
+ gv.access_id = gv.var_id;
+ }
+ _ => {
+ if var.space == crate::AddressSpace::Handle {
+ let var_type_id = self.get_type_id(LookupType::Handle(var.ty));
+ let id = self.id_gen.next();
+ prelude
+ .body
+ .push(Instruction::load(var_type_id, id, gv.var_id, None));
+ gv.access_id = gv.var_id;
+ gv.handle_id = id;
+ } else if global_needs_wrapper(ir_module, var) {
+ let class = map_storage_class(var.space);
+ let pointer_type_id =
+ self.get_pointer_id(&ir_module.types, var.ty, class)?;
+ let index_id = self.get_index_constant(0);
+ let id = self.id_gen.next();
+ prelude.body.push(Instruction::access_chain(
+ pointer_type_id,
+ id,
+ gv.var_id,
+ &[index_id],
+ ));
+ gv.access_id = id;
+ } else {
+ // by default, the variable ID is accessed as is
+ gv.access_id = gv.var_id;
+ };
+ }
+ }
// work around borrow checking in the presence of `self.xxx()` calls
self.global_variables[handle.index()] = gv;
@@ -1858,9 +1860,15 @@ impl Writer {
.iter()
.flat_map(|entry| entry.function.arguments.iter())
.any(|arg| has_view_index_check(ir_module, arg.binding.as_ref(), arg.ty));
- let has_ray_query = ir_module.special_types.ray_desc.is_some()
+ let mut has_ray_query = ir_module.special_types.ray_desc.is_some()
| ir_module.special_types.ray_intersection.is_some();
+ for (_, &crate::Type { ref inner, .. }) in ir_module.types.iter() {
+ if let &crate::TypeInner::AccelerationStructure | &crate::TypeInner::RayQuery = inner {
+ has_ray_query = true
+ }
+ }
+
if self.physical_layout.version < 0x10300 && has_storage_buffers {
// enable the storage buffer class on < SPV-1.3
Instruction::extension("SPV_KHR_storage_buffer_storage_class")
diff --git a/third_party/rust/naga/src/front/glsl/variables.rs b/third_party/rust/naga/src/front/glsl/variables.rs
index 5af2b228f0..9d2e7a0e7b 100644
--- a/third_party/rust/naga/src/front/glsl/variables.rs
+++ b/third_party/rust/naga/src/front/glsl/variables.rs
@@ -65,7 +65,7 @@ impl Frontend {
let idx = self.entry_args.len();
self.entry_args.push(EntryArg {
- name: None,
+ name: Some(name.into()),
binding: Binding::BuiltIn(data.builtin),
handle,
storage: data.storage,
diff --git a/third_party/rust/naga/src/span.rs b/third_party/rust/naga/src/span.rs
index 53246b25d6..10744647e9 100644
--- a/third_party/rust/naga/src/span.rs
+++ b/third_party/rust/naga/src/span.rs
@@ -104,16 +104,17 @@ impl std::ops::Index<Span> for str {
/// A human-readable representation for a span, tailored for text source.
///
-/// Corresponds to the positional members of [`GPUCompilationMessage`][gcm] from
-/// the WebGPU specification, except that `offset` and `length` are in bytes
-/// (UTF-8 code units), instead of UTF-16 code units.
+/// Roughly corresponds to the positional members of [`GPUCompilationMessage`][gcm] from
+/// the WebGPU specification, except
+/// - `offset` and `length` are in bytes (UTF-8 code units), instead of UTF-16 code units.
+/// - `line_position` counts entire Unicode code points, instead of UTF-16 code units.
///
/// [gcm]: https://www.w3.org/TR/webgpu/#gpucompilationmessage
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct SourceLocation {
/// 1-based line number.
pub line_number: u32,
- /// 1-based column of the start of this span
+ /// 1-based column of the start of this span, counted in Unicode code points.
pub line_position: u32,
/// 0-based Offset in code units (in bytes) of the start of the span.
pub offset: u32,
diff --git a/third_party/rust/naga/src/valid/analyzer.rs b/third_party/rust/naga/src/valid/analyzer.rs
index df6fc5e9b0..03fbc4089b 100644
--- a/third_party/rust/naga/src/valid/analyzer.rs
+++ b/third_party/rust/naga/src/valid/analyzer.rs
@@ -145,10 +145,35 @@ pub struct SamplingKey {
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
+/// Information about an expression in a function body.
pub struct ExpressionInfo {
+ /// Whether this expression is uniform, and why.
+ ///
+ /// If this expression's value is not uniform, this is the handle
+ /// of the expression from which this one's non-uniformity
+ /// originates. Otherwise, this is `None`.
pub uniformity: Uniformity,
+
+ /// The number of statements and other expressions using this
+ /// expression's value.
pub ref_count: usize,
+
+ /// The global variable into which this expression produces a pointer.
+ ///
+ /// This is `None` unless this expression is either a
+ /// [`GlobalVariable`], or an [`Access`] or [`AccessIndex`] that
+ /// ultimately refers to some part of a global.
+ ///
+ /// [`Load`] expressions applied to pointer-typed arguments could
+ /// refer to globals, but we leave this as `None` for them.
+ ///
+ /// [`GlobalVariable`]: crate::Expression::GlobalVariable
+ /// [`Access`]: crate::Expression::Access
+ /// [`AccessIndex`]: crate::Expression::AccessIndex
+ /// [`Load`]: crate::Expression::Load
assignable_global: Option<Handle<crate::GlobalVariable>>,
+
+ /// The type of this expression.
pub ty: TypeResolution,
}
@@ -311,14 +336,20 @@ pub enum UniformityDisruptor {
}
impl FunctionInfo {
- /// Adds a value-type reference to an expression.
+ /// Record a use of `expr` of the sort given by `global_use`.
+ ///
+ /// Bump `expr`'s reference count, and return its uniformity.
+ ///
+ /// If `expr` is a pointer to a global variable, or some part of
+ /// a global variable, add `global_use` to that global's set of
+ /// uses.
#[must_use]
fn add_ref_impl(
&mut self,
- handle: Handle<crate::Expression>,
+ expr: Handle<crate::Expression>,
global_use: GlobalUse,
) -> NonUniformResult {
- let info = &mut self.expressions[handle.index()];
+ let info = &mut self.expressions[expr.index()];
info.ref_count += 1;
// mark the used global as read
if let Some(global) = info.assignable_global {
@@ -327,22 +358,38 @@ impl FunctionInfo {
info.uniformity.non_uniform_result
}
- /// Adds a value-type reference to an expression.
+ /// Record a use of `expr` for its value.
+ ///
+ /// This is used for almost all expression references. Anything
+ /// that writes to the value `expr` points to, or otherwise wants
+ /// contribute flags other than `GlobalUse::READ`, should use
+ /// `add_ref_impl` directly.
#[must_use]
- fn add_ref(&mut self, handle: Handle<crate::Expression>) -> NonUniformResult {
- self.add_ref_impl(handle, GlobalUse::READ)
+ fn add_ref(&mut self, expr: Handle<crate::Expression>) -> NonUniformResult {
+ self.add_ref_impl(expr, GlobalUse::READ)
}
- /// Adds a potentially assignable reference to an expression.
- /// These are destinations for `Store` and `ImageStore` statements,
- /// which can transit through `Access` and `AccessIndex`.
+ /// Record a use of `expr`, and indicate which global variable it
+ /// refers to, if any.
+ ///
+ /// Bump `expr`'s reference count, and return its uniformity.
+ ///
+ /// If `expr` is a pointer to a global variable, or some part
+ /// thereof, store that global in `*assignable_global`. Leave the
+ /// global's uses unchanged.
+ ///
+ /// This is used to determine the [`assignable_global`] for
+ /// [`Access`] and [`AccessIndex`] expressions that ultimately
+ /// refer to a global variable. Those expressions don't contribute
+ /// any usage to the global themselves; that depends on how other
+ /// expressions use them.
#[must_use]
fn add_assignable_ref(
&mut self,
- handle: Handle<crate::Expression>,
+ expr: Handle<crate::Expression>,
assignable_global: &mut Option<Handle<crate::GlobalVariable>>,
) -> NonUniformResult {
- let info = &mut self.expressions[handle.index()];
+ let info = &mut self.expressions[expr.index()];
info.ref_count += 1;
// propagate the assignable global up the chain, till it either hits
// a value-type expression, or the assignment statement.
diff --git a/third_party/rust/naga/src/valid/type.rs b/third_party/rust/naga/src/valid/type.rs
index d44a295b1a..b8eb618ed4 100644
--- a/third_party/rust/naga/src/valid/type.rs
+++ b/third_party/rust/naga/src/valid/type.rs
@@ -510,7 +510,6 @@ impl super::Validator {
ti.uniform_layout = Ok(Alignment::MIN_UNIFORM);
let mut min_offset = 0;
-
let mut prev_struct_data: Option<(u32, u32)> = None;
for (i, member) in members.iter().enumerate() {
@@ -662,6 +661,7 @@ impl super::Validator {
// Currently Naga only supports binding arrays of structs for non-handle types.
match gctx.types[base].inner {
crate::TypeInner::Struct { .. } => {}
+ crate::TypeInner::Array { .. } => {}
_ => return Err(TypeError::BindingArrayBaseTypeNotStruct(base)),
};
}
diff --git a/third_party/rust/neqo-common/.cargo-checksum.json b/third_party/rust/neqo-common/.cargo-checksum.json
index e7daca1191..64d5739014 100644
--- a/third_party/rust/neqo-common/.cargo-checksum.json
+++ b/third_party/rust/neqo-common/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"b49758e5e8f0a6955d761e689be39530f193f7089de07f2295a7a3aef4df5898","build.rs":"306b2f909a25ae38daf5404a4e128d2a94e8975b70870864c2a71cafec9717c7","src/codec.rs":"fd239f75d374db6ff744211344c82bcd19ecf753e07410e1fe37732bbb81dfe9","src/datagram.rs":"f2ff56faa0e513edbf4331b6ee2c9e6d6111483bda7aff08d16b9f05bce5c320","src/event.rs":"106ca6c4afb107fa49a1bc72f5eb4ae95f4baa1ba19736aa38c8ba973774c160","src/header.rs":"467b947f78bfe354d8bb51e8df0c2be69e75a45e2be688d81f0d268aa77c89ef","src/hrtime.rs":"112dc758e65301b8a7a508b125d3d61063180d432bffaec566a050d4f907ab18","src/incrdecoder.rs":"577c32b9ace51f2daaf940be6d0c391c4f55cd42ef6848c68c1ffc970d8c57b5","src/lib.rs":"a86aae69900933bf83044fa96166ee51216277415eafcdb15c04a907bb2dd10e","src/log.rs":"7246053bffd704b264d42fc82f986b9d62079472a76a9fc3749c25cfc7698532","src/qlog.rs":"9b081f32bf158fd340300693acc97fe0554b617ae664eba86e4d3572e2b1e16e","src/timer.rs":"350a730cc5a159dfdac5d78ec8e8a34c5172a476d827a566703edec24c791842","src/tos.rs":"440616cb0aee9082abe00623b33e68dbe80eda47aec889ac5f4145b1566bf692","src/udp.rs":"2b92132e078791e35b66f68d99d79ff5df55efd03e788474f7781a00403a5533","tests/log.rs":"a11e21fb570258ca93bb40e3923817d381e1e605accbc3aed1df5a0a9918b41d"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"28a963b1a9067fef18e945a938a6ae5ea14a3a29a937f3be2ec4c0e3ae33854f","benches/timer.rs":"52d35abe1e06b92e913f43d95295b4eded0f19809677a7a63857fe92dad2c6fa","build.rs":"306b2f909a25ae38daf5404a4e128d2a94e8975b70870864c2a71cafec9717c7","src/codec.rs":"fd239f75d374db6ff744211344c82bcd19ecf753e07410e1fe37732bbb81dfe9","src/datagram.rs":"691ad94a3618d6bf5202a7911419b5e75e318d09c8cc57a9a542a864dcc764ec","src/event.rs":"106ca6c4afb107fa49a1bc72f5eb4ae95f4baa1ba19736aa38c8ba973774c160","src/header.rs":"467b947f78bfe354d8bb51e8df0c2be69e75a45e2be688d81f0d268aa77c89ef","src/hrtime.rs":"112dc758e65301b8a7a508b125d3d61063180d432bffaec566a050d4f907ab18","src/incrdecoder.rs":"577c32b9ace51f2daaf940be6d0c391c4f55cd42ef6848c68c1ffc970d8c57b5","src/lib.rs":"c917282134f43d0ddfbd67bbceea9f615a7db8a23608f809b4746808c08a9b3f","src/log.rs":"6ed99e15707c4256ae793011ed2f4b33aa81fed70205aaf5f8d3cd11ad451cf0","src/qlog.rs":"9b081f32bf158fd340300693acc97fe0554b617ae664eba86e4d3572e2b1e16e","src/timer.rs":"f6da86baf3b5d91c1230d5296ef886fb7233cdefa8c8e2b4197fcf82425a54fa","src/tos.rs":"baec87b4f8a6253b88cd257730bd1e3147c046ef993288b08235d54a24f88fbe","tests/log.rs":"a11e21fb570258ca93bb40e3923817d381e1e605accbc3aed1df5a0a9918b41d"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/neqo-common/Cargo.toml b/third_party/rust/neqo-common/Cargo.toml
index dc5bed385f..90b254c888 100644
--- a/third_party/rust/neqo-common/Cargo.toml
+++ b/third_party/rust/neqo-common/Cargo.toml
@@ -13,7 +13,7 @@
edition = "2021"
rust-version = "1.74.0"
name = "neqo-common"
-version = "0.7.2"
+version = "0.7.5"
authors = ["The Neqo Authors <necko@mozilla.com>"]
build = "build.rs"
homepage = "https://github.com/mozilla/neqo/"
@@ -23,6 +23,10 @@ repository = "https://github.com/mozilla/neqo/"
[lib]
bench = false
+[[bench]]
+name = "timer"
+harness = false
+
[dependencies.enum-map]
version = "2.7"
default-features = false
@@ -39,26 +43,14 @@ default-features = false
version = "0.12"
default-features = false
-[dependencies.quinn-udp]
-git = "https://github.com/quinn-rs/quinn/"
-rev = "a947962131aba8a6521253d03cc948b20098a2d6"
-optional = true
-
[dependencies.time]
version = "0.3"
features = ["formatting"]
default-features = false
-[dependencies.tokio]
-version = "1"
-features = [
- "net",
- "time",
- "macros",
- "rt",
- "rt-multi-thread",
-]
-optional = true
+[dev-dependencies.criterion]
+version = "0.5"
+features = ["html_reports"]
default-features = false
[dev-dependencies.test-fixture]
@@ -66,10 +58,6 @@ path = "../test-fixture"
[features]
ci = []
-udp = [
- "dep:quinn-udp",
- "dep:tokio",
-]
[target."cfg(windows)".dependencies.winapi]
version = "0.3"
diff --git a/third_party/rust/neqo-common/benches/timer.rs b/third_party/rust/neqo-common/benches/timer.rs
new file mode 100644
index 0000000000..5ac8019db4
--- /dev/null
+++ b/third_party/rust/neqo-common/benches/timer.rs
@@ -0,0 +1,39 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::time::{Duration, Instant};
+
+use criterion::{criterion_group, criterion_main, Criterion};
+use neqo_common::timer::Timer;
+use test_fixture::now;
+
+fn benchmark_timer(c: &mut Criterion) {
+ c.bench_function("drain a timer quickly", |b| {
+ b.iter_batched_ref(
+ make_timer,
+ |(_now, timer)| {
+ while let Some(t) = timer.next_time() {
+ assert!(timer.take_next(t).is_some());
+ }
+ },
+ criterion::BatchSize::SmallInput,
+ );
+ });
+}
+
+fn make_timer() -> (Instant, Timer<()>) {
+ const TIMES: &[u64] = &[1, 2, 3, 5, 8, 13, 21, 34];
+
+ let now = now();
+ let mut timer = Timer::new(now, Duration::from_millis(777), 100);
+ for &t in TIMES {
+ timer.add(now + Duration::from_secs(t), ());
+ }
+ (now, timer)
+}
+
+criterion_group!(benches, benchmark_timer);
+criterion_main!(benches);
diff --git a/third_party/rust/neqo-common/src/datagram.rs b/third_party/rust/neqo-common/src/datagram.rs
index 04ba1a45a1..cc2cb7d113 100644
--- a/third_party/rust/neqo-common/src/datagram.rs
+++ b/third_party/rust/neqo-common/src/datagram.rs
@@ -54,10 +54,8 @@ impl Datagram {
self.ttl
}
- #[cfg(feature = "udp")]
- #[must_use]
- pub(crate) fn into_data(self) -> Vec<u8> {
- self.d
+ pub fn set_tos(&mut self, tos: IpTos) {
+ self.tos = tos;
}
}
@@ -83,6 +81,12 @@ impl std::fmt::Debug for Datagram {
}
}
+impl From<Datagram> for Vec<u8> {
+ fn from(datagram: Datagram) -> Self {
+ datagram.d
+ }
+}
+
#[cfg(test)]
use test_fixture::datagram;
@@ -90,8 +94,7 @@ use test_fixture::datagram;
fn fmt_datagram() {
let d = datagram([0; 1].to_vec());
assert_eq!(
- format!("{d:?}"),
+ &format!("{d:?}"),
"Datagram IpTos(Cs0, NotEct) TTL Some(128) [fe80::1]:443->[fe80::1]:443: [1]: 00"
- .to_string()
);
}
diff --git a/third_party/rust/neqo-common/src/lib.rs b/third_party/rust/neqo-common/src/lib.rs
index fe88097983..e988c6071d 100644
--- a/third_party/rust/neqo-common/src/lib.rs
+++ b/third_party/rust/neqo-common/src/lib.rs
@@ -16,8 +16,6 @@ pub mod log;
pub mod qlog;
pub mod timer;
pub mod tos;
-#[cfg(feature = "udp")]
-pub mod udp;
use std::fmt::Write;
diff --git a/third_party/rust/neqo-common/src/log.rs b/third_party/rust/neqo-common/src/log.rs
index c5b89be8a6..04028a26bd 100644
--- a/third_party/rust/neqo-common/src/log.rs
+++ b/third_party/rust/neqo-common/src/log.rs
@@ -50,7 +50,7 @@ fn since_start() -> Duration {
START_TIME.get_or_init(Instant::now).elapsed()
}
-pub fn init() {
+pub fn init(level_filter: Option<log::LevelFilter>) {
static INIT_ONCE: Once = Once::new();
if ::log::STATIC_MAX_LEVEL == ::log::LevelFilter::Off {
@@ -59,6 +59,9 @@ pub fn init() {
INIT_ONCE.call_once(|| {
let mut builder = Builder::from_env("RUST_LOG");
+ if let Some(filter) = level_filter {
+ builder.filter_level(filter);
+ }
builder.format(|buf, record| {
let elapsed = since_start();
writeln!(
@@ -71,9 +74,9 @@ pub fn init() {
)
});
if let Err(e) = builder.try_init() {
- do_log!(::log::Level::Info, "Logging initialization error {:?}", e);
+ do_log!(::log::Level::Warn, "Logging initialization error {:?}", e);
} else {
- do_log!(::log::Level::Info, "Logging initialized");
+ do_log!(::log::Level::Debug, "Logging initialized");
}
});
}
@@ -81,32 +84,32 @@ pub fn init() {
#[macro_export]
macro_rules! log_invoke {
($lvl:expr, $ctx:expr, $($arg:tt)*) => ( {
- ::neqo_common::log::init();
+ ::neqo_common::log::init(None);
::neqo_common::do_log!($lvl, "[{}] {}", $ctx, format!($($arg)*));
} )
}
#[macro_export]
macro_rules! qerror {
([$ctx:expr], $($arg:tt)*) => (::neqo_common::log_invoke!(::log::Level::Error, $ctx, $($arg)*););
- ($($arg:tt)*) => ( { ::neqo_common::log::init(); ::neqo_common::do_log!(::log::Level::Error, $($arg)*); } );
+ ($($arg:tt)*) => ( { ::neqo_common::log::init(None); ::neqo_common::do_log!(::log::Level::Error, $($arg)*); } );
}
#[macro_export]
macro_rules! qwarn {
([$ctx:expr], $($arg:tt)*) => (::neqo_common::log_invoke!(::log::Level::Warn, $ctx, $($arg)*););
- ($($arg:tt)*) => ( { ::neqo_common::log::init(); ::neqo_common::do_log!(::log::Level::Warn, $($arg)*); } );
+ ($($arg:tt)*) => ( { ::neqo_common::log::init(None); ::neqo_common::do_log!(::log::Level::Warn, $($arg)*); } );
}
#[macro_export]
macro_rules! qinfo {
([$ctx:expr], $($arg:tt)*) => (::neqo_common::log_invoke!(::log::Level::Info, $ctx, $($arg)*););
- ($($arg:tt)*) => ( { ::neqo_common::log::init(); ::neqo_common::do_log!(::log::Level::Info, $($arg)*); } );
+ ($($arg:tt)*) => ( { ::neqo_common::log::init(None); ::neqo_common::do_log!(::log::Level::Info, $($arg)*); } );
}
#[macro_export]
macro_rules! qdebug {
([$ctx:expr], $($arg:tt)*) => (::neqo_common::log_invoke!(::log::Level::Debug, $ctx, $($arg)*););
- ($($arg:tt)*) => ( { ::neqo_common::log::init(); ::neqo_common::do_log!(::log::Level::Debug, $($arg)*); } );
+ ($($arg:tt)*) => ( { ::neqo_common::log::init(None); ::neqo_common::do_log!(::log::Level::Debug, $($arg)*); } );
}
#[macro_export]
macro_rules! qtrace {
([$ctx:expr], $($arg:tt)*) => (::neqo_common::log_invoke!(::log::Level::Trace, $ctx, $($arg)*););
- ($($arg:tt)*) => ( { ::neqo_common::log::init(); ::neqo_common::do_log!(::log::Level::Trace, $($arg)*); } );
+ ($($arg:tt)*) => ( { ::neqo_common::log::init(None); ::neqo_common::do_log!(::log::Level::Trace, $($arg)*); } );
}
diff --git a/third_party/rust/neqo-common/src/timer.rs b/third_party/rust/neqo-common/src/timer.rs
index a413252e08..3feddb2226 100644
--- a/third_party/rust/neqo-common/src/timer.rs
+++ b/third_party/rust/neqo-common/src/timer.rs
@@ -5,6 +5,7 @@
// except according to those terms.
use std::{
+ collections::VecDeque,
mem,
time::{Duration, Instant},
};
@@ -27,7 +28,7 @@ impl<T> TimerItem<T> {
/// points). Time is relative, the wheel has an origin time and it is unable to represent times that
/// are more than `granularity * capacity` past that time.
pub struct Timer<T> {
- items: Vec<Vec<TimerItem<T>>>,
+ items: Vec<VecDeque<TimerItem<T>>>,
now: Instant,
granularity: Duration,
cursor: usize,
@@ -55,9 +56,14 @@ impl<T> Timer<T> {
/// Return a reference to the time of the next entry.
#[must_use]
pub fn next_time(&self) -> Option<Instant> {
- for i in 0..self.items.len() {
- let idx = self.bucket(i);
- if let Some(t) = self.items[idx].first() {
+ let idx = self.bucket(0);
+ for i in idx..self.items.len() {
+ if let Some(t) = self.items[i].front() {
+ return Some(t.time);
+ }
+ }
+ for i in 0..idx {
+ if let Some(t) = self.items[i].front() {
return Some(t.time);
}
}
@@ -145,6 +151,9 @@ impl<T> Timer<T> {
/// Given knowledge of the time an item was added, remove it.
/// This requires use of a predicate that identifies matching items.
+ ///
+ /// # Panics
+ /// Impossible, I think.
pub fn remove<F>(&mut self, time: Instant, mut selector: F) -> Option<T>
where
F: FnMut(&T) -> bool,
@@ -167,7 +176,7 @@ impl<T> Timer<T> {
break;
}
if selector(&self.items[bucket][i].item) {
- return Some(self.items[bucket].remove(i).item);
+ return Some(self.items[bucket].remove(i).unwrap().item);
}
}
// ... then forwards.
@@ -176,7 +185,7 @@ impl<T> Timer<T> {
break;
}
if selector(&self.items[bucket][i].item) {
- return Some(self.items[bucket].remove(i).item);
+ return Some(self.items[bucket].remove(i).unwrap().item);
}
}
None
@@ -185,10 +194,25 @@ impl<T> Timer<T> {
/// Take the next item, unless there are no items with
/// a timeout in the past relative to `until`.
pub fn take_next(&mut self, until: Instant) -> Option<T> {
- for i in 0..self.items.len() {
- let idx = self.bucket(i);
- if !self.items[idx].is_empty() && self.items[idx][0].time <= until {
- return Some(self.items[idx].remove(0).item);
+ fn maybe_take<T>(v: &mut VecDeque<TimerItem<T>>, until: Instant) -> Option<T> {
+ if !v.is_empty() && v[0].time <= until {
+ Some(v.pop_front().unwrap().item)
+ } else {
+ None
+ }
+ }
+
+ let idx = self.bucket(0);
+ for i in idx..self.items.len() {
+ let res = maybe_take(&mut self.items[i], until);
+ if res.is_some() {
+ return res;
+ }
+ }
+ for i in 0..idx {
+ let res = maybe_take(&mut self.items[i], until);
+ if res.is_some() {
+ return res;
}
}
None
@@ -201,7 +225,7 @@ impl<T> Timer<T> {
if until >= self.now + self.span() {
// Drain everything, so a clean sweep.
let mut empty_items = Vec::with_capacity(self.items.len());
- empty_items.resize_with(self.items.len(), Vec::default);
+ empty_items.resize_with(self.items.len(), VecDeque::default);
let mut items = mem::replace(&mut self.items, empty_items);
self.now = until;
self.cursor = 0;
diff --git a/third_party/rust/neqo-common/src/tos.rs b/third_party/rust/neqo-common/src/tos.rs
index 3610f72750..533c5447e2 100644
--- a/third_party/rust/neqo-common/src/tos.rs
+++ b/third_party/rust/neqo-common/src/tos.rs
@@ -36,7 +36,7 @@ impl From<IpTosEcn> for u8 {
impl From<u8> for IpTosEcn {
fn from(v: u8) -> Self {
- match v & 0b11 {
+ match v & 0b0000_0011 {
0b00 => IpTosEcn::NotEct,
0b01 => IpTosEcn::Ect1,
0b10 => IpTosEcn::Ect0,
@@ -47,8 +47,8 @@ impl From<u8> for IpTosEcn {
}
impl From<IpTos> for IpTosEcn {
- fn from(value: IpTos) -> Self {
- IpTosEcn::from(value.0 & 0x3)
+ fn from(v: IpTos) -> Self {
+ IpTosEcn::from(u8::from(v))
}
}
@@ -166,14 +166,13 @@ impl From<u8> for IpTosDscp {
}
impl From<IpTos> for IpTosDscp {
- fn from(value: IpTos) -> Self {
- IpTosDscp::from(value.0 & 0xfc)
+ fn from(v: IpTos) -> Self {
+ IpTosDscp::from(u8::from(v))
}
}
/// The type-of-service field in an IP packet.
-#[allow(clippy::module_name_repetitions)]
-#[derive(Copy, Clone, PartialEq, Eq)]
+#[derive(Copy, Clone, PartialEq, Eq, Default)]
pub struct IpTos(u8);
impl From<IpTosEcn> for IpTos {
@@ -215,15 +214,19 @@ impl From<u8> for IpTos {
impl Debug for IpTos {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("IpTos")
- .field(&IpTosDscp::from(self.0 & 0xfc))
- .field(&IpTosEcn::from(self.0 & 0x3))
+ .field(&IpTosDscp::from(*self))
+ .field(&IpTosEcn::from(*self))
.finish()
}
}
-impl Default for IpTos {
- fn default() -> Self {
- (IpTosDscp::default(), IpTosEcn::default()).into()
+impl IpTos {
+ pub fn set_ecn(&mut self, ecn: IpTosEcn) {
+ self.0 = u8::from(IpTosDscp::from(*self)) | u8::from(ecn);
+ }
+
+ pub fn set_dscp(&mut self, dscp: IpTosDscp) {
+ self.0 = u8::from(IpTosEcn::from(*self)) | u8::from(dscp);
}
}
@@ -322,4 +325,25 @@ mod tests {
assert_eq!(tos, u8::from(iptos));
assert_eq!(IpTos::from(tos), iptos);
}
+
+ #[test]
+ fn iptos_to_iptosdscp() {
+ let tos = IpTos::from((IpTosDscp::Af41, IpTosEcn::NotEct));
+ let dscp = IpTosDscp::from(tos);
+ assert_eq!(dscp, IpTosDscp::Af41);
+ }
+
+ #[test]
+ fn tos_modify_ecn() {
+ let mut iptos: IpTos = (IpTosDscp::Af41, IpTosEcn::NotEct).into();
+ iptos.set_ecn(IpTosEcn::Ce);
+ assert_eq!(u8::from(iptos), 0b1000_1011);
+ }
+
+ #[test]
+ fn tos_modify_dscp() {
+ let mut iptos: IpTos = (IpTosDscp::Af41, IpTosEcn::Ect1).into();
+ iptos.set_dscp(IpTosDscp::Le);
+ assert_eq!(u8::from(iptos), 0b0000_0101);
+ }
}
diff --git a/third_party/rust/neqo-common/src/udp.rs b/third_party/rust/neqo-common/src/udp.rs
deleted file mode 100644
index c27b0632ff..0000000000
--- a/third_party/rust/neqo-common/src/udp.rs
+++ /dev/null
@@ -1,222 +0,0 @@
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-#![allow(clippy::missing_errors_doc)] // Functions simply delegate to tokio and quinn-udp.
-#![allow(clippy::missing_panics_doc)] // Functions simply delegate to tokio and quinn-udp.
-
-use std::{
- io::{self, IoSliceMut},
- net::{SocketAddr, ToSocketAddrs},
- slice,
-};
-
-use quinn_udp::{EcnCodepoint, RecvMeta, Transmit, UdpSocketState};
-use tokio::io::Interest;
-
-use crate::{Datagram, IpTos};
-
-/// Socket receive buffer size.
-///
-/// Allows reading multiple datagrams in a single [`Socket::recv`] call.
-const RECV_BUF_SIZE: usize = u16::MAX as usize;
-
-pub struct Socket {
- socket: tokio::net::UdpSocket,
- state: UdpSocketState,
- recv_buf: Vec<u8>,
-}
-
-impl Socket {
- /// Calls [`std::net::UdpSocket::bind`] and instantiates [`quinn_udp::UdpSocketState`].
- pub fn bind<A: ToSocketAddrs>(addr: A) -> Result<Self, io::Error> {
- let socket = std::net::UdpSocket::bind(addr)?;
-
- Ok(Self {
- state: quinn_udp::UdpSocketState::new((&socket).into())?,
- socket: tokio::net::UdpSocket::from_std(socket)?,
- recv_buf: vec![0; RECV_BUF_SIZE],
- })
- }
-
- /// See [`tokio::net::UdpSocket::local_addr`].
- pub fn local_addr(&self) -> io::Result<SocketAddr> {
- self.socket.local_addr()
- }
-
- /// See [`tokio::net::UdpSocket::writable`].
- pub async fn writable(&self) -> Result<(), io::Error> {
- self.socket.writable().await
- }
-
- /// See [`tokio::net::UdpSocket::readable`].
- pub async fn readable(&self) -> Result<(), io::Error> {
- self.socket.readable().await
- }
-
- /// Send the UDP datagram on the specified socket.
- pub fn send(&self, d: Datagram) -> io::Result<usize> {
- let transmit = Transmit {
- destination: d.destination(),
- ecn: EcnCodepoint::from_bits(Into::<u8>::into(d.tos())),
- contents: d.into_data().into(),
- segment_size: None,
- src_ip: None,
- };
-
- let n = self.socket.try_io(Interest::WRITABLE, || {
- self.state
- .send((&self.socket).into(), slice::from_ref(&transmit))
- })?;
-
- assert_eq!(n, 1, "only passed one slice");
-
- Ok(n)
- }
-
- /// Receive a UDP datagram on the specified socket.
- pub fn recv(&mut self, local_address: &SocketAddr) -> Result<Vec<Datagram>, io::Error> {
- let mut meta = RecvMeta::default();
-
- match self.socket.try_io(Interest::READABLE, || {
- self.state.recv(
- (&self.socket).into(),
- &mut [IoSliceMut::new(&mut self.recv_buf)],
- slice::from_mut(&mut meta),
- )
- }) {
- Ok(n) => {
- assert_eq!(n, 1, "only passed one slice");
- }
- Err(ref err)
- if err.kind() == io::ErrorKind::WouldBlock
- || err.kind() == io::ErrorKind::Interrupted =>
- {
- return Ok(vec![])
- }
- Err(err) => {
- return Err(err);
- }
- };
-
- if meta.len == 0 {
- eprintln!("zero length datagram received?");
- return Ok(vec![]);
- }
- if meta.len == self.recv_buf.len() {
- eprintln!(
- "Might have received more than {} bytes",
- self.recv_buf.len()
- );
- }
-
- Ok(self.recv_buf[0..meta.len]
- .chunks(meta.stride.min(self.recv_buf.len()))
- .map(|d| {
- Datagram::new(
- meta.addr,
- *local_address,
- meta.ecn.map(|n| IpTos::from(n as u8)).unwrap_or_default(),
- None, // TODO: get the real TTL https://github.com/quinn-rs/quinn/issues/1749
- d,
- )
- })
- .collect())
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::{IpTosDscp, IpTosEcn};
-
- #[tokio::test]
- async fn datagram_tos() -> Result<(), io::Error> {
- let sender = Socket::bind("127.0.0.1:0")?;
- let receiver_addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
- let mut receiver = Socket::bind(receiver_addr)?;
-
- let datagram = Datagram::new(
- sender.local_addr()?,
- receiver.local_addr()?,
- IpTos::from((IpTosDscp::Le, IpTosEcn::Ect1)),
- None,
- "Hello, world!".as_bytes().to_vec(),
- );
-
- sender.writable().await?;
- sender.send(datagram.clone())?;
-
- receiver.readable().await?;
- let received_datagram = receiver
- .recv(&receiver_addr)
- .expect("receive to succeed")
- .into_iter()
- .next()
- .expect("receive to yield datagram");
-
- // Assert that the ECN is correct.
- assert_eq!(
- IpTosEcn::from(datagram.tos()),
- IpTosEcn::from(received_datagram.tos())
- );
-
- Ok(())
- }
-
- /// Expect [`Socket::recv`] to handle multiple [`Datagram`]s on GRO read.
- #[tokio::test]
- #[cfg_attr(not(any(target_os = "linux", target_os = "windows")), ignore)]
- async fn many_datagrams_through_gro() -> Result<(), io::Error> {
- const SEGMENT_SIZE: usize = 128;
-
- let sender = Socket::bind("127.0.0.1:0")?;
- let receiver_addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
- let mut receiver = Socket::bind(receiver_addr)?;
-
- // `neqo_common::udp::Socket::send` does not yet
- // (https://github.com/mozilla/neqo/issues/1693) support GSO. Use
- // `quinn_udp` directly.
- let max_gso_segments = sender.state.max_gso_segments();
- let msg = vec![0xAB; SEGMENT_SIZE * max_gso_segments];
- let transmit = Transmit {
- destination: receiver.local_addr()?,
- ecn: EcnCodepoint::from_bits(Into::<u8>::into(IpTos::from((
- IpTosDscp::Le,
- IpTosEcn::Ect1,
- )))),
- contents: msg.clone().into(),
- segment_size: Some(SEGMENT_SIZE),
- src_ip: None,
- };
- sender.writable().await?;
- let n = sender.socket.try_io(Interest::WRITABLE, || {
- sender
- .state
- .send((&sender.socket).into(), slice::from_ref(&transmit))
- })?;
- assert_eq!(n, 1, "only passed one slice");
-
- // Allow for one GSO sendmmsg to result in multiple GRO recvmmsg.
- let mut num_received = 0;
- while num_received < max_gso_segments {
- receiver.readable().await?;
- receiver
- .recv(&receiver_addr)
- .expect("receive to succeed")
- .into_iter()
- .for_each(|d| {
- assert_eq!(
- SEGMENT_SIZE,
- d.len(),
- "Expect received datagrams to have same length as sent datagrams."
- );
- num_received += 1;
- });
- }
-
- Ok(())
- }
-}
diff --git a/third_party/rust/neqo-crypto/.cargo-checksum.json b/third_party/rust/neqo-crypto/.cargo-checksum.json
index 5622e7f4ad..c3e603bd5a 100644
--- a/third_party/rust/neqo-crypto/.cargo-checksum.json
+++ b/third_party/rust/neqo-crypto/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"6f1917fbd4cbf53cb4883c30e8fcb9c20f8ebe15e19576c7d37cb6ba0ab9e42b","bindings/bindings.toml":"0660c1661318b8a5094834c2f1bb12266287ef467307f66947eff7762528f70a","bindings/mozpkix.hpp":"77072c8bb0f6eb6bfe8cbadc111dcd92e0c79936d13f2e501aae1e5d289a6675","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nspr_io.h":"085b289849ef0e77f88512a27b4d9bdc28252bd4d39c6a17303204e46ef45f72","bindings/nspr_time.h":"2e637fd338a5cf0fd3fb0070a47f474a34c2a7f4447f31b6875f5a9928d0a261","bindings/nss_ciphers.h":"95ec6344a607558b3c5ba8510f463b6295f3a2fb3f538a01410531045a5f62d1","bindings/nss_init.h":"ef49045063782fb612aff459172cc6a89340f15005808608ade5320ca9974310","bindings/nss_p11.h":"0b81e64fe6db49b2ecff94edd850be111ef99ec11220e88ceb1c67be90143a78","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","bindings/nss_ssl.h":"af222fb957b989e392e762fa2125c82608a0053aff4fb97e556691646c88c335","bindings/nss_sslerr.h":"24b97f092183d8486f774cdaef5030d0249221c78343570d83a4ee5b594210ae","bindings/nss_sslopt.h":"b7807eb7abdad14db6ad7bc51048a46b065a0ea65a4508c95a12ce90e59d1eea","build.rs":"21d9a0140b2afd708583f58f2af0a4ba93ab07ec088680b4cbf0e184aeb8785b","src/aead.rs":"8f50e4557b7829edb67f57c80c777c6ae23c868e2b2eeaaae0736af04dc0d298","src/aead_fuzzing.rs":"c3e590572314e0bb3fafa13dac3c831358b8a7b5570fe9cfe592752fce8cbdee","src/agent.rs":"e995e9cc5108470594bae1b0d4e4bc6b7a8ac2b66488f71ea99e2836c0edbd7e","src/agentio.rs":"c4cb1b3cd92ef53eb0b4fb0b34a597068d82d78ba470dae5821670a0f06c9cda","src/auth.rs":"ced1a18f691894984244088020ea25dc1ee678603317f0c7dfc8b8842fa750b4","src/cert.rs":"8942cb3ce25a61f92b6ffc30fb286052ed6f56eeda3be12fd46ea76ceba6c1cf","src/constants.rs":"f22bf16bd8cb539862cb1e47138dbba79e93fe738f4b907e465891326f98883c","src/ech.rs":"9d322fcc01c0886f1dfe9bb6273cb9f88a746452ac9a802761b1816a05930c1f","src/err.rs":"fca0222167883231a5e0a569a593f44214501819adf5aadf814be27891c87c24","src/exp.rs":"cec59d61fc95914f9703d2fb6490a8507af993c9db710dde894f2f8fd38123c7","src/ext.rs":"cbf7d9f5ecabf4b8c9efd6c334637ab1596ec5266d38ab8d2d6ceae305283deb","src/hkdf.rs":"ef32f20e30a9bd7f094199536d19c87c4231b7fbbe4a9c54c70e84ca9c6575be","src/hp.rs":"644f1bed67f1c6189a67c8d02ab3358aaa7f63af4b913dd7395becbc01a84291","src/lib.rs":"23732c7799be038c0e0835b54e7c40cf6c6536113e0adb6ae3b41b216a6e5220","src/p11.rs":"e8c366def0df470101f3d120dcc4391f74f921fe59e2f3db2a56832e2852b855","src/prio.rs":"e5e169296c0ac69919c59fb6c1f8bd6bf079452eaa13d75da0edd41d435d3f6f","src/replay.rs":"96b7af8eff9e14313e79303092018b12e8834f780c96b8e247c497fdc680c696","src/result.rs":"0587cbb6aace71a7f9765ef7c01dcd9f73a49dcc6331e1d8fe4de2aef6ca65b6","src/secrets.rs":"4ffaa66f25df47dadf042063bff5953effa7bf2f4920cafe827757d6a659cb58","src/selfencrypt.rs":"ac65b13f5bade9d03ab4709364f9ec937fa4ca009965c77ca73b481534a0a470","src/ssl.rs":"c83baa5518b81dd06f2e4072ea3c2d666ccdeb8b1ff6e3746eea9f1af47023a6","src/time.rs":"3b2829a98a1648eb052db19bb470808b6b015a1eca27ab7be64b5d196c0271c0","tests/aead.rs":"3ac4fe4ab79922b5d0191a9717058fc8d0710380ce9b25448095f870f511844f","tests/agent.rs":"824735f88e487a3748200844e9481e81a72163ad74d82faa9aa16594d9b9bb25","tests/ext.rs":"1b047d23d9b224ad06eb65d8f3a7b351e263774e404c79bbcbe8f43790e29c18","tests/handshake.rs":"e892a2839b31414be16e96cdf3b1a65978716094700c1a4989229f7edbf578a0","tests/hkdf.rs":"1d2098dc8398395864baf13e4886cfd1da6d36118727c3b264f457ee3da6b048","tests/hp.rs":"b24fec53771c169be788772532d2617a5349196cf87d6444dc74214f7c73e92c","tests/init.rs":"44fe7626b75ab8c57adfee361bb70a83d5958797e1eb6c4531bb74988ba3a990","tests/selfencrypt.rs":"25813b0c6f32fc8383bb7685745feb750eb3fdc0a6a172a50d961c68d39f2a46"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"e0ddb5aa433c742c87b94760fc995afb8091b8fa1360bf1ce66ed59d4e34a44d","bindings/bindings.toml":"29ec7a8ef3d5f1e4a632003e2d36c270e1caf12fd3fcf108a22d1893b90a41a6","bindings/nspr_err.h":"2d5205d017b536c2d838bcf9bc4ec79f96dd50e7bb9b73892328781f1ee6629d","bindings/nspr_error.h":"e41c03c77b8c22046f8618832c9569fbcc7b26d8b9bbc35eea7168f35e346889","bindings/nspr_io.h":"085b289849ef0e77f88512a27b4d9bdc28252bd4d39c6a17303204e46ef45f72","bindings/nspr_time.h":"2e637fd338a5cf0fd3fb0070a47f474a34c2a7f4447f31b6875f5a9928d0a261","bindings/nss_ciphers.h":"95ec6344a607558b3c5ba8510f463b6295f3a2fb3f538a01410531045a5f62d1","bindings/nss_init.h":"ef49045063782fb612aff459172cc6a89340f15005808608ade5320ca9974310","bindings/nss_p11.h":"0b81e64fe6db49b2ecff94edd850be111ef99ec11220e88ceb1c67be90143a78","bindings/nss_secerr.h":"713e8368bdae5159af7893cfa517dabfe5103cede051dee9c9557c850a2defc6","bindings/nss_ssl.h":"af222fb957b989e392e762fa2125c82608a0053aff4fb97e556691646c88c335","bindings/nss_sslerr.h":"24b97f092183d8486f774cdaef5030d0249221c78343570d83a4ee5b594210ae","bindings/nss_sslopt.h":"b7807eb7abdad14db6ad7bc51048a46b065a0ea65a4508c95a12ce90e59d1eea","build.rs":"cbf6a7d912314784c8c124cf7319c910a786d0e263f466843edd3f43826f036c","min_version.txt":"7e98f86c69cddb4f65cf96a6de1f4297e3ce224a4c4628609e29042b6c4dcfb9","src/aead.rs":"fc42bc20b84d2e5ccfd56271ae2d2db082e55586ea2926470c102da177f22296","src/aead_null.rs":"664f80bbb56d0abd3794b99cc927fd5f678ddb4ce95456001413ec18a6c6a6a9","src/agent.rs":"b12004faee4a136c10e8168848d397443b5927e9497edb62c72e6db3eb1c10a0","src/agentio.rs":"c4cb1b3cd92ef53eb0b4fb0b34a597068d82d78ba470dae5821670a0f06c9cda","src/auth.rs":"ced1a18f691894984244088020ea25dc1ee678603317f0c7dfc8b8842fa750b4","src/cert.rs":"8942cb3ce25a61f92b6ffc30fb286052ed6f56eeda3be12fd46ea76ceba6c1cf","src/constants.rs":"f22bf16bd8cb539862cb1e47138dbba79e93fe738f4b907e465891326f98883c","src/ech.rs":"9d322fcc01c0886f1dfe9bb6273cb9f88a746452ac9a802761b1816a05930c1f","src/err.rs":"ae979f334604aba89640c4491262641910033f0bd790d58671f649f5039b291c","src/exp.rs":"cec59d61fc95914f9703d2fb6490a8507af993c9db710dde894f2f8fd38123c7","src/ext.rs":"cbf7d9f5ecabf4b8c9efd6c334637ab1596ec5266d38ab8d2d6ceae305283deb","src/hkdf.rs":"ef32f20e30a9bd7f094199536d19c87c4231b7fbbe4a9c54c70e84ca9c6575be","src/hp.rs":"644f1bed67f1c6189a67c8d02ab3358aaa7f63af4b913dd7395becbc01a84291","src/lib.rs":"6b2d0eb2c55f6351d673d3a3e5fc5adac8d1030c67dae9af4c79552de0f57455","src/min_version.rs":"89b7ef6f9d2301db4f689f4d963b58375d577f705b92003a804048441e00cfd1","src/p11.rs":"e8c366def0df470101f3d120dcc4391f74f921fe59e2f3db2a56832e2852b855","src/prio.rs":"e5e169296c0ac69919c59fb6c1f8bd6bf079452eaa13d75da0edd41d435d3f6f","src/replay.rs":"96b7af8eff9e14313e79303092018b12e8834f780c96b8e247c497fdc680c696","src/result.rs":"0587cbb6aace71a7f9765ef7c01dcd9f73a49dcc6331e1d8fe4de2aef6ca65b6","src/secrets.rs":"4ffaa66f25df47dadf042063bff5953effa7bf2f4920cafe827757d6a659cb58","src/selfencrypt.rs":"b7cc1c896c7661c37461fc3a8bcbfdf2589433b907fa5f968ae4f6907704b441","src/ssl.rs":"c83baa5518b81dd06f2e4072ea3c2d666ccdeb8b1ff6e3746eea9f1af47023a6","src/time.rs":"c71a01ff8aa2c0e97fb16ad620df4ed6b7cc1819ff93f46634e2f1c9551627ec","tests/aead.rs":"e36ae77802df1ea6d17cfd1bd2178a3706089577d6fd1554ca86e748b8b235b9","tests/agent.rs":"824735f88e487a3748200844e9481e81a72163ad74d82faa9aa16594d9b9bb25","tests/ext.rs":"1b047d23d9b224ad06eb65d8f3a7b351e263774e404c79bbcbe8f43790e29c18","tests/handshake.rs":"e892a2839b31414be16e96cdf3b1a65978716094700c1a4989229f7edbf578a0","tests/hkdf.rs":"1d2098dc8398395864baf13e4886cfd1da6d36118727c3b264f457ee3da6b048","tests/hp.rs":"b24fec53771c169be788772532d2617a5349196cf87d6444dc74214f7c73e92c","tests/init.rs":"616313cb38eac44b8c71a1d23a52a7d7b4c7c07d4c20dc9ea6600c3317f92613","tests/selfencrypt.rs":"8d10840b41629bf449a6b3a551377315e8a05ca26c6b041548748196652c5909"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/neqo-crypto/Cargo.toml b/third_party/rust/neqo-crypto/Cargo.toml
index 499921e531..9507a066df 100644
--- a/third_party/rust/neqo-crypto/Cargo.toml
+++ b/third_party/rust/neqo-crypto/Cargo.toml
@@ -13,7 +13,7 @@
edition = "2021"
rust-version = "1.74.0"
name = "neqo-crypto"
-version = "0.7.2"
+version = "0.7.5"
authors = ["The Neqo Authors <necko@mozilla.com>"]
build = "build.rs"
homepage = "https://github.com/mozilla/neqo/"
@@ -43,6 +43,10 @@ version = "0.1"
optional = true
default-features = false
+[build-dependencies.semver]
+version = "1.0"
+default-features = false
+
[build-dependencies.serde]
version = "1.0"
default-features = false
@@ -56,7 +60,7 @@ version = "0.5"
default-features = false
[features]
-fuzzing = []
+disable-encryption = []
gecko = ["mozbuild"]
[lints.clippy.pedantic]
diff --git a/third_party/rust/neqo-crypto/bindings/bindings.toml b/third_party/rust/neqo-crypto/bindings/bindings.toml
index 3e5c1fdf7d..72c6d524d5 100644
--- a/third_party/rust/neqo-crypto/bindings/bindings.toml
+++ b/third_party/rust/neqo-crypto/bindings/bindings.toml
@@ -265,8 +265,3 @@ enums = [
[nspr_time]
types = ["PRTime"]
functions = ["PR_Now"]
-
-[mozpkix]
-cplusplus = true
-types = ["mozilla::pkix::ErrorCode"]
-enums = ["mozilla::pkix::ErrorCode"]
diff --git a/third_party/rust/neqo-crypto/bindings/mozpkix.hpp b/third_party/rust/neqo-crypto/bindings/mozpkix.hpp
deleted file mode 100644
index d0a6cb5861..0000000000
--- a/third_party/rust/neqo-crypto/bindings/mozpkix.hpp
+++ /dev/null
@@ -1 +0,0 @@
-#include "mozpkix/pkixnss.h" \ No newline at end of file
diff --git a/third_party/rust/neqo-crypto/build.rs b/third_party/rust/neqo-crypto/build.rs
index c4c2a73e75..2dd4543797 100644
--- a/third_party/rust/neqo-crypto/build.rs
+++ b/third_party/rust/neqo-crypto/build.rs
@@ -12,8 +12,13 @@ use std::{
};
use bindgen::Builder;
+use semver::{Version, VersionReq};
use serde_derive::Deserialize;
+#[path = "src/min_version.rs"]
+mod min_version;
+use min_version::MINIMUM_NSS_VERSION;
+
const BINDINGS_DIR: &str = "bindings";
const BINDINGS_CONFIG: &str = "bindings.toml";
@@ -90,46 +95,6 @@ fn setup_clang() {
}
}
-fn nss_dir() -> PathBuf {
- let dir = if let Ok(dir) = env::var("NSS_DIR") {
- let path = PathBuf::from(dir.trim());
- assert!(
- !path.is_relative(),
- "The NSS_DIR environment variable is expected to be an absolute path."
- );
- path
- } else {
- let out_dir = env::var("OUT_DIR").unwrap();
- let dir = Path::new(&out_dir).join("nss");
- if !dir.exists() {
- Command::new("hg")
- .args([
- "clone",
- "https://hg.mozilla.org/projects/nss",
- dir.to_str().unwrap(),
- ])
- .status()
- .expect("can't clone nss");
- }
- let nspr_dir = Path::new(&out_dir).join("nspr");
- if !nspr_dir.exists() {
- Command::new("hg")
- .args([
- "clone",
- "https://hg.mozilla.org/projects/nspr",
- nspr_dir.to_str().unwrap(),
- ])
- .status()
- .expect("can't clone nspr");
- }
- dir
- };
- assert!(dir.is_dir(), "NSS_DIR {dir:?} doesn't exist");
- // Note that this returns a relative path because UNC
- // paths on windows cause certain tools to explode.
- dir
-}
-
fn get_bash() -> PathBuf {
// If BASH is set, use that.
if let Ok(bash) = env::var("BASH") {
@@ -295,11 +260,63 @@ fn build_bindings(base: &str, bindings: &Bindings, flags: &[String], gecko: bool
.expect("couldn't write bindings");
}
-fn setup_standalone() -> Vec<String> {
+fn pkg_config() -> Vec<String> {
+ let modversion = Command::new("pkg-config")
+ .args(["--modversion", "nss"])
+ .output()
+ .expect("pkg-config reports NSS as absent")
+ .stdout;
+ let modversion = String::from_utf8(modversion).expect("non-UTF8 from pkg-config");
+ let modversion = modversion.trim();
+ // The NSS version number does not follow semver numbering, because it omits the patch version
+ // when that's 0. Deal with that.
+ let modversion_for_cmp = if modversion.chars().filter(|c| *c == '.').count() == 1 {
+ modversion.to_owned() + ".0"
+ } else {
+ modversion.to_owned()
+ };
+ let modversion_for_cmp =
+ Version::parse(&modversion_for_cmp).expect("NSS version not in semver format");
+ let version_req = VersionReq::parse(&format!(">={}", MINIMUM_NSS_VERSION.trim())).unwrap();
+ assert!(
+ version_req.matches(&modversion_for_cmp),
+ "neqo has NSS version requirement {version_req}, found {modversion}"
+ );
+
+ let cfg = Command::new("pkg-config")
+ .args(["--cflags", "--libs", "nss"])
+ .output()
+ .expect("NSS flags not returned by pkg-config")
+ .stdout;
+ let cfg_str = String::from_utf8(cfg).expect("non-UTF8 from pkg-config");
+
+ let mut flags: Vec<String> = Vec::new();
+ for f in cfg_str.split(' ') {
+ if let Some(include) = f.strip_prefix("-I") {
+ flags.push(String::from(f));
+ println!("cargo:include={include}");
+ } else if let Some(path) = f.strip_prefix("-L") {
+ println!("cargo:rustc-link-search=native={path}");
+ } else if let Some(lib) = f.strip_prefix("-l") {
+ println!("cargo:rustc-link-lib=dylib={lib}");
+ } else {
+ println!("Warning: Unknown flag from pkg-config: {f}");
+ }
+ }
+
+ flags
+}
+
+fn setup_standalone(nss: &str) -> Vec<String> {
setup_clang();
println!("cargo:rerun-if-env-changed=NSS_DIR");
- let nss = nss_dir();
+ let nss = PathBuf::from(nss);
+ assert!(
+ !nss.is_relative(),
+ "The NSS_DIR environment variable is expected to be an absolute path."
+ );
+
build_nss(nss.clone());
// $NSS_DIR/../dist/
@@ -406,8 +423,10 @@ fn setup_for_gecko() -> Vec<String> {
fn main() {
let flags = if cfg!(feature = "gecko") {
setup_for_gecko()
+ } else if let Ok(nss_dir) = env::var("NSS_DIR") {
+ setup_standalone(nss_dir.trim())
} else {
- setup_standalone()
+ pkg_config()
};
let config_file = PathBuf::from(BINDINGS_DIR).join(BINDINGS_CONFIG);
diff --git a/third_party/rust/neqo-crypto/min_version.txt b/third_party/rust/neqo-crypto/min_version.txt
new file mode 100644
index 0000000000..422c9c7093
--- /dev/null
+++ b/third_party/rust/neqo-crypto/min_version.txt
@@ -0,0 +1 @@
+3.98
diff --git a/third_party/rust/neqo-crypto/src/aead.rs b/third_party/rust/neqo-crypto/src/aead.rs
index bf7d7fe9d7..21027d55b2 100644
--- a/third_party/rust/neqo-crypto/src/aead.rs
+++ b/third_party/rust/neqo-crypto/src/aead.rs
@@ -63,13 +63,7 @@ impl RealAead {
/// # Errors
///
/// Returns `Error` when the supporting NSS functions fail.
- pub fn new(
- _fuzzing: bool,
- version: Version,
- cipher: Cipher,
- secret: &SymKey,
- prefix: &str,
- ) -> Res<Self> {
+ pub fn new(version: Version, cipher: Cipher, secret: &SymKey, prefix: &str) -> Res<Self> {
let s: *mut PK11SymKey = **secret;
unsafe { Self::from_raw(version, cipher, s, prefix) }
}
diff --git a/third_party/rust/neqo-crypto/src/aead_fuzzing.rs b/third_party/rust/neqo-crypto/src/aead_null.rs
index 4e5a6de07f..2d5656de73 100644
--- a/third_party/rust/neqo-crypto/src/aead_fuzzing.rs
+++ b/third_party/rust/neqo-crypto/src/aead_null.rs
@@ -4,84 +4,63 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
+#![cfg(feature = "disable-encryption")]
+
use std::fmt;
use crate::{
constants::{Cipher, Version},
err::{sec::SEC_ERROR_BAD_DATA, Error, Res},
p11::SymKey,
- RealAead,
};
-pub const FIXED_TAG_FUZZING: &[u8] = &[0x0a; 16];
+pub const AEAD_NULL_TAG: &[u8] = &[0x0a; 16];
-pub struct FuzzingAead {
- real: Option<RealAead>,
-}
+pub struct AeadNull {}
-impl FuzzingAead {
- pub fn new(
- fuzzing: bool,
- version: Version,
- cipher: Cipher,
- secret: &SymKey,
- prefix: &str,
- ) -> Res<Self> {
- let real = if fuzzing {
- None
- } else {
- Some(RealAead::new(false, version, cipher, secret, prefix)?)
- };
- Ok(Self { real })
+impl AeadNull {
+ #[allow(clippy::missing_errors_doc)]
+ pub fn new(_version: Version, _cipher: Cipher, _secret: &SymKey, _prefix: &str) -> Res<Self> {
+ Ok(Self {})
}
#[must_use]
pub fn expansion(&self) -> usize {
- if let Some(aead) = &self.real {
- aead.expansion()
- } else {
- FIXED_TAG_FUZZING.len()
- }
+ AEAD_NULL_TAG.len()
}
+ #[allow(clippy::missing_errors_doc)]
pub fn encrypt<'a>(
&self,
- count: u64,
- aad: &[u8],
+ _count: u64,
+ _aad: &[u8],
input: &[u8],
output: &'a mut [u8],
) -> Res<&'a [u8]> {
- if let Some(aead) = &self.real {
- return aead.encrypt(count, aad, input, output);
- }
-
let l = input.len();
output[..l].copy_from_slice(input);
- output[l..l + 16].copy_from_slice(FIXED_TAG_FUZZING);
+ output[l..l + 16].copy_from_slice(AEAD_NULL_TAG);
Ok(&output[..l + 16])
}
+ #[allow(clippy::missing_errors_doc)]
pub fn decrypt<'a>(
&self,
- count: u64,
- aad: &[u8],
+ _count: u64,
+ _aad: &[u8],
input: &[u8],
output: &'a mut [u8],
) -> Res<&'a [u8]> {
- if let Some(aead) = &self.real {
- return aead.decrypt(count, aad, input, output);
- }
-
- if input.len() < FIXED_TAG_FUZZING.len() {
+ if input.len() < AEAD_NULL_TAG.len() {
return Err(Error::from(SEC_ERROR_BAD_DATA));
}
- let len_encrypted = input.len() - FIXED_TAG_FUZZING.len();
+ let len_encrypted = input.len() - AEAD_NULL_TAG.len();
// Check that:
// 1) expansion is all zeros and
// 2) if the encrypted data is also supplied that at least some values are no zero
// (otherwise padding will be interpreted as a valid packet)
- if &input[len_encrypted..] == FIXED_TAG_FUZZING
+ if &input[len_encrypted..] == AEAD_NULL_TAG
&& (len_encrypted == 0 || input[..len_encrypted].iter().any(|x| *x != 0x0))
{
output[..len_encrypted].copy_from_slice(&input[..len_encrypted]);
@@ -92,12 +71,8 @@ impl FuzzingAead {
}
}
-impl fmt::Debug for FuzzingAead {
+impl fmt::Debug for AeadNull {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- if let Some(a) = &self.real {
- a.fmt(f)
- } else {
- write!(f, "[FUZZING AEAD]")
- }
+ write!(f, "[NULL AEAD]")
}
}
diff --git a/third_party/rust/neqo-crypto/src/agent.rs b/third_party/rust/neqo-crypto/src/agent.rs
index 82a6dacd48..3d5a8b9f35 100644
--- a/third_party/rust/neqo-crypto/src/agent.rs
+++ b/third_party/rust/neqo-crypto/src/agent.rs
@@ -16,7 +16,7 @@ use std::{
time::Instant,
};
-use neqo_common::{hex_snip_middle, hex_with_len, qdebug, qinfo, qtrace, qwarn};
+use neqo_common::{hex_snip_middle, hex_with_len, qdebug, qtrace, qwarn};
pub use crate::{
agentio::{as_c_void, Record, RecordList},
@@ -406,10 +406,7 @@ impl SecretAgent {
self.set_option(ssl::Opt::Locking, false)?;
self.set_option(ssl::Opt::Tickets, false)?;
self.set_option(ssl::Opt::OcspStapling, true)?;
- if let Err(e) = self.set_option(ssl::Opt::Grease, grease) {
- // Until NSS supports greasing, it's OK to fail here.
- qinfo!([self], "Failed to enable greasing {:?}", e);
- }
+ self.set_option(ssl::Opt::Grease, grease)?;
Ok(())
}
@@ -670,7 +667,7 @@ impl SecretAgent {
let info = self.capture_error(SecretAgentInfo::new(self.fd))?;
HandshakeState::Complete(info)
};
- qinfo!([self], "state -> {:?}", self.state);
+ qdebug!([self], "state -> {:?}", self.state);
Ok(())
}
@@ -898,7 +895,7 @@ impl Client {
let len = usize::try_from(len).unwrap();
let mut v = Vec::with_capacity(len);
v.extend_from_slice(null_safe_slice(token, len));
- qinfo!(
+ qdebug!(
[format!("{fd:p}")],
"Got resumption token {}",
hex_snip_middle(&v)
diff --git a/third_party/rust/neqo-crypto/src/err.rs b/third_party/rust/neqo-crypto/src/err.rs
index 187303d2a9..8d4f239a0b 100644
--- a/third_party/rust/neqo-crypto/src/err.rs
+++ b/third_party/rust/neqo-crypto/src/err.rs
@@ -16,13 +16,39 @@ mod codes {
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/nss_secerr.rs"));
include!(concat!(env!("OUT_DIR"), "/nss_sslerr.rs"));
- include!(concat!(env!("OUT_DIR"), "/mozpkix.rs"));
}
-pub use codes::{mozilla_pkix_ErrorCode as mozpkix, SECErrorCodes as sec, SSLErrorCodes as ssl};
+pub use codes::{SECErrorCodes as sec, SSLErrorCodes as ssl};
pub mod nspr {
include!(concat!(env!("OUT_DIR"), "/nspr_err.rs"));
}
+pub mod mozpkix {
+ // These are manually extracted from the many bindings generated
+ // by bindgen when provided with the simple header:
+ // #include "mozpkix/pkixnss.h"
+
+ #[allow(non_camel_case_types)]
+ pub type mozilla_pkix_ErrorCode = ::std::os::raw::c_int;
+ pub const MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE: mozilla_pkix_ErrorCode = -16384;
+ pub const MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY: mozilla_pkix_ErrorCode = -16383;
+ pub const MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE: mozilla_pkix_ErrorCode = -16382;
+ pub const MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA: mozilla_pkix_ErrorCode = -16381;
+ pub const MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH: mozilla_pkix_ErrorCode = -16380;
+ pub const MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE: mozilla_pkix_ErrorCode = -16379;
+ pub const MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE: mozilla_pkix_ErrorCode = -16378;
+ pub const MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH: mozilla_pkix_ErrorCode = -16377;
+ pub const MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING: mozilla_pkix_ErrorCode = -16376;
+ pub const MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG: mozilla_pkix_ErrorCode = -16375;
+ pub const MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING: mozilla_pkix_ErrorCode = -16374;
+ pub const MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING: mozilla_pkix_ErrorCode = -16373;
+ pub const MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME: mozilla_pkix_ErrorCode = -16372;
+ pub const MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED: mozilla_pkix_ErrorCode =
+ -16371;
+ pub const MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT: mozilla_pkix_ErrorCode = -16370;
+ pub const MOZILLA_PKIX_ERROR_MITM_DETECTED: mozilla_pkix_ErrorCode = -16369;
+ pub const END_OF_LIST: mozilla_pkix_ErrorCode = -16368;
+}
+
pub type Res<T> = Result<T, Error>;
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)]
diff --git a/third_party/rust/neqo-crypto/src/lib.rs b/third_party/rust/neqo-crypto/src/lib.rs
index 2ec1b4a3ea..2db985e8ee 100644
--- a/third_party/rust/neqo-crypto/src/lib.rs
+++ b/third_party/rust/neqo-crypto/src/lib.rs
@@ -8,8 +8,8 @@
#![allow(clippy::unseparated_literal_suffix, clippy::used_underscore_binding)] // For bindgen code.
mod aead;
-#[cfg(feature = "fuzzing")]
-mod aead_fuzzing;
+#[cfg(feature = "disable-encryption")]
+pub mod aead_null;
pub mod agent;
mod agentio;
mod auth;
@@ -33,12 +33,12 @@ mod time;
use std::{ffi::CString, path::PathBuf, ptr::null, sync::OnceLock};
-#[cfg(not(feature = "fuzzing"))]
+#[cfg(not(feature = "disable-encryption"))]
pub use self::aead::RealAead as Aead;
-#[cfg(feature = "fuzzing")]
+#[cfg(feature = "disable-encryption")]
pub use self::aead::RealAead;
-#[cfg(feature = "fuzzing")]
-pub use self::aead_fuzzing::FuzzingAead as Aead;
+#[cfg(feature = "disable-encryption")]
+pub use self::aead_null::AeadNull as Aead;
pub use self::{
agent::{
Agent, AllowZeroRtt, Client, HandshakeState, Record, RecordList, ResumptionToken,
@@ -59,7 +59,8 @@ pub use self::{
ssl::Opt,
};
-const MINIMUM_NSS_VERSION: &str = "3.97";
+mod min_version;
+use min_version::MINIMUM_NSS_VERSION;
#[allow(non_upper_case_globals, clippy::redundant_static_lifetimes)]
#[allow(clippy::upper_case_acronyms)]
@@ -89,7 +90,7 @@ impl Drop for NssLoaded {
}
}
-static INITIALIZED: OnceLock<NssLoaded> = OnceLock::new();
+static INITIALIZED: OnceLock<Res<NssLoaded>> = OnceLock::new();
fn already_initialized() -> bool {
unsafe { nss::NSS_IsInitialized() != 0 }
@@ -107,24 +108,24 @@ fn version_check() {
/// Initialize NSS. This only executes the initialization routines once, so if there is any chance
/// that
///
-/// # Panics
+/// # Errors
///
/// When NSS initialization fails.
-pub fn init() {
+pub fn init() -> Res<()> {
// Set time zero.
time::init();
- _ = INITIALIZED.get_or_init(|| {
+ let res = INITIALIZED.get_or_init(|| {
version_check();
if already_initialized() {
- return NssLoaded::External;
+ return Ok(NssLoaded::External);
}
- secstatus_to_res(unsafe { nss::NSS_NoDB_Init(null()) }).expect("NSS_NoDB_Init failed");
- secstatus_to_res(unsafe { nss::NSS_SetDomesticPolicy() })
- .expect("NSS_SetDomesticPolicy failed");
+ secstatus_to_res(unsafe { nss::NSS_NoDB_Init(null()) })?;
+ secstatus_to_res(unsafe { nss::NSS_SetDomesticPolicy() })?;
- NssLoaded::NoDb
+ Ok(NssLoaded::NoDb)
});
+ res.as_ref().map(|_| ()).map_err(Clone::clone)
}
/// This enables SSLTRACE by calling a simple, harmless function to trigger its
@@ -132,31 +133,32 @@ pub fn init() {
/// global options are accessed. Reading an option is the least impact approach.
/// This allows us to use SSLTRACE in all of our unit tests and programs.
#[cfg(debug_assertions)]
-fn enable_ssl_trace() {
+fn enable_ssl_trace() -> Res<()> {
let opt = ssl::Opt::Locking.as_int();
let mut v: ::std::os::raw::c_int = 0;
secstatus_to_res(unsafe { ssl::SSL_OptionGetDefault(opt, &mut v) })
- .expect("SSL_OptionGetDefault failed");
}
/// Initialize with a database.
///
-/// # Panics
+/// # Errors
///
/// If NSS cannot be initialized.
-pub fn init_db<P: Into<PathBuf>>(dir: P) {
+pub fn init_db<P: Into<PathBuf>>(dir: P) -> Res<()> {
time::init();
- _ = INITIALIZED.get_or_init(|| {
+ let res = INITIALIZED.get_or_init(|| {
version_check();
if already_initialized() {
- return NssLoaded::External;
+ return Ok(NssLoaded::External);
}
let path = dir.into();
- assert!(path.is_dir());
- let pathstr = path.to_str().expect("path converts to string").to_string();
- let dircstr = CString::new(pathstr).unwrap();
- let empty = CString::new("").unwrap();
+ if !path.is_dir() {
+ return Err(Error::InternalError);
+ }
+ let pathstr = path.to_str().ok_or(Error::InternalError)?;
+ let dircstr = CString::new(pathstr)?;
+ let empty = CString::new("")?;
secstatus_to_res(unsafe {
nss::NSS_Initialize(
dircstr.as_ptr(),
@@ -165,21 +167,19 @@ pub fn init_db<P: Into<PathBuf>>(dir: P) {
nss::SECMOD_DB.as_ptr().cast(),
nss::NSS_INIT_READONLY,
)
- })
- .expect("NSS_Initialize failed");
+ })?;
- secstatus_to_res(unsafe { nss::NSS_SetDomesticPolicy() })
- .expect("NSS_SetDomesticPolicy failed");
+ secstatus_to_res(unsafe { nss::NSS_SetDomesticPolicy() })?;
secstatus_to_res(unsafe {
ssl::SSL_ConfigServerSessionIDCache(1024, 0, 0, dircstr.as_ptr())
- })
- .expect("SSL_ConfigServerSessionIDCache failed");
+ })?;
#[cfg(debug_assertions)]
- enable_ssl_trace();
+ enable_ssl_trace()?;
- NssLoaded::Db
+ Ok(NssLoaded::Db)
});
+ res.as_ref().map(|_| ()).map_err(Clone::clone)
}
/// # Panics
diff --git a/third_party/rust/neqo-crypto/src/min_version.rs b/third_party/rust/neqo-crypto/src/min_version.rs
new file mode 100644
index 0000000000..4386371b1b
--- /dev/null
+++ b/third_party/rust/neqo-crypto/src/min_version.rs
@@ -0,0 +1,9 @@
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+/// The minimum version of NSS that is required by this version of neqo.
+/// Note that the string may contain whitespace at the beginning and/or end.
+pub(crate) const MINIMUM_NSS_VERSION: &str = include_str!("../min_version.txt");
diff --git a/third_party/rust/neqo-crypto/src/selfencrypt.rs b/third_party/rust/neqo-crypto/src/selfencrypt.rs
index 1130c35250..d0a85830b0 100644
--- a/third_party/rust/neqo-crypto/src/selfencrypt.rs
+++ b/third_party/rust/neqo-crypto/src/selfencrypt.rs
@@ -47,7 +47,7 @@ impl SelfEncrypt {
debug_assert_eq!(salt.len(), Self::SALT_LENGTH);
let salt = hkdf::import_key(self.version, salt)?;
let secret = hkdf::extract(self.version, self.cipher, Some(&salt), k)?;
- Aead::new(false, self.version, self.cipher, &secret, "neqo self")
+ Aead::new(self.version, self.cipher, &secret, "neqo self")
}
/// Rotate keys. This causes any previous key that is being held to be replaced by the current
diff --git a/third_party/rust/neqo-crypto/src/time.rs b/third_party/rust/neqo-crypto/src/time.rs
index 0e59c4f5e2..359436a854 100644
--- a/third_party/rust/neqo-crypto/src/time.rs
+++ b/third_party/rust/neqo-crypto/src/time.rs
@@ -258,11 +258,11 @@ mod test {
#[test]
// We allow replace_consts here because
- // std::u64::max_value() isn't available
+ // std::u64::MAX isn't available
// in all of our targets
fn overflow_interval() {
init();
- let interval = Interval::from(Duration::from_micros(u64::max_value()));
+ let interval = Interval::from(Duration::from_micros(u64::MAX));
let res: Res<PRTime> = interval.try_into();
assert!(res.is_err());
}
diff --git a/third_party/rust/neqo-crypto/tests/aead.rs b/third_party/rust/neqo-crypto/tests/aead.rs
index 5cf0034aec..f8416ed9a7 100644
--- a/third_party/rust/neqo-crypto/tests/aead.rs
+++ b/third_party/rust/neqo-crypto/tests/aead.rs
@@ -5,7 +5,7 @@
// except according to those terms.
#![warn(clippy::pedantic)]
-#![cfg(not(feature = "fuzzing"))]
+#![cfg(not(feature = "disable-encryption"))]
use neqo_crypto::{
constants::{Cipher, TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3},
@@ -40,7 +40,6 @@ fn make_aead(cipher: Cipher) -> Aead {
)
.expect("make a secret");
Aead::new(
- false,
TLS_VERSION_1_3,
cipher,
&secret,
diff --git a/third_party/rust/neqo-crypto/tests/init.rs b/third_party/rust/neqo-crypto/tests/init.rs
index 13218cc340..ee7d808e29 100644
--- a/third_party/rust/neqo-crypto/tests/init.rs
+++ b/third_party/rust/neqo-crypto/tests/init.rs
@@ -15,13 +15,7 @@ use neqo_crypto::{assert_initialized, init_db};
// Pull in the NSS internals so that we can ask NSS if it thinks that
// it is properly initialized.
-#[allow(
- dead_code,
- non_upper_case_globals,
- clippy::redundant_static_lifetimes,
- clippy::unseparated_literal_suffix,
- clippy::upper_case_acronyms
-)]
+#[allow(dead_code, non_upper_case_globals)]
mod nss {
include!(concat!(env!("OUT_DIR"), "/nss_init.rs"));
}
@@ -29,19 +23,54 @@ mod nss {
#[cfg(nss_nodb)]
#[test]
fn init_nodb() {
- init();
+ neqo_crypto::init().unwrap();
assert_initialized();
unsafe {
- assert!(nss::NSS_IsInitialized() != 0);
+ assert_ne!(nss::NSS_IsInitialized(), 0);
}
}
+#[cfg(nss_nodb)]
+#[test]
+fn init_twice_nodb() {
+ unsafe {
+ nss::NSS_NoDB_Init(std::ptr::null());
+ assert_ne!(nss::NSS_IsInitialized(), 0);
+ }
+ // Now do it again
+ init_nodb();
+}
+
#[cfg(not(nss_nodb))]
#[test]
fn init_withdb() {
- init_db(::test_fixture::NSS_DB_PATH);
+ init_db(::test_fixture::NSS_DB_PATH).unwrap();
assert_initialized();
unsafe {
- assert!(nss::NSS_IsInitialized() != 0);
+ assert_ne!(nss::NSS_IsInitialized(), 0);
+ }
+}
+
+#[cfg(not(nss_nodb))]
+#[test]
+fn init_twice_withdb() {
+ use std::{ffi::CString, path::PathBuf};
+
+ let empty = CString::new("").unwrap();
+ let path: PathBuf = ::test_fixture::NSS_DB_PATH.into();
+ assert!(path.is_dir());
+ let pathstr = path.to_str().unwrap();
+ let dircstr = CString::new(pathstr).unwrap();
+ unsafe {
+ nss::NSS_Initialize(
+ dircstr.as_ptr(),
+ empty.as_ptr(),
+ empty.as_ptr(),
+ nss::SECMOD_DB.as_ptr().cast(),
+ nss::NSS_INIT_READONLY,
+ );
+ assert_ne!(nss::NSS_IsInitialized(), 0);
}
+ // Now do it again
+ init_withdb();
}
diff --git a/third_party/rust/neqo-crypto/tests/selfencrypt.rs b/third_party/rust/neqo-crypto/tests/selfencrypt.rs
index 4c574a3ae9..9fc2162fe2 100644
--- a/third_party/rust/neqo-crypto/tests/selfencrypt.rs
+++ b/third_party/rust/neqo-crypto/tests/selfencrypt.rs
@@ -4,7 +4,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
-#![cfg(not(feature = "fuzzing"))]
+#![cfg(not(feature = "disable-encryption"))]
use neqo_crypto::{
constants::{TLS_AES_128_GCM_SHA256, TLS_VERSION_1_3},
@@ -15,7 +15,7 @@ use neqo_crypto::{
#[test]
fn se_create() {
- init();
+ init().unwrap();
SelfEncrypt::new(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256).expect("constructor works");
}
@@ -23,7 +23,7 @@ const PLAINTEXT: &[u8] = b"PLAINTEXT";
const AAD: &[u8] = b"AAD";
fn sealed() -> (SelfEncrypt, Vec<u8>) {
- init();
+ init().unwrap();
let se = SelfEncrypt::new(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256).unwrap();
let sealed = se.seal(AAD, PLAINTEXT).expect("sealing works");
(se, sealed)
diff --git a/third_party/rust/neqo-http3/.cargo-checksum.json b/third_party/rust/neqo-http3/.cargo-checksum.json
index 0459fea7cc..dff33cc964 100644
--- a/third_party/rust/neqo-http3/.cargo-checksum.json
+++ b/third_party/rust/neqo-http3/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"458f04261cda071d61402c52cf64062ad7cfc24f3f312bfaa5d52cae47409010","src/buffered_send_stream.rs":"f45bdf9ad2a04b3828c74ff5440681d3c9d1af39b55470e4f729842dc2412295","src/client_events.rs":"77fedca72ce54956eaba3fb7103085d196a631b764662584ea2629224c5c234e","src/conn_params.rs":"224a8ea6ef632930a7788a1cabf47ce69ad41bd4bc8dcf3053fbd998fdb38e82","src/connection.rs":"9384cdfd8481a30a0cd13f56f590188ccfa47b4472f35f7a4978537bab19adc1","src/connection_client.rs":"8db29409f3a265f7dff7c7a7eaf2ac607d6923e4b3238e82eab6dc22854e4303","src/connection_server.rs":"ca33b50650bd1ca2a952851b72712d55ec2e48b48f1f06e4184c808b8e1e009a","src/control_stream_local.rs":"ae52e3286f1686ca1265e7de841392addd42616db02799bb967a59feb6039cb5","src/control_stream_remote.rs":"59eb4041e366d92f9f294e8446755caa5e91fd943bba7b79b726698ba13be248","src/features/extended_connect/mod.rs":"3b02f6b18627f3855465a81b1d9b285e6f13839e75a8a6db648ed9082908d7f0","src/features/extended_connect/tests/mod.rs":"fd6aee37243713e80fc526552f21f0222338cec9890409b6575a2a637b17ec1f","src/features/extended_connect/tests/webtransport/datagrams.rs":"4c85a90afb753ce588e3fdeb773669bc49c013aebc28912340359eb01b74fd70","src/features/extended_connect/tests/webtransport/mod.rs":"a30ea715f5271a826a739278b18e145964dedbce7026eed45f1b7d0355c407d5","src/features/extended_connect/tests/webtransport/negotiation.rs":"98254ef8446581ec520026b04ef9549645602181b61602c9936f6660141edf0b","src/features/extended_connect/tests/webtransport/sessions.rs":"de3d836f666c2bec31e70b33bdc2669572cabbe17df2225db7282613a224a364","src/features/extended_connect/tests/webtransport/streams.rs":"8b3c34cac1b2171252a4bb53d420ac2098549a20309c327bf56e2e9ba9e33538","src/features/extended_connect/webtransport_session.rs":"239d92c06fbc5f6226078bb411a803f57b555dea0077349d49d7f57671cf2eab","src/features/extended_connect/webtransport_streams.rs":"5d7507aaf6a819d266fbea9b7a415c8324329df0f6936d9045b73e17a5b844ee","src/features/mod.rs":"925aae4427ad82e4d019354802b223d53db5e5585d4a940f5417a24a9503d7ee","src/frames/hframe.rs":"56c36ac597504f28c73cf2370acd82104f8c7a7b9ffc0f6d222378abc524482d","src/frames/mod.rs":"7d0a46ca147336d14781edb8dbee8b03c2e4bcd6646f5473a9d93d31fe73fecb","src/frames/reader.rs":"e07ee9de74bc499c10afcda592fefd9a7eef3381c045aa14f6596d67313546ca","src/frames/tests/hframe.rs":"01ec74eb3eb25d95042aa0263f9267f89535e6b7b8c1161fab4ba9ee5352d4a7","src/frames/tests/mod.rs":"0610609b316767a6a022837d32ee0452e37ea296fde37e51bec87e7c77e923a3","src/frames/tests/reader.rs":"2bfadc7afbc41bff9f5f930b31550259a8a92484d35f6c5d8dd8fd9acfb88f5b","src/frames/tests/wtframe.rs":"589ebe1e62ce4da63b37b7d22cde7ba572ddbf29336fdcdbbcd0a745f79dacd8","src/frames/wtframe.rs":"1d9d0256ace2ba7262343ed035df795f21a4d45065792d3fd45b3391b6916b2f","src/headers_checks.rs":"be0f0109298dcc3a40350b7c0950076ddfe20617d195b305e3ffc8582557ab18","src/lib.rs":"4f908a021222bcc79b9d569bc3759a493379a20b47dfa228fddf51600bf6e446","src/priority.rs":"f3b77c208962e44a4e2d13138c6998b703d40e7bcf8f73ea84d8ef5b556e0aee","src/push_controller.rs":"13bccf2834ae19109504cf695a5948c3b2d03fd101bc032a92bb77a033423854","src/qlog.rs":"2debd75c7ea103c95ff79e44412f1408c3e496e324976100c55d5a833912b6c3","src/qpack_decoder_receiver.rs":"c927dfc3e58c71d282210ba79280f6f03e789733bc3bedc247e68bab516b9e9e","src/qpack_encoder_receiver.rs":"d0ac03cc111b6e1c555a8654d3234116f2b135b5b040edac23cefe2d640beba9","src/recv_message.rs":"eb711dbc6b3371373c26b75333ac5858edf0d30184b0e05d67ab02c656eb6619","src/request_target.rs":"6041a69a0a74969ec08bc164509c055e9bad99f53bbeb16c0aa17d108dd68b8c","src/send_message.rs":"7785af11b77cee398faf3f7a2875b41e251ed7a1b272c23f81a48334596ab836","src/server.rs":"b9e6060da36cfb467478f5b78b17e22a123214ad2d64c919ce688ea2bc0e24bb","src/server_connection_events.rs":"12d353ca6301467f6d475dde3b789951a5716c89ddd7dbf1383efef8082361f3","src/server_events.rs":"463dd2cb6f97a800bac32c93c4aa2a6289f71e33a89f3b33152460cb941fc378","src/settings.rs":"476b154b5eea4c8d69a4a790fee3e527cef4d375df1cfb5eed04ec56406fe15a","src/stream_type_reader.rs":"7a7226b7911d69f7e00ec4987c2a32a5e8a33463203398cbee1e6645d2691478","tests/httpconn.rs":"bb6927801a8c75e4f05eb6cdb1e7f2d57be69b74e68ddad2a1614f2aeed04369","tests/priority.rs":"364754507873298612ad12e8d1d106d26d993712142d0be4cbf056da5338854c","tests/send_message.rs":"b5435045b16429d9e626ea94a8f10e2937e1a5a878af0035763a4f5ec09bf53c","tests/webtransport.rs":"25794305017ff58e57dc3c3b9b078e5bfc1814ea82a521b7b7156228e613c092"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"f3d4e7ace03bbc6ee36fb30540e0a96aebdf5222090cd95abdff14b840cfa68e","src/buffered_send_stream.rs":"f45bdf9ad2a04b3828c74ff5440681d3c9d1af39b55470e4f729842dc2412295","src/client_events.rs":"77fedca72ce54956eaba3fb7103085d196a631b764662584ea2629224c5c234e","src/conn_params.rs":"224a8ea6ef632930a7788a1cabf47ce69ad41bd4bc8dcf3053fbd998fdb38e82","src/connection.rs":"e9c869ec4c650162927543cfe608721fad037554c5101962b323496bf69e29a5","src/connection_client.rs":"2026e7b2c11f4d5a71b35eedaf14175d7d7cf96ce2bb64c4a97d6062cbb646c7","src/connection_server.rs":"de5a6cb42b8c4dc08fb1f626f681b45cd22435892b11e6053b61a5401490db94","src/control_stream_local.rs":"ae52e3286f1686ca1265e7de841392addd42616db02799bb967a59feb6039cb5","src/control_stream_remote.rs":"59eb4041e366d92f9f294e8446755caa5e91fd943bba7b79b726698ba13be248","src/features/extended_connect/mod.rs":"3b02f6b18627f3855465a81b1d9b285e6f13839e75a8a6db648ed9082908d7f0","src/features/extended_connect/tests/mod.rs":"fd6aee37243713e80fc526552f21f0222338cec9890409b6575a2a637b17ec1f","src/features/extended_connect/tests/webtransport/datagrams.rs":"4c85a90afb753ce588e3fdeb773669bc49c013aebc28912340359eb01b74fd70","src/features/extended_connect/tests/webtransport/mod.rs":"a30ea715f5271a826a739278b18e145964dedbce7026eed45f1b7d0355c407d5","src/features/extended_connect/tests/webtransport/negotiation.rs":"98254ef8446581ec520026b04ef9549645602181b61602c9936f6660141edf0b","src/features/extended_connect/tests/webtransport/sessions.rs":"de3d836f666c2bec31e70b33bdc2669572cabbe17df2225db7282613a224a364","src/features/extended_connect/tests/webtransport/streams.rs":"8b3c34cac1b2171252a4bb53d420ac2098549a20309c327bf56e2e9ba9e33538","src/features/extended_connect/webtransport_session.rs":"239d92c06fbc5f6226078bb411a803f57b555dea0077349d49d7f57671cf2eab","src/features/extended_connect/webtransport_streams.rs":"5d7507aaf6a819d266fbea9b7a415c8324329df0f6936d9045b73e17a5b844ee","src/features/mod.rs":"925aae4427ad82e4d019354802b223d53db5e5585d4a940f5417a24a9503d7ee","src/frames/hframe.rs":"56c36ac597504f28c73cf2370acd82104f8c7a7b9ffc0f6d222378abc524482d","src/frames/mod.rs":"7d0a46ca147336d14781edb8dbee8b03c2e4bcd6646f5473a9d93d31fe73fecb","src/frames/reader.rs":"e07ee9de74bc499c10afcda592fefd9a7eef3381c045aa14f6596d67313546ca","src/frames/tests/hframe.rs":"01ec74eb3eb25d95042aa0263f9267f89535e6b7b8c1161fab4ba9ee5352d4a7","src/frames/tests/mod.rs":"0610609b316767a6a022837d32ee0452e37ea296fde37e51bec87e7c77e923a3","src/frames/tests/reader.rs":"2bfadc7afbc41bff9f5f930b31550259a8a92484d35f6c5d8dd8fd9acfb88f5b","src/frames/tests/wtframe.rs":"589ebe1e62ce4da63b37b7d22cde7ba572ddbf29336fdcdbbcd0a745f79dacd8","src/frames/wtframe.rs":"1d9d0256ace2ba7262343ed035df795f21a4d45065792d3fd45b3391b6916b2f","src/headers_checks.rs":"be0f0109298dcc3a40350b7c0950076ddfe20617d195b305e3ffc8582557ab18","src/lib.rs":"4f908a021222bcc79b9d569bc3759a493379a20b47dfa228fddf51600bf6e446","src/priority.rs":"f3b77c208962e44a4e2d13138c6998b703d40e7bcf8f73ea84d8ef5b556e0aee","src/push_controller.rs":"13bccf2834ae19109504cf695a5948c3b2d03fd101bc032a92bb77a033423854","src/qlog.rs":"2debd75c7ea103c95ff79e44412f1408c3e496e324976100c55d5a833912b6c3","src/qpack_decoder_receiver.rs":"c927dfc3e58c71d282210ba79280f6f03e789733bc3bedc247e68bab516b9e9e","src/qpack_encoder_receiver.rs":"d0ac03cc111b6e1c555a8654d3234116f2b135b5b040edac23cefe2d640beba9","src/recv_message.rs":"7ac8d4057ba53874e4edfc62cd25ad5d3f0b10aaac5bf6e156103c3bc44e18cc","src/request_target.rs":"6041a69a0a74969ec08bc164509c055e9bad99f53bbeb16c0aa17d108dd68b8c","src/send_message.rs":"374e168f60063b8102a2aff52c719ae2e1e5078527cf50d095b3e7217f6ec7d2","src/server.rs":"b9e6060da36cfb467478f5b78b17e22a123214ad2d64c919ce688ea2bc0e24bb","src/server_connection_events.rs":"12d353ca6301467f6d475dde3b789951a5716c89ddd7dbf1383efef8082361f3","src/server_events.rs":"1cda8d6c413fad0fa67fcfd7cb78e795bf7ef7f0e09b5720992646a82d51ce16","src/settings.rs":"476b154b5eea4c8d69a4a790fee3e527cef4d375df1cfb5eed04ec56406fe15a","src/stream_type_reader.rs":"7a7226b7911d69f7e00ec4987c2a32a5e8a33463203398cbee1e6645d2691478","tests/httpconn.rs":"bb6927801a8c75e4f05eb6cdb1e7f2d57be69b74e68ddad2a1614f2aeed04369","tests/priority.rs":"364754507873298612ad12e8d1d106d26d993712142d0be4cbf056da5338854c","tests/send_message.rs":"b5435045b16429d9e626ea94a8f10e2937e1a5a878af0035763a4f5ec09bf53c","tests/webtransport.rs":"25794305017ff58e57dc3c3b9b078e5bfc1814ea82a521b7b7156228e613c092"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/neqo-http3/Cargo.toml b/third_party/rust/neqo-http3/Cargo.toml
index 12b9a125d0..c844ba43ef 100644
--- a/third_party/rust/neqo-http3/Cargo.toml
+++ b/third_party/rust/neqo-http3/Cargo.toml
@@ -13,7 +13,7 @@
edition = "2021"
rust-version = "1.74.0"
name = "neqo-http3"
-version = "0.7.2"
+version = "0.7.5"
authors = ["The Neqo Authors <necko@mozilla.com>"]
homepage = "https://github.com/mozilla/neqo/"
license = "MIT OR Apache-2.0"
@@ -62,9 +62,9 @@ default-features = false
path = "../test-fixture"
[features]
-fuzzing = [
- "neqo-transport/fuzzing",
- "neqo-crypto/fuzzing",
+disable-encryption = [
+ "neqo-transport/disable-encryption",
+ "neqo-crypto/disable-encryption",
]
[lints.clippy.pedantic]
diff --git a/third_party/rust/neqo-http3/src/connection.rs b/third_party/rust/neqo-http3/src/connection.rs
index 287ea2c2af..dd45797baa 100644
--- a/third_party/rust/neqo-http3/src/connection.rs
+++ b/third_party/rust/neqo-http3/src/connection.rs
@@ -354,7 +354,7 @@ impl Http3Connection {
/// This function creates and initializes, i.e. send stream type, the control and qpack
/// streams.
fn initialize_http3_connection(&mut self, conn: &mut Connection) -> Res<()> {
- qinfo!([self], "Initialize the http3 connection.");
+ qdebug!([self], "Initialize the http3 connection.");
self.control_stream_local.create(conn)?;
self.send_settings();
@@ -386,8 +386,8 @@ impl Http3Connection {
Ok(())
}
- /// Inform a `HttpConnection` that a stream has data to send and that `send` should be called
- /// for the stream.
+ /// Inform an [`Http3Connection`] that a stream has data to send and that
+ /// [`SendStream::send`] should be called for the stream.
pub fn stream_has_pending_data(&mut self, stream_id: StreamId) {
self.streams_with_pending_data.insert(stream_id);
}
@@ -704,7 +704,7 @@ impl Http3Connection {
);
}
NewStreamType::Decoder => {
- qinfo!([self], "A new remote qpack encoder stream {}", stream_id);
+ qdebug!([self], "A new remote qpack encoder stream {}", stream_id);
self.check_stream_exists(Http3StreamType::Decoder)?;
self.recv_streams.insert(
stream_id,
@@ -715,7 +715,7 @@ impl Http3Connection {
);
}
NewStreamType::Encoder => {
- qinfo!([self], "A new remote qpack decoder stream {}", stream_id);
+ qdebug!([self], "A new remote qpack decoder stream {}", stream_id);
self.check_stream_exists(Http3StreamType::Encoder)?;
self.recv_streams.insert(
stream_id,
@@ -766,7 +766,7 @@ impl Http3Connection {
/// This is called when an application closes the connection.
pub fn close(&mut self, error: AppError) {
- qinfo!([self], "Close connection error {:?}.", error);
+ qdebug!([self], "Close connection error {:?}.", error);
self.state = Http3State::Closing(ConnectionError::Application(error));
if (!self.send_streams.is_empty() || !self.recv_streams.is_empty()) && (error == 0) {
qwarn!("close(0) called when streams still active");
@@ -952,7 +952,7 @@ impl Http3Connection {
stream_id: StreamId,
buf: &mut [u8],
) -> Res<(usize, bool)> {
- qinfo!([self], "read_data from stream {}.", stream_id);
+ qdebug!([self], "read_data from stream {}.", stream_id);
let res = self
.recv_streams
.get_mut(&stream_id)
@@ -1091,7 +1091,7 @@ impl Http3Connection {
/// This is called when an application wants to close the sending side of a stream.
pub fn stream_close_send(&mut self, conn: &mut Connection, stream_id: StreamId) -> Res<()> {
- qinfo!([self], "Close the sending side for stream {}.", stream_id);
+ qdebug!([self], "Close the sending side for stream {}.", stream_id);
debug_assert!(self.state.active());
let send_stream = self
.send_streams
@@ -1402,7 +1402,7 @@ impl Http3Connection {
/// `PriorityUpdateRequestPush` which handling is specific to the client and server, we must
/// give them to the specific client/server handler.
fn handle_control_frame(&mut self, f: HFrame) -> Res<Option<HFrame>> {
- qinfo!([self], "Handle a control frame {:?}", f);
+ qdebug!([self], "Handle a control frame {:?}", f);
if !matches!(f, HFrame::Settings { .. })
&& !matches!(
self.settings_state,
@@ -1433,7 +1433,7 @@ impl Http3Connection {
}
fn handle_settings(&mut self, new_settings: HSettings) -> Res<()> {
- qinfo!([self], "Handle SETTINGS frame.");
+ qdebug!([self], "Handle SETTINGS frame.");
match &self.settings_state {
Http3RemoteSettingsState::NotReceived => {
self.set_qpack_settings(&new_settings)?;
diff --git a/third_party/rust/neqo-http3/src/connection_client.rs b/third_party/rust/neqo-http3/src/connection_client.rs
index 52572a760d..4c8772d14a 100644
--- a/third_party/rust/neqo-http3/src/connection_client.rs
+++ b/third_party/rust/neqo-http3/src/connection_client.rs
@@ -7,7 +7,7 @@
use std::{
cell::RefCell,
fmt::{Debug, Display},
- mem,
+ iter, mem,
net::SocketAddr,
rc::Rc,
time::Instant,
@@ -590,7 +590,7 @@ impl Http3Client {
///
/// An error will be return if stream does not exist.
pub fn stream_close_send(&mut self, stream_id: StreamId) -> Res<()> {
- qinfo!([self], "Close sending side stream={}.", stream_id);
+ qdebug!([self], "Close sending side stream={}.", stream_id);
self.base_handler
.stream_close_send(&mut self.conn, stream_id)
}
@@ -652,7 +652,7 @@ impl Http3Client {
stream_id: StreamId,
buf: &mut [u8],
) -> Res<(usize, bool)> {
- qinfo!([self], "read_data from stream {}.", stream_id);
+ qdebug!([self], "read_data from stream {}.", stream_id);
let res = self.base_handler.read_data(&mut self.conn, stream_id, buf);
if let Err(e) = &res {
if e.connection_error() {
@@ -874,19 +874,16 @@ impl Http3Client {
///
/// [1]: ../neqo_transport/enum.ConnectionEvent.html
pub fn process_input(&mut self, dgram: &Datagram, now: Instant) {
- qtrace!([self], "Process input.");
- self.conn.process_input(dgram, now);
- self.process_http3(now);
+ self.process_multiple_input(iter::once(dgram), now);
}
pub fn process_multiple_input<'a, I>(&mut self, dgrams: I, now: Instant)
where
I: IntoIterator<Item = &'a Datagram>,
- I::IntoIter: ExactSizeIterator,
{
- let dgrams = dgrams.into_iter();
- qtrace!([self], "Process multiple datagrams, len={}", dgrams.len());
- if dgrams.len() == 0 {
+ let mut dgrams = dgrams.into_iter().peekable();
+ qtrace!([self], "Process multiple datagrams");
+ if dgrams.peek().is_none() {
return;
}
self.conn.process_multiple_input(dgrams, now);
diff --git a/third_party/rust/neqo-http3/src/connection_server.rs b/third_party/rust/neqo-http3/src/connection_server.rs
index 097209a226..cc887a26fc 100644
--- a/third_party/rust/neqo-http3/src/connection_server.rs
+++ b/third_party/rust/neqo-http3/src/connection_server.rs
@@ -64,13 +64,17 @@ impl Http3ServerHandler {
data: &[u8],
conn: &mut Connection,
) -> Res<usize> {
- self.base_handler.stream_has_pending_data(stream_id);
- self.needs_processing = true;
- self.base_handler
+ let n = self
+ .base_handler
.send_streams
.get_mut(&stream_id)
.ok_or(Error::InvalidStreamId)?
- .send_data(conn, data)
+ .send_data(conn, data)?;
+ if n > 0 {
+ self.base_handler.stream_has_pending_data(stream_id);
+ }
+ self.needs_processing = true;
+ Ok(n)
}
/// Supply response heeaders for a request.
@@ -98,9 +102,8 @@ impl Http3ServerHandler {
///
/// An error will be returned if stream does not exist.
pub fn stream_close_send(&mut self, stream_id: StreamId, conn: &mut Connection) -> Res<()> {
- qinfo!([self], "Close sending side stream={}.", stream_id);
+ qdebug!([self], "Close sending side stream={}.", stream_id);
self.base_handler.stream_close_send(conn, stream_id)?;
- self.base_handler.stream_has_pending_data(stream_id);
self.needs_processing = true;
Ok(())
}
@@ -408,7 +411,7 @@ impl Http3ServerHandler {
stream_id: StreamId,
buf: &mut [u8],
) -> Res<(usize, bool)> {
- qinfo!([self], "read_data from stream {}.", stream_id);
+ qdebug!([self], "read_data from stream {}.", stream_id);
let res = self.base_handler.read_data(conn, stream_id, buf);
if let Err(e) = &res {
if e.connection_error() {
diff --git a/third_party/rust/neqo-http3/src/recv_message.rs b/third_party/rust/neqo-http3/src/recv_message.rs
index be58b7e47c..55970849ef 100644
--- a/third_party/rust/neqo-http3/src/recv_message.rs
+++ b/third_party/rust/neqo-http3/src/recv_message.rs
@@ -271,7 +271,7 @@ impl RecvMessage {
}
(None, false) => break Ok(()),
(Some(frame), fin) => {
- qinfo!(
+ qdebug!(
[self],
"A new frame has been received: {:?}; state={:?} fin={}",
frame,
diff --git a/third_party/rust/neqo-http3/src/send_message.rs b/third_party/rust/neqo-http3/src/send_message.rs
index c50e3e056a..15965c44f6 100644
--- a/third_party/rust/neqo-http3/src/send_message.rs
+++ b/third_party/rust/neqo-http3/src/send_message.rs
@@ -6,7 +6,7 @@
use std::{cell::RefCell, cmp::min, fmt::Debug, rc::Rc};
-use neqo_common::{qdebug, qinfo, qtrace, Encoder, Header, MessageType};
+use neqo_common::{qdebug, qtrace, Encoder, Header, MessageType};
use neqo_qpack::encoder::QPackEncoder;
use neqo_transport::{Connection, StreamId};
@@ -119,7 +119,7 @@ impl SendMessage {
encoder: Rc<RefCell<QPackEncoder>>,
conn_events: Box<dyn SendStreamEvents>,
) -> Self {
- qinfo!("Create a request stream_id={}", stream_id);
+ qdebug!("Create a request stream_id={}", stream_id);
Self {
state: MessageState::WaitingForHeaders,
message_type,
@@ -193,7 +193,7 @@ impl SendStream for SendMessage {
min(buf.len(), available - 9)
};
- qinfo!(
+ qdebug!(
[self],
"send_request_body: available={} to_send={}.",
available,
diff --git a/third_party/rust/neqo-http3/src/server_events.rs b/third_party/rust/neqo-http3/src/server_events.rs
index a85ece0bfb..214a48c757 100644
--- a/third_party/rust/neqo-http3/src/server_events.rs
+++ b/third_party/rust/neqo-http3/src/server_events.rs
@@ -13,7 +13,7 @@ use std::{
rc::Rc,
};
-use neqo_common::{qdebug, qinfo, Encoder, Header};
+use neqo_common::{qdebug, Encoder, Header};
use neqo_transport::{
server::ActiveConnectionRef, AppError, Connection, DatagramTracking, StreamId, StreamType,
};
@@ -189,7 +189,7 @@ impl Http3OrWebTransportStream {
///
/// It may return `InvalidStreamId` if a stream does not exist anymore.
pub fn send_data(&mut self, data: &[u8]) -> Res<usize> {
- qinfo!([self], "Set new response.");
+ qdebug!([self], "Set new response.");
self.stream_handler.send_data(data)
}
@@ -199,7 +199,7 @@ impl Http3OrWebTransportStream {
///
/// It may return `InvalidStreamId` if a stream does not exist anymore.
pub fn stream_close_send(&mut self) -> Res<()> {
- qinfo!([self], "Set new response.");
+ qdebug!([self], "Set new response.");
self.stream_handler.stream_close_send()
}
}
@@ -270,7 +270,7 @@ impl WebTransportRequest {
///
/// It may return `InvalidStreamId` if a stream does not exist anymore.
pub fn response(&mut self, accept: &WebTransportSessionAcceptAction) -> Res<()> {
- qinfo!([self], "Set a response for a WebTransport session.");
+ qdebug!([self], "Set a response for a WebTransport session.");
self.stream_handler
.handler
.borrow_mut()
diff --git a/third_party/rust/neqo-qpack/.cargo-checksum.json b/third_party/rust/neqo-qpack/.cargo-checksum.json
index aae0a1e594..d717487018 100644
--- a/third_party/rust/neqo-qpack/.cargo-checksum.json
+++ b/third_party/rust/neqo-qpack/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"c2152600379c3961ba79e661e164630a63531744f79e082fce39cdf1cbe75ddd","src/decoder.rs":"0675444129e074e9d5d56f0d45d2eaed614c85e22cfe9f2d28cdee912c15b420","src/decoder_instructions.rs":"d991d70e51f079bc5b30d3982fd0176edfa9bb7ba14c17a20ec3eea878c56206","src/encoder.rs":"84649cbee81e050f55d7ea691ac871e072741abd8bbf96303eb2e98aa8ee0aea","src/encoder_instructions.rs":"86e3abbd9cf94332041326ac6cf806ed64623e3fd38dbc0385b1f63c37e73fd9","src/header_block.rs":"3925476df69b90d950594faadc5cb24c374d46de8c75a374a235f0d27323a7d8","src/huffman.rs":"71ec740426eee0abb6205104e504f5b97f525a76c4a5f5827b78034d28ce1876","src/huffman_decode_helper.rs":"9ce470e318b3664f58aa109bed483ab15bfd9e0b17d261ea2b609668a42a9d80","src/huffman_table.rs":"06fea766a6276ac56c7ee0326faed800a742c15fda1f33bf2513e6cc6a5e6d27","src/lib.rs":"fd673630b5ed64197851c9a9758685096d3c0aa04f4994290733a38057004ee6","src/prefix.rs":"fb4a9acbcf6fd3178f4474404cd3d3b131abca934f69fe14a9d744bc7e636dc5","src/qlog.rs":"e320007ea8309546b26f9c0019ab8722da80dbd38fa976233fd8ae19a0af637c","src/qpack_send_buf.rs":"755af90fe077b1bcca34a1a2a1bdce5ce601ea490b2ca3f1313e0107d13e67e2","src/reader.rs":"1581261741a0922b147a6975cc8b1a3503846f6dbfdb771d254760c298996982","src/static_table.rs":"fda9d5c6f38f94b0bf92d3afdf8432dce6e27e189736596e16727090c77b78ec","src/stats.rs":"624dfa3b40858c304097bb0ce5b1be1bb4d7916b1abfc222f1aa705907009730","src/table.rs":"6e16debdceadc453546f247f8316883af9eeeedd12f2070219d8484a0a131d46"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"b963363c6eb1b0b3318d5901d372599db89dff87131e0979b2eb09f42c84d72a","src/decoder.rs":"0675444129e074e9d5d56f0d45d2eaed614c85e22cfe9f2d28cdee912c15b420","src/decoder_instructions.rs":"d991d70e51f079bc5b30d3982fd0176edfa9bb7ba14c17a20ec3eea878c56206","src/encoder.rs":"84649cbee81e050f55d7ea691ac871e072741abd8bbf96303eb2e98aa8ee0aea","src/encoder_instructions.rs":"86e3abbd9cf94332041326ac6cf806ed64623e3fd38dbc0385b1f63c37e73fd9","src/header_block.rs":"3925476df69b90d950594faadc5cb24c374d46de8c75a374a235f0d27323a7d8","src/huffman.rs":"71ec740426eee0abb6205104e504f5b97f525a76c4a5f5827b78034d28ce1876","src/huffman_decode_helper.rs":"9ce470e318b3664f58aa109bed483ab15bfd9e0b17d261ea2b609668a42a9d80","src/huffman_table.rs":"06fea766a6276ac56c7ee0326faed800a742c15fda1f33bf2513e6cc6a5e6d27","src/lib.rs":"fd673630b5ed64197851c9a9758685096d3c0aa04f4994290733a38057004ee6","src/prefix.rs":"fb4a9acbcf6fd3178f4474404cd3d3b131abca934f69fe14a9d744bc7e636dc5","src/qlog.rs":"e320007ea8309546b26f9c0019ab8722da80dbd38fa976233fd8ae19a0af637c","src/qpack_send_buf.rs":"755af90fe077b1bcca34a1a2a1bdce5ce601ea490b2ca3f1313e0107d13e67e2","src/reader.rs":"1581261741a0922b147a6975cc8b1a3503846f6dbfdb771d254760c298996982","src/static_table.rs":"fda9d5c6f38f94b0bf92d3afdf8432dce6e27e189736596e16727090c77b78ec","src/stats.rs":"624dfa3b40858c304097bb0ce5b1be1bb4d7916b1abfc222f1aa705907009730","src/table.rs":"ddf055a228bed575d640d9a06e19e1e9fd98a48e393a7d326f8254438fb94889"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/neqo-qpack/Cargo.toml b/third_party/rust/neqo-qpack/Cargo.toml
index 1becac8190..822cddd9c0 100644
--- a/third_party/rust/neqo-qpack/Cargo.toml
+++ b/third_party/rust/neqo-qpack/Cargo.toml
@@ -13,7 +13,7 @@
edition = "2021"
rust-version = "1.74.0"
name = "neqo-qpack"
-version = "0.7.2"
+version = "0.7.5"
authors = ["The Neqo Authors <necko@mozilla.com>"]
homepage = "https://github.com/mozilla/neqo/"
license = "MIT OR Apache-2.0"
diff --git a/third_party/rust/neqo-qpack/src/table.rs b/third_party/rust/neqo-qpack/src/table.rs
index 517e98db09..d5275ec98f 100644
--- a/third_party/rust/neqo-qpack/src/table.rs
+++ b/third_party/rust/neqo-qpack/src/table.rs
@@ -94,7 +94,7 @@ impl HeaderTable {
capacity: 0,
used: 0,
base: 0,
- acked_inserts_cnt: if encoder { 0 } else { u64::max_value() },
+ acked_inserts_cnt: if encoder { 0 } else { u64::MAX },
}
}
diff --git a/third_party/rust/neqo-transport/.cargo-checksum.json b/third_party/rust/neqo-transport/.cargo-checksum.json
index 669c0120f0..3fc724515c 100644
--- a/third_party/rust/neqo-transport/.cargo-checksum.json
+++ b/third_party/rust/neqo-transport/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"8eac0a271cef9232d100eb45093a3f7978a93e0576c64c99c161092ff445825d","benches/range_tracker.rs":"4821443d3cccc697b8976b7c50d787a7aa8cb86ab8633a7582be3f85135168db","benches/rx_stream_orderer.rs":"a8db922390d8506c483a3a1f40ac9bf12806ebdb4f501716904776dd58e995be","benches/transfer.rs":"11343c1ac9131585c42236749d32d9e272a33b6acd58831fa3415be4d4f1cf86","src/ackrate.rs":"4bb882e1069a0707dc85338b75327e2910c93ee5f36575767a0d58c4c41c9d4f","src/addr_valid.rs":"03c0b2ff85254179c5d425b12acfdcc6b1ea5735aeb0f604b9b3603451b3ef0a","src/cc/classic_cc.rs":"4528bb4e9059524942ee7ef931de5de90c78ee13f76489185a964ad45c12c0b3","src/cc/cubic.rs":"24c6913cc6346e5361007221c26e8096ece51583431fc3ab9c99e4ce4b0a9f5d","src/cc/mod.rs":"e0837937c9991b37edad15cd870ea9e0623489babfccc340074dd8322e1ef401","src/cc/new_reno.rs":"25d0921005688e0f0666efd0a4931b4f8cd44363587d98e5b6404818c5d05dd4","src/cc/tests/cubic.rs":"109fc8be5efba8959e777288c32ae8f2db581fc08719f318ad676e187f655478","src/cc/tests/mod.rs":"44f8df551e742ae1037cd1cdb85b2c1334c2e5ab3c23ed63d856dbc6b8743afc","src/cc/tests/new_reno.rs":"5414e26b6c928c5f82c5eeb42f04772b05be1ec2c8ee21c2b698ce8cb32829a1","src/cid.rs":"9686a3070c593cfca846d7549863728e31211b304b9fa876220f79bff5e24173","src/connection/dump.rs":"c539caffdf5b4dfaf0173bb20d1974f5242b5432a0a32fc0b8ab56ee682cb1eb","src/connection/idle.rs":"b3bc2ad1290e54278d8703092d135eda973eb12316d1f6dffedaffdf25e2a47e","src/connection/mod.rs":"dcfba9574b707318292f460dc40f54f3cdf8fd883f5f0d604f1d0d466f99f481","src/connection/params.rs":"9731bc5faa584874c48538ed19839c7a310277df39144c580cdf3001153f5a56","src/connection/saved.rs":"97eb19792be3c4d721057021a43ea50a52f89a3cfa583d3d3dcf5d9144b332f5","src/connection/state.rs":"c1820864cc63073e1f44b875be1fcde9835df644e0fa8c2e05652421ad78b7b2","src/connection/test_internal.rs":"f3ebfe97b25c9c716d41406066295e5aff4e96a3051ef4e2b5fb258282bbc14c","src/connection/tests/ackrate.rs":"4a2b835575850ae4a14209d3e51883ecb1e69afb44ef91b5e13a5e6cb7174fab","src/connection/tests/cc.rs":"d0d6ac038572ad3dcd9e6734596eaeedc6d3222d24e31b023aaab3540d195e46","src/connection/tests/close.rs":"20bf9b044ba52517693c2bd10820ff04a8c07de01d59c8c47b4e9478aa730211","src/connection/tests/datagram.rs":"f4c85099b6a8739fb99eadd8711b02066ad80fc8341a2e5e0dae2520378af9fe","src/connection/tests/fuzzing.rs":"79d9ac83fe2d952a3a13140d603569c332d29dbba2e0d2b8ee5f6e42e8f4708a","src/connection/tests/handshake.rs":"eda7308fdd46570ee3b5569ad34e63761ccde89eb5ca854c877e3a53e7de5ec8","src/connection/tests/idle.rs":"f3bcb12cd79cb8eabc969ce3fb0fab4eea26d6383b81a323c0e18ca9c42cfb59","src/connection/tests/keys.rs":"55558c057beb4221245beb262076de3991dca3f2661411db61c09d21194873df","src/connection/tests/migration.rs":"624985d925236be197eee52208dbdebe35c0df5bd9d30982d6f183dfda4cbab5","src/connection/tests/mod.rs":"8b6709a5c89becf2daed407515f894ba3337e87b2d45b21acffa02e67f37eeec","src/connection/tests/priority.rs":"dd3504f52d3fce7a96441624bc1c82c733e6bb556b9b79d24d0f4fb4efaf5a9e","src/connection/tests/recovery.rs":"7f28767f3cca2ff60e3dcfa803e12ef043486a222f54681a8faf2ea2fee564a1","src/connection/tests/resumption.rs":"1a0de0993cd325224fc79a3c094d22636d5b122ab1123d16265d4fafb23574bd","src/connection/tests/stream.rs":"e5590c2b52d33fbe1b4e995edf1c016dda460ecfa2a9f021005e4abe8ea13580","src/connection/tests/vn.rs":"550eb6b4d39d5960aafc70037c25a1a0f5db1232ce0ec6080b2c29a731a9574e","src/connection/tests/zerortt.rs":"67f77721e33d9fa2691c5ea3ef4a90935987541d81f0f42fbcfca31e690b352a","src/crypto.rs":"c5780ab85ca84e830024c31346a416f1f470694372d732ee5e5b7c5df3adc202","src/events.rs":"6e115f309c5c46f30f6223e1347bea477ada457f8bb2189ecccc6d65483318d6","src/fc.rs":"ec9de1028286870c0adf88a92e1355acf13dede8b1e91179230df3263e3827a9","src/frame.rs":"eb35c4add314f0013ad7837157fa9daeb76a2286fc7f8c922993624f54a09569","src/lib.rs":"f8d83b370cab19b3d172d0689f8d76115f5fd26c742e394fca62e253809cedc4","src/pace.rs":"86a674ac4d086148ea297214910458c3705918bd627b996ba8bbb12f2c4bf99e","src/packet/mod.rs":"9fac8f4046ada084dbbcc6601391a2bf8bbc23a09d6fe7df3c135a36840dbee3","src/packet/retry.rs":"1f10bb2c39ae4335e65b8d5d97f2b6df62e04877740af27c7b965a65e7f7ca66","src/path.rs":"3eb7e5e3bc496bfefc729c1e15fba0f9f83572151a850bf13b9c931297789279","src/qlog.rs":"b94aa36d5bac2799d8635cf6b25b9bb9383944d5607ea85aff55715f70af5f7b","src/quic_datagrams.rs":"3d33ecb9e6e80c77b812e8260fc807352300fb2305a29b797259ae34c52b67c5","src/recovery.rs":"1dadc6717dd133007943e762231a50680087392466904c2f2e6fface084e2ba9","src/recv_stream.rs":"f21ae0bb786901bb7d726a94cb3352607b0057128beaa331808137f2f57a330b","src/rtt.rs":"4635dc0c401b78a1fd9d34da6f9bf7f6e7f5be3a57ed9716e0efc8f0f94f1e47","src/send_stream.rs":"f717f64b75e368cf5fa4ca43078aa7c1b5aff48b4f6266713e6fa7dc458328aa","src/sender.rs":"5f760988bdd6fbbd5956877a97abe7c17370dd531f68b751a9e4e4459583f87b","src/server.rs":"048aaac84e15d49fd25850294759107fe1855bbbc0481c16f8bd888d8f2a8f6d","src/stats.rs":"b2a4c03d5b24edeecd00d809019c56f1a22a4e35967309ae6e6053331aafcf30","src/stream_id.rs":"fd07cbb81709a54bdb0659f676ef851cd145c004b817044ede5b21e54fdb60e4","src/streams.rs":"062b1b61edd1a76a86914f2cc1ca007c03edd9136c0c3409d960ddb805805fc6","src/tparams.rs":"10d62ac64865e0606c3c14141f9674631c610b3f042e274e110bdcef5d388491","src/tracking.rs":"f9a9aa01abc79fdd7a2cfb2c3ae342b9ab709e6a2a11076ec5c475fc89c1f598","src/version.rs":"182484ed9ecc2e17cab73cc61914a86a2d206936cab313825ae76fd37eeade77","tests/common/mod.rs":"0aa6674ae4efd2f151a65737ed5eab9e700bd1b3da5b4c165cb24de2b01598ce","tests/conn_vectors.rs":"290550072bd0c37652b79ac119729064dd486452c3a740353a6669bcdb2b82cf","tests/connection.rs":"b3c2ce0c62c4b79f80e42289eadd51933931b0ae44c0adc20ce5141edd454e00","tests/network.rs":"9e30b8610124250262fceef27d09fdecf2d6e9c3a96b1e676ff4189b9e06d5ba","tests/retry.rs":"da5c6a6f9ec1a8f556073b2d2e11fbcd2f58463818b0f08f8d23158016fea0d5","tests/server.rs":"cb83de909d858950bfd75a789fc23c3c44fcdf1d965b63800b2c7b498507987f"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"28025605522925d25700870cd043f2a169acad5193b2da09c7a296bd9f73d389","benches/range_tracker.rs":"590dd1f81c92e89ce28af1efdda583d85240438bd9c4c68767286d22a299ad4b","benches/rx_stream_orderer.rs":"53a008357703251a18100521a12d8fa9443c5601ddc3cbd1b3c2899074da4c4f","benches/transfer.rs":"94eb0ec1a0a7d0a4863ddc1c6d006521e52c1f2e7f03c69428b18f7eb827d33f","src/ackrate.rs":"4bb882e1069a0707dc85338b75327e2910c93ee5f36575767a0d58c4c41c9d4f","src/addr_valid.rs":"03c0b2ff85254179c5d425b12acfdcc6b1ea5735aeb0f604b9b3603451b3ef0a","src/cc/classic_cc.rs":"aaaf3670bfaacf10c2ab77b547b4aeec2618ec6b2bb2d921000a8b795f38ea87","src/cc/cubic.rs":"24c6913cc6346e5361007221c26e8096ece51583431fc3ab9c99e4ce4b0a9f5d","src/cc/mod.rs":"e0837937c9991b37edad15cd870ea9e0623489babfccc340074dd8322e1ef401","src/cc/new_reno.rs":"25d0921005688e0f0666efd0a4931b4f8cd44363587d98e5b6404818c5d05dd4","src/cc/tests/cubic.rs":"109fc8be5efba8959e777288c32ae8f2db581fc08719f318ad676e187f655478","src/cc/tests/mod.rs":"44f8df551e742ae1037cd1cdb85b2c1334c2e5ab3c23ed63d856dbc6b8743afc","src/cc/tests/new_reno.rs":"5414e26b6c928c5f82c5eeb42f04772b05be1ec2c8ee21c2b698ce8cb32829a1","src/cid.rs":"9686a3070c593cfca846d7549863728e31211b304b9fa876220f79bff5e24173","src/connection/dump.rs":"bd4fb55785fe42f5c94f7bcc14ccf4ae377d28b691fb55dbf1139ae9412b0ea9","src/connection/idle.rs":"b3bc2ad1290e54278d8703092d135eda973eb12316d1f6dffedaffdf25e2a47e","src/connection/mod.rs":"907ded3ba14ec8ef675e1ea55c5698d6ffe023de5e81a006746d9759eb243640","src/connection/params.rs":"38e0b47c8cc5fbe602e3174d7a70df410829bc240b42f21cebd10818e606ef7c","src/connection/saved.rs":"97eb19792be3c4d721057021a43ea50a52f89a3cfa583d3d3dcf5d9144b332f5","src/connection/state.rs":"c0c4b1c15624a8762eabc8d5fa76f169f3f93945a9ee86f30fbd7714f1ac1d37","src/connection/test_internal.rs":"f3ebfe97b25c9c716d41406066295e5aff4e96a3051ef4e2b5fb258282bbc14c","src/connection/tests/ackrate.rs":"4a2b835575850ae4a14209d3e51883ecb1e69afb44ef91b5e13a5e6cb7174fab","src/connection/tests/cc.rs":"d0d6ac038572ad3dcd9e6734596eaeedc6d3222d24e31b023aaab3540d195e46","src/connection/tests/close.rs":"20bf9b044ba52517693c2bd10820ff04a8c07de01d59c8c47b4e9478aa730211","src/connection/tests/datagram.rs":"f4c85099b6a8739fb99eadd8711b02066ad80fc8341a2e5e0dae2520378af9fe","src/connection/tests/handshake.rs":"c759737ee98c7b33b2327eb7d521f45c63aed15dc8f272b7bbcc510ee8e48877","src/connection/tests/idle.rs":"f3bcb12cd79cb8eabc969ce3fb0fab4eea26d6383b81a323c0e18ca9c42cfb59","src/connection/tests/keys.rs":"55558c057beb4221245beb262076de3991dca3f2661411db61c09d21194873df","src/connection/tests/migration.rs":"624985d925236be197eee52208dbdebe35c0df5bd9d30982d6f183dfda4cbab5","src/connection/tests/mod.rs":"280077d4e69faabd2fc1fe03f754096b8b83a8e2b2438fd05b3d7cd924154489","src/connection/tests/null.rs":"38f76a4ea15e6b11634d4374cb0f2a68bd250e5d35831edfce0fa48deeaa420d","src/connection/tests/priority.rs":"dd3504f52d3fce7a96441624bc1c82c733e6bb556b9b79d24d0f4fb4efaf5a9e","src/connection/tests/recovery.rs":"7f28767f3cca2ff60e3dcfa803e12ef043486a222f54681a8faf2ea2fee564a1","src/connection/tests/resumption.rs":"1a0de0993cd325224fc79a3c094d22636d5b122ab1123d16265d4fafb23574bd","src/connection/tests/stream.rs":"8e4af07d8033a951fc57f2afda570a08843b36931eca53ba3781a5992978afb2","src/connection/tests/vn.rs":"550eb6b4d39d5960aafc70037c25a1a0f5db1232ce0ec6080b2c29a731a9574e","src/connection/tests/zerortt.rs":"67f77721e33d9fa2691c5ea3ef4a90935987541d81f0f42fbcfca31e690b352a","src/crypto.rs":"416b73c06fcc2812cc252936bcb039fc13cf0b715e7e22a54314a3f72aee743c","src/events.rs":"6e115f309c5c46f30f6223e1347bea477ada457f8bb2189ecccc6d65483318d6","src/fc.rs":"ec9de1028286870c0adf88a92e1355acf13dede8b1e91179230df3263e3827a9","src/frame.rs":"5c8e5bc21e1052367f7db31523ee422efa4278ccdfc8cd581bb44a50ee205f16","src/lib.rs":"95810fd3ec1b7da9e42f4786e1360a6e40444d69c427065856e751fd1cf411bb","src/pace.rs":"86a674ac4d086148ea297214910458c3705918bd627b996ba8bbb12f2c4bf99e","src/packet/mod.rs":"e21e594c28c568c5d21bfa1dff2903ff3fe6f9dcb98c478eeca120c535d763ef","src/packet/retry.rs":"d5f999485f21b388a7383cd011fc6e96109c1a9fb5aef79b19017df6844271ff","src/path.rs":"610e6ce83da91b785d0690995591fa4da7b5a1add3d0022eea0be5050612cee9","src/qlog.rs":"f3d3661835a29e6023014f7a0996494fc8dc1f2d062154b94346a0c21bbf6fd1","src/quic_datagrams.rs":"3d33ecb9e6e80c77b812e8260fc807352300fb2305a29b797259ae34c52b67c5","src/recovery.rs":"1dadc6717dd133007943e762231a50680087392466904c2f2e6fface084e2ba9","src/recv_stream.rs":"f21ae0bb786901bb7d726a94cb3352607b0057128beaa331808137f2f57a330b","src/rtt.rs":"4635dc0c401b78a1fd9d34da6f9bf7f6e7f5be3a57ed9716e0efc8f0f94f1e47","src/send_stream.rs":"f717f64b75e368cf5fa4ca43078aa7c1b5aff48b4f6266713e6fa7dc458328aa","src/sender.rs":"5f760988bdd6fbbd5956877a97abe7c17370dd531f68b751a9e4e4459583f87b","src/server.rs":"048aaac84e15d49fd25850294759107fe1855bbbc0481c16f8bd888d8f2a8f6d","src/stats.rs":"257ab1242ea2e6bfac0900e6c4bdad794bc67b666930323d24e022e46b9be82b","src/stream_id.rs":"fd07cbb81709a54bdb0659f676ef851cd145c004b817044ede5b21e54fdb60e4","src/streams.rs":"062b1b61edd1a76a86914f2cc1ca007c03edd9136c0c3409d960ddb805805fc6","src/tparams.rs":"10d62ac64865e0606c3c14141f9674631c610b3f042e274e110bdcef5d388491","src/tracking.rs":"f9a9aa01abc79fdd7a2cfb2c3ae342b9ab709e6a2a11076ec5c475fc89c1f598","src/version.rs":"182484ed9ecc2e17cab73cc61914a86a2d206936cab313825ae76fd37eeade77","tests/common/mod.rs":"a6584d268da0157190f8f61842a655ffe81ee68702b3e6569ae300a169080eab","tests/conn_vectors.rs":"997702f4d8b8fa3b987b33077a0eb325e968b25b61fb4703532f8d97e1d4c98c","tests/connection.rs":"d1bc28294d70a5a484eb869162115e399862742caa791749fbd6b923b702b7cc","tests/network.rs":"9e30b8610124250262fceef27d09fdecf2d6e9c3a96b1e676ff4189b9e06d5ba","tests/retry.rs":"3225b64c0c0ca918df12d94df21f6023091e72606701c1dc8c060ce3c1e09c52","tests/server.rs":"cb83de909d858950bfd75a789fc23c3c44fcdf1d965b63800b2c7b498507987f"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/neqo-transport/Cargo.toml b/third_party/rust/neqo-transport/Cargo.toml
index a309987434..9abdf9b984 100644
--- a/third_party/rust/neqo-transport/Cargo.toml
+++ b/third_party/rust/neqo-transport/Cargo.toml
@@ -13,7 +13,7 @@
edition = "2021"
rust-version = "1.74.0"
name = "neqo-transport"
-version = "0.7.2"
+version = "0.7.5"
authors = ["The Neqo Authors <necko@mozilla.com>"]
homepage = "https://github.com/mozilla/neqo/"
license = "MIT OR Apache-2.0"
@@ -73,7 +73,7 @@ path = "../test-fixture"
[features]
bench = []
-fuzzing = ["neqo-crypto/fuzzing"]
+disable-encryption = ["neqo-crypto/disable-encryption"]
[lints.clippy.pedantic]
level = "warn"
diff --git a/third_party/rust/neqo-transport/benches/range_tracker.rs b/third_party/rust/neqo-transport/benches/range_tracker.rs
index c2f78f4874..ee611cf4ea 100644
--- a/third_party/rust/neqo-transport/benches/range_tracker.rs
+++ b/third_party/rust/neqo-transport/benches/range_tracker.rs
@@ -11,30 +11,32 @@ const CHUNK: u64 = 1000;
const END: u64 = 100_000;
fn build_coalesce(len: u64) -> RangeTracker {
let mut used = RangeTracker::default();
- used.mark_acked(0, CHUNK as usize);
- used.mark_sent(CHUNK, END as usize);
+ let chunk = usize::try_from(CHUNK).expect("should fit");
+ used.mark_acked(0, chunk);
+ used.mark_sent(CHUNK, usize::try_from(END).expect("should fit"));
// leave a gap or it will coalesce here
for i in 2..=len {
// These do not get immediately coalesced when marking since they're not at the end or start
- used.mark_acked(i * CHUNK, CHUNK as usize);
+ used.mark_acked(i * CHUNK, chunk);
}
used
}
fn coalesce(c: &mut Criterion, count: u64) {
+ let chunk = usize::try_from(CHUNK).expect("should fit");
c.bench_function(
&format!("coalesce_acked_from_zero {count}+1 entries"),
|b| {
b.iter_batched_ref(
|| build_coalesce(count),
|used| {
- used.mark_acked(CHUNK, CHUNK as usize);
+ used.mark_acked(CHUNK, chunk);
let tail = (count + 1) * CHUNK;
- used.mark_sent(tail, CHUNK as usize);
- used.mark_acked(tail, CHUNK as usize);
+ used.mark_sent(tail, chunk);
+ used.mark_acked(tail, chunk);
},
criterion::BatchSize::SmallInput,
- )
+ );
},
);
}
diff --git a/third_party/rust/neqo-transport/benches/rx_stream_orderer.rs b/third_party/rust/neqo-transport/benches/rx_stream_orderer.rs
index 0a1e763e97..d58e11ee86 100644
--- a/third_party/rust/neqo-transport/benches/rx_stream_orderer.rs
+++ b/third_party/rust/neqo-transport/benches/rx_stream_orderer.rs
@@ -11,14 +11,14 @@ fn rx_stream_orderer() {
let mut rx = RxStreamOrderer::new();
let data: &[u8] = &[0; 1337];
- for i in 0..100000 {
+ for i in 0..100_000 {
rx.inbound_frame(i * 1337, data);
}
}
fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("RxStreamOrderer::inbound_frame()", |b| {
- b.iter(rx_stream_orderer)
+ b.iter(rx_stream_orderer);
});
}
diff --git a/third_party/rust/neqo-transport/benches/transfer.rs b/third_party/rust/neqo-transport/benches/transfer.rs
index 444f738f9c..32959f6cb5 100644
--- a/third_party/rust/neqo-transport/benches/transfer.rs
+++ b/third_party/rust/neqo-transport/benches/transfer.rs
@@ -6,7 +6,7 @@
use std::time::Duration;
-use criterion::{criterion_group, criterion_main, BatchSize::SmallInput, Criterion};
+use criterion::{criterion_group, criterion_main, BatchSize::SmallInput, Criterion, Throughput};
use test_fixture::{
boxed,
sim::{
@@ -20,8 +20,11 @@ const ZERO: Duration = Duration::from_millis(0);
const JITTER: Duration = Duration::from_millis(10);
const TRANSFER_AMOUNT: usize = 1 << 22; // 4Mbyte
-fn benchmark_transfer(c: &mut Criterion, label: &str, seed: Option<impl AsRef<str>>) {
- c.bench_function(label, |b| {
+fn benchmark_transfer(c: &mut Criterion, label: &str, seed: &Option<impl AsRef<str>>) {
+ let mut group = c.benchmark_group("transfer");
+ group.throughput(Throughput::Bytes(u64::try_from(TRANSFER_AMOUNT).unwrap()));
+ group.noise_threshold(0.03);
+ group.bench_function(label, |b| {
b.iter_batched(
|| {
let nodes = boxed![
@@ -42,15 +45,16 @@ fn benchmark_transfer(c: &mut Criterion, label: &str, seed: Option<impl AsRef<st
sim.run();
},
SmallInput,
- )
+ );
});
+ group.finish();
}
fn benchmark_transfer_variable(c: &mut Criterion) {
benchmark_transfer(
c,
"Run multiple transfers with varying seeds",
- std::env::var("SIMULATION_SEED").ok(),
+ &std::env::var("SIMULATION_SEED").ok(),
);
}
@@ -58,7 +62,7 @@ fn benchmark_transfer_fixed(c: &mut Criterion) {
benchmark_transfer(
c,
"Run multiple transfers with the same seed",
- Some("62df6933ba1f543cece01db8f27fb2025529b27f93df39e19f006e1db3b8c843"),
+ &Some("62df6933ba1f543cece01db8f27fb2025529b27f93df39e19f006e1db3b8c843"),
);
}
diff --git a/third_party/rust/neqo-transport/src/cc/classic_cc.rs b/third_party/rust/neqo-transport/src/cc/classic_cc.rs
index 89be6c4b0f..f8bcee6722 100644
--- a/third_party/rust/neqo-transport/src/cc/classic_cc.rs
+++ b/third_party/rust/neqo-transport/src/cc/classic_cc.rs
@@ -164,7 +164,7 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
let mut is_app_limited = true;
let mut new_acked = 0;
for pkt in acked_pkts {
- qinfo!(
+ qdebug!(
"packet_acked this={:p}, pn={}, ps={}, ignored={}, lost={}, rtt_est={:?}",
self,
pkt.pn,
@@ -179,8 +179,9 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
if pkt.pn < self.first_app_limited {
is_app_limited = false;
}
- assert!(self.bytes_in_flight >= pkt.size);
- self.bytes_in_flight -= pkt.size;
+ // BIF is set to 0 on a path change, but in case that was because of a simple rebinding
+ // event, we may still get ACKs for packets sent before the rebinding.
+ self.bytes_in_flight = self.bytes_in_flight.saturating_sub(pkt.size);
if !self.after_recovery_start(pkt) {
// Do not increase congestion window for packets sent before
@@ -198,7 +199,7 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
if is_app_limited {
self.cc_algorithm.on_app_limited();
- qinfo!("on_packets_acked this={:p}, limited=1, bytes_in_flight={}, cwnd={}, state={:?}, new_acked={}", self, self.bytes_in_flight, self.congestion_window, self.state, new_acked);
+ qdebug!("on_packets_acked this={:p}, limited=1, bytes_in_flight={}, cwnd={}, state={:?}, new_acked={}", self, self.bytes_in_flight, self.congestion_window, self.state, new_acked);
return;
}
@@ -208,7 +209,7 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
let increase = min(self.ssthresh - self.congestion_window, self.acked_bytes);
self.congestion_window += increase;
self.acked_bytes -= increase;
- qinfo!([self], "slow start += {}", increase);
+ qdebug!([self], "slow start += {}", increase);
if self.congestion_window == self.ssthresh {
// This doesn't look like it is necessary, but it can happen
// after persistent congestion.
@@ -249,7 +250,7 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
QlogMetric::BytesInFlight(self.bytes_in_flight),
],
);
- qinfo!([self], "on_packets_acked this={:p}, limited=0, bytes_in_flight={}, cwnd={}, state={:?}, new_acked={}", self, self.bytes_in_flight, self.congestion_window, self.state, new_acked);
+ qdebug!([self], "on_packets_acked this={:p}, limited=0, bytes_in_flight={}, cwnd={}, state={:?}, new_acked={}", self, self.bytes_in_flight, self.congestion_window, self.state, new_acked);
}
/// Update congestion controller state based on lost packets.
@@ -265,14 +266,15 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
}
for pkt in lost_packets.iter().filter(|pkt| pkt.cc_in_flight()) {
- qinfo!(
+ qdebug!(
"packet_lost this={:p}, pn={}, ps={}",
self,
pkt.pn,
pkt.size
);
- assert!(self.bytes_in_flight >= pkt.size);
- self.bytes_in_flight -= pkt.size;
+ // BIF is set to 0 on a path change, but in case that was because of a simple rebinding
+ // event, we may still declare packets lost that were sent before the rebinding.
+ self.bytes_in_flight = self.bytes_in_flight.saturating_sub(pkt.size);
}
qlog::metrics_updated(
&mut self.qlog,
@@ -286,7 +288,7 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
pto,
lost_packets,
);
- qinfo!(
+ qdebug!(
"on_packets_lost this={:p}, bytes_in_flight={}, cwnd={}, state={:?}",
self,
self.bytes_in_flight,
@@ -335,7 +337,7 @@ impl<T: WindowAdjustment> CongestionControl for ClassicCongestionControl<T> {
}
self.bytes_in_flight += pkt.size;
- qinfo!(
+ qdebug!(
"packet_sent this={:p}, pn={}, ps={}",
self,
pkt.pn,
@@ -498,7 +500,7 @@ impl<T: WindowAdjustment> ClassicCongestionControl<T> {
self.congestion_window = max(cwnd, CWND_MIN);
self.acked_bytes = acked_bytes;
self.ssthresh = self.congestion_window;
- qinfo!(
+ qdebug!(
[self],
"Cong event -> recovery; cwnd {}, ssthresh {}",
self.congestion_window,
@@ -516,7 +518,6 @@ impl<T: WindowAdjustment> ClassicCongestionControl<T> {
true
}
- #[allow(clippy::unused_self)]
fn app_limited(&self) -> bool {
if self.bytes_in_flight >= self.congestion_window {
false
diff --git a/third_party/rust/neqo-transport/src/connection/dump.rs b/third_party/rust/neqo-transport/src/connection/dump.rs
index 8a4f34dbb8..12d337c570 100644
--- a/third_party/rust/neqo-transport/src/connection/dump.rs
+++ b/third_party/rust/neqo-transport/src/connection/dump.rs
@@ -9,7 +9,7 @@
use std::fmt::Write;
-use neqo_common::{qdebug, Decoder};
+use neqo_common::{qdebug, Decoder, IpTos};
use crate::{
connection::Connection,
@@ -26,6 +26,7 @@ pub fn dump_packet(
pt: PacketType,
pn: PacketNumber,
payload: &[u8],
+ tos: IpTos,
) {
if log::STATIC_MAX_LEVEL == log::LevelFilter::Off || !log::log_enabled!(log::Level::Debug) {
return;
@@ -38,9 +39,18 @@ pub fn dump_packet(
s.push_str(" [broken]...");
break;
};
- if let Some(x) = f.dump() {
+ let x = f.dump();
+ if !x.is_empty() {
write!(&mut s, "\n {} {}", dir, &x).unwrap();
}
}
- qdebug!([conn], "pn={} type={:?} {}{}", pn, pt, path.borrow(), s);
+ qdebug!(
+ [conn],
+ "pn={} type={:?} {} {:?}{}",
+ pn,
+ pt,
+ path.borrow(),
+ tos,
+ s
+ );
}
diff --git a/third_party/rust/neqo-transport/src/connection/mod.rs b/third_party/rust/neqo-transport/src/connection/mod.rs
index c81a3727c6..8522507a69 100644
--- a/third_party/rust/neqo-transport/src/connection/mod.rs
+++ b/third_party/rust/neqo-transport/src/connection/mod.rs
@@ -10,7 +10,7 @@ use std::{
cell::RefCell,
cmp::{max, min},
fmt::{self, Debug},
- mem,
+ iter, mem,
net::{IpAddr, SocketAddr},
ops::RangeInclusive,
rc::{Rc, Weak},
@@ -19,7 +19,7 @@ use std::{
use neqo_common::{
event::Provider as EventProvider, hex, hex_snip_middle, hrtime, qdebug, qerror, qinfo,
- qlog::NeqoQlog, qtrace, qwarn, Datagram, Decoder, Encoder, Role,
+ qlog::NeqoQlog, qtrace, qwarn, Datagram, Decoder, Encoder, IpTos, Role,
};
use neqo_crypto::{
agent::CertificateInfo, Agent, AntiReplay, AuthenticationStatus, Cipher, Client, Group,
@@ -383,7 +383,6 @@ impl Connection {
agent,
protocols.iter().map(P::as_ref).map(String::from).collect(),
Rc::clone(&tphandler),
- conn_params.is_fuzzing(),
)?;
let stats = StatsCell::default();
@@ -461,7 +460,7 @@ impl Connection {
}
/// # Errors
- /// When the operation fails.
+ /// When the operation fails.
pub fn client_enable_ech(&mut self, ech_config_list: impl AsRef<[u8]>) -> Res<()> {
self.crypto.client_enable_ech(ech_config_list)
}
@@ -778,7 +777,7 @@ impl Connection {
});
enc.encode(extra);
let records = s.send_ticket(now, enc.as_ref())?;
- qinfo!([self], "send session ticket {}", hex(&enc));
+ qdebug!([self], "send session ticket {}", hex(&enc));
self.crypto.buffer_records(records)?;
} else {
unreachable!();
@@ -824,7 +823,7 @@ impl Connection {
/// the connection to fail. However, if no packets have been
/// exchanged, it's not OK.
pub fn authenticated(&mut self, status: AuthenticationStatus, now: Instant) {
- qinfo!([self], "Authenticated {:?}", status);
+ qdebug!([self], "Authenticated {:?}", status);
self.crypto.tls.authenticated(status);
let res = self.handshake(now, self.version, PacketNumberSpace::Handshake, None);
self.absorb_error(now, res);
@@ -979,19 +978,16 @@ impl Connection {
/// Process new input datagrams on the connection.
pub fn process_input(&mut self, d: &Datagram, now: Instant) {
- self.input(d, now, now);
- self.process_saved(now);
- self.streams.cleanup_closed_streams();
+ self.process_multiple_input(iter::once(d), now);
}
/// Process new input datagrams on the connection.
pub fn process_multiple_input<'a, I>(&mut self, dgrams: I, now: Instant)
where
I: IntoIterator<Item = &'a Datagram>,
- I::IntoIter: ExactSizeIterator,
{
- let dgrams = dgrams.into_iter();
- if dgrams.len() == 0 {
+ let mut dgrams = dgrams.into_iter().peekable();
+ if dgrams.peek().is_none() {
return;
}
@@ -1154,7 +1150,7 @@ impl Connection {
fn discard_keys(&mut self, space: PacketNumberSpace, now: Instant) {
if self.crypto.discard(space) {
- qinfo!([self], "Drop packet number space {}", space);
+ qdebug!([self], "Drop packet number space {}", space);
let primary = self.paths.primary();
self.loss_recovery.discard(&primary, space, now);
self.acks.drop_space(space);
@@ -1492,6 +1488,7 @@ impl Connection {
payload.packet_type(),
payload.pn(),
&payload[..],
+ d.tos(),
);
qlog::packet_received(&mut self.qlog, &packet, &payload);
@@ -1552,6 +1549,10 @@ impl Connection {
packet: &DecryptedPacket,
now: Instant,
) -> Res<bool> {
+ (!packet.is_empty())
+ .then_some(())
+ .ok_or(Error::ProtocolViolation)?;
+
// TODO(ekr@rtfm.com): Have the server blow away the initial
// crypto state if this fails? Otherwise, we will get a panic
// on the assert for doesn't exist.
@@ -1560,24 +1561,8 @@ impl Connection {
let mut ack_eliciting = false;
let mut probing = true;
let mut d = Decoder::from(&packet[..]);
- let mut consecutive_padding = 0;
while d.remaining() > 0 {
- let mut f = Frame::decode(&mut d)?;
-
- // Skip padding
- while f == Frame::Padding && d.remaining() > 0 {
- consecutive_padding += 1;
- f = Frame::decode(&mut d)?;
- }
- if consecutive_padding > 0 {
- qdebug!(
- [self],
- "PADDING frame repeated {} times",
- consecutive_padding
- );
- consecutive_padding = 0;
- }
-
+ let f = Frame::decode(&mut d)?;
ack_eliciting |= f.ack_eliciting();
probing &= f.path_probing();
let t = f.get_type();
@@ -1841,7 +1826,7 @@ impl Connection {
| State::Connected
| State::Confirmed => {
if let Some(path) = self.paths.select_path() {
- let res = self.output_path(&path, now);
+ let res = self.output_path(&path, now, &None);
self.capture_error(Some(path), now, 0, res)
} else {
Ok(SendOption::default())
@@ -1850,7 +1835,16 @@ impl Connection {
State::Closing { .. } | State::Draining { .. } | State::Closed(_) => {
if let Some(details) = self.state_signaling.close_frame() {
let path = Rc::clone(details.path());
- let res = self.output_close(&details);
+ // In some error cases, we will not be able to make a new, permanent path.
+ // For example, if we run out of connection IDs and the error results from
+ // a packet on a new path, we avoid sending (and the privacy risk) rather
+ // than reuse a connection ID.
+ let res = if path.borrow().is_temporary() {
+ assert!(!cfg!(test), "attempting to close with a temporary path");
+ Err(Error::InternalError)
+ } else {
+ self.output_path(&path, now, &Some(details))
+ };
self.capture_error(Some(path), now, 0, res)
} else {
Ok(SendOption::default())
@@ -1927,62 +1921,6 @@ impl Connection {
}
}
- fn output_close(&mut self, close: &ClosingFrame) -> Res<SendOption> {
- let mut encoder = Encoder::with_capacity(256);
- let grease_quic_bit = self.can_grease_quic_bit();
- let version = self.version();
- for space in PacketNumberSpace::iter() {
- let Some((cspace, tx)) = self.crypto.states.select_tx_mut(self.version, *space) else {
- continue;
- };
-
- let path = close.path().borrow();
- // In some error cases, we will not be able to make a new, permanent path.
- // For example, if we run out of connection IDs and the error results from
- // a packet on a new path, we avoid sending (and the privacy risk) rather
- // than reuse a connection ID.
- if path.is_temporary() {
- assert!(!cfg!(test), "attempting to close with a temporary path");
- return Err(Error::InternalError);
- }
- let (_, mut builder) = Self::build_packet_header(
- &path,
- cspace,
- encoder,
- tx,
- &AddressValidationInfo::None,
- version,
- grease_quic_bit,
- );
- _ = Self::add_packet_number(
- &mut builder,
- tx,
- self.loss_recovery.largest_acknowledged_pn(*space),
- );
- // The builder will set the limit to 0 if there isn't enough space for the header.
- if builder.is_full() {
- encoder = builder.abort();
- break;
- }
- builder.set_limit(min(path.amplification_limit(), path.mtu()) - tx.expansion());
- debug_assert!(builder.limit() <= 2048);
-
- // ConnectionError::Application is only allowed at 1RTT.
- let sanitized = if *space == PacketNumberSpace::ApplicationData {
- None
- } else {
- close.sanitize()
- };
- sanitized
- .as_ref()
- .unwrap_or(close)
- .write_frame(&mut builder);
- encoder = builder.build(tx)?;
- }
-
- Ok(SendOption::Yes(close.path().borrow().datagram(encoder)))
- }
-
/// Write the frames that are exchanged in the application data space.
/// The order of calls here determines the relative priority of frames.
fn write_appdata_frames(
@@ -2203,7 +2141,12 @@ impl Connection {
/// Build a datagram, possibly from multiple packets (for different PN
/// spaces) and each containing 1+ frames.
#[allow(clippy::too_many_lines)] // Yeah, that's just the way it is.
- fn output_path(&mut self, path: &PathRef, now: Instant) -> Res<SendOption> {
+ fn output_path(
+ &mut self,
+ path: &PathRef,
+ now: Instant,
+ closing_frame: &Option<ClosingFrame>,
+ ) -> Res<SendOption> {
let mut initial_sent = None;
let mut needs_padding = false;
let grease_quic_bit = self.can_grease_quic_bit();
@@ -2256,8 +2199,23 @@ impl Connection {
// Add frames to the packet.
let payload_start = builder.len();
- let (tokens, ack_eliciting, padded) =
- self.write_frames(path, *space, &profile, &mut builder, now);
+ let (mut tokens, mut ack_eliciting, mut padded) = (Vec::new(), false, false);
+ if let Some(ref close) = closing_frame {
+ // ConnectionError::Application is only allowed at 1RTT.
+ let sanitized = if *space == PacketNumberSpace::ApplicationData {
+ None
+ } else {
+ close.sanitize()
+ };
+ sanitized
+ .as_ref()
+ .unwrap_or(close)
+ .write_frame(&mut builder);
+ self.stats.borrow_mut().frame_tx.connection_close += 1;
+ } else {
+ (tokens, ack_eliciting, padded) =
+ self.write_frames(path, *space, &profile, &mut builder, now);
+ }
if builder.packet_empty() {
// Nothing to include in this packet.
encoder = builder.abort();
@@ -2271,6 +2229,7 @@ impl Connection {
pt,
pn,
&builder.as_ref()[payload_start..],
+ IpTos::default(), // TODO: set from path
);
qlog::packet_sent(
&mut self.qlog,
@@ -2323,7 +2282,7 @@ impl Connection {
}
if encoder.is_empty() {
- qinfo!("TX blocked, profile={:?} ", profile);
+ qdebug!("TX blocked, profile={:?} ", profile);
Ok(SendOption::No(profile.paced()))
} else {
// Perform additional padding for Initial packets as necessary.
@@ -2337,6 +2296,8 @@ impl Connection {
mtu
);
initial.size += mtu - packets.len();
+ // These zeros aren't padding frames, they are an invalid all-zero coalesced
+ // packet, which is why we don't increase `frame_tx.padding` count here.
packets.resize(mtu, 0);
}
self.loss_recovery.on_packet_sent(path, initial);
@@ -2367,7 +2328,7 @@ impl Connection {
}
fn client_start(&mut self, now: Instant) -> Res<()> {
- qinfo!([self], "client_start");
+ qdebug!([self], "client_start");
debug_assert_eq!(self.role, Role::Client);
qlog::client_connection_started(&mut self.qlog, &self.paths.primary());
qlog::client_version_information_initiated(&mut self.qlog, self.conn_params.get_versions());
@@ -2599,7 +2560,7 @@ impl Connection {
fn confirm_version(&mut self, v: Version) {
if self.version != v {
- qinfo!([self], "Compatible upgrade {:?} ==> {:?}", self.version, v);
+ qdebug!([self], "Compatible upgrade {:?} ==> {:?}", self.version, v);
}
self.crypto.confirm_version(v);
self.version = v;
@@ -2694,9 +2655,8 @@ impl Connection {
.input_frame(&frame, &mut self.stats.borrow_mut().frame_rx);
}
match frame {
- Frame::Padding => {
- // Note: This counts contiguous padding as a single frame.
- self.stats.borrow_mut().frame_rx.padding += 1;
+ Frame::Padding(length) => {
+ self.stats.borrow_mut().frame_rx.padding += usize::from(length);
}
Frame::Ping => {
// If we get a PING and there are outstanding CRYPTO frames,
@@ -2899,7 +2859,7 @@ impl Connection {
R: IntoIterator<Item = RangeInclusive<u64>> + Debug,
R::IntoIter: ExactSizeIterator,
{
- qinfo!([self], "Rx ACK space={}, ranges={:?}", space, ack_ranges);
+ qdebug!([self], "Rx ACK space={}, ranges={:?}", space, ack_ranges);
let (acked_packets, lost_packets) = self.loss_recovery.on_ack_received(
&self.paths.primary(),
@@ -2953,7 +2913,7 @@ impl Connection {
}
fn set_connected(&mut self, now: Instant) -> Res<()> {
- qinfo!([self], "TLS connection complete");
+ qdebug!([self], "TLS connection complete");
if self.crypto.tls.info().map(SecretAgentInfo::alpn).is_none() {
qwarn!([self], "No ALPN. Closing connection.");
// 120 = no_application_protocol
@@ -2996,7 +2956,7 @@ impl Connection {
fn set_state(&mut self, state: State) {
if state > self.state {
- qinfo!([self], "State change from {:?} -> {:?}", self.state, state);
+ qdebug!([self], "State change from {:?} -> {:?}", self.state, state);
self.state = state.clone();
if self.state.closed() {
self.streams.clear_streams();
diff --git a/third_party/rust/neqo-transport/src/connection/params.rs b/third_party/rust/neqo-transport/src/connection/params.rs
index 72d1efa3ee..d8aa617024 100644
--- a/third_party/rust/neqo-transport/src/connection/params.rs
+++ b/third_party/rust/neqo-transport/src/connection/params.rs
@@ -77,7 +77,6 @@ pub struct ConnectionParameters {
outgoing_datagram_queue: usize,
incoming_datagram_queue: usize,
fast_pto: u8,
- fuzzing: bool,
grease: bool,
pacing: bool,
}
@@ -100,7 +99,6 @@ impl Default for ConnectionParameters {
outgoing_datagram_queue: MAX_QUEUED_DATAGRAMS_DEFAULT,
incoming_datagram_queue: MAX_QUEUED_DATAGRAMS_DEFAULT,
fast_pto: FAST_PTO_SCALE,
- fuzzing: false,
grease: true,
pacing: true,
}
@@ -325,17 +323,6 @@ impl ConnectionParameters {
}
#[must_use]
- pub fn is_fuzzing(&self) -> bool {
- self.fuzzing
- }
-
- #[must_use]
- pub fn fuzzing(mut self, enable: bool) -> Self {
- self.fuzzing = enable;
- self
- }
-
- #[must_use]
pub fn is_greasing(&self) -> bool {
self.grease
}
diff --git a/third_party/rust/neqo-transport/src/connection/state.rs b/third_party/rust/neqo-transport/src/connection/state.rs
index 9789151d3f..cc2f6e30d2 100644
--- a/third_party/rust/neqo-transport/src/connection/state.rs
+++ b/third_party/rust/neqo-transport/src/connection/state.rs
@@ -209,7 +209,10 @@ pub enum StateSignaling {
impl StateSignaling {
pub fn handshake_done(&mut self) {
if !matches!(self, Self::Idle) {
- debug_assert!(false, "StateSignaling must be in Idle state.");
+ debug_assert!(
+ false,
+ "StateSignaling must be in Idle state but is in {self:?} state.",
+ );
return;
}
*self = Self::HandshakeDone;
diff --git a/third_party/rust/neqo-transport/src/connection/tests/handshake.rs b/third_party/rust/neqo-transport/src/connection/tests/handshake.rs
index af0352ce90..f2103523ec 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/handshake.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/handshake.rs
@@ -16,9 +16,10 @@ use neqo_common::{event::Provider, qdebug, Datagram};
use neqo_crypto::{
constants::TLS_CHACHA20_POLY1305_SHA256, generate_ech_keys, AuthenticationStatus,
};
+#[cfg(not(feature = "disable-encryption"))]
+use test_fixture::datagram;
use test_fixture::{
- assertions, assertions::assert_coalesced_0rtt, datagram, fixture_init, now, split_datagram,
- DEFAULT_ADDR,
+ assertions, assertions::assert_coalesced_0rtt, fixture_init, now, split_datagram, DEFAULT_ADDR,
};
use super::{
@@ -458,7 +459,7 @@ fn coalesce_05rtt() {
assert_eq!(client.stats().dropped_rx, 0); // No Initial padding.
assert_eq!(client.stats().packets_rx, 4);
assert_eq!(client.stats().saved_datagrams, 1);
- assert_eq!(client.stats().frame_rx.padding, 1); // Padding uses frames.
+ assert!(client.stats().frame_rx.padding > 0); // Padding uses frames.
// Allow the handshake to complete.
now += RTT / 2;
@@ -605,7 +606,7 @@ fn reorder_1rtt() {
}
}
-#[cfg(not(feature = "fuzzing"))]
+#[cfg(not(feature = "disable-encryption"))]
#[test]
fn corrupted_initial() {
let mut client = default_client();
@@ -808,7 +809,7 @@ fn anti_amplification() {
assert_eq!(*server.state(), State::Confirmed);
}
-#[cfg(not(feature = "fuzzing"))]
+#[cfg(not(feature = "disable-encryption"))]
#[test]
fn garbage_initial() {
let mut client = default_client();
diff --git a/third_party/rust/neqo-transport/src/connection/tests/mod.rs b/third_party/rust/neqo-transport/src/connection/tests/mod.rs
index b6ce08f8d1..c8c87a0df0 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/mod.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/mod.rs
@@ -37,11 +37,11 @@ mod ackrate;
mod cc;
mod close;
mod datagram;
-mod fuzzing;
mod handshake;
mod idle;
mod keys;
mod migration;
+mod null;
mod priority;
mod recovery;
mod resumption;
@@ -170,12 +170,17 @@ impl crate::connection::test_internal::FrameWriter for PingWriter {
}
}
+trait DatagramModifier: FnMut(Datagram) -> Option<Datagram> {}
+
+impl<T> DatagramModifier for T where T: FnMut(Datagram) -> Option<Datagram> {}
+
/// Drive the handshake between the client and server.
-fn handshake(
+fn handshake_with_modifier(
client: &mut Connection,
server: &mut Connection,
now: Instant,
rtt: Duration,
+ mut modifier: impl DatagramModifier,
) -> Instant {
let mut a = client;
let mut b = server;
@@ -212,7 +217,11 @@ fn handshake(
did_ping[a.role()] = true;
}
assert!(had_input || output.is_some());
- input = output;
+ if let Some(d) = output {
+ input = modifier(d);
+ } else {
+ input = output;
+ }
qtrace!("handshake: t += {:?}", rtt / 2);
now += rtt / 2;
mem::swap(&mut a, &mut b);
@@ -223,6 +232,15 @@ fn handshake(
now
}
+fn handshake(
+ client: &mut Connection,
+ server: &mut Connection,
+ now: Instant,
+ rtt: Duration,
+) -> Instant {
+ handshake_with_modifier(client, server, now, rtt, Some)
+}
+
fn connect_fail(
client: &mut Connection,
server: &mut Connection,
@@ -234,11 +252,12 @@ fn connect_fail(
assert_error(server, &ConnectionError::Transport(server_error));
}
-fn connect_with_rtt(
+fn connect_with_rtt_and_modifier(
client: &mut Connection,
server: &mut Connection,
now: Instant,
rtt: Duration,
+ modifier: impl DatagramModifier,
) -> Instant {
fn check_rtt(stats: &Stats, rtt: Duration) {
assert_eq!(stats.rtt, rtt);
@@ -246,7 +265,7 @@ fn connect_with_rtt(
let n = stats.frame_rx.ack + usize::from(stats.rtt_init_guess);
assert_eq!(stats.rttvar, rttvar_after_n_updates(n, rtt));
}
- let now = handshake(client, server, now, rtt);
+ let now = handshake_with_modifier(client, server, now, rtt, modifier);
assert_eq!(*client.state(), State::Confirmed);
assert_eq!(*server.state(), State::Confirmed);
@@ -255,6 +274,15 @@ fn connect_with_rtt(
now
}
+fn connect_with_rtt(
+ client: &mut Connection,
+ server: &mut Connection,
+ now: Instant,
+ rtt: Duration,
+) -> Instant {
+ connect_with_rtt_and_modifier(client, server, now, rtt, Some)
+}
+
fn connect(client: &mut Connection, server: &mut Connection) {
connect_with_rtt(client, server, now(), Duration::new(0, 0));
}
@@ -301,8 +329,13 @@ fn assert_idle(client: &mut Connection, server: &mut Connection, rtt: Duration,
}
/// Connect with an RTT and then force both peers to be idle.
-fn connect_rtt_idle(client: &mut Connection, server: &mut Connection, rtt: Duration) -> Instant {
- let now = connect_with_rtt(client, server, now(), rtt);
+fn connect_rtt_idle_with_modifier(
+ client: &mut Connection,
+ server: &mut Connection,
+ rtt: Duration,
+ modifier: impl DatagramModifier,
+) -> Instant {
+ let now = connect_with_rtt_and_modifier(client, server, now(), rtt, modifier);
assert_idle(client, server, rtt, now);
// Drain events from both as well.
_ = client.events().count();
@@ -311,8 +344,20 @@ fn connect_rtt_idle(client: &mut Connection, server: &mut Connection, rtt: Durat
now
}
+fn connect_rtt_idle(client: &mut Connection, server: &mut Connection, rtt: Duration) -> Instant {
+ connect_rtt_idle_with_modifier(client, server, rtt, Some)
+}
+
+fn connect_force_idle_with_modifier(
+ client: &mut Connection,
+ server: &mut Connection,
+ modifier: impl DatagramModifier,
+) {
+ connect_rtt_idle_with_modifier(client, server, Duration::new(0, 0), modifier);
+}
+
fn connect_force_idle(client: &mut Connection, server: &mut Connection) {
- connect_rtt_idle(client, server, Duration::new(0, 0));
+ connect_force_idle_with_modifier(client, server, Some);
}
fn fill_stream(c: &mut Connection, stream: StreamId) {
@@ -524,12 +569,14 @@ fn assert_full_cwnd(packets: &[Datagram], cwnd: usize) {
}
/// Send something on a stream from `sender` to `receiver`, maybe allowing for pacing.
+/// Takes a modifier function that can be used to modify the datagram before it is sent.
/// Return the resulting datagram and the new time.
#[must_use]
-fn send_something_paced(
+fn send_something_paced_with_modifier(
sender: &mut Connection,
mut now: Instant,
allow_pacing: bool,
+ mut modifier: impl DatagramModifier,
) -> (Datagram, Instant) {
let stream_id = sender.stream_create(StreamType::UniDi).unwrap();
assert!(sender.stream_send(stream_id, DEFAULT_STREAM_DATA).is_ok());
@@ -544,16 +591,32 @@ fn send_something_paced(
.dgram()
.expect("send_something: should have something to send")
}
- Output::Datagram(d) => d,
+ Output::Datagram(d) => modifier(d).unwrap(),
Output::None => panic!("send_something: got Output::None"),
};
(dgram, now)
}
+fn send_something_paced(
+ sender: &mut Connection,
+ now: Instant,
+ allow_pacing: bool,
+) -> (Datagram, Instant) {
+ send_something_paced_with_modifier(sender, now, allow_pacing, Some)
+}
+
+fn send_something_with_modifier(
+ sender: &mut Connection,
+ now: Instant,
+ modifier: impl DatagramModifier,
+) -> Datagram {
+ send_something_paced_with_modifier(sender, now, false, modifier).0
+}
+
/// Send something on a stream from `sender` to `receiver`.
/// Return the resulting datagram.
fn send_something(sender: &mut Connection, now: Instant) -> Datagram {
- send_something_paced(sender, now, false).0
+ send_something_with_modifier(sender, now, Some)
}
/// Send something on a stream from `sender` to `receiver`.
diff --git a/third_party/rust/neqo-transport/src/connection/tests/fuzzing.rs b/third_party/rust/neqo-transport/src/connection/tests/null.rs
index 9924c06fa4..e4d60445c6 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/fuzzing.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/null.rs
@@ -4,9 +4,9 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
-#![cfg(feature = "fuzzing")]
+#![cfg(feature = "disable-encryption")]
-use neqo_crypto::FIXED_TAG_FUZZING;
+use neqo_crypto::aead_null::AEAD_NULL_TAG;
use test_fixture::now;
use super::{connect_force_idle, default_client, default_server};
@@ -24,7 +24,7 @@ fn no_encryption() {
client.stream_send(stream_id, DATA_CLIENT).unwrap();
let client_pkt = client.process_output(now()).dgram().unwrap();
- assert!(client_pkt[..client_pkt.len() - FIXED_TAG_FUZZING.len()].ends_with(DATA_CLIENT));
+ assert!(client_pkt[..client_pkt.len() - AEAD_NULL_TAG.len()].ends_with(DATA_CLIENT));
server.process_input(&client_pkt, now());
let mut buf = vec![0; 100];
@@ -33,7 +33,7 @@ fn no_encryption() {
assert_eq!(&buf[..len], DATA_CLIENT);
server.stream_send(stream_id, DATA_SERVER).unwrap();
let server_pkt = server.process_output(now()).dgram().unwrap();
- assert!(server_pkt[..server_pkt.len() - FIXED_TAG_FUZZING.len()].ends_with(DATA_SERVER));
+ assert!(server_pkt[..server_pkt.len() - AEAD_NULL_TAG.len()].ends_with(DATA_SERVER));
client.process_input(&server_pkt, now());
let (len, _) = client.stream_recv(stream_id, &mut buf).unwrap();
diff --git a/third_party/rust/neqo-transport/src/connection/tests/stream.rs b/third_party/rust/neqo-transport/src/connection/tests/stream.rs
index f469866d50..66d3bf32f3 100644
--- a/third_party/rust/neqo-transport/src/connection/tests/stream.rs
+++ b/third_party/rust/neqo-transport/src/connection/tests/stream.rs
@@ -116,12 +116,6 @@ fn transfer() {
assert!(fin3);
}
-#[derive(PartialEq, Eq, PartialOrd, Ord)]
-struct IdEntry {
- sendorder: StreamOrder,
- stream_id: StreamId,
-}
-
// tests stream sendorder priorization
fn sendorder_test(order_of_sendorder: &[Option<SendOrder>]) {
let mut client = default_client();
diff --git a/third_party/rust/neqo-transport/src/crypto.rs b/third_party/rust/neqo-transport/src/crypto.rs
index 9840eaa1e1..60d056f2d2 100644
--- a/third_party/rust/neqo-transport/src/crypto.rs
+++ b/third_party/rust/neqo-transport/src/crypto.rs
@@ -69,7 +69,6 @@ impl Crypto {
mut agent: Agent,
protocols: Vec<String>,
tphandler: TpHandler,
- fuzzing: bool,
) -> Res<Self> {
agent.set_version_range(TLS_VERSION_1_3, TLS_VERSION_1_3)?;
agent.set_ciphers(&[
@@ -102,7 +101,6 @@ impl Crypto {
tls: agent,
streams: CryptoStreams::default(),
states: CryptoStates {
- fuzzing,
..CryptoStates::default()
},
})
@@ -317,7 +315,7 @@ impl Crypto {
}
pub fn acked(&mut self, token: &CryptoRecoveryToken) {
- qinfo!(
+ qdebug!(
"Acked crypto frame space={} offset={} length={}",
token.space,
token.offset,
@@ -367,7 +365,7 @@ impl Crypto {
});
enc.encode_vvec(new_token.unwrap_or(&[]));
enc.encode(t.as_ref());
- qinfo!("resumption token {}", hex_snip_middle(enc.as_ref()));
+ qdebug!("resumption token {}", hex_snip_middle(enc.as_ref()));
Some(ResumptionToken::new(enc.into(), t.expiration_time()))
} else {
None
@@ -420,7 +418,6 @@ pub struct CryptoDxState {
/// The total number of operations that are remaining before the keys
/// become exhausted and can't be used any more.
invocations: PacketNumber,
- fuzzing: bool,
}
impl CryptoDxState {
@@ -431,9 +428,8 @@ impl CryptoDxState {
epoch: Epoch,
secret: &SymKey,
cipher: Cipher,
- fuzzing: bool,
) -> Self {
- qinfo!(
+ qdebug!(
"Making {:?} {} CryptoDxState, v={:?} cipher={}",
direction,
epoch,
@@ -445,19 +441,11 @@ impl CryptoDxState {
version,
direction,
epoch: usize::from(epoch),
- aead: Aead::new(
- fuzzing,
- TLS_VERSION_1_3,
- cipher,
- secret,
- version.label_prefix(),
- )
- .unwrap(),
+ aead: Aead::new(TLS_VERSION_1_3, cipher, secret, version.label_prefix()).unwrap(),
hpkey: HpKey::extract(TLS_VERSION_1_3, cipher, secret, &hplabel).unwrap(),
used_pn: 0..0,
min_pn: 0,
invocations: Self::limit(direction, cipher),
- fuzzing,
}
}
@@ -466,7 +454,6 @@ impl CryptoDxState {
direction: CryptoDxDirection,
label: &str,
dcid: &[u8],
- fuzzing: bool,
) -> Self {
qtrace!("new_initial {:?} {}", version, ConnectionIdRef::from(dcid));
let salt = version.initial_salt();
@@ -482,14 +469,7 @@ impl CryptoDxState {
let secret =
hkdf::expand_label(TLS_VERSION_1_3, cipher, &initial_secret, &[], label).unwrap();
- Self::new(
- version,
- direction,
- TLS_EPOCH_INITIAL,
- &secret,
- cipher,
- fuzzing,
- )
+ Self::new(version, direction, TLS_EPOCH_INITIAL, &secret, cipher)
}
/// Determine the confidentiality and integrity limits for the cipher.
@@ -549,7 +529,6 @@ impl CryptoDxState {
direction: self.direction,
epoch: self.epoch + 1,
aead: Aead::new(
- self.fuzzing,
TLS_VERSION_1_3,
cipher,
next_secret,
@@ -560,7 +539,6 @@ impl CryptoDxState {
used_pn: pn..pn,
min_pn: pn,
invocations,
- fuzzing: self.fuzzing,
}
}
@@ -696,7 +674,7 @@ impl CryptoDxState {
Ok(res.to_vec())
}
- #[cfg(all(test, not(feature = "fuzzing")))]
+ #[cfg(all(test, not(feature = "disable-encryption")))]
pub(crate) fn test_default() -> Self {
// This matches the value in packet.rs
const CLIENT_CID: &[u8] = &[0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08];
@@ -705,7 +683,6 @@ impl CryptoDxState {
CryptoDxDirection::Write,
"server in",
CLIENT_CID,
- false,
)
}
@@ -759,7 +736,6 @@ pub(crate) struct CryptoDxAppData {
cipher: Cipher,
// Not the secret used to create `self.dx`, but the one needed for the next iteration.
next_secret: SymKey,
- fuzzing: bool,
}
impl CryptoDxAppData {
@@ -768,20 +744,11 @@ impl CryptoDxAppData {
dir: CryptoDxDirection,
secret: &SymKey,
cipher: Cipher,
- fuzzing: bool,
) -> Res<Self> {
Ok(Self {
- dx: CryptoDxState::new(
- version,
- dir,
- TLS_EPOCH_APPLICATION_DATA,
- secret,
- cipher,
- fuzzing,
- ),
+ dx: CryptoDxState::new(version, dir, TLS_EPOCH_APPLICATION_DATA, secret, cipher),
cipher,
next_secret: Self::update_secret(cipher, secret)?,
- fuzzing,
})
}
@@ -791,7 +758,7 @@ impl CryptoDxAppData {
}
pub fn next(&self) -> Res<Self> {
- if self.dx.epoch == usize::max_value() {
+ if self.dx.epoch == usize::MAX {
// Guard against too many key updates.
return Err(Error::KeysExhausted);
}
@@ -800,7 +767,6 @@ impl CryptoDxAppData {
dx: self.dx.next(&self.next_secret, self.cipher),
cipher: self.cipher,
next_secret,
- fuzzing: self.fuzzing,
})
}
@@ -834,7 +800,6 @@ pub struct CryptoStates {
// If this is set, then we have noticed a genuine update.
// Once this time passes, we should switch in new keys.
read_update_time: Option<Instant>,
- fuzzing: bool,
}
impl CryptoStates {
@@ -980,7 +945,7 @@ impl CryptoStates {
};
for v in versions {
- qinfo!(
+ qdebug!(
[self],
"Creating initial cipher state v={:?}, role={:?} dcid={}",
v,
@@ -989,20 +954,8 @@ impl CryptoStates {
);
let mut initial = CryptoState {
- tx: CryptoDxState::new_initial(
- *v,
- CryptoDxDirection::Write,
- write,
- dcid,
- self.fuzzing,
- ),
- rx: CryptoDxState::new_initial(
- *v,
- CryptoDxDirection::Read,
- read,
- dcid,
- self.fuzzing,
- ),
+ tx: CryptoDxState::new_initial(*v, CryptoDxDirection::Write, write, dcid),
+ rx: CryptoDxState::new_initial(*v, CryptoDxDirection::Read, read, dcid),
};
if let Some(prev) = self.initials.get(v) {
qinfo!(
@@ -1056,7 +1009,6 @@ impl CryptoStates {
TLS_EPOCH_ZERO_RTT,
secret,
cipher,
- self.fuzzing,
));
}
@@ -1097,7 +1049,6 @@ impl CryptoStates {
TLS_EPOCH_HANDSHAKE,
write_secret,
cipher,
- self.fuzzing,
),
rx: CryptoDxState::new(
version,
@@ -1105,7 +1056,6 @@ impl CryptoStates {
TLS_EPOCH_HANDSHAKE,
read_secret,
cipher,
- self.fuzzing,
),
});
}
@@ -1113,13 +1063,7 @@ impl CryptoStates {
pub fn set_application_write_key(&mut self, version: Version, secret: &SymKey) -> Res<()> {
debug_assert!(self.app_write.is_none());
debug_assert_ne!(self.cipher, 0);
- let mut app = CryptoDxAppData::new(
- version,
- CryptoDxDirection::Write,
- secret,
- self.cipher,
- self.fuzzing,
- )?;
+ let mut app = CryptoDxAppData::new(version, CryptoDxDirection::Write, secret, self.cipher)?;
if let Some(z) = &self.zero_rtt {
if z.direction == CryptoDxDirection::Write {
app.dx.continuation(z)?;
@@ -1138,13 +1082,7 @@ impl CryptoStates {
) -> Res<()> {
debug_assert!(self.app_write.is_some(), "should have write keys installed");
debug_assert!(self.app_read.is_none());
- let mut app = CryptoDxAppData::new(
- version,
- CryptoDxDirection::Read,
- secret,
- self.cipher,
- self.fuzzing,
- )?;
+ let mut app = CryptoDxAppData::new(version, CryptoDxDirection::Read, secret, self.cipher)?;
if let Some(z) = &self.zero_rtt {
if z.direction == CryptoDxDirection::Read {
app.dx.continuation(z)?;
@@ -1286,7 +1224,7 @@ impl CryptoStates {
}
/// Make some state for removing protection in tests.
- #[cfg(not(feature = "fuzzing"))]
+ #[cfg(not(feature = "disable-encryption"))]
#[cfg(test)]
pub(crate) fn test_default() -> Self {
let read = |epoch| {
@@ -1299,7 +1237,6 @@ impl CryptoStates {
dx: read(epoch),
cipher: TLS_AES_128_GCM_SHA256,
next_secret: hkdf::import_key(TLS_VERSION_1_3, &[0xaa; 32]).unwrap(),
- fuzzing: false,
};
let mut initials = HashMap::new();
initials.insert(
@@ -1319,11 +1256,10 @@ impl CryptoStates {
app_read: Some(app_read(3)),
app_read_next: Some(app_read(4)),
read_update_time: None,
- fuzzing: false,
}
}
- #[cfg(all(not(feature = "fuzzing"), test))]
+ #[cfg(all(not(feature = "disable-encryption"), test))]
pub(crate) fn test_chacha() -> Self {
const SECRET: &[u8] = &[
0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42, 0x27, 0x48, 0xad,
@@ -1337,7 +1273,6 @@ impl CryptoStates {
direction: CryptoDxDirection::Read,
epoch,
aead: Aead::new(
- false,
TLS_VERSION_1_3,
TLS_CHACHA20_POLY1305_SHA256,
&secret,
@@ -1354,11 +1289,9 @@ impl CryptoStates {
used_pn: 0..645_971_972,
min_pn: 0,
invocations: 10,
- fuzzing: false,
},
cipher: TLS_CHACHA20_POLY1305_SHA256,
next_secret: secret.clone(),
- fuzzing: false,
};
Self {
initials: HashMap::new(),
@@ -1369,7 +1302,6 @@ impl CryptoStates {
app_read: Some(app_read(3)),
app_read_next: Some(app_read(4)),
read_update_time: None,
- fuzzing: false,
}
}
}
diff --git a/third_party/rust/neqo-transport/src/frame.rs b/third_party/rust/neqo-transport/src/frame.rs
index b3bb024a2c..d84eb61ce8 100644
--- a/third_party/rust/neqo-transport/src/frame.rs
+++ b/third_party/rust/neqo-transport/src/frame.rs
@@ -20,7 +20,7 @@ use crate::{
#[allow(clippy::module_name_repetitions)]
pub type FrameType = u64;
-const FRAME_TYPE_PADDING: FrameType = 0x0;
+pub const FRAME_TYPE_PADDING: FrameType = 0x0;
pub const FRAME_TYPE_PING: FrameType = 0x1;
pub const FRAME_TYPE_ACK: FrameType = 0x2;
const FRAME_TYPE_ACK_ECN: FrameType = 0x3;
@@ -95,6 +95,12 @@ impl From<ConnectionError> for CloseError {
}
}
+impl From<std::array::TryFromSliceError> for Error {
+ fn from(_err: std::array::TryFromSliceError) -> Self {
+ Self::FrameEncodingError
+ }
+}
+
#[derive(PartialEq, Eq, Debug, Default, Clone)]
pub struct AckRange {
pub(crate) gap: u64,
@@ -103,7 +109,7 @@ pub struct AckRange {
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum Frame<'a> {
- Padding,
+ Padding(u16),
Ping,
Ack {
largest_acknowledged: u64,
@@ -213,9 +219,10 @@ impl<'a> Frame<'a> {
}
}
+ #[must_use]
pub fn get_type(&self) -> FrameType {
match self {
- Self::Padding => FRAME_TYPE_PADDING,
+ Self::Padding { .. } => FRAME_TYPE_PADDING,
Self::Ping => FRAME_TYPE_PING,
Self::Ack { .. } => FRAME_TYPE_ACK, // We don't do ACK ECN.
Self::ResetStream { .. } => FRAME_TYPE_RESET_STREAM,
@@ -254,6 +261,7 @@ impl<'a> Frame<'a> {
}
}
+ #[must_use]
pub fn is_stream(&self) -> bool {
matches!(
self,
@@ -269,6 +277,7 @@ impl<'a> Frame<'a> {
)
}
+ #[must_use]
pub fn stream_type(fin: bool, nonzero_offset: bool, fill: bool) -> u64 {
let mut t = FRAME_TYPE_STREAM;
if fin {
@@ -285,19 +294,21 @@ impl<'a> Frame<'a> {
/// If the frame causes a recipient to generate an ACK within its
/// advertised maximum acknowledgement delay.
+ #[must_use]
pub fn ack_eliciting(&self) -> bool {
!matches!(
self,
- Self::Ack { .. } | Self::Padding | Self::ConnectionClose { .. }
+ Self::Ack { .. } | Self::Padding { .. } | Self::ConnectionClose { .. }
)
}
/// If the frame can be sent in a path probe
/// without initiating migration to that path.
+ #[must_use]
pub fn path_probing(&self) -> bool {
matches!(
self,
- Self::Padding
+ Self::Padding { .. }
| Self::NewConnectionId { .. }
| Self::PathChallenge { .. }
| Self::PathResponse { .. }
@@ -307,6 +318,10 @@ impl<'a> Frame<'a> {
/// Converts `AckRanges` as encoded in a ACK frame (see -transport
/// 19.3.1) into ranges of acked packets (end, start), inclusive of
/// start and end values.
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the ranges are invalid.
pub fn decode_ack_frame(
largest_acked: u64,
first_ack_range: u64,
@@ -347,36 +362,36 @@ impl<'a> Frame<'a> {
Ok(acked_ranges)
}
- pub fn dump(&self) -> Option<String> {
+ #[must_use]
+ pub fn dump(&self) -> String {
match self {
- Self::Crypto { offset, data } => Some(format!(
- "Crypto {{ offset: {}, len: {} }}",
- offset,
- data.len()
- )),
+ Self::Crypto { offset, data } => {
+ format!("Crypto {{ offset: {}, len: {} }}", offset, data.len())
+ }
Self::Stream {
stream_id,
offset,
fill,
data,
fin,
- } => Some(format!(
+ } => format!(
"Stream {{ stream_id: {}, offset: {}, len: {}{}, fin: {} }}",
stream_id.as_u64(),
offset,
if *fill { ">>" } else { "" },
data.len(),
fin,
- )),
- Self::Padding => None,
- Self::Datagram { data, .. } => Some(format!("Datagram {{ len: {} }}", data.len())),
- _ => Some(format!("{self:?}")),
+ ),
+ Self::Padding(length) => format!("Padding {{ len: {length} }}"),
+ Self::Datagram { data, .. } => format!("Datagram {{ len: {} }}", data.len()),
+ _ => format!("{self:?}"),
}
}
+ #[must_use]
pub fn is_allowed(&self, pt: PacketType) -> bool {
match self {
- Self::Padding | Self::Ping => true,
+ Self::Padding { .. } | Self::Ping => true,
Self::Crypto { .. }
| Self::Ack { .. }
| Self::ConnectionClose {
@@ -388,6 +403,9 @@ impl<'a> Frame<'a> {
}
}
+ /// # Errors
+ ///
+ /// Returns an error if the frame cannot be decoded.
#[allow(clippy::too_many_lines)] // Yeah, but it's a nice match statement.
pub fn decode(dec: &mut Decoder<'a>) -> Res<Self> {
/// Maximum ACK Range Count in ACK Frame
@@ -409,13 +427,23 @@ impl<'a> Frame<'a> {
}
// TODO(ekr@rtfm.com): check for minimal encoding
- let t = d(dec.decode_varint())?;
+ let t = dv(dec)?;
match t {
- FRAME_TYPE_PADDING => Ok(Self::Padding),
+ FRAME_TYPE_PADDING => {
+ let mut length: u16 = 1;
+ while let Some(b) = dec.peek_byte() {
+ if u64::from(b) != FRAME_TYPE_PADDING {
+ break;
+ }
+ length += 1;
+ dec.skip(1);
+ }
+ Ok(Self::Padding(length))
+ }
FRAME_TYPE_PING => Ok(Self::Ping),
FRAME_TYPE_RESET_STREAM => Ok(Self::ResetStream {
stream_id: StreamId::from(dv(dec)?),
- application_error_code: d(dec.decode_varint())?,
+ application_error_code: dv(dec)?,
final_size: match dec.decode_varint() {
Some(v) => v,
_ => return Err(Error::NoMoreData),
@@ -457,12 +485,12 @@ impl<'a> Frame<'a> {
}
FRAME_TYPE_STOP_SENDING => Ok(Self::StopSending {
stream_id: StreamId::from(dv(dec)?),
- application_error_code: d(dec.decode_varint())?,
+ application_error_code: dv(dec)?,
}),
FRAME_TYPE_CRYPTO => {
let offset = dv(dec)?;
let data = d(dec.decode_vvec())?;
- if offset + u64::try_from(data.len()).unwrap() > ((1 << 62) - 1) {
+ if offset + u64::try_from(data.len())? > ((1 << 62) - 1) {
return Err(Error::FrameEncodingError);
}
Ok(Self::Crypto { offset, data })
@@ -489,7 +517,7 @@ impl<'a> Frame<'a> {
qtrace!("STREAM frame, with length");
d(dec.decode_vvec())?
};
- if o + u64::try_from(data.len()).unwrap() > ((1 << 62) - 1) {
+ if o + u64::try_from(data.len())? > ((1 << 62) - 1) {
return Err(Error::FrameEncodingError);
}
Ok(Self::Stream {
@@ -538,7 +566,7 @@ impl<'a> Frame<'a> {
return Err(Error::DecodingFrame);
}
let srt = d(dec.decode(16))?;
- let stateless_reset_token = <&[_; 16]>::try_from(srt).unwrap();
+ let stateless_reset_token = <&[_; 16]>::try_from(srt)?;
Ok(Self::NewConnectionId {
sequence_number,
@@ -563,7 +591,7 @@ impl<'a> Frame<'a> {
Ok(Self::PathResponse { data: datav })
}
FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT | FRAME_TYPE_CONNECTION_CLOSE_APPLICATION => {
- let error_code = CloseError::from_type_bit(t, d(dec.decode_varint())?);
+ let error_code = CloseError::from_type_bit(t, dv(dec)?);
let frame_type = if t == FRAME_TYPE_CONNECTION_CLOSE_TRANSPORT {
dv(dec)?
} else {
@@ -631,8 +659,10 @@ mod tests {
#[test]
fn padding() {
- let f = Frame::Padding;
+ let f = Frame::Padding(1);
just_dec(&f, "00");
+ let f = Frame::Padding(2);
+ just_dec(&f, "0000");
}
#[test]
@@ -888,8 +918,8 @@ mod tests {
#[test]
fn test_compare() {
- let f1 = Frame::Padding;
- let f2 = Frame::Padding;
+ let f1 = Frame::Padding(1);
+ let f2 = Frame::Padding(1);
let f3 = Frame::Crypto {
offset: 0,
data: &[1, 2, 3],
diff --git a/third_party/rust/neqo-transport/src/lib.rs b/third_party/rust/neqo-transport/src/lib.rs
index be482c466f..5488472b58 100644
--- a/third_party/rust/neqo-transport/src/lib.rs
+++ b/third_party/rust/neqo-transport/src/lib.rs
@@ -6,7 +6,7 @@
#![allow(clippy::module_name_repetitions)] // This lint doesn't work here.
-use neqo_common::qinfo;
+use neqo_common::qwarn;
use neqo_crypto::Error as CryptoError;
mod ackrate;
@@ -70,8 +70,8 @@ const ERROR_AEAD_LIMIT_REACHED: TransportError = 15;
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq)]
pub enum Error {
NoError,
- // Each time tihe error is return a different parameter is supply.
- // This will be use to distinguish each occurance of this error.
+ // Each time this error is returned a different parameter is supplied.
+ // This will be used to distinguish each occurance of this error.
InternalError,
ConnectionRefused,
FlowControlError,
@@ -165,7 +165,7 @@ impl Error {
impl From<CryptoError> for Error {
fn from(err: CryptoError) -> Self {
- qinfo!("Crypto operation failed {:?}", err);
+ qwarn!("Crypto operation failed {:?}", err);
match err {
CryptoError::EchRetry(config) => Self::EchRetry(config),
_ => Self::CryptoError(err),
diff --git a/third_party/rust/neqo-transport/src/packet/mod.rs b/third_party/rust/neqo-transport/src/packet/mod.rs
index 8458f69779..ce611a9664 100644
--- a/third_party/rust/neqo-transport/src/packet/mod.rs
+++ b/third_party/rust/neqo-transport/src/packet/mod.rs
@@ -18,6 +18,7 @@ use neqo_crypto::random;
use crate::{
cid::{ConnectionId, ConnectionIdDecoder, ConnectionIdRef, MAX_CONNECTION_ID_LEN},
crypto::{CryptoDxState, CryptoSpace, CryptoStates},
+ frame::FRAME_TYPE_PADDING,
version::{Version, WireVersion},
Error, Res,
};
@@ -157,7 +158,7 @@ impl PacketBuilder {
}
Self {
encoder,
- pn: u64::max_value(),
+ pn: u64::MAX,
header: header_start..header_start,
offsets: PacketBuilderOffsets {
first_byte_mask: PACKET_HP_MASK_SHORT,
@@ -200,7 +201,7 @@ impl PacketBuilder {
Self {
encoder,
- pn: u64::max_value(),
+ pn: u64::MAX,
header: header_start..header_start,
offsets: PacketBuilderOffsets {
first_byte_mask: PACKET_HP_MASK_LONG,
@@ -255,9 +256,14 @@ impl PacketBuilder {
/// Maybe pad with "PADDING" frames.
/// Only does so if padding was needed and this is a short packet.
/// Returns true if padding was added.
+ ///
+ /// # Panics
+ ///
+ /// Cannot happen.
pub fn pad(&mut self) -> bool {
if self.padding && !self.is_long() {
- self.encoder.pad_to(self.limit, 0);
+ self.encoder
+ .pad_to(self.limit, FRAME_TYPE_PADDING.try_into().unwrap());
true
} else {
false
@@ -288,6 +294,10 @@ impl PacketBuilder {
/// The length is filled in after calling `build`.
/// Does nothing if there isn't 4 bytes available other than render this builder
/// unusable; if `remaining()` returns 0 at any point, call `abort()`.
+ ///
+ /// # Panics
+ ///
+ /// This will panic if the packet number length is too large.
pub fn pn(&mut self, pn: PacketNumber, pn_len: usize) {
if self.remaining() < 4 {
self.limit = 0;
@@ -352,6 +362,10 @@ impl PacketBuilder {
}
/// Build the packet and return the encoder.
+ ///
+ /// # Errors
+ ///
+ /// This will return an error if the packet is too large.
pub fn build(mut self, crypto: &mut CryptoDxState) -> Res<Encoder> {
if self.len() > self.limit {
qwarn!("Packet contents are more than the limit");
@@ -376,7 +390,9 @@ impl PacketBuilder {
// Calculate the mask.
let offset = SAMPLE_OFFSET - self.offsets.pn.len();
- assert!(offset + SAMPLE_SIZE <= ciphertext.len());
+ if offset + SAMPLE_SIZE > ciphertext.len() {
+ return Err(Error::InternalError);
+ }
let sample = &ciphertext[offset..offset + SAMPLE_SIZE];
let mask = crypto.compute_mask(sample)?;
@@ -410,6 +426,10 @@ impl PacketBuilder {
/// As this is a simple packet, this is just an associated function.
/// As Retry is odd (it has to be constructed with leading bytes),
/// this returns a [`Vec<u8>`] rather than building on an encoder.
+ ///
+ /// # Errors
+ ///
+ /// This will return an error if AEAD encrypt fails.
#[allow(clippy::similar_names)] // scid and dcid are fine here.
pub fn retry(
version: Version,
@@ -443,6 +463,7 @@ impl PacketBuilder {
/// Make a Version Negotiation packet.
#[allow(clippy::similar_names)] // scid and dcid are fine here.
+ #[must_use]
pub fn version_negotiation(
dcid: &[u8],
scid: &[u8],
@@ -534,7 +555,10 @@ impl<'a> PublicPacket<'a> {
if packet_type == PacketType::Retry {
let header_len = decoder.offset();
let expansion = retry::expansion(version);
- let token = Self::opt(decoder.decode(decoder.remaining() - expansion))?;
+ let token = decoder
+ .remaining()
+ .checked_sub(expansion)
+ .map_or(Err(Error::InvalidPacket), |v| Self::opt(decoder.decode(v)))?;
if token.is_empty() {
return Err(Error::InvalidPacket);
}
@@ -554,6 +578,10 @@ impl<'a> PublicPacket<'a> {
/// Decode the common parts of a packet. This provides minimal parsing and validation.
/// Returns a tuple of a `PublicPacket` and a slice with any remainder from the datagram.
+ ///
+ /// # Errors
+ ///
+ /// This will return an error if the packet could not be decoded.
#[allow(clippy::similar_names)] // For dcid and scid, which are fine.
pub fn decode(data: &'a [u8], dcid_decoder: &dyn ConnectionIdDecoder) -> Res<(Self, &'a [u8])> {
let mut decoder = Decoder::new(data);
@@ -585,7 +613,7 @@ impl<'a> PublicPacket<'a> {
}
// Generic long header.
- let version = WireVersion::try_from(Self::opt(decoder.decode_uint(4))?).unwrap();
+ let version = WireVersion::try_from(Self::opt(decoder.decode_uint(4))?)?;
let dcid = ConnectionIdRef::from(Self::opt(decoder.decode_vec(1))?);
let scid = ConnectionIdRef::from(Self::opt(decoder.decode_vec(1))?);
@@ -645,11 +673,14 @@ impl<'a> PublicPacket<'a> {
}
/// Validate the given packet as though it were a retry.
+ #[must_use]
pub fn is_valid_retry(&self, odcid: &ConnectionId) -> bool {
if self.packet_type != PacketType::Retry {
return false;
}
- let version = self.version().unwrap();
+ let Some(version) = self.version() else {
+ return false;
+ };
let expansion = retry::expansion(version);
if self.data.len() <= expansion {
return false;
@@ -665,6 +696,7 @@ impl<'a> PublicPacket<'a> {
.unwrap_or(false)
}
+ #[must_use]
pub fn is_valid_initial(&self) -> bool {
// Packet has to be an initial, with a DCID of 8 bytes, or a token.
// Note: the Server class validates the token and checks the length.
@@ -672,32 +704,42 @@ impl<'a> PublicPacket<'a> {
&& (self.dcid().len() >= 8 || !self.token.is_empty())
}
+ #[must_use]
pub fn packet_type(&self) -> PacketType {
self.packet_type
}
+ #[must_use]
pub fn dcid(&self) -> ConnectionIdRef<'a> {
self.dcid
}
+ /// # Panics
+ ///
+ /// This will panic if called for a short header packet.
+ #[must_use]
pub fn scid(&self) -> ConnectionIdRef<'a> {
self.scid
.expect("should only be called for long header packets")
}
+ #[must_use]
pub fn token(&self) -> &'a [u8] {
self.token
}
+ #[must_use]
pub fn version(&self) -> Option<Version> {
self.version.and_then(|v| Version::try_from(v).ok())
}
+ #[must_use]
pub fn wire_version(&self) -> WireVersion {
debug_assert!(self.version.is_some());
self.version.unwrap_or(0)
}
+ #[must_use]
pub fn len(&self) -> usize {
self.data.len()
}
@@ -725,14 +767,10 @@ impl<'a> PublicPacket<'a> {
assert_ne!(self.packet_type, PacketType::Retry);
assert_ne!(self.packet_type, PacketType::VersionNegotiation);
- qtrace!(
- "unmask hdr={}",
- hex(&self.data[..self.header_len + SAMPLE_OFFSET])
- );
-
let sample_offset = self.header_len + SAMPLE_OFFSET;
let mask = if let Some(sample) = self.data.get(sample_offset..(sample_offset + SAMPLE_SIZE))
{
+ qtrace!("unmask hdr={}", hex(&self.data[..sample_offset]));
crypto.compute_mask(sample)
} else {
Err(Error::NoMoreData)
@@ -776,6 +814,9 @@ impl<'a> PublicPacket<'a> {
))
}
+ /// # Errors
+ ///
+ /// This will return an error if the packet cannot be decrypted.
pub fn decrypt(&self, crypto: &mut CryptoStates, release_at: Instant) -> Res<DecryptedPacket> {
let cspace: CryptoSpace = self.packet_type.into();
// When we don't have a version, the crypto code doesn't need a version
@@ -790,7 +831,9 @@ impl<'a> PublicPacket<'a> {
// too small (which is public information).
let (key_phase, pn, header, body) = self.decrypt_header(rx)?;
qtrace!([rx], "decoded header: {:?}", header);
- let rx = crypto.rx(version, cspace, key_phase).unwrap();
+ let Some(rx) = crypto.rx(version, cspace, key_phase) else {
+ return Err(Error::DecryptError);
+ };
let version = rx.version(); // Version fixup; see above.
let d = rx.decrypt(pn, &header, body)?;
// If this is the first packet ever successfully decrypted
@@ -813,8 +856,14 @@ impl<'a> PublicPacket<'a> {
}
}
+ /// # Errors
+ ///
+ /// This will return an error if the packet is not a version negotiation packet
+ /// or if the versions cannot be decoded.
pub fn supported_versions(&self) -> Res<Vec<WireVersion>> {
- assert_eq!(self.packet_type, PacketType::VersionNegotiation);
+ if self.packet_type != PacketType::VersionNegotiation {
+ return Err(Error::InvalidPacket);
+ }
let mut decoder = Decoder::new(&self.data[self.header_len..]);
let mut res = Vec::new();
while decoder.remaining() > 0 {
@@ -845,14 +894,17 @@ pub struct DecryptedPacket {
}
impl DecryptedPacket {
+ #[must_use]
pub fn version(&self) -> Version {
self.version
}
+ #[must_use]
pub fn packet_type(&self) -> PacketType {
self.pt
}
+ #[must_use]
pub fn pn(&self) -> PacketNumber {
self.pn
}
@@ -866,7 +918,7 @@ impl Deref for DecryptedPacket {
}
}
-#[cfg(all(test, not(feature = "fuzzing")))]
+#[cfg(all(test, not(feature = "disable-encryption")))]
mod tests {
use neqo_common::Encoder;
use test_fixture::{fixture_init, now};
@@ -1469,4 +1521,21 @@ mod tests {
assert_eq!(decrypted.pn(), 654_360_564);
assert_eq!(&decrypted[..], &[0x01]);
}
+
+ #[test]
+ fn decode_empty() {
+ neqo_crypto::init().unwrap();
+ let res = PublicPacket::decode(&[], &EmptyConnectionIdGenerator::default());
+ assert!(res.is_err());
+ }
+
+ #[test]
+ fn decode_too_short() {
+ neqo_crypto::init().unwrap();
+ let res = PublicPacket::decode(
+ &[179, 255, 0, 0, 32, 0, 0],
+ &EmptyConnectionIdGenerator::default(),
+ );
+ assert!(res.is_err());
+ }
}
diff --git a/third_party/rust/neqo-transport/src/packet/retry.rs b/third_party/rust/neqo-transport/src/packet/retry.rs
index 72036d3b49..71193b9100 100644
--- a/third_party/rust/neqo-transport/src/packet/retry.rs
+++ b/third_party/rust/neqo-transport/src/packet/retry.rs
@@ -18,7 +18,6 @@ fn make_aead(version: Version) -> Aead {
let secret = hkdf::import_key(TLS_VERSION_1_3, version.retry_secret()).unwrap();
Aead::new(
- false,
TLS_VERSION_1_3,
TLS_AES_128_GCM_SHA256,
&secret,
diff --git a/third_party/rust/neqo-transport/src/path.rs b/third_party/rust/neqo-transport/src/path.rs
index 4e8d9958ab..50e458ff36 100644
--- a/third_party/rust/neqo-transport/src/path.rs
+++ b/third_party/rust/neqo-transport/src/path.rs
@@ -216,7 +216,7 @@ impl Paths {
/// to a migration from a peer, in which case the old path needs to be probed.
#[must_use]
fn select_primary(&mut self, path: &PathRef) -> Option<PathRef> {
- qinfo!([path.borrow()], "set as primary path");
+ qdebug!([path.borrow()], "set as primary path");
let old_path = self.primary.replace(Rc::clone(path)).map(|old| {
old.borrow_mut().set_primary(false);
old
diff --git a/third_party/rust/neqo-transport/src/qlog.rs b/third_party/rust/neqo-transport/src/qlog.rs
index 2572966104..a8ad986d2a 100644
--- a/third_party/rust/neqo-transport/src/qlog.rs
+++ b/third_party/rust/neqo-transport/src/qlog.rs
@@ -195,7 +195,7 @@ pub fn packet_sent(
) {
qlog.add_event_with_stream(|stream| {
let mut d = Decoder::from(body);
- let header = PacketHeader::with_type(to_qlog_pkt_type(pt), Some(pn), None, None, None);
+ let header = PacketHeader::with_type(pt.into(), Some(pn), None, None, None);
let raw = RawInfo {
length: Some(plen as u64),
payload_length: None,
@@ -205,7 +205,7 @@ pub fn packet_sent(
let mut frames = SmallVec::new();
while d.remaining() > 0 {
if let Ok(f) = Frame::decode(&mut d) {
- frames.push(frame_to_qlogframe(&f));
+ frames.push(QuicFrame::from(&f));
} else {
qinfo!("qlog: invalid frame");
break;
@@ -231,13 +231,8 @@ pub fn packet_sent(
pub fn packet_dropped(qlog: &mut NeqoQlog, public_packet: &PublicPacket) {
qlog.add_event_data(|| {
- let header = PacketHeader::with_type(
- to_qlog_pkt_type(public_packet.packet_type()),
- None,
- None,
- None,
- None,
- );
+ let header =
+ PacketHeader::with_type(public_packet.packet_type().into(), None, None, None, None);
let raw = RawInfo {
length: Some(public_packet.len() as u64),
payload_length: None,
@@ -259,8 +254,7 @@ pub fn packet_dropped(qlog: &mut NeqoQlog, public_packet: &PublicPacket) {
pub fn packets_lost(qlog: &mut NeqoQlog, pkts: &[SentPacket]) {
qlog.add_event_with_stream(|stream| {
for pkt in pkts {
- let header =
- PacketHeader::with_type(to_qlog_pkt_type(pkt.pt), Some(pkt.pn), None, None, None);
+ let header = PacketHeader::with_type(pkt.pt.into(), Some(pkt.pn), None, None, None);
let ev_data = EventData::PacketLost(PacketLost {
header: Some(header),
@@ -283,7 +277,7 @@ pub fn packet_received(
let mut d = Decoder::from(&payload[..]);
let header = PacketHeader::with_type(
- to_qlog_pkt_type(public_packet.packet_type()),
+ public_packet.packet_type().into(),
Some(payload.pn()),
None,
None,
@@ -299,7 +293,7 @@ pub fn packet_received(
while d.remaining() > 0 {
if let Ok(f) = Frame::decode(&mut d) {
- frames.push(frame_to_qlogframe(&f));
+ frames.push(QuicFrame::from(&f));
} else {
qinfo!("qlog: invalid frame");
break;
@@ -393,173 +387,180 @@ pub fn metrics_updated(qlog: &mut NeqoQlog, updated_metrics: &[QlogMetric]) {
#[allow(clippy::too_many_lines)] // Yeah, but it's a nice match.
#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] // No choice here.
-fn frame_to_qlogframe(frame: &Frame) -> QuicFrame {
- match frame {
- Frame::Padding => QuicFrame::Padding,
- Frame::Ping => QuicFrame::Ping,
- Frame::Ack {
- largest_acknowledged,
- ack_delay,
- first_ack_range,
- ack_ranges,
- } => {
- let ranges =
- Frame::decode_ack_frame(*largest_acknowledged, *first_ack_range, ack_ranges).ok();
-
- let acked_ranges = ranges.map(|all| {
- AckedRanges::Double(
- all.into_iter()
- .map(RangeInclusive::into_inner)
- .collect::<Vec<_>>(),
- )
- });
-
- QuicFrame::Ack {
- ack_delay: Some(*ack_delay as f32 / 1000.0),
- acked_ranges,
- ect1: None,
- ect0: None,
- ce: None,
+impl From<&Frame<'_>> for QuicFrame {
+ fn from(frame: &Frame) -> Self {
+ match frame {
+ // TODO: Add payload length to `QuicFrame::Padding` once
+ // https://github.com/cloudflare/quiche/pull/1745 is available via the qlog crate.
+ Frame::Padding { .. } => QuicFrame::Padding,
+ Frame::Ping => QuicFrame::Ping,
+ Frame::Ack {
+ largest_acknowledged,
+ ack_delay,
+ first_ack_range,
+ ack_ranges,
+ } => {
+ let ranges =
+ Frame::decode_ack_frame(*largest_acknowledged, *first_ack_range, ack_ranges)
+ .ok();
+
+ let acked_ranges = ranges.map(|all| {
+ AckedRanges::Double(
+ all.into_iter()
+ .map(RangeInclusive::into_inner)
+ .collect::<Vec<_>>(),
+ )
+ });
+
+ QuicFrame::Ack {
+ ack_delay: Some(*ack_delay as f32 / 1000.0),
+ acked_ranges,
+ ect1: None,
+ ect0: None,
+ ce: None,
+ }
}
- }
- Frame::ResetStream {
- stream_id,
- application_error_code,
- final_size,
- } => QuicFrame::ResetStream {
- stream_id: stream_id.as_u64(),
- error_code: *application_error_code,
- final_size: *final_size,
- },
- Frame::StopSending {
- stream_id,
- application_error_code,
- } => QuicFrame::StopSending {
- stream_id: stream_id.as_u64(),
- error_code: *application_error_code,
- },
- Frame::Crypto { offset, data } => QuicFrame::Crypto {
- offset: *offset,
- length: data.len() as u64,
- },
- Frame::NewToken { token } => QuicFrame::NewToken {
- token: qlog::Token {
- ty: Some(qlog::TokenType::Retry),
- details: None,
- raw: Some(RawInfo {
- data: Some(hex(token)),
- length: Some(token.len() as u64),
- payload_length: None,
- }),
+ Frame::ResetStream {
+ stream_id,
+ application_error_code,
+ final_size,
+ } => QuicFrame::ResetStream {
+ stream_id: stream_id.as_u64(),
+ error_code: *application_error_code,
+ final_size: *final_size,
+ },
+ Frame::StopSending {
+ stream_id,
+ application_error_code,
+ } => QuicFrame::StopSending {
+ stream_id: stream_id.as_u64(),
+ error_code: *application_error_code,
+ },
+ Frame::Crypto { offset, data } => QuicFrame::Crypto {
+ offset: *offset,
+ length: data.len() as u64,
+ },
+ Frame::NewToken { token } => QuicFrame::NewToken {
+ token: qlog::Token {
+ ty: Some(qlog::TokenType::Retry),
+ details: None,
+ raw: Some(RawInfo {
+ data: Some(hex(token)),
+ length: Some(token.len() as u64),
+ payload_length: None,
+ }),
+ },
},
- },
- Frame::Stream {
- fin,
- stream_id,
- offset,
- data,
- ..
- } => QuicFrame::Stream {
- stream_id: stream_id.as_u64(),
- offset: *offset,
- length: data.len() as u64,
- fin: Some(*fin),
- raw: None,
- },
- Frame::MaxData { maximum_data } => QuicFrame::MaxData {
- maximum: *maximum_data,
- },
- Frame::MaxStreamData {
- stream_id,
- maximum_stream_data,
- } => QuicFrame::MaxStreamData {
- stream_id: stream_id.as_u64(),
- maximum: *maximum_stream_data,
- },
- Frame::MaxStreams {
- stream_type,
- maximum_streams,
- } => QuicFrame::MaxStreams {
- stream_type: match stream_type {
- NeqoStreamType::BiDi => StreamType::Bidirectional,
- NeqoStreamType::UniDi => StreamType::Unidirectional,
+ Frame::Stream {
+ fin,
+ stream_id,
+ offset,
+ data,
+ ..
+ } => QuicFrame::Stream {
+ stream_id: stream_id.as_u64(),
+ offset: *offset,
+ length: data.len() as u64,
+ fin: Some(*fin),
+ raw: None,
},
- maximum: *maximum_streams,
- },
- Frame::DataBlocked { data_limit } => QuicFrame::DataBlocked { limit: *data_limit },
- Frame::StreamDataBlocked {
- stream_id,
- stream_data_limit,
- } => QuicFrame::StreamDataBlocked {
- stream_id: stream_id.as_u64(),
- limit: *stream_data_limit,
- },
- Frame::StreamsBlocked {
- stream_type,
- stream_limit,
- } => QuicFrame::StreamsBlocked {
- stream_type: match stream_type {
- NeqoStreamType::BiDi => StreamType::Bidirectional,
- NeqoStreamType::UniDi => StreamType::Unidirectional,
+ Frame::MaxData { maximum_data } => QuicFrame::MaxData {
+ maximum: *maximum_data,
},
- limit: *stream_limit,
- },
- Frame::NewConnectionId {
- sequence_number,
- retire_prior,
- connection_id,
- stateless_reset_token,
- } => QuicFrame::NewConnectionId {
- sequence_number: *sequence_number as u32,
- retire_prior_to: *retire_prior as u32,
- connection_id_length: Some(connection_id.len() as u8),
- connection_id: hex(connection_id),
- stateless_reset_token: Some(hex(stateless_reset_token)),
- },
- Frame::RetireConnectionId { sequence_number } => QuicFrame::RetireConnectionId {
- sequence_number: *sequence_number as u32,
- },
- Frame::PathChallenge { data } => QuicFrame::PathChallenge {
- data: Some(hex(data)),
- },
- Frame::PathResponse { data } => QuicFrame::PathResponse {
- data: Some(hex(data)),
- },
- Frame::ConnectionClose {
- error_code,
- frame_type,
- reason_phrase,
- } => QuicFrame::ConnectionClose {
- error_space: match error_code {
- CloseError::Transport(_) => Some(ErrorSpace::TransportError),
- CloseError::Application(_) => Some(ErrorSpace::ApplicationError),
+ Frame::MaxStreamData {
+ stream_id,
+ maximum_stream_data,
+ } => QuicFrame::MaxStreamData {
+ stream_id: stream_id.as_u64(),
+ maximum: *maximum_stream_data,
},
- error_code: Some(error_code.code()),
- error_code_value: Some(0),
- reason: Some(String::from_utf8_lossy(reason_phrase).to_string()),
- trigger_frame_type: Some(*frame_type),
- },
- Frame::HandshakeDone => QuicFrame::HandshakeDone,
- Frame::AckFrequency { .. } => QuicFrame::Unknown {
- frame_type_value: None,
- raw_frame_type: frame.get_type(),
- raw: None,
- },
- Frame::Datagram { data, .. } => QuicFrame::Datagram {
- length: data.len() as u64,
- raw: None,
- },
+ Frame::MaxStreams {
+ stream_type,
+ maximum_streams,
+ } => QuicFrame::MaxStreams {
+ stream_type: match stream_type {
+ NeqoStreamType::BiDi => StreamType::Bidirectional,
+ NeqoStreamType::UniDi => StreamType::Unidirectional,
+ },
+ maximum: *maximum_streams,
+ },
+ Frame::DataBlocked { data_limit } => QuicFrame::DataBlocked { limit: *data_limit },
+ Frame::StreamDataBlocked {
+ stream_id,
+ stream_data_limit,
+ } => QuicFrame::StreamDataBlocked {
+ stream_id: stream_id.as_u64(),
+ limit: *stream_data_limit,
+ },
+ Frame::StreamsBlocked {
+ stream_type,
+ stream_limit,
+ } => QuicFrame::StreamsBlocked {
+ stream_type: match stream_type {
+ NeqoStreamType::BiDi => StreamType::Bidirectional,
+ NeqoStreamType::UniDi => StreamType::Unidirectional,
+ },
+ limit: *stream_limit,
+ },
+ Frame::NewConnectionId {
+ sequence_number,
+ retire_prior,
+ connection_id,
+ stateless_reset_token,
+ } => QuicFrame::NewConnectionId {
+ sequence_number: *sequence_number as u32,
+ retire_prior_to: *retire_prior as u32,
+ connection_id_length: Some(connection_id.len() as u8),
+ connection_id: hex(connection_id),
+ stateless_reset_token: Some(hex(stateless_reset_token)),
+ },
+ Frame::RetireConnectionId { sequence_number } => QuicFrame::RetireConnectionId {
+ sequence_number: *sequence_number as u32,
+ },
+ Frame::PathChallenge { data } => QuicFrame::PathChallenge {
+ data: Some(hex(data)),
+ },
+ Frame::PathResponse { data } => QuicFrame::PathResponse {
+ data: Some(hex(data)),
+ },
+ Frame::ConnectionClose {
+ error_code,
+ frame_type,
+ reason_phrase,
+ } => QuicFrame::ConnectionClose {
+ error_space: match error_code {
+ CloseError::Transport(_) => Some(ErrorSpace::TransportError),
+ CloseError::Application(_) => Some(ErrorSpace::ApplicationError),
+ },
+ error_code: Some(error_code.code()),
+ error_code_value: Some(0),
+ reason: Some(String::from_utf8_lossy(reason_phrase).to_string()),
+ trigger_frame_type: Some(*frame_type),
+ },
+ Frame::HandshakeDone => QuicFrame::HandshakeDone,
+ Frame::AckFrequency { .. } => QuicFrame::Unknown {
+ frame_type_value: None,
+ raw_frame_type: frame.get_type(),
+ raw: None,
+ },
+ Frame::Datagram { data, .. } => QuicFrame::Datagram {
+ length: data.len() as u64,
+ raw: None,
+ },
+ }
}
}
-fn to_qlog_pkt_type(ptype: PacketType) -> qlog::events::quic::PacketType {
- match ptype {
- PacketType::Initial => qlog::events::quic::PacketType::Initial,
- PacketType::Handshake => qlog::events::quic::PacketType::Handshake,
- PacketType::ZeroRtt => qlog::events::quic::PacketType::ZeroRtt,
- PacketType::Short => qlog::events::quic::PacketType::OneRtt,
- PacketType::Retry => qlog::events::quic::PacketType::Retry,
- PacketType::VersionNegotiation => qlog::events::quic::PacketType::VersionNegotiation,
- PacketType::OtherVersion => qlog::events::quic::PacketType::Unknown,
+impl From<PacketType> for qlog::events::quic::PacketType {
+ fn from(value: PacketType) -> Self {
+ match value {
+ PacketType::Initial => qlog::events::quic::PacketType::Initial,
+ PacketType::Handshake => qlog::events::quic::PacketType::Handshake,
+ PacketType::ZeroRtt => qlog::events::quic::PacketType::ZeroRtt,
+ PacketType::Short => qlog::events::quic::PacketType::OneRtt,
+ PacketType::Retry => qlog::events::quic::PacketType::Retry,
+ PacketType::VersionNegotiation => qlog::events::quic::PacketType::VersionNegotiation,
+ PacketType::OtherVersion => qlog::events::quic::PacketType::Unknown,
+ }
}
}
diff --git a/third_party/rust/neqo-transport/src/stats.rs b/third_party/rust/neqo-transport/src/stats.rs
index 9eff503dcf..0a61097010 100644
--- a/third_party/rust/neqo-transport/src/stats.rs
+++ b/third_party/rust/neqo-transport/src/stats.rs
@@ -14,7 +14,7 @@ use std::{
time::Duration,
};
-use neqo_common::qinfo;
+use neqo_common::qwarn;
use crate::packet::PacketNumber;
@@ -168,7 +168,7 @@ impl Stats {
pub fn pkt_dropped(&mut self, reason: impl AsRef<str>) {
self.dropped_rx += 1;
- qinfo!(
+ qwarn!(
[self.info],
"Dropped received packet: {}; Total: {}",
reason.as_ref(),
@@ -206,7 +206,7 @@ impl Debug for Stats {
" tx: {} lost {} lateack {} ptoack {}",
self.packets_tx, self.lost, self.late_ack, self.pto_ack
)?;
- writeln!(f, " resumed: {} ", self.resumed)?;
+ writeln!(f, " resumed: {}", self.resumed)?;
writeln!(f, " frames rx:")?;
self.frame_rx.fmt(f)?;
writeln!(f, " frames tx:")?;
diff --git a/third_party/rust/neqo-transport/tests/common/mod.rs b/third_party/rust/neqo-transport/tests/common/mod.rs
index faff216eb9..e36e66f753 100644
--- a/third_party/rust/neqo-transport/tests/common/mod.rs
+++ b/third_party/rust/neqo-transport/tests/common/mod.rs
@@ -146,14 +146,7 @@ pub fn initial_aead_and_hp(dcid: &[u8], role: Role) -> (Aead, HpKey) {
)
.unwrap();
(
- Aead::new(
- false,
- TLS_VERSION_1_3,
- TLS_AES_128_GCM_SHA256,
- &secret,
- "quic ",
- )
- .unwrap(),
+ Aead::new(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256, &secret, "quic ").unwrap(),
HpKey::extract(TLS_VERSION_1_3, TLS_AES_128_GCM_SHA256, &secret, "quic hp").unwrap(),
)
}
diff --git a/third_party/rust/neqo-transport/tests/conn_vectors.rs b/third_party/rust/neqo-transport/tests/conn_vectors.rs
index f478883075..86fe9d36fc 100644
--- a/third_party/rust/neqo-transport/tests/conn_vectors.rs
+++ b/third_party/rust/neqo-transport/tests/conn_vectors.rs
@@ -6,7 +6,7 @@
// Tests with the test vectors from the spec.
-#![cfg(not(feature = "fuzzing"))]
+#![cfg(not(feature = "disable-encryption"))]
use std::{cell::RefCell, rc::Rc};
diff --git a/third_party/rust/neqo-transport/tests/connection.rs b/third_party/rust/neqo-transport/tests/connection.rs
index 0b91fcf306..b8877b946d 100644
--- a/third_party/rust/neqo-transport/tests/connection.rs
+++ b/third_party/rust/neqo-transport/tests/connection.rs
@@ -127,6 +127,76 @@ fn reorder_server_initial() {
assert_eq!(*client.state(), State::Confirmed);
}
+fn set_payload(server_packet: &Option<Datagram>, client_dcid: &[u8], payload: &[u8]) -> Datagram {
+ let (server_initial, _server_hs) = split_datagram(server_packet.as_ref().unwrap());
+ let (protected_header, _, _, orig_payload) =
+ decode_initial_header(&server_initial, Role::Server);
+
+ // Now decrypt the packet.
+ let (aead, hp) = initial_aead_and_hp(client_dcid, Role::Server);
+ let (mut header, pn) = remove_header_protection(&hp, protected_header, orig_payload);
+ assert_eq!(pn, 0);
+ // Re-encode the packet number as four bytes, so we have enough material for the header
+ // protection sample if payload is empty.
+ let pn_pos = header.len() - 2;
+ header[pn_pos] = u8::try_from(4 + aead.expansion()).unwrap();
+ header.resize(header.len() + 3, 0);
+ header[0] |= 0b0000_0011; // Set the packet number length to 4.
+
+ // And build a packet containing the given payload.
+ let mut packet = header.clone();
+ packet.resize(header.len() + payload.len() + aead.expansion(), 0);
+ aead.encrypt(pn, &header, payload, &mut packet[header.len()..])
+ .unwrap();
+ apply_header_protection(&hp, &mut packet, protected_header.len()..header.len());
+ Datagram::new(
+ server_initial.source(),
+ server_initial.destination(),
+ server_initial.tos(),
+ server_initial.ttl(),
+ packet,
+ )
+}
+
+/// Test that the stack treats a packet without any frames as a protocol violation.
+#[test]
+fn packet_without_frames() {
+ let mut client = new_client(
+ ConnectionParameters::default().versions(Version::Version1, vec![Version::Version1]),
+ );
+ let mut server = default_server();
+
+ let client_initial = client.process_output(now());
+ let (_, client_dcid, _, _) =
+ decode_initial_header(client_initial.as_dgram_ref().unwrap(), Role::Client);
+
+ let server_packet = server.process(client_initial.as_dgram_ref(), now()).dgram();
+ let modified = set_payload(&server_packet, client_dcid, &[]);
+ client.process_input(&modified, now());
+ assert_eq!(
+ client.state(),
+ &State::Closed(ConnectionError::Transport(Error::ProtocolViolation))
+ );
+}
+
+/// Test that the stack permits a packet containing only padding.
+#[test]
+fn packet_with_only_padding() {
+ let mut client = new_client(
+ ConnectionParameters::default().versions(Version::Version1, vec![Version::Version1]),
+ );
+ let mut server = default_server();
+
+ let client_initial = client.process_output(now());
+ let (_, client_dcid, _, _) =
+ decode_initial_header(client_initial.as_dgram_ref().unwrap(), Role::Client);
+
+ let server_packet = server.process(client_initial.as_dgram_ref(), now()).dgram();
+ let modified = set_payload(&server_packet, client_dcid, &[0]);
+ client.process_input(&modified, now());
+ assert_eq!(client.state(), &State::WaitInitial);
+}
+
/// Overflow the crypto buffer.
#[allow(clippy::similar_names)] // For ..._scid and ..._dcid, which are fine.
#[test]
diff --git a/third_party/rust/neqo-transport/tests/retry.rs b/third_party/rust/neqo-transport/tests/retry.rs
index e583fcae0f..36eff71e7b 100644
--- a/third_party/rust/neqo-transport/tests/retry.rs
+++ b/third_party/rust/neqo-transport/tests/retry.rs
@@ -4,7 +4,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
-#![cfg(not(feature = "fuzzing"))]
+#![cfg(not(feature = "disable-encryption"))]
mod common;
diff --git a/third_party/rust/oneshot-uniffi/.cargo-checksum.json b/third_party/rust/oneshot-uniffi/.cargo-checksum.json
index 6b252c6773..985beded51 100644
--- a/third_party/rust/oneshot-uniffi/.cargo-checksum.json
+++ b/third_party/rust/oneshot-uniffi/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"CHANGELOG.md":"e1165d97c283b915d87e22f209494be39933723a0b544e725f69cfa5cef3876c","Cargo.lock":"7625529900ca1e3626b90e74ef268b31e06da85e8334a885711fdfd80821ddda","Cargo.toml":"81dde8ad3180c7b97325a6a67bfbefb145590606de9008d881c4e04808865f0a","README.md":"811ea1c958d5a65583d0223b7ab09bb282e7a51ed60f9a2cb90ef6d555325a68","benches/benches.rs":"67dcc916d0b7e28e396c28dac0499726366e1cb10e9991948d1c881a5abf5faa","check_mem_leaks.sh":"c1ab6ef27997c7f971352ab1c86a184004843c499bc24925da953aefcf1c624c","examples/recv_before_send.rs":"9a3cabcc2878990b61787d0048061b382555a8cd1a08b1ddec63a6e8a4a31e56","examples/recv_before_send_then_drop_sender.rs":"14706c6b4308a690662ceaa47f1699588bd833b3ec020eb9f42f220f3ffc7ae7","examples/recv_ref_before_send.rs":"43699f4720c46b5f138c260b866eb708ddf616e2b442ffa74a97373f4f48d4d0","examples/recv_ref_before_send_then_drop_sender.rs":"a190ed220cb4288d4965485365c9afaed30535cbfad5f8cb7389071b82d67cac","examples/recv_timeout_before_send.rs":"2262aa6531afce7816d43182ad9cbec2c04f3dc129064e11e89452278ce8b163","examples/recv_timeout_before_send_then_drop_sender.rs":"4cc8eade4c211f52f5b9be0f72a5906689b894490f4cb5255525e44106e7a4a8","examples/recv_with_dropped_sender.rs":"7906685053ce1c53ff6c26ce11d3221d4bf5ca3429d1d4d2c28de9237cb151c6","examples/send_before_recv.rs":"5555bd61ad52273b663007794128d8f012fc54272bd3225259b5546221bcd591","examples/send_then_drop_receiver.rs":"c3612de207309098404b057468687a2d2311d07f354b7e046398e35e93c4cdcf","examples/send_with_dropped_receiver.rs":"f5a7762b231a24a0db4397c5139437cba155d09b9dbb59872d662c7923080706","src/errors.rs":"df6a1db663fdb1c54d6941d737f6591bfe0dc6f01bd627ba0a94d67ed50b27a9","src/lib.rs":"86893f56e8e762b41ee079b42f4248608e9efb68bd76aa9550fce61e7466bbb0","src/loombox.rs":"fc85d1c2d3fda432be60f0c4d1d528e5998ec2b738a5b395a242285051b94d65","tests/assert_mem.rs":"b1e5190af01af22e55c7c1cd1ff2711807591f788e4eb8b6c6d89123e146105e","tests/async.rs":"6fd2826e589b94677d4eeed1080deda8bcc429aa05a20d843d1442a3a48ea757","tests/future.rs":"0e71f0293cd5a8c44210e8882aca20cfbf1e3771ecd4e4f6b59b924c0d01dd97","tests/helpers/mod.rs":"19161ed33e0ba8862746f04678d0606dee90205896083f85d8c1dcd4d211ccb0","tests/helpers/waker.rs":"77494d49f62d0d320df3830643c306e06e6e20751d210cf6fa58b238bd96c3f9","tests/loom.rs":"ea350fa424a95581e1871bc0037badecc5a090f28fd10532917abbaf561218ab","tests/sync.rs":"1186fa6cdb5a180944fa7d793ccb8be412c4a4e88bb504daa70bc097ee081b06"},"package":"9ae4988774e7a7e6a0783d119bdc683ea8c1d01a24d4fff9b4bdc280e07bd99e"} \ No newline at end of file
+{"files":{"CHANGELOG.md":"4ad03d95d5532e8f2551e3e53877e6347c04c32f479c4edf517244ecd5921ac7","Cargo.lock":"5d85bcfda2ee559d243099fb26f3724ae239777d891e780a924804e30f6733ad","Cargo.toml":"07a73ff74274df3a7439fccc8acfe306fae0f51ad79a80edbc54b51a730314c0","README.md":"811ea1c958d5a65583d0223b7ab09bb282e7a51ed60f9a2cb90ef6d555325a68","benches/benches.rs":"67dcc916d0b7e28e396c28dac0499726366e1cb10e9991948d1c881a5abf5faa","check_mem_leaks.sh":"c1ab6ef27997c7f971352ab1c86a184004843c499bc24925da953aefcf1c624c","examples/recv_before_send.rs":"9a3cabcc2878990b61787d0048061b382555a8cd1a08b1ddec63a6e8a4a31e56","examples/recv_before_send_then_drop_sender.rs":"14706c6b4308a690662ceaa47f1699588bd833b3ec020eb9f42f220f3ffc7ae7","examples/recv_ref_before_send.rs":"43699f4720c46b5f138c260b866eb708ddf616e2b442ffa74a97373f4f48d4d0","examples/recv_ref_before_send_then_drop_sender.rs":"a190ed220cb4288d4965485365c9afaed30535cbfad5f8cb7389071b82d67cac","examples/recv_timeout_before_send.rs":"2262aa6531afce7816d43182ad9cbec2c04f3dc129064e11e89452278ce8b163","examples/recv_timeout_before_send_then_drop_sender.rs":"4cc8eade4c211f52f5b9be0f72a5906689b894490f4cb5255525e44106e7a4a8","examples/recv_with_dropped_sender.rs":"7906685053ce1c53ff6c26ce11d3221d4bf5ca3429d1d4d2c28de9237cb151c6","examples/send_before_recv.rs":"5555bd61ad52273b663007794128d8f012fc54272bd3225259b5546221bcd591","examples/send_then_drop_receiver.rs":"c3612de207309098404b057468687a2d2311d07f354b7e046398e35e93c4cdcf","examples/send_with_dropped_receiver.rs":"f5a7762b231a24a0db4397c5139437cba155d09b9dbb59872d662c7923080706","src/errors.rs":"a5aa56bc497dccdbdbe15b9070360f50835c762f11be4ee96e0d25b150168ac9","src/lib.rs":"4bef3602ff4f5d2b42ce963d722a48c9ff07275e75ef6bed7b523e8f45e459fe","src/loombox.rs":"fc85d1c2d3fda432be60f0c4d1d528e5998ec2b738a5b395a242285051b94d65","tests/assert_mem.rs":"b1e5190af01af22e55c7c1cd1ff2711807591f788e4eb8b6c6d89123e146105e","tests/async.rs":"6fd2826e589b94677d4eeed1080deda8bcc429aa05a20d843d1442a3a48ea757","tests/future.rs":"0e71f0293cd5a8c44210e8882aca20cfbf1e3771ecd4e4f6b59b924c0d01dd97","tests/helpers/mod.rs":"19161ed33e0ba8862746f04678d0606dee90205896083f85d8c1dcd4d211ccb0","tests/helpers/waker.rs":"77494d49f62d0d320df3830643c306e06e6e20751d210cf6fa58b238bd96c3f9","tests/loom.rs":"ea350fa424a95581e1871bc0037badecc5a090f28fd10532917abbaf561218ab","tests/raw.rs":"5564615fea811b0061d8ad801356e60e0018ec4e3fb99cc739287ed5b96cb7cf","tests/sync.rs":"1186fa6cdb5a180944fa7d793ccb8be412c4a4e88bb504daa70bc097ee081b06"},"package":"6c548d5c78976f6955d72d0ced18c48ca07030f7a1d4024529fedd7c1c01b29c"} \ No newline at end of file
diff --git a/third_party/rust/oneshot-uniffi/CHANGELOG.md b/third_party/rust/oneshot-uniffi/CHANGELOG.md
index e12f6cc979..c4c928386b 100644
--- a/third_party/rust/oneshot-uniffi/CHANGELOG.md
+++ b/third_party/rust/oneshot-uniffi/CHANGELOG.md
@@ -16,6 +16,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
+
+## [0.1.6] - 2023-09-14
+### Added
+* Add `into_raw` and `from_raw` methods on both `Sender` and `Receiver`. Allows passing `oneshot`
+ channels over FFI without an extra layer of heap allocation.
+
+
## [0.1.5] - 2022-09-01
### Fixed
- Handle the UNPARKING state correctly in all recv methods. `try_recv` will now not panic
diff --git a/third_party/rust/oneshot-uniffi/Cargo.lock b/third_party/rust/oneshot-uniffi/Cargo.lock
index 1b75d43675..b71644917f 100644
--- a/third_party/rust/oneshot-uniffi/Cargo.lock
+++ b/third_party/rust/oneshot-uniffi/Cargo.lock
@@ -601,7 +601,7 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "oneshot-uniffi"
-version = "0.1.5"
+version = "0.1.6"
dependencies = [
"async-std",
"criterion",
diff --git a/third_party/rust/oneshot-uniffi/Cargo.toml b/third_party/rust/oneshot-uniffi/Cargo.toml
index 078a477fb2..cba7266915 100644
--- a/third_party/rust/oneshot-uniffi/Cargo.toml
+++ b/third_party/rust/oneshot-uniffi/Cargo.toml
@@ -13,7 +13,7 @@
edition = "2021"
rust-version = "1.60.0"
name = "oneshot-uniffi"
-version = "0.1.5"
+version = "0.1.6"
authors = ["Linus Färnstrand <faern@faern.net>"]
description = """
Patched version of oneshot specifically for the UniFFI project.
diff --git a/third_party/rust/oneshot-uniffi/src/errors.rs b/third_party/rust/oneshot-uniffi/src/errors.rs
index afc48acd03..1fd0de1eed 100644
--- a/third_party/rust/oneshot-uniffi/src/errors.rs
+++ b/third_party/rust/oneshot-uniffi/src/errors.rs
@@ -4,7 +4,8 @@ use core::mem;
use core::ptr::NonNull;
/// An error returned when trying to send on a closed channel. Returned from
-/// [`Sender::send`] if the corresponding [`Receiver`] has already been dropped.
+/// [`Sender::send`](crate::Sender::send) if the corresponding [`Receiver`](crate::Receiver)
+/// has already been dropped.
///
/// The message that could not be sent can be retreived again with [`SendError::into_inner`].
pub struct SendError<T> {
@@ -79,10 +80,10 @@ impl<T> fmt::Debug for SendError<T> {
#[cfg(feature = "std")]
impl<T> std::error::Error for SendError<T> {}
-/// An error returned from the indefinitely blocking recv functions on a [`Receiver`].
+/// An error returned from the blocking [`Receiver::recv`](crate::Receiver::recv) method.
///
-/// The recv operation can only fail if the corresponding [`Sender`] was dropped before sending
-/// any message. Or if a message has already been sent and received on the channel.
+/// The receive operation can only fail if the corresponding [`Sender`](crate::Sender) was dropped
+/// before sending any message, or if a message has already been sent and received on the channel.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct RecvError;
@@ -95,7 +96,8 @@ impl fmt::Display for RecvError {
#[cfg(feature = "std")]
impl std::error::Error for RecvError {}
-/// An error returned when trying a non blocking receive on a [`Receiver`].
+/// An error returned when failing to receive a message in the non-blocking
+/// [`Receiver::try_recv`](crate::Receiver::try_recv).
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum TryRecvError {
/// The channel is still open, but there was no message present in it.
@@ -119,7 +121,8 @@ impl fmt::Display for TryRecvError {
#[cfg(feature = "std")]
impl std::error::Error for TryRecvError {}
-/// An error returned when trying a time limited blocking receive on a [`Receiver`].
+/// An error returned when failing to receive a message in
+/// [`Receiver::recv_timeout`](crate::Receiver::recv_timeout).
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum RecvTimeoutError {
/// No message arrived on the channel before the timeout was reached. The channel is still open.
diff --git a/third_party/rust/oneshot-uniffi/src/lib.rs b/third_party/rust/oneshot-uniffi/src/lib.rs
index 94bb35d12a..8da012b8c0 100644
--- a/third_party/rust/oneshot-uniffi/src/lib.rs
+++ b/third_party/rust/oneshot-uniffi/src/lib.rs
@@ -314,6 +314,31 @@ impl<T> Sender<T> {
_ => unreachable!(),
}
}
+
+ /// Consumes the Sender, returning a raw pointer to the channel on the heap.
+ ///
+ /// This is intended to simplify using oneshot channels with some FFI code. The only safe thing
+ /// to do with the returned pointer is to later reconstruct the Sender with [Sender::from_raw].
+ /// Memory will leak if the Sender is never reconstructed.
+ pub fn into_raw(self) -> *mut () {
+ let raw = self.channel_ptr.as_ptr() as *mut ();
+ mem::forget(self);
+ raw
+ }
+
+ /// Consumes a raw pointer from [Sender::into_raw], recreating the Sender.
+ ///
+ /// # Safety
+ ///
+ /// This pointer must have come from [`Sender<T>::into_raw`] with the same message type, `T`.
+ /// At most one Sender must exist for a channel at any point in time.
+ /// Constructing multiple Senders from the same raw pointer leads to undefined behavior.
+ pub unsafe fn from_raw(raw: *mut ()) -> Self {
+ Self {
+ channel_ptr: NonNull::new_unchecked(raw as *mut Channel<T>),
+ _invariant: PhantomData,
+ }
+ }
}
impl<T> Drop for Sender<T> {
@@ -816,6 +841,30 @@ impl<T> Receiver<T> {
_ => unreachable!(),
}
}
+
+ /// Consumes the Receiver, returning a raw pointer to the channel on the heap.
+ ///
+ /// This is intended to simplify using oneshot channels with some FFI code. The only safe thing
+ /// to do with the returned pointer is to later reconstruct the Receiver with
+ /// [Receiver::from_raw]. Memory will leak if the Receiver is never reconstructed.
+ pub fn into_raw(self) -> *mut () {
+ let raw = self.channel_ptr.as_ptr() as *mut ();
+ mem::forget(self);
+ raw
+ }
+
+ /// Consumes a raw pointer from [Receiver::into_raw], recreating the Receiver.
+ ///
+ /// # Safety
+ ///
+ /// This pointer must have come from [`Receiver<T>::into_raw`] with the same message type, `T`.
+ /// At most one Receiver must exist for a channel at any point in time.
+ /// Constructing multiple Receivers from the same raw pointer leads to undefined behavior.
+ pub unsafe fn from_raw(raw: *mut ()) -> Self {
+ Self {
+ channel_ptr: NonNull::new_unchecked(raw as *mut Channel<T>),
+ }
+ }
}
#[cfg(feature = "async")]
@@ -1178,7 +1227,7 @@ fn receiver_waker_size() {
(false, false) => 0,
(false, true) => 16,
(true, false) => 8,
- (true, true) => 24,
+ (true, true) => 16,
};
assert_eq!(mem::size_of::<ReceiverWaker>(), expected);
}
diff --git a/third_party/rust/oneshot-uniffi/tests/raw.rs b/third_party/rust/oneshot-uniffi/tests/raw.rs
new file mode 100644
index 0000000000..e38dc45c48
--- /dev/null
+++ b/third_party/rust/oneshot-uniffi/tests/raw.rs
@@ -0,0 +1,46 @@
+#![cfg(not(loom))]
+
+use oneshot::{channel, Receiver, Sender};
+
+#[test]
+fn test_raw_sender() {
+ let (sender, receiver) = channel::<u32>();
+ let raw = sender.into_raw();
+ let recreated = unsafe { Sender::<u32>::from_raw(raw) };
+ recreated
+ .send(100)
+ .unwrap_or_else(|e| panic!("error sending after into_raw/from_raw roundtrip: {e}"));
+ assert_eq!(receiver.try_recv(), Ok(100))
+}
+
+#[test]
+fn test_raw_receiver() {
+ let (sender, receiver) = channel::<u32>();
+ let raw = receiver.into_raw();
+ sender.send(100).unwrap();
+ let recreated = unsafe { Receiver::<u32>::from_raw(raw) };
+ assert_eq!(
+ recreated
+ .try_recv()
+ .unwrap_or_else(|e| panic!("error receiving after into_raw/from_raw roundtrip: {e}")),
+ 100
+ )
+}
+
+#[test]
+fn test_raw_sender_and_receiver() {
+ let (sender, receiver) = channel::<u32>();
+ let raw_receiver = receiver.into_raw();
+ let raw_sender = sender.into_raw();
+
+ let recreated_sender = unsafe { Sender::<u32>::from_raw(raw_sender) };
+ recreated_sender.send(100).unwrap();
+
+ let recreated_receiver = unsafe { Receiver::<u32>::from_raw(raw_receiver) };
+ assert_eq!(
+ recreated_receiver
+ .try_recv()
+ .unwrap_or_else(|e| panic!("error receiving after into_raw/from_raw roundtrip: {e}")),
+ 100
+ )
+}
diff --git a/third_party/rust/relevancy/.cargo-checksum.json b/third_party/rust/relevancy/.cargo-checksum.json
new file mode 100644
index 0000000000..c8d8187c8d
--- /dev/null
+++ b/third_party/rust/relevancy/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"76d64a839128f51662d1c10728ceddbb6a9ebdfce803915874cd654117d1b14e","build.rs":"a562bfe527d21c4e8a1a44b892defa83cdff141ec5dd51ed6f3862330e50ddd7","src/bin/generate-test-data.rs":"7f1c9dc445418c7627f89d1f2aa8e550d0f85b3d1f05edb7c378ab9441714f1f","src/db.rs":"0b45180f3031759213a0421231b6f109ed4f5c88aca556df159ce2717416cfec","src/error.rs":"6831fc329044174a8451b8b008c0b96c47404c591eb42e880562e65da0adfd0f","src/interest.rs":"ce6298ef8f69fcb57c8e5797467cbe1c0212a0d94daf828b12845740ac14a166","src/lib.rs":"7a0f0ad0a43f371035d9c0b73d143cf1b387d4b8cfad0d0db79314b5b91fd43c","src/populate_interests.rs":"b8905b52f9fc80719c175253b758413f606b27660e660635094421eec8b24c8f","src/relevancy.udl":"a3fae5097f9e8b39bb6c74ed6789906748c46f22d377e3dcb73b08731908f5bc","src/schema.rs":"f782c712f10c4f1af2f9e1424d6b52f59a2bacfcc452a8feb763f36478f5dd5d","src/url_hash.rs":"5619a249d471e7b642d889bad09e93212559c8b947010d49492c1423da2b310e","test-data":"392fc950363c9953ea6ab144b81d84021c4af1e1177cc0adac4eda5688c8bc33"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/relevancy/Cargo.toml b/third_party/rust/relevancy/Cargo.toml
new file mode 100644
index 0000000000..eddd8fd25c
--- /dev/null
+++ b/third_party/rust/relevancy/Cargo.toml
@@ -0,0 +1,47 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "relevancy"
+version = "0.1.0"
+exclude = [
+ "/android",
+ "/ios",
+]
+license = "MPL-2.0"
+
+[lib]
+
+[[bin]]
+name = "generate-test-data"
+
+[dependencies]
+log = "0.4"
+md-5 = "0.10"
+parking_lot = ">=0.11,<=0.12"
+thiserror = "1.0"
+uniffi = "0.27.1"
+url = "2.5"
+
+[dependencies.error-support]
+path = "../support/error"
+
+[dependencies.rusqlite]
+version = "0.30.0"
+features = ["bundled"]
+
+[dependencies.sql-support]
+path = "../support/sql"
+
+[build-dependencies.uniffi]
+version = "0.27.1"
+features = ["build"]
diff --git a/third_party/rust/relevancy/build.rs b/third_party/rust/relevancy/build.rs
new file mode 100644
index 0000000000..5439f41d68
--- /dev/null
+++ b/third_party/rust/relevancy/build.rs
@@ -0,0 +1,8 @@
+/* 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/.
+ */
+
+fn main() {
+ uniffi::generate_scaffolding("./src/relevancy.udl").unwrap();
+}
diff --git a/third_party/rust/relevancy/src/bin/generate-test-data.rs b/third_party/rust/relevancy/src/bin/generate-test-data.rs
new file mode 100644
index 0000000000..04c5827275
--- /dev/null
+++ b/third_party/rust/relevancy/src/bin/generate-test-data.rs
@@ -0,0 +1,43 @@
+use relevancy::{
+ url_hash::{hash_url, UrlHash},
+ Interest,
+};
+use std::{collections::HashMap, fs::File, io::Write};
+
+// Generate a set of test data and output it to the `test-data` file.
+//
+// This is meant to be a placeholder until we can get this data stored in remote settings.
+
+const TEST_INTEREST_DATA: &[(&str, Interest)] = &[
+ ("https://espn.com/", Interest::Sports),
+ ("https://dogs.com/", Interest::Animals),
+ ("https://cars.com/", Interest::Autos),
+ ("https://www.vouge.com/", Interest::Fashion),
+ ("https://slashdot.org/", Interest::Tech),
+ ("https://www.nascar.com/", Interest::Autos),
+ ("https://www.nascar.com/", Interest::Sports),
+];
+
+fn main() {
+ let mut interest_map: HashMap<Interest, Vec<UrlHash>> =
+ HashMap::from_iter(Interest::all().into_iter().map(|i| (i, vec![])));
+ for (url, interest) in TEST_INTEREST_DATA {
+ if let Some(hash) = hash_url(url) {
+ interest_map.get_mut(interest).unwrap().push(hash)
+ }
+ }
+
+ let mut f = File::create("test-data").expect("Error opening file");
+ // Loop over all possible interests
+ for interest in Interest::all() {
+ // Get the list of URL hashes for that interest
+ let hashes = interest_map.get(&interest).unwrap();
+ // Write the count
+ f.write_all(&(hashes.len() as u32).to_le_bytes())
+ .expect("Error writing file");
+ // Write the hashes
+ for hash in hashes {
+ f.write_all(hash).expect("Error writing file");
+ }
+ }
+}
diff --git a/third_party/rust/relevancy/src/db.rs b/third_party/rust/relevancy/src/db.rs
new file mode 100644
index 0000000000..08684c45af
--- /dev/null
+++ b/third_party/rust/relevancy/src/db.rs
@@ -0,0 +1,118 @@
+/* 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/.
+ */
+
+use crate::{
+ schema::RelevancyConnectionInitializer,
+ url_hash::{hash_url, UrlHash},
+ Interest, InterestVector, Result,
+};
+use parking_lot::Mutex;
+use rusqlite::{Connection, OpenFlags};
+use sql_support::{open_database::open_database_with_flags, ConnExt};
+use std::path::Path;
+
+/// A thread-safe wrapper around an SQLite connection to the Relevancy database
+pub struct RelevancyDb {
+ pub conn: Mutex<Connection>,
+}
+
+impl RelevancyDb {
+ pub fn open(path: impl AsRef<Path>) -> Result<Self> {
+ let conn = open_database_with_flags(
+ path,
+ OpenFlags::SQLITE_OPEN_URI
+ | OpenFlags::SQLITE_OPEN_NO_MUTEX
+ | OpenFlags::SQLITE_OPEN_CREATE
+ | OpenFlags::SQLITE_OPEN_READ_WRITE,
+ &RelevancyConnectionInitializer,
+ )?;
+ Ok(Self {
+ conn: Mutex::new(conn),
+ })
+ }
+
+ #[cfg(test)]
+ pub fn open_for_test() -> Self {
+ use std::sync::atomic::{AtomicU32, Ordering};
+ static COUNTER: AtomicU32 = AtomicU32::new(0);
+ let count = COUNTER.fetch_add(1, Ordering::Relaxed);
+ Self::open(format!("file:test{count}.sqlite?mode=memory&cache=shared")).unwrap()
+ }
+
+ /// Accesses the Suggest database in a transaction for reading.
+ pub fn read<T>(&self, op: impl FnOnce(&RelevancyDao) -> Result<T>) -> Result<T> {
+ let mut conn = self.conn.lock();
+ let tx = conn.transaction()?;
+ let dao = RelevancyDao::new(&tx);
+ op(&dao)
+ }
+
+ /// Accesses the Suggest database in a transaction for reading and writing.
+ pub fn read_write<T>(&self, op: impl FnOnce(&mut RelevancyDao) -> Result<T>) -> Result<T> {
+ let mut conn = self.conn.lock();
+ let tx = conn.transaction()?;
+ let mut dao = RelevancyDao::new(&tx);
+ let result = op(&mut dao)?;
+ tx.commit()?;
+ Ok(result)
+ }
+}
+
+/// A data access object (DAO) that wraps a connection to the Relevancy database
+///
+/// Methods that only read from the database take an immutable reference to
+/// `self` (`&self`), and methods that write to the database take a mutable
+/// reference (`&mut self`).
+pub struct RelevancyDao<'a> {
+ pub conn: &'a Connection,
+}
+
+impl<'a> RelevancyDao<'a> {
+ fn new(conn: &'a Connection) -> Self {
+ Self { conn }
+ }
+
+ /// Associate a URL with an interest
+ pub fn add_url_interest(&mut self, url_hash: UrlHash, interest: Interest) -> Result<()> {
+ let sql = "
+ INSERT OR REPLACE INTO url_interest(url_hash, interest_code)
+ VALUES (?, ?)
+ ";
+ self.conn.execute(sql, (url_hash, interest as u32))?;
+ Ok(())
+ }
+
+ /// Get an interest vector for a URL
+ pub fn get_url_interest_vector(&self, url: &str) -> Result<InterestVector> {
+ let hash = match hash_url(url) {
+ Some(u) => u,
+ None => return Ok(InterestVector::default()),
+ };
+ let mut stmt = self.conn.prepare_cached(
+ "
+ SELECT interest_code
+ FROM url_interest
+ WHERE url_hash=?
+ ",
+ )?;
+ let interests = stmt.query_and_then((hash,), |row| -> Result<Interest> {
+ Ok(row.get::<_, u32>(0)?.into())
+ })?;
+
+ let mut interest_vec = InterestVector::default();
+ for interest in interests {
+ interest_vec[interest?] += 1
+ }
+ Ok(interest_vec)
+ }
+
+ /// Do we need to load the interest data?
+ pub fn need_to_load_url_interests(&self) -> Result<bool> {
+ // TODO: we probably will need a better check than this.
+ Ok(self
+ .conn
+ .query_one("SELECT NOT EXISTS (SELECT 1 FROM url_interest)")?)
+ }
+}
diff --git a/third_party/rust/relevancy/src/error.rs b/third_party/rust/relevancy/src/error.rs
new file mode 100644
index 0000000000..93ca7aabaa
--- /dev/null
+++ b/third_party/rust/relevancy/src/error.rs
@@ -0,0 +1,44 @@
+/* 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/.
+*/
+
+use error_support::{ErrorHandling, GetErrorHandling};
+
+/// Errors we return via the public interface.
+#[derive(Debug, thiserror::Error)]
+pub enum RelevancyApiError {
+ #[error("Unexpected Error: {reason}")]
+ Unexpected { reason: String },
+}
+
+/// Errors we use internally
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("Error opening database: {0}")]
+ OpenDatabase(#[from] sql_support::open_database::Error),
+
+ #[error("Sql error: {0}")]
+ SqlError(#[from] rusqlite::Error),
+
+ #[error("Error fetching interest data")]
+ FetchInterestDataError,
+}
+
+/// Result enum for the public API
+pub type ApiResult<T> = std::result::Result<T, RelevancyApiError>;
+
+/// Result enum for internal functions
+pub type Result<T> = std::result::Result<T, Error>;
+
+// Define how our internal errors are handled and converted to external errors
+// See `support/error/README.md` for how this works, especially the warning about PII.
+impl GetErrorHandling for Error {
+ type ExternalError = RelevancyApiError;
+
+ fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
+ ErrorHandling::convert(RelevancyApiError::Unexpected {
+ reason: self.to_string(),
+ })
+ }
+}
diff --git a/third_party/rust/relevancy/src/interest.rs b/third_party/rust/relevancy/src/interest.rs
new file mode 100644
index 0000000000..0573c743fc
--- /dev/null
+++ b/third_party/rust/relevancy/src/interest.rs
@@ -0,0 +1,167 @@
+/* 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/. */
+
+/// List of possible interests for a domain. Domains can have be associated with one or multiple
+/// interests. `Inconclusive` is used for domains in the user's top sites that we can't classify
+/// because there's no corresponding entry in the interest database.
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
+#[repr(u32)]
+pub enum Interest {
+ Animals,
+ Arts,
+ Autos,
+ Business,
+ Career,
+ Education,
+ Fashion,
+ Finance,
+ Food,
+ Government,
+ Health,
+ Hobbies,
+ Home,
+ News,
+ RealEstate,
+ Society,
+ Sports,
+ Tech,
+ Travel,
+ Inconclusive,
+}
+
+impl From<Interest> for u32 {
+ fn from(interest: Interest) -> Self {
+ interest as u32
+ }
+}
+
+impl From<Interest> for usize {
+ fn from(interest: Interest) -> Self {
+ interest as usize
+ }
+}
+
+impl From<u32> for Interest {
+ fn from(code: u32) -> Self {
+ if code as usize > Self::COUNT {
+ panic!("Invalid interest code: {code}")
+ }
+ // Safety: This is safe since Interest has a u32 representation and we've done a bounds
+ // check
+ unsafe { std::mem::transmute(code) }
+ }
+}
+
+impl Interest {
+ const COUNT: usize = 20;
+
+ pub fn all() -> [Interest; Self::COUNT] {
+ [
+ Self::Animals,
+ Self::Arts,
+ Self::Autos,
+ Self::Business,
+ Self::Career,
+ Self::Education,
+ Self::Fashion,
+ Self::Finance,
+ Self::Food,
+ Self::Government,
+ Self::Health,
+ Self::Hobbies,
+ Self::Home,
+ Self::News,
+ Self::RealEstate,
+ Self::Society,
+ Self::Sports,
+ Self::Tech,
+ Self::Travel,
+ Self::Inconclusive,
+ ]
+ }
+}
+
+/// Vector storing a count value for each interest
+///
+/// Here "vector" refers to the mathematical object, not a Rust `Vec`. It always has a fixed
+/// number of elements.
+#[derive(Debug, Default, PartialEq, Eq)]
+pub struct InterestVector {
+ pub animals: u32,
+ pub arts: u32,
+ pub autos: u32,
+ pub business: u32,
+ pub career: u32,
+ pub education: u32,
+ pub fashion: u32,
+ pub finance: u32,
+ pub food: u32,
+ pub government: u32,
+ pub health: u32,
+ pub hobbies: u32,
+ pub home: u32,
+ pub news: u32,
+ pub real_estate: u32,
+ pub society: u32,
+ pub sports: u32,
+ pub tech: u32,
+ pub travel: u32,
+ pub inconclusive: u32,
+}
+
+impl std::ops::Index<Interest> for InterestVector {
+ type Output = u32;
+
+ fn index(&self, index: Interest) -> &u32 {
+ match index {
+ Interest::Animals => &self.animals,
+ Interest::Arts => &self.arts,
+ Interest::Autos => &self.autos,
+ Interest::Business => &self.business,
+ Interest::Career => &self.career,
+ Interest::Education => &self.education,
+ Interest::Fashion => &self.fashion,
+ Interest::Finance => &self.finance,
+ Interest::Food => &self.food,
+ Interest::Government => &self.government,
+ Interest::Health => &self.health,
+ Interest::Hobbies => &self.hobbies,
+ Interest::Home => &self.home,
+ Interest::News => &self.news,
+ Interest::RealEstate => &self.real_estate,
+ Interest::Society => &self.society,
+ Interest::Sports => &self.sports,
+ Interest::Tech => &self.tech,
+ Interest::Travel => &self.travel,
+ Interest::Inconclusive => &self.inconclusive,
+ }
+ }
+}
+
+impl std::ops::IndexMut<Interest> for InterestVector {
+ fn index_mut(&mut self, index: Interest) -> &mut u32 {
+ match index {
+ Interest::Animals => &mut self.animals,
+ Interest::Arts => &mut self.arts,
+ Interest::Autos => &mut self.autos,
+ Interest::Business => &mut self.business,
+ Interest::Career => &mut self.career,
+ Interest::Education => &mut self.education,
+ Interest::Fashion => &mut self.fashion,
+ Interest::Finance => &mut self.finance,
+ Interest::Food => &mut self.food,
+ Interest::Government => &mut self.government,
+ Interest::Health => &mut self.health,
+ Interest::Hobbies => &mut self.hobbies,
+ Interest::Home => &mut self.home,
+ Interest::News => &mut self.news,
+ Interest::RealEstate => &mut self.real_estate,
+ Interest::Society => &mut self.society,
+ Interest::Sports => &mut self.sports,
+ Interest::Tech => &mut self.tech,
+ Interest::Travel => &mut self.travel,
+ Interest::Inconclusive => &mut self.inconclusive,
+ }
+ }
+}
diff --git a/third_party/rust/relevancy/src/lib.rs b/third_party/rust/relevancy/src/lib.rs
new file mode 100644
index 0000000000..157a26277e
--- /dev/null
+++ b/third_party/rust/relevancy/src/lib.rs
@@ -0,0 +1,81 @@
+/* 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/. */
+
+//! Proposed API for the relevancy component (validation phase)
+//!
+//! The goal here is to allow us to validate that we can reliably detect user interests from
+//! history data, without spending too much time building the API out. There's some hand-waving
+//! towards how we would use this data to rank search results, but we don't need to come to a final
+//! decision on that yet.
+
+mod db;
+mod error;
+mod interest;
+mod populate_interests;
+mod schema;
+pub mod url_hash;
+
+pub use db::RelevancyDb;
+pub use error::{ApiResult, Error, RelevancyApiError, Result};
+pub use interest::{Interest, InterestVector};
+
+use error_support::handle_error;
+
+pub struct RelevancyStore {
+ db: RelevancyDb,
+}
+
+/// Top-level API for the Relevancy component
+impl RelevancyStore {
+ #[handle_error(Error)]
+ pub fn new(db_path: String) -> ApiResult<Self> {
+ Ok(Self {
+ db: RelevancyDb::open(db_path)?,
+ })
+ }
+
+ /// Ingest top URLs to build the user's interest vector.
+ ///
+ /// Consumer should pass a list of the user's top URLs by frecency to this method. It will
+ /// then:
+ ///
+ /// - Download the URL interest data from remote settings. Eventually this should be cached /
+ /// stored in the database, but for now it would be fine to download fresh data each time.
+ /// - Match the user's top URls against the interest data to build up their interest vector.
+ /// - Store the user's interest vector in the database.
+ ///
+ /// This method may execute for a long time and should only be called from a worker thread.
+ #[handle_error(Error)]
+ pub fn ingest(&self, _top_urls_by_frecency: Vec<String>) -> ApiResult<()> {
+ populate_interests::ensure_interest_data_populated(&self.db)?;
+ todo!()
+ }
+
+ /// Calculate metrics for the validation phase
+ ///
+ /// This runs after [Self::ingest]. It takes the interest vector that ingest created and
+ /// calculates a set of metrics that we can report to glean.
+ #[handle_error(Error)]
+ pub fn calculate_metrics(&self) -> ApiResult<InterestMetrics> {
+ todo!()
+ }
+
+ /// Get the user's interest vector directly.
+ ///
+ /// This runs after [Self::ingest]. It returns the interest vector directly so that the
+ /// consumer can show it in an `about:` page.
+ #[handle_error(Error)]
+ pub fn user_interest_vector(&self) -> ApiResult<InterestVector> {
+ todo!()
+ }
+}
+
+/// Interest metric data. See `relevancy.udl` for details.
+pub struct InterestMetrics {
+ pub top_single_interest_similarity: u32,
+ pub top_2interest_similarity: u32,
+ pub top_3interest_similarity: u32,
+}
+
+uniffi::include_scaffolding!("relevancy");
diff --git a/third_party/rust/relevancy/src/populate_interests.rs b/third_party/rust/relevancy/src/populate_interests.rs
new file mode 100644
index 0000000000..e33b677dd6
--- /dev/null
+++ b/third_party/rust/relevancy/src/populate_interests.rs
@@ -0,0 +1,157 @@
+/* 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/. */
+
+use crate::{url_hash::UrlHash, Error, Interest, RelevancyDb, Result};
+use std::io::{Cursor, Read};
+
+pub fn ensure_interest_data_populated(db: &RelevancyDb) -> Result<()> {
+ if !db.read(|dao| dao.need_to_load_url_interests())? {
+ return Ok(());
+ }
+ let interest_data = match fetch_interest_data() {
+ Ok(data) => data,
+ Err(e) => {
+ log::warn!("error fetching interest data: {e}");
+ return Err(Error::FetchInterestDataError);
+ }
+ };
+ db.read_write(move |dao| {
+ for (url_hash, interest) in interest_data {
+ dao.add_url_interest(url_hash, interest)?;
+ }
+ Ok(())
+ })
+}
+
+/// Fetch the interest data
+fn fetch_interest_data() -> std::io::Result<Vec<(UrlHash, Interest)>> {
+ // TODO: this hack should be replaced with something that fetches from remote settings
+ let bytes = include_bytes!("../test-data");
+ let mut reader = Cursor::new(&bytes);
+ let mut data = vec![];
+
+ // Loop over all possible interests
+ for interest in Interest::all() {
+ // read the count
+ let mut buf = [0u8; 4];
+ reader.read_exact(&mut buf)?;
+ let count = u32::from_le_bytes(buf);
+ for _ in 0..count {
+ let mut url_hash: UrlHash = [0u8; 16];
+ reader.read_exact(&mut url_hash)?;
+ data.push((url_hash, interest));
+ }
+ }
+ Ok(data)
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::InterestVector;
+
+ #[test]
+ fn test_interest_vectors() {
+ let db = RelevancyDb::open_for_test();
+ ensure_interest_data_populated(&db).unwrap();
+ db.read(|dao| {
+ // Test that the interest data matches the values we started from in
+ // `bin/generate-test-data.rs`
+ assert_eq!(
+ dao.get_url_interest_vector("https://espn.com/").unwrap(),
+ InterestVector {
+ sports: 1,
+ ..InterestVector::default()
+ }
+ );
+ assert_eq!(
+ dao.get_url_interest_vector("https://dogs.com/").unwrap(),
+ InterestVector {
+ animals: 1,
+ ..InterestVector::default()
+ }
+ );
+ assert_eq!(
+ dao.get_url_interest_vector("https://cars.com/").unwrap(),
+ InterestVector {
+ autos: 1,
+ ..InterestVector::default()
+ }
+ );
+ assert_eq!(
+ dao.get_url_interest_vector("https://www.vouge.com/")
+ .unwrap(),
+ InterestVector {
+ fashion: 1,
+ ..InterestVector::default()
+ }
+ );
+ assert_eq!(
+ dao.get_url_interest_vector("https://slashdot.org/")
+ .unwrap(),
+ InterestVector {
+ tech: 1,
+ ..InterestVector::default()
+ }
+ );
+ assert_eq!(
+ dao.get_url_interest_vector("https://www.nascar.com/")
+ .unwrap(),
+ InterestVector {
+ autos: 1,
+ sports: 1,
+ ..InterestVector::default()
+ }
+ );
+ assert_eq!(
+ dao.get_url_interest_vector("https://unknown.url/").unwrap(),
+ InterestVector::default()
+ );
+ Ok(())
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn test_variations_on_the_url() {
+ let db = RelevancyDb::open_for_test();
+ ensure_interest_data_populated(&db).unwrap();
+ db.read(|dao| {
+ // Different paths/queries should work
+ assert_eq!(
+ dao.get_url_interest_vector("https://espn.com/foo/bar/?baz")
+ .unwrap(),
+ InterestVector {
+ sports: 1,
+ ..InterestVector::default()
+ }
+ );
+ // Different schemes should too
+ assert_eq!(
+ dao.get_url_interest_vector("http://espn.com/").unwrap(),
+ InterestVector {
+ sports: 1,
+ ..InterestVector::default()
+ }
+ );
+ // But changes to the domain shouldn't
+ assert_eq!(
+ dao.get_url_interest_vector("http://www.espn.com/").unwrap(),
+ InterestVector::default()
+ );
+ // However, extra components past the 3rd one in the domain are ignored
+ assert_eq!(
+ dao.get_url_interest_vector("https://foo.www.nascar.com/")
+ .unwrap(),
+ InterestVector {
+ autos: 1,
+ sports: 1,
+ ..InterestVector::default()
+ }
+ );
+ Ok(())
+ })
+ .unwrap();
+ }
+}
diff --git a/third_party/rust/relevancy/src/relevancy.udl b/third_party/rust/relevancy/src/relevancy.udl
new file mode 100644
index 0000000000..e07243ec28
--- /dev/null
+++ b/third_party/rust/relevancy/src/relevancy.udl
@@ -0,0 +1,106 @@
+namespace relevancy { };
+
+[Error]
+interface RelevancyApiError {
+ Unexpected(string reason);
+};
+
+// Top-level class for the Relevancy component
+interface RelevancyStore {
+ // Construct a new RelevancyStore
+ [Throws=RelevancyApiError]
+ constructor(string dbpath);
+
+ // Ingest the top URLs by frequency to build up the user's interest vector
+ [Throws=RelevancyApiError]
+ void ingest(sequence<string> top_urls);
+
+ // Calculate metrics for the user's interest vector in order to measure how strongly we're
+ // identifying interests. See the `InterestMetrics` struct for details.
+ [Throws=RelevancyApiError]
+ InterestMetrics calculate_metrics();
+
+ // Get the interest vector for the user.
+ //
+ // This is intended to be show to the user in an `about:` page so that users can judge if it
+ // feels correct.
+ [Throws=RelevancyApiError]
+ InterestVector user_interest_vector();
+};
+
+enum Interest {
+ "Animals",
+ "Arts",
+ "Autos",
+ "Business",
+ "Career",
+ "Education",
+ "Fashion",
+ "Finance",
+ "Food",
+ "Government",
+ "Health",
+ "Hobbies",
+ "Home",
+ "News",
+ "RealEstate",
+ "Society",
+ "Sports",
+ "Tech",
+ "Travel",
+ "Inconclusive",
+};
+
+// Interest metrics that we want to send to Glean as part of the validation process. These contain
+// the cosine similarity when comparing the user's interest against various interest vectors that
+// consumers may use.
+//
+// Cosine similary was chosen because it seems easy to calculate. This was then matched against
+// some semi-plausible real-world interest vectors that consumers might use. This is all up for
+// debate and we may decide to switch to some other metrics.
+//
+// Similarity values are transformed to integers by multiplying the floating point value by 1000 and
+// rounding. This is to make them compatible with Glean's distribution metrics.
+dictionary InterestMetrics {
+ // Similarity between the user's interest vector and an interest vector where the element for
+ // the user's top interest is copied, but all other interests are set to zero. This measures
+ // the highest possible similarity with consumers that used interest vectors with a single
+ // interest set.
+ u32 top_single_interest_similarity;
+
+ // The same as before, but the top 2 interests are copied. This measures the highest possible
+ // similarity with consumers that used interest vectors with a two interests (note: this means
+ // they would need to choose the user's top two interests and have the exact same proportion
+ // between them as the user).
+ u32 top_2interest_similarity;
+
+ // The same as before, but the top 3 interests are copied.
+ u32 top_3interest_similarity;
+};
+
+// Vector storing a count value for each interest
+//
+// Here "vector" refers to the mathematical object, not a Rust `Vec`. It always has a fixed
+// number of elements.
+dictionary InterestVector {
+ u32 animals;
+ u32 arts;
+ u32 autos;
+ u32 business;
+ u32 career;
+ u32 education;
+ u32 fashion;
+ u32 finance;
+ u32 food;
+ u32 government;
+ u32 health;
+ u32 hobbies;
+ u32 home;
+ u32 news;
+ u32 real_estate;
+ u32 society;
+ u32 sports;
+ u32 tech;
+ u32 travel;
+ u32 inconclusive;
+};
diff --git a/third_party/rust/relevancy/src/schema.rs b/third_party/rust/relevancy/src/schema.rs
new file mode 100644
index 0000000000..bcb2f260d9
--- /dev/null
+++ b/third_party/rust/relevancy/src/schema.rs
@@ -0,0 +1,53 @@
+/* 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/.
+ */
+
+use rusqlite::{Connection, Transaction};
+use sql_support::open_database::{self, ConnectionInitializer};
+
+/// The current database schema version.
+///
+/// For any changes to the schema [`SQL`], please make sure to:
+///
+/// 1. Bump this version.
+/// 2. Add a migration from the old version to the new version in
+/// [`RelevancyConnectionInitializer::upgrade_from`].
+pub const VERSION: u32 = 13;
+
+/// The current database schema.
+pub const SQL: &str = "
+ CREATE TABLE url_interest(
+ url_hash BLOB NOT NULL,
+ interest_code INTEGER NOT NULL,
+ PRIMARY KEY (url_hash, interest_code)
+ ) WITHOUT ROWID;
+";
+
+/// Initializes an SQLite connection to the Relevancy database, performing
+/// migrations as needed.
+pub struct RelevancyConnectionInitializer;
+
+impl ConnectionInitializer for RelevancyConnectionInitializer {
+ const NAME: &'static str = "relevancy db";
+ const END_VERSION: u32 = VERSION;
+
+ fn prepare(&self, conn: &Connection, _db_empty: bool) -> open_database::Result<()> {
+ let initial_pragmas = "
+ -- Use in-memory storage for TEMP tables.
+ PRAGMA temp_store = 2;
+ PRAGMA journal_mode = WAL;
+ PRAGMA foreign_keys = ON;
+ ";
+ conn.execute_batch(initial_pragmas)?;
+ Ok(())
+ }
+
+ fn init(&self, db: &Transaction<'_>) -> open_database::Result<()> {
+ Ok(db.execute_batch(SQL)?)
+ }
+
+ fn upgrade_from(&self, _db: &Transaction<'_>, version: u32) -> open_database::Result<()> {
+ Err(open_database::Error::IncompatibleVersion(version))
+ }
+}
diff --git a/third_party/rust/relevancy/src/url_hash.rs b/third_party/rust/relevancy/src/url_hash.rs
new file mode 100644
index 0000000000..d31a45d06b
--- /dev/null
+++ b/third_party/rust/relevancy/src/url_hash.rs
@@ -0,0 +1,63 @@
+/* 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/. */
+
+use md5::{Digest, Md5};
+use url::{Host, Url};
+
+pub type UrlHash = [u8; 16];
+
+/// Given a URL, extract the part of it that we want to use to identify it.
+///
+/// We currently use the final 3 components of the URL domain.
+///
+/// TODO: decide if this should be 3 or 3 components.
+pub fn url_hash_source(url: &str) -> Option<String> {
+ let url = Url::parse(url).ok()?;
+ let domain = match url.host() {
+ Some(Host::Domain(d)) => d,
+ _ => return None,
+ };
+ // This will store indexes of `.` chars as we search backwards.
+ let mut pos = domain.len();
+ for _ in 0..3 {
+ match domain[0..pos].rfind('.') {
+ Some(p) => pos = p,
+ // The domain has less than 3 dots, return it all
+ None => return Some(domain.to_owned()),
+ }
+ }
+ Some(domain[pos + 1..].to_owned())
+}
+
+pub fn hash_url(url: &str) -> Option<UrlHash> {
+ url_hash_source(url).map(|hash_source| {
+ let mut hasher = Md5::new();
+ hasher.update(hash_source);
+ let result = hasher.finalize();
+ result.into()
+ })
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_url_hash_source() {
+ let table = [
+ ("http://example.com/some-path", Some("example.com")),
+ ("http://foo.example.com/some-path", Some("foo.example.com")),
+ (
+ "http://foo.bar.baz.example.com/some-path",
+ Some("baz.example.com"),
+ ),
+ ("http://foo.com.uk/some-path", Some("foo.com.uk")),
+ ("http://amazon.com/some-path", Some("amazon.com")),
+ ("http://192.168.0.1/some-path", None),
+ ];
+ for (url, expected) in table {
+ assert_eq!(url_hash_source(url).as_deref(), expected)
+ }
+ }
+}
diff --git a/third_party/rust/relevancy/test-data b/third_party/rust/relevancy/test-data
new file mode 100644
index 0000000000..c645914143
--- /dev/null
+++ b/third_party/rust/relevancy/test-data
Binary files differ
diff --git a/third_party/rust/remote_settings/.cargo-checksum.json b/third_party/rust/remote_settings/.cargo-checksum.json
index 8a2b63f314..619a9d13ca 100644
--- a/third_party/rust/remote_settings/.cargo-checksum.json
+++ b/third_party/rust/remote_settings/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"4fa89b0606fe8ec8ac8c479b8b9adf33d0c936b09fa5af108ded74139ace37fb","build.rs":"4326f03729cf8f1673e4228e6dc111de1ea4d8bcc06351f7ae563efb2613f866","src/client.rs":"fb3f2cd47460e5ae07a5e8d61b358d588d14075bd9dd6b6e818e1af74abd5dba","src/config.rs":"7bb678addfae3b4ed5f2892d32263e5b33cc05e5a12a250f664150e78211f94a","src/error.rs":"192ca42af7c6b882f3129378c23b45dab8a0d2b179e23a8813a335ffd56b21dc","src/lib.rs":"416e99894e152f6cea7418ad2fabfd94bc3d907efd9f33fbd2a83fb99452b2df","src/remote_settings.udl":"2e71491ad3894d17e5bde0663d9490bfea6294d99cdbe9d67a36137faeedc593","uniffi.toml":"f8ec8dc593e0d501c2e9e40368ec93ec33b1edd8608e29495e0a54b63144e880"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"1029f571c66d33c4dfc5e9fc55287a780329ce183f5d2b672de79737155c4227","build.rs":"4326f03729cf8f1673e4228e6dc111de1ea4d8bcc06351f7ae563efb2613f866","src/client.rs":"7510ae0d5bcb9fbaa2c43c4773aa0fd518edc78fe0f396c0e1d6dd442446f429","src/config.rs":"7bb678addfae3b4ed5f2892d32263e5b33cc05e5a12a250f664150e78211f94a","src/error.rs":"192ca42af7c6b882f3129378c23b45dab8a0d2b179e23a8813a335ffd56b21dc","src/lib.rs":"416e99894e152f6cea7418ad2fabfd94bc3d907efd9f33fbd2a83fb99452b2df","src/remote_settings.udl":"2e71491ad3894d17e5bde0663d9490bfea6294d99cdbe9d67a36137faeedc593","uniffi.toml":"f8ec8dc593e0d501c2e9e40368ec93ec33b1edd8608e29495e0a54b63144e880"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/remote_settings/Cargo.toml b/third_party/rust/remote_settings/Cargo.toml
index b04e6ed6c6..71d72079b7 100644
--- a/third_party/rust/remote_settings/Cargo.toml
+++ b/third_party/rust/remote_settings/Cargo.toml
@@ -28,7 +28,7 @@ license = "MPL-2.0"
parking_lot = "0.12"
serde_json = "1"
thiserror = "1.0"
-uniffi = "0.25.2"
+uniffi = "0.27.1"
url = "2.1"
[dependencies.serde]
@@ -46,5 +46,5 @@ mockito = "0.31"
path = "../support/viaduct-reqwest"
[build-dependencies.uniffi]
-version = "0.25.2"
+version = "0.27.1"
features = ["build"]
diff --git a/third_party/rust/remote_settings/src/client.rs b/third_party/rust/remote_settings/src/client.rs
index 0d99de9cc1..2cf26b7319 100644
--- a/third_party/rust/remote_settings/src/client.rs
+++ b/third_party/rust/remote_settings/src/client.rs
@@ -64,7 +64,7 @@ impl Client {
/// collection defined by the [ClientConfig] used to generate this [Client].
pub fn get_records_since(&self, timestamp: u64) -> Result<RemoteSettingsResponse> {
self.get_records_with_options(
- GetItemsOptions::new().gt("last_modified", timestamp.to_string()),
+ GetItemsOptions::new().filter_gt("last_modified", timestamp.to_string()),
)
}
@@ -307,7 +307,7 @@ struct AttachmentsCapability {
}
/// Options for requests to endpoints that return multiple items.
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct GetItemsOptions {
filters: Vec<Filter>,
sort: Vec<Sort>,
@@ -328,14 +328,14 @@ impl GetItemsOptions {
/// `author.name`. `value` can be a bare number or string (like
/// `2` or `Ben`), or a stringified JSON value (`"2.0"`, `[1, 2]`,
/// `{"checked": true}`).
- pub fn eq(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+ pub fn filter_eq(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.filters.push(Filter::Eq(field.into(), value.into()));
self
}
/// Sets an option to only return items whose `field` is not equal to the
/// given `value`.
- pub fn not(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+ pub fn filter_not(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.filters.push(Filter::Not(field.into(), value.into()));
self
}
@@ -343,7 +343,11 @@ impl GetItemsOptions {
/// Sets an option to only return items whose `field` is an array that
/// contains the given `value`. If `value` is a stringified JSON array, the
/// field must contain all its elements.
- pub fn contains(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+ pub fn filter_contains(
+ &mut self,
+ field: impl Into<String>,
+ value: impl Into<String>,
+ ) -> &mut Self {
self.filters
.push(Filter::Contains(field.into(), value.into()));
self
@@ -351,47 +355,47 @@ impl GetItemsOptions {
/// Sets an option to only return items whose `field` is strictly less
/// than the given `value`.
- pub fn lt(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+ pub fn filter_lt(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.filters.push(Filter::Lt(field.into(), value.into()));
self
}
/// Sets an option to only return items whose `field` is strictly greater
/// than the given `value`.
- pub fn gt(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+ pub fn filter_gt(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.filters.push(Filter::Gt(field.into(), value.into()));
self
}
/// Sets an option to only return items whose `field` is less than or equal
/// to the given `value`.
- pub fn max(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+ pub fn filter_max(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.filters.push(Filter::Max(field.into(), value.into()));
self
}
/// Sets an option to only return items whose `field` is greater than or
/// equal to the given `value`.
- pub fn min(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+ pub fn filter_min(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.filters.push(Filter::Min(field.into(), value.into()));
self
}
/// Sets an option to only return items whose `field` is a string that
/// contains the substring `value`. `value` can contain `*` wildcards.
- pub fn like(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+ pub fn filter_like(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
self.filters.push(Filter::Like(field.into(), value.into()));
self
}
/// Sets an option to only return items that have the given `field`.
- pub fn has(&mut self, field: impl Into<String>) -> &mut Self {
+ pub fn filter_has(&mut self, field: impl Into<String>) -> &mut Self {
self.filters.push(Filter::Has(field.into()));
self
}
/// Sets an option to only return items that do not have the given `field`.
- pub fn has_not(&mut self, field: impl Into<String>) -> &mut Self {
+ pub fn filter_has_not(&mut self, field: impl Into<String>) -> &mut Self {
self.filters.push(Filter::HasNot(field.into()));
self
}
@@ -454,7 +458,7 @@ impl GetItemsOptions {
}
/// The order in which to return items.
-#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum SortOrder {
/// Smaller values first.
Ascending,
@@ -462,7 +466,7 @@ pub enum SortOrder {
Descending,
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
enum Filter {
Eq(String, String),
Not(String, String),
@@ -495,7 +499,7 @@ impl Filter {
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct Sort(String, SortOrder);
impl Sort {
@@ -692,16 +696,16 @@ mod test {
.field("a")
.field("c")
.field("b")
- .eq("a", "b")
- .lt("c.d", "5")
- .gt("e", "15")
- .max("f", "20")
- .min("g", "10")
- .not("h", "i")
- .like("j", "*k*")
- .has("l")
- .has_not("m")
- .contains("n", "o")
+ .filter_eq("a", "b")
+ .filter_lt("c.d", "5")
+ .filter_gt("e", "15")
+ .filter_max("f", "20")
+ .filter_min("g", "10")
+ .filter_not("h", "i")
+ .filter_like("j", "*k*")
+ .filter_has("l")
+ .filter_has_not("m")
+ .filter_contains("n", "o")
.sort("b", SortOrder::Descending)
.sort("a", SortOrder::Ascending)
.limit(3);
diff --git a/third_party/rust/rure/src/lib.rs b/third_party/rust/rure/src/lib.rs
index aa03c60aff..1c1eef9251 100644
--- a/third_party/rust/rure/src/lib.rs
+++ b/third_party/rust/rure/src/lib.rs
@@ -1,3 +1,7 @@
+// This library is patched, this considered first-party. Ignore warnings
+// as if it were third-party.
+#![allow(warnings)]
+
#[macro_use]
mod macros;
mod error;
diff --git a/third_party/rust/scroll/.cargo-checksum.json b/third_party/rust/scroll/.cargo-checksum.json
index 406f97faa2..4cdf2841c7 100644
--- a/third_party/rust/scroll/.cargo-checksum.json
+++ b/third_party/rust/scroll/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"CHANGELOG.md":"de2bbf4669561405d402322f4cc2604218d4986b73b75b41708b9505aebcb02c","Cargo.lock":"d6a215b7466d37e08551c56949e77be4ee488f989bdef3e507713c729bbda0e6","Cargo.toml":"c240c5768d23ea9611ef57308f08b8ee4372ede6c04f0783dc9fd1710e664c19","LICENSE":"6e24b7455f0b9afefdf4f3efd59a56ce76a3020c2dc4371937e281fc5e587fd7","README.md":"e4fe9aabcd87d85a5ec93241eeefc0d69aa0d98fbd67da2fe1849e4cbddac3ce","benches/bench.rs":"12ae02c383c91f1b0e11e9201eb8a9d44dadfb2b5987e7e71b0ef7c6589af1ca","examples/data_ctx.rs":"79684fc44d499d0b13a173184793837fbaba70d2f74f075e796eb37a1803ce3d","src/ctx.rs":"8f58672c5f3bc09b8f09c76f1d423431cbff786af75f5b39a0cef23b820d48c6","src/endian.rs":"5b717eb5ed0dc2b536779316b020df4e6489c05b13b4fd9b5f5e683aca1b2c28","src/error.rs":"a6a0ec9a6237d23febd608637c0e3926d147511e7983195366bc5a11f12d9093","src/greater.rs":"29d9736f9d35a0f92ca054c7a36878ade0a77b4e8ee27441c34cd81c6bdb68e6","src/leb128.rs":"e343f4e104ca6d8660a3dded30934b83bad4c04d8888ce2cbebfa562f5ac115d","src/lesser.rs":"d3028781977e60d67003512e45666935deab9a03c76a3ba9316a5dbdddf432eb","src/lib.rs":"49d02fa761bb2a771d1857ffd150aa4b6f55b4f03aee1a7a23d8181c76a55fd6","src/pread.rs":"64afdcf2c2785f1f23d065ec5e565d78569086dfd9ece0a3d2553b05aee5df9b","src/pwrite.rs":"05e3129ec666790a61f5b5f894ad863103e213eb798243cfe5f2cbb54d042ba1","tests/api.rs":"1bef345e020a6a4e590350ea4f6069c5836941656379e252bfbdaee6edbbc0de"},"package":"04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da"} \ No newline at end of file
+{"files":{"Cargo.toml":"c9242ab52e0b3ba02d9557c3ef2070bc173dfbef05870c3c4831937233de38c0","LICENSE":"6e24b7455f0b9afefdf4f3efd59a56ce76a3020c2dc4371937e281fc5e587fd7","README.md":"7a7f6695853fbc174e3b016d72a8ef0113e313c897269779c7c368f102ed0c23","src/ctx.rs":"9bd92f1038962a8034450b64818cc7b5eaebacde2a229eec5b9cda3ec99c5ae4","src/endian.rs":"e3e0fcb99d0f71f739b6f0ea466a5d3479ed9c90f29269adb1aa2d725ac12af4","src/error.rs":"d91d332a87bde35738cc5915279fc0fde65301fe86ef98ec36126e1de9fd0474","src/greater.rs":"29d9736f9d35a0f92ca054c7a36878ade0a77b4e8ee27441c34cd81c6bdb68e6","src/leb128.rs":"eb71761d708f78c785e6dbe8d385fd90317d08369d1c3ac57d142ca7c0e09e9e","src/lesser.rs":"16fa2c3a737c126b7ac40117c960bc025fb418abc99559c244e8a5ae4348c730","src/lib.rs":"e9a1b9b0ee06ba39de6925f4bc23cb847c8ec3831ca37280c3660dc6d1b28826","src/pread.rs":"80eb931ad7340bba7e1a03a7cbef62c93537bdf4703e467210957d07b82f6489","src/pwrite.rs":"5384d97a57a245e057bca70bd3a386c2942c89f6f7555bcad498b348ee555543"},"package":"6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6"} \ No newline at end of file
diff --git a/third_party/rust/scroll/CHANGELOG.md b/third_party/rust/scroll/CHANGELOG.md
deleted file mode 100644
index bae87ee590..0000000000
--- a/third_party/rust/scroll/CHANGELOG.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# Changelog
-All notable changes to this project will be documented in this file.
-
-Before 1.0, this project does not adhere to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
-
-## [0.10.0] - unreleased
-### Added
- - scroll is now 2018 compliant, thanks @lzutao: https://github.com/m4b/scroll/pull/49
- - scroll_derive now lives in scroll repo itself
-### Removed
- - BREAKING: removed units/size generics in SizeWith, thanks @willglynn: https://github.com/m4b/scroll/pull/45
-
-## [0.9.1] - 2018-9-22
-### Added
- - pread primitive references: https://github.com/m4b/scroll/pull/35
- - u128/i128 support: https://github.com/m4b/scroll/pull/32
- - CStr support: https://github.com/m4b/scroll/pull/30
diff --git a/third_party/rust/scroll/Cargo.lock b/third_party/rust/scroll/Cargo.lock
deleted file mode 100644
index baf29fe049..0000000000
--- a/third_party/rust/scroll/Cargo.lock
+++ /dev/null
@@ -1,205 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "autocfg"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
-
-[[package]]
-name = "byteorder"
-version = "1.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
-
-[[package]]
-name = "cfg-if"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-
-[[package]]
-name = "const_fn"
-version = "0.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
-
-[[package]]
-name = "crossbeam-channel"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
-dependencies = [
- "cfg-if",
- "crossbeam-utils",
-]
-
-[[package]]
-name = "crossbeam-deque"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
-dependencies = [
- "cfg-if",
- "crossbeam-epoch",
- "crossbeam-utils",
-]
-
-[[package]]
-name = "crossbeam-epoch"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
-dependencies = [
- "cfg-if",
- "const_fn",
- "crossbeam-utils",
- "lazy_static",
- "memoffset",
- "scopeguard",
-]
-
-[[package]]
-name = "crossbeam-utils"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
-dependencies = [
- "autocfg",
- "cfg-if",
- "lazy_static",
-]
-
-[[package]]
-name = "either"
-version = "1.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
-
-[[package]]
-name = "hermit-abi"
-version = "0.1.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "lazy_static"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-
-[[package]]
-name = "libc"
-version = "0.2.82"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
-
-[[package]]
-name = "memoffset"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "num_cpus"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
-dependencies = [
- "hermit-abi",
- "libc",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
-dependencies = [
- "unicode-xid",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "rayon"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
-dependencies = [
- "autocfg",
- "crossbeam-deque",
- "either",
- "rayon-core",
-]
-
-[[package]]
-name = "rayon-core"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
-dependencies = [
- "crossbeam-channel",
- "crossbeam-deque",
- "crossbeam-utils",
- "lazy_static",
- "num_cpus",
-]
-
-[[package]]
-name = "scopeguard"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
-
-[[package]]
-name = "scroll"
-version = "0.11.0"
-dependencies = [
- "byteorder",
- "rayon",
- "scroll_derive",
-]
-
-[[package]]
-name = "scroll_derive"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdbda6ac5cd1321e724fa9cee216f3a61885889b896f073b8f82322789c5250e"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "syn"
-version = "1.0.60"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
-
-[[package]]
-name = "unicode-xid"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
diff --git a/third_party/rust/scroll/Cargo.toml b/third_party/rust/scroll/Cargo.toml
index 548be72db9..70e9e8d1c3 100644
--- a/third_party/rust/scroll/Cargo.toml
+++ b/third_party/rust/scroll/Cargo.toml
@@ -11,26 +11,37 @@
[package]
edition = "2021"
+rust-version = "1.63"
name = "scroll"
-version = "0.11.0"
-authors = ["m4b <m4b.github.io@gmail.com>", "Ted Mielczarek <ted@mielczarek.org>"]
+version = "0.12.0"
+authors = [
+ "m4b <m4b.github.io@gmail.com>",
+ "Ted Mielczarek <ted@mielczarek.org>",
+]
+include = [
+ "src/**/*",
+ "Cargo.toml",
+ "LICENSE",
+ "README.md",
+]
description = "A suite of powerful, extensible, generic, endian-aware Read/Write traits for byte buffers"
documentation = "https://docs.rs/scroll"
readme = "README.md"
-keywords = ["bytes", "endian", "immutable", "pread", "pwrite"]
+keywords = [
+ "bytes",
+ "endian",
+ "immutable",
+ "pread",
+ "pwrite",
+]
license = "MIT"
repository = "https://github.com/m4b/scroll"
-resolver = "2"
+
[dependencies.scroll_derive]
-version = "0.11"
+version = "0.12"
optional = true
-[dev-dependencies.byteorder]
-version = "1"
-
-[dev-dependencies.rayon]
-version = "1"
[features]
default = ["std"]
-derive = ["scroll_derive"]
+derive = ["dep:scroll_derive"]
std = []
diff --git a/third_party/rust/scroll/README.md b/third_party/rust/scroll/README.md
index 717fe6a234..50dde54d7e 100644
--- a/third_party/rust/scroll/README.md
+++ b/third_party/rust/scroll/README.md
@@ -1,4 +1,13 @@
- [![Build Status](https://travis-ci.org/m4b/scroll.svg?branch=master)](https://travis-ci.org/m4b/scroll)
+[![Actions][actions-badge]][actions-url]
+[![crates.io version][crates-scroll-badge]][crates-scroll]
+
+<!-- Badges' links -->
+
+[actions-badge]: https://github.com/m4b/scroll/workflows/CI/badge.svg?branch=master
+[actions-url]: https://github.com/m4b/scroll/actions
+[crates-scroll-badge]: https://img.shields.io/crates/v/scroll.svg
+[crates-scroll]: https://crates.io/crates/scroll
+
## Scroll - cast some magic
```text
@@ -23,7 +32,7 @@ Add to your `Cargo.toml`
```toml, no_test
[dependencies]
-scroll = "0.10"
+scroll = "0.11"
```
### Overview
diff --git a/third_party/rust/scroll/benches/bench.rs b/third_party/rust/scroll/benches/bench.rs
deleted file mode 100644
index 0787dbe14b..0000000000
--- a/third_party/rust/scroll/benches/bench.rs
+++ /dev/null
@@ -1,157 +0,0 @@
-#![feature(test)]
-extern crate test;
-
-use scroll::{Cread, Pread, LE};
-use test::black_box;
-
-#[bench]
-fn bench_parallel_cread_with(b: &mut test::Bencher) {
- use rayon::prelude::*;
- let vec = vec![0u8; 1_000_000];
- let nums = vec![0usize; 500_000];
- b.iter(|| {
- let data = black_box(&vec[..]);
- nums.par_iter().for_each(|offset| {
- let _: u16 = black_box(data.cread_with(*offset, LE));
- });
- });
- b.bytes = vec.len() as u64;
-}
-
-#[bench]
-fn bench_cread_vec(b: &mut test::Bencher) {
- let vec = vec![0u8; 1_000_000];
- b.iter(|| {
- let data = black_box(&vec[..]);
- for val in data.chunks(2) {
- let _: u16 = black_box(val.cread_with(0, LE));
- }
- });
- b.bytes = vec.len() as u64;
-}
-
-#[bench]
-fn bench_cread(b: &mut test::Bencher) {
- const NITER: i32 = 100_000;
- b.iter(|| {
- for _ in 1..NITER {
- let data = black_box([1, 2]);
- let _: u16 = black_box(data.cread(0));
- }
- });
- b.bytes = 2 * NITER as u64;
-}
-
-#[bench]
-fn bench_pread_ctx_vec(b: &mut test::Bencher) {
- let vec = vec![0u8; 1_000_000];
- b.iter(|| {
- let data = black_box(&vec[..]);
- for val in data.chunks(2) {
- let _: Result<u16, _> = black_box(val.pread(0));
- }
- });
- b.bytes = vec.len() as u64;
-}
-
-#[bench]
-fn bench_pread_with_unwrap(b: &mut test::Bencher) {
- const NITER: i32 = 100_000;
- b.iter(|| {
- for _ in 1..NITER {
- let data: &[u8] = &black_box([1, 2]);
- let _: u16 = black_box(data.pread_with(0, LE).unwrap());
- }
- });
- b.bytes = 2 * NITER as u64;
-}
-
-#[bench]
-fn bench_pread_vec(b: &mut test::Bencher) {
- let vec = vec![0u8; 1_000_000];
- b.iter(|| {
- let data = black_box(&vec[..]);
- for val in data.chunks(2) {
- let _: Result<u16, _> = black_box(val.pread_with(0, LE));
- }
- });
- b.bytes = vec.len() as u64;
-}
-
-#[bench]
-fn bench_pread_unwrap(b: &mut test::Bencher) {
- const NITER: i32 = 100_000;
- b.iter(|| {
- for _ in 1..NITER {
- let data = black_box([1, 2]);
- let _: u16 = black_box(data.pread(0)).unwrap();
- }
- });
- b.bytes = 2 * NITER as u64;
-}
-
-#[bench]
-fn bench_gread_vec(b: &mut test::Bencher) {
- let vec = vec![0u8; 1_000_000];
- b.iter(|| {
- let data = black_box(&vec[..]);
- for val in data.chunks(2) {
- let mut offset = 0;
- let _: Result<u16, _> = black_box(val.gread(&mut offset));
- }
- });
- b.bytes = vec.len() as u64;
-}
-
-#[bench]
-fn bench_gread_unwrap(b: &mut test::Bencher) {
- const NITER: i32 = 100_000;
- b.iter(|| {
- for _ in 1..NITER {
- let data = black_box([1, 2]);
- let mut offset = 0;
- let _: u16 = black_box(data.gread_with(&mut offset, LE).unwrap());
- }
- });
- b.bytes = 2 * NITER as u64;
-}
-
-#[bench]
-fn bench_parallel_pread_with(b: &mut test::Bencher) {
- use rayon::prelude::*;
- let vec = vec![0u8; 1_000_000];
- let nums = vec![0usize; 500_000];
- b.iter(|| {
- let data = black_box(&vec[..]);
- nums.par_iter().for_each(|offset| {
- let _: Result<u16, _> = black_box(data.pread_with(*offset, LE));
- });
- });
- b.bytes = vec.len() as u64;
-}
-
-#[bench]
-fn bench_byteorder_vec(b: &mut test::Bencher) {
- use byteorder::ReadBytesExt;
- let vec = vec![0u8; 1_000_000];
- b.iter(|| {
- let data = black_box(&vec[..]);
- for mut val in data.chunks(2) {
- let _: Result<u16, _> = black_box(val.read_u16::<byteorder::LittleEndian>());
- }
- });
- b.bytes = vec.len() as u64;
-}
-
-#[bench]
-fn bench_byteorder(b: &mut test::Bencher) {
- use byteorder::ByteOrder;
- const NITER: i32 = 100_000;
- b.iter(|| {
- for _ in 1..NITER {
- let data = black_box([1, 2]);
- let _: u16 = black_box(byteorder::LittleEndian::read_u16(&data));
- }
- });
- b.bytes = 2 * NITER as u64;
-}
diff --git a/third_party/rust/scroll/examples/data_ctx.rs b/third_party/rust/scroll/examples/data_ctx.rs
deleted file mode 100644
index 667f4b18f0..0000000000
--- a/third_party/rust/scroll/examples/data_ctx.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-use scroll::{ctx, Endian, Pread, BE};
-
-#[derive(Debug)]
-struct Data<'a> {
- name: &'a str,
- id: u32,
-}
-
-impl<'a> ctx::TryFromCtx<'a, Endian> for Data<'a> {
- type Error = scroll::Error;
- fn try_from_ctx(src: &'a [u8], endian: Endian) -> Result<(Self, usize), Self::Error> {
- let name = src.pread::<&'a str>(0)?;
- let id = src.pread_with(name.len() + 1, endian)?;
- Ok((Data { name: name, id: id }, name.len() + 4))
- }
-}
-
-fn main() {
- let bytes = b"UserName\x00\x01\x02\x03\x04";
- let data = bytes.pread_with::<Data>(0, BE).unwrap();
- assert_eq!(data.id, 0x01020304);
- assert_eq!(data.name.to_string(), "UserName".to_string());
- println!("Data: {:?}", &data);
-}
diff --git a/third_party/rust/scroll/src/ctx.rs b/third_party/rust/scroll/src/ctx.rs
index 1f982b82fa..e24d2dc506 100644
--- a/third_party/rust/scroll/src/ctx.rs
+++ b/third_party/rust/scroll/src/ctx.rs
@@ -180,17 +180,14 @@
//! }
//! ```
-use core::mem::size_of;
-use core::mem::transmute;
+use core::mem::{size_of, MaybeUninit};
use core::ptr::copy_nonoverlapping;
-use core::result;
-use core::str;
-
+use core::{result, str};
#[cfg(feature = "std")]
use std::ffi::{CStr, CString};
use crate::endian::Endian;
-use crate::error;
+use crate::{error, Pread, Pwrite};
/// A trait for measuring how large something is; for a byte sequence, it will be its length.
pub trait MeasureWith<Ctx> {
@@ -240,18 +237,14 @@ impl Default for StrCtx {
impl StrCtx {
pub fn len(&self) -> usize {
- match *self {
+ match self {
StrCtx::Delimiter(_) | StrCtx::DelimiterUntil(_, _) => 1,
StrCtx::Length(_) => 0,
}
}
pub fn is_empty(&self) -> bool {
- if let StrCtx::Length(_) = *self {
- true
- } else {
- false
- }
+ matches!(self, StrCtx::Length(_))
}
}
@@ -267,6 +260,7 @@ pub trait FromCtx<Ctx: Copy = (), This: ?Sized = [u8]> {
/// `[u8]`), then you need to implement this trait
///
/// ```rust
+/// ##[cfg(feature = "std")] {
/// use scroll::{self, ctx, Pread};
/// #[derive(Debug, PartialEq, Eq)]
/// pub struct Foo(u16);
@@ -286,6 +280,7 @@ pub trait FromCtx<Ctx: Copy = (), This: ?Sized = [u8]> {
///
/// let foo2 = bytes.pread_with::<Foo>(0, scroll::BE).unwrap();
/// assert_eq!(Foo(0xdeadu16), foo2);
+/// # }
/// ```
///
/// # Advanced: Using Your Own Error in `TryFromCtx`
@@ -350,6 +345,7 @@ pub trait IntoCtx<Ctx: Copy = (), This: ?Sized = [u8]>: Sized {
/// To implement writing into an arbitrary byte buffer, implement `TryIntoCtx`
/// # Example
/// ```rust
+/// ##[cfg(feature = "std")] {
/// use scroll::{self, ctx, LE, Endian, Pwrite};
/// #[derive(Debug, PartialEq, Eq)]
/// pub struct Foo(u16);
@@ -369,6 +365,7 @@ pub trait IntoCtx<Ctx: Copy = (), This: ?Sized = [u8]>: Sized {
///
/// let mut bytes: [u8; 4] = [0, 0, 0, 0];
/// bytes.pwrite_with(Foo(0x7f), 1, LE).unwrap();
+/// # }
/// ```
pub trait TryIntoCtx<Ctx: Copy = (), This: ?Sized = [u8]>: Sized {
type Error;
@@ -403,13 +400,14 @@ macro_rules! signed_to_unsigned {
macro_rules! write_into {
($typ:ty, $size:expr, $n:expr, $dst:expr, $endian:expr) => {{
+ assert!($dst.len() >= $size);
+ let bytes = if $endian.is_little() {
+ $n.to_le()
+ } else {
+ $n.to_be()
+ }
+ .to_ne_bytes();
unsafe {
- assert!($dst.len() >= $size);
- let bytes = transmute::<$typ, [u8; $size]>(if $endian.is_little() {
- $n.to_le()
- } else {
- $n.to_be()
- });
copy_nonoverlapping((&bytes).as_ptr(), $dst.as_mut_ptr(), $size);
}
}};
@@ -570,12 +568,12 @@ macro_rules! from_ctx_float_impl {
&mut data as *mut signed_to_unsigned!($typ) as *mut u8,
$size,
);
- transmute(if le.is_little() {
- data.to_le()
- } else {
- data.to_be()
- })
}
+ $typ::from_bits(if le.is_little() {
+ data.to_le()
+ } else {
+ data.to_be()
+ })
}
}
impl<'a> TryFromCtx<'a, Endian> for $typ
@@ -621,13 +619,7 @@ macro_rules! into_ctx_float_impl {
#[inline]
fn into_ctx(self, dst: &mut [u8], le: Endian) {
assert!(dst.len() >= $size);
- write_into!(
- signed_to_unsigned!($typ),
- $size,
- transmute::<$typ, signed_to_unsigned!($typ)>(self),
- dst,
- le
- );
+ write_into!(signed_to_unsigned!($typ), $size, self.to_bits(), dst, le);
}
}
impl<'a> IntoCtx<Endian> for &'a $typ {
@@ -725,7 +717,7 @@ impl<'a> TryIntoCtx for &'a [u8] {
let src_len = self.len() as isize;
let dst_len = dst.len() as isize;
// if src_len < 0 || dst_len < 0 || offset < 0 {
- // return Err(error::Error::BadOffset(format!("requested operation has negative casts: src len: {} dst len: {} offset: {}", src_len, dst_len, offset)).into())
+ // return Err(error::Error::BadOffset(format!("requested operation has negative casts: src len: {src_len} dst len: {dst_len} offset: {offset}")).into())
// }
if src_len > dst_len {
Err(error::Error::TooBig {
@@ -789,6 +781,56 @@ impl<'a> TryFromCtx<'a, usize> for &'a [u8] {
}
}
+impl<'a, Ctx: Copy, T: TryFromCtx<'a, Ctx, Error = error::Error>, const N: usize>
+ TryFromCtx<'a, Ctx> for [T; N]
+{
+ type Error = error::Error;
+ fn try_from_ctx(src: &'a [u8], ctx: Ctx) -> Result<(Self, usize), Self::Error> {
+ let mut offset = 0;
+
+ let mut buf: [MaybeUninit<T>; N] = core::array::from_fn(|_| MaybeUninit::uninit());
+
+ let mut error_ctx = None;
+ for (idx, element) in buf.iter_mut().enumerate() {
+ match src.gread_with::<T>(&mut offset, ctx) {
+ Ok(val) => {
+ *element = MaybeUninit::new(val);
+ }
+ Err(e) => {
+ error_ctx = Some((e, idx));
+ break;
+ }
+ }
+ }
+ if let Some((e, idx)) = error_ctx {
+ for element in &mut buf[0..idx].iter_mut() {
+ // SAFETY: Any element upto idx must have already been initialized, since
+ // we iterate until we encounter an error.
+ unsafe {
+ element.assume_init_drop();
+ }
+ }
+ Err(e)
+ } else {
+ // SAFETY: we initialized each element above by preading them out, correctness
+ // of the initialized element is guaranted by pread itself
+ Ok((buf.map(|element| unsafe { element.assume_init() }), offset))
+ }
+ }
+}
+impl<Ctx: Copy, T: TryIntoCtx<Ctx, Error = error::Error>, const N: usize> TryIntoCtx<Ctx>
+ for [T; N]
+{
+ type Error = error::Error;
+ fn try_into_ctx(self, buf: &mut [u8], ctx: Ctx) -> Result<usize, Self::Error> {
+ let mut offset = 0;
+ for element in self {
+ buf.gwrite_with(element, &mut offset, ctx)?;
+ }
+ Ok(offset)
+ }
+}
+
#[cfg(feature = "std")]
impl<'a> TryFromCtx<'a> for &'a CStr {
type Error = error::Error;
@@ -863,11 +905,11 @@ impl TryIntoCtx for CString {
// }
#[cfg(test)]
+#[cfg(feature = "std")]
mod tests {
use super::*;
#[test]
- #[cfg(feature = "std")]
fn parse_a_cstr() {
let src = CString::new("Hello World").unwrap();
let as_bytes = src.as_bytes_with_nul();
@@ -879,7 +921,6 @@ mod tests {
}
#[test]
- #[cfg(feature = "std")]
fn round_trip_a_c_str() {
let src = CString::new("Hello World").unwrap();
let src = src.as_c_str();
diff --git a/third_party/rust/scroll/src/endian.rs b/third_party/rust/scroll/src/endian.rs
index 06d7a1dc1c..7b83c348d5 100644
--- a/third_party/rust/scroll/src/endian.rs
+++ b/third_party/rust/scroll/src/endian.rs
@@ -43,9 +43,6 @@ impl Endian {
}
#[inline]
pub fn is_little(&self) -> bool {
- match *self {
- LE => true,
- _ => false,
- }
+ *self == LE
}
}
diff --git a/third_party/rust/scroll/src/error.rs b/third_party/rust/scroll/src/error.rs
index 7740254774..1b68c2e4c7 100644
--- a/third_party/rust/scroll/src/error.rs
+++ b/third_party/rust/scroll/src/error.rs
@@ -1,10 +1,7 @@
use core::fmt::{self, Display};
use core::result;
-
-#[cfg(feature = "std")]
-use std::error;
#[cfg(feature = "std")]
-use std::io;
+use std::{error, io};
#[derive(Debug)]
/// A custom Scroll error
@@ -20,18 +17,19 @@ pub enum Error {
size: usize,
msg: &'static str,
},
+ /// A custom Scroll error for reporting messages to clients.
+ /// For no-std, use [`Error::BadInput`] with a static string.
#[cfg(feature = "std")]
- /// A custom Scroll error for reporting messages to clients
Custom(String),
- #[cfg(feature = "std")]
/// Returned when IO based errors are encountered
+ #[cfg(feature = "std")]
IO(io::Error),
}
#[cfg(feature = "std")]
impl error::Error for Error {
fn description(&self) -> &str {
- match *self {
+ match self {
Error::TooBig { .. } => "TooBig",
Error::BadOffset(_) => "BadOffset",
Error::BadInput { .. } => "BadInput",
@@ -40,7 +38,7 @@ impl error::Error for Error {
}
}
fn cause(&self) -> Option<&dyn error::Error> {
- match *self {
+ match self {
Error::TooBig { .. } => None,
Error::BadOffset(_) => None,
Error::BadInput { .. } => None,
@@ -59,23 +57,23 @@ impl From<io::Error> for Error {
impl Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
- match *self {
+ match self {
Error::TooBig { ref size, ref len } => {
- write!(fmt, "type is too big ({}) for {}", size, len)
+ write!(fmt, "type is too big ({size}) for {len}")
}
Error::BadOffset(ref offset) => {
- write!(fmt, "bad offset {}", offset)
+ write!(fmt, "bad offset {offset}")
}
Error::BadInput { ref msg, ref size } => {
- write!(fmt, "bad input {} ({})", msg, size)
+ write!(fmt, "bad input {msg} ({size})")
}
#[cfg(feature = "std")]
Error::Custom(ref msg) => {
- write!(fmt, "{}", msg)
+ write!(fmt, "{msg}")
}
#[cfg(feature = "std")]
Error::IO(ref err) => {
- write!(fmt, "{}", err)
+ write!(fmt, "{err}")
}
}
}
diff --git a/third_party/rust/scroll/src/leb128.rs b/third_party/rust/scroll/src/leb128.rs
index 43f50b95f1..eceec6d166 100644
--- a/third_party/rust/scroll/src/leb128.rs
+++ b/third_party/rust/scroll/src/leb128.rs
@@ -1,9 +1,8 @@
-use crate::ctx::TryFromCtx;
-use crate::error;
-use crate::Pread;
use core::convert::{AsRef, From};
-use core::result;
-use core::u8;
+use core::{result, u8};
+
+use crate::ctx::TryFromCtx;
+use crate::{error, Pread};
#[derive(Debug, PartialEq, Copy, Clone)]
/// An unsigned leb128 integer
@@ -184,21 +183,24 @@ mod tests {
let buf = [2u8 | CONTINUATION_BIT, 1];
let bytes = &buf[..];
let num = bytes.pread::<Uleb128>(0).unwrap();
- println!("num: {:?}", &num);
+ #[cfg(feature = "std")]
+ println!("num: {num:?}");
assert_eq!(130u64, num.into());
assert_eq!(num.size(), 2);
let buf = [0x00, 0x01];
let bytes = &buf[..];
let num = bytes.pread::<Uleb128>(0).unwrap();
- println!("num: {:?}", &num);
+ #[cfg(feature = "std")]
+ println!("num: {num:?}");
assert_eq!(0u64, num.into());
assert_eq!(num.size(), 1);
let buf = [0x21];
let bytes = &buf[..];
let num = bytes.pread::<Uleb128>(0).unwrap();
- println!("num: {:?}", &num);
+ #[cfg(feature = "std")]
+ println!("num: {num:?}");
assert_eq!(0x21u64, num.into());
assert_eq!(num.size(), 1);
}
diff --git a/third_party/rust/scroll/src/lesser.rs b/third_party/rust/scroll/src/lesser.rs
index 46ef4c5b11..636bf2553e 100644
--- a/third_party/rust/scroll/src/lesser.rs
+++ b/third_party/rust/scroll/src/lesser.rs
@@ -1,6 +1,7 @@
-use crate::ctx::{FromCtx, IntoCtx, SizeWith};
use std::io::{Read, Result, Write};
+use crate::ctx::{FromCtx, IntoCtx, SizeWith};
+
/// An extension trait to `std::io::Read` streams; mainly targeted at reading primitive types with
/// a known size.
///
@@ -104,8 +105,8 @@ pub trait IOread<Ctx: Copy>: Read {
fn ioread_with<N: FromCtx<Ctx> + SizeWith<Ctx>>(&mut self, ctx: Ctx) -> Result<N> {
let mut scratch = [0u8; 256];
let size = N::size_with(&ctx);
- let mut buf = &mut scratch[0..size];
- self.read_exact(&mut buf)?;
+ let buf = &mut scratch[0..size];
+ self.read_exact(buf)?;
Ok(N::from_ctx(buf, ctx))
}
}
diff --git a/third_party/rust/scroll/src/lib.rs b/third_party/rust/scroll/src/lib.rs
index dcb58e7564..2740648517 100644
--- a/third_party/rust/scroll/src/lib.rs
+++ b/third_party/rust/scroll/src/lib.rs
@@ -119,6 +119,7 @@
//! [FromCtx](ctx/trait.FromCtx.html) and [SizeWith](ctx/trait.SizeWith.html).
//!
//! ```rust
+//! ##[cfg(feature = "std")] {
//! use std::io::Cursor;
//! use scroll::{IOread, ctx, Endian};
//! let bytes = [0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xef,0xbe,0x00,0x00,];
@@ -139,12 +140,14 @@
//! // read/written, e.g. switching between ELF32 or ELF64 at runtime.
//! let size = <u64 as ctx::SizeWith<Endian>>::size_with(&Endian::Little) as u64;
//! assert_eq!(prev + size, after);
+//! # }
//! ```
//!
//! In the same vein as IOread we can use IOwrite to write a type to anything implementing
//! `std::io::Write`:
//!
//! ```rust
+//! ##[cfg(feature = "std")] {
//! use std::io::Cursor;
//! use scroll::{IOwrite};
//!
@@ -155,6 +158,7 @@
//! cursor.iowrite_with(0xdeadbeef as u32, scroll::BE).unwrap();
//!
//! assert_eq!(cursor.into_inner(), [0xde, 0xad, 0xbe, 0xef, 0x0]);
+//! # }
//! ```
//!
//! ## Complex use cases
@@ -249,8 +253,7 @@ pub use crate::pwrite::*;
#[doc(hidden)]
pub mod export {
- pub use ::core::mem;
- pub use ::core::result;
+ pub use ::core::{mem, result};
}
#[allow(unused)]
@@ -267,7 +270,6 @@ doc_comment!(include_str!("../README.md"));
#[cfg(test)]
mod tests {
- #[allow(overflowing_literals)]
use super::LE;
#[test]
@@ -355,36 +357,48 @@ mod tests {
let bytes: [u8; 2] = [0x2e, 0x0];
let b = &bytes[..];
let s: &str = b.pread(0).unwrap();
- println!("str: {}", s);
+ #[cfg(feature = "std")]
+ println!("str: {s}");
assert_eq!(s.len(), bytes[..].len() - 1);
let bytes: &[u8] = b"hello, world!\0some_other_things";
let hello_world: &str = bytes.pread_with(0, StrCtx::Delimiter(NULL)).unwrap();
- println!("{:?}", &hello_world);
+ #[cfg(feature = "std")]
+ println!("{hello_world:?}");
assert_eq!(hello_world.len(), 13);
let hello: &str = bytes.pread_with(0, StrCtx::Delimiter(SPACE)).unwrap();
- println!("{:?}", &hello);
+ #[cfg(feature = "std")]
+ println!("{hello:?}");
assert_eq!(hello.len(), 6);
// this could result in underflow so we just try it
let _error = bytes.pread_with::<&str>(6, StrCtx::Delimiter(SPACE));
let error = bytes.pread_with::<&str>(7, StrCtx::Delimiter(SPACE));
- println!("{:?}", &error);
+ #[cfg(feature = "std")]
+ println!("{error:?}");
assert!(error.is_ok());
}
+ /// In this test, we are testing preading
+ /// at length boundaries.
+ /// In the past, this test was supposed to test failures for `hello_world`.
+ /// Since PR#94, this test is unwrapping as we exploit
+ /// the fact that if you do &x[x.len()..] you get an empty slice.
#[test]
fn pread_str_weird() {
use super::ctx::*;
use super::Pread;
let bytes: &[u8] = b"";
let hello_world = bytes.pread_with::<&str>(0, StrCtx::Delimiter(NULL));
- println!("1 {:?}", &hello_world);
- assert_eq!(hello_world.is_err(), true);
+ #[cfg(feature = "std")]
+ println!("1 {hello_world:?}");
+ assert!(hello_world.unwrap().is_empty());
let error = bytes.pread_with::<&str>(7, StrCtx::Delimiter(SPACE));
- println!("2 {:?}", &error);
+ #[cfg(feature = "std")]
+ println!("2 {error:?}");
assert!(error.is_err());
let bytes: &[u8] = b"\0";
let null = bytes.pread::<&str>(0).unwrap();
- println!("3 {:?}", &null);
+ #[cfg(feature = "std")]
+ println!("3 {null:?}");
assert_eq!(null.len(), 0);
}
@@ -413,8 +427,7 @@ mod tests {
assert_eq!(bytes, "bytes");
}
- use std::error;
- use std::fmt::{self, Display};
+ use core::fmt::{self, Display};
#[derive(Debug)]
pub struct ExternalError {}
@@ -425,11 +438,12 @@ mod tests {
}
}
- impl error::Error for ExternalError {
+ #[cfg(feature = "std")]
+ impl std::error::Error for ExternalError {
fn description(&self) -> &str {
"ExternalError"
}
- fn cause(&self) -> Option<&dyn error::Error> {
+ fn cause(&self) -> Option<&dyn std::error::Error> {
None
}
}
@@ -451,7 +465,7 @@ mod tests {
fn try_into_ctx(self, this: &mut [u8], le: super::Endian) -> Result<usize, Self::Error> {
use super::Pwrite;
if this.len() < 2 {
- return Err((ExternalError {}).into());
+ return Err(ExternalError {});
}
this.pwrite_with(self.0, 0, le)?;
Ok(2)
@@ -463,7 +477,7 @@ mod tests {
fn try_from_ctx(this: &'a [u8], le: super::Endian) -> Result<(Self, usize), Self::Error> {
use super::Pread;
if this.len() > 2 {
- return Err((ExternalError {}).into());
+ return Err(ExternalError {});
}
let n = this.pread_with(0, le)?;
Ok((Foo(n), 2))
@@ -499,7 +513,7 @@ mod tests {
let mut offset = 0;
let deadbeef: $typ = bytes.gread_with(&mut offset, LE).unwrap();
assert_eq!(deadbeef, $deadbeef as $typ);
- assert_eq!(offset, ::std::mem::size_of::<$typ>());
+ assert_eq!(offset, ::core::mem::size_of::<$typ>());
}
};
}
@@ -518,7 +532,7 @@ mod tests {
let mut offset = 0;
let deadbeef: $typ = bytes.gread_with(&mut offset, LE).unwrap();
assert_eq!(deadbeef, $deadbeef as $typ);
- assert_eq!(offset, ::std::mem::size_of::<$typ>());
+ assert_eq!(offset, ::core::mem::size_of::<$typ>());
}
};
}
@@ -537,8 +551,8 @@ mod tests {
let o2 = &mut 0;
let val: $typ = buffer.gread_with(o2, LE).unwrap();
assert_eq!(val, $val);
- assert_eq!(*offset, ::std::mem::size_of::<$typ>());
- assert_eq!(*o2, ::std::mem::size_of::<$typ>());
+ assert_eq!(*offset, ::core::mem::size_of::<$typ>());
+ assert_eq!(*o2, ::core::mem::size_of::<$typ>());
assert_eq!(*o2, *offset);
buffer.gwrite_with($val.clone(), offset, BE).unwrap();
let val: $typ = buffer.gread_with(o2, BE).unwrap();
@@ -612,16 +626,17 @@ mod tests {
let res = b.gread_with::<&str>(offset, StrCtx::Length(3));
assert!(res.is_err());
*offset = 0;
- let astring: [u8; 3] = [0x45, 042, 0x44];
+ let astring: [u8; 3] = [0x45, 0x42, 0x44];
let string = astring.gread_with::<&str>(offset, StrCtx::Length(2));
match &string {
- &Ok(_) => {}
- &Err(ref err) => {
- println!("{}", &err);
+ Ok(_) => {}
+ Err(_err) => {
+ #[cfg(feature = "std")]
+ println!("{_err}");
panic!();
}
}
- assert_eq!(string.unwrap(), "E*");
+ assert_eq!(string.unwrap(), "EB");
*offset = 0;
let bytes2: &[u8] = b.gread_with(offset, 2).unwrap();
assert_eq!(*offset, 2);
diff --git a/third_party/rust/scroll/src/pread.rs b/third_party/rust/scroll/src/pread.rs
index 72ba877054..15bf1426be 100644
--- a/third_party/rust/scroll/src/pread.rs
+++ b/third_party/rust/scroll/src/pread.rs
@@ -20,6 +20,11 @@ use crate::error;
/// over chunks of memory or any other indexable type — but scroll does come with a set of powerful
/// blanket implementations for data being a continous block of byte-addressable memory.
///
+/// Note that in the particular case of the implementation of `Pread` for `[u8]`,
+/// reading it at the length boundary of that slice will cause to read from an empty slice.
+/// i.e. we make use of the fact that `&bytes[bytes.len()..]` will return an empty slice, rather
+/// than returning an error. In the past, scroll returned an offset error.
+///
/// Pread provides two main groups of functions: pread and gread.
///
/// `pread` is the basic function that simply extracts a given type from a given data store - either
@@ -167,7 +172,7 @@ impl<Ctx: Copy, E: From<error::Error>> Pread<Ctx, E> for [u8] {
ctx: Ctx,
) -> result::Result<N, E> {
let start = *offset;
- if start >= self.len() {
+ if start > self.len() {
return Err(error::Error::BadOffset(start).into());
}
N::try_from_ctx(&self[start..], ctx).map(|(n, size)| {
diff --git a/third_party/rust/scroll/src/pwrite.rs b/third_party/rust/scroll/src/pwrite.rs
index ab6d96157d..7a07f2daba 100644
--- a/third_party/rust/scroll/src/pwrite.rs
+++ b/third_party/rust/scroll/src/pwrite.rs
@@ -19,6 +19,13 @@ use crate::error;
/// with 'read' switched for 'write' and 'From' switched with 'Into' so if you haven't yet you
/// should read the documentation of `Pread` first.
///
+/// As with `Pread`, note that in the particular case of the implementation of `Pwrite` for `[u8]`,
+/// writing it at the length boundary of that slice will cause to write in an empty slice.
+/// i.e. we make use of the fact that `&bytes[bytes.len()..]` will return an empty slice, rather
+/// than returning an error. In the past, scroll returned an offset error.
+/// In this case, this is relevant if you are writing an empty slice inside an empty slice and
+/// expected this to work.
+///
/// Unless you need to implement your own data store — that is either can't convert to `&[u8]` or
/// have a data that does not expose a `&mut [u8]` — you will probably want to implement
/// [TryIntoCtx](ctx/trait.TryIntoCtx.html) on your Rust types to be written.
@@ -87,7 +94,7 @@ impl<Ctx: Copy, E: From<error::Error>> Pwrite<Ctx, E> for [u8] {
offset: usize,
ctx: Ctx,
) -> result::Result<usize, E> {
- if offset >= self.len() {
+ if offset > self.len() {
return Err(error::Error::BadOffset(offset).into());
}
let dst = &mut self[offset..];
diff --git a/third_party/rust/scroll/tests/api.rs b/third_party/rust/scroll/tests/api.rs
deleted file mode 100644
index e10726f22a..0000000000
--- a/third_party/rust/scroll/tests/api.rs
+++ /dev/null
@@ -1,292 +0,0 @@
-// this exists primarily to test various API usages of scroll; e.g., must compile
-
-// guard against potential undefined behaviour when borrowing from
-// packed structs. See https://github.com/rust-lang/rust/issues/46043
-#![deny(unaligned_references)]
-
-// #[macro_use] extern crate scroll_derive;
-
-use scroll::ctx::SizeWith;
-use scroll::{ctx, Cread, Pread, Result};
-use std::ops::{Deref, DerefMut};
-
-#[derive(Default)]
-pub struct Section<'a> {
- pub sectname: [u8; 16],
- pub segname: [u8; 16],
- pub addr: u64,
- pub size: u64,
- pub offset: u32,
- pub align: u32,
- pub reloff: u32,
- pub nreloc: u32,
- pub flags: u32,
- pub data: &'a [u8],
-}
-
-impl<'a> Section<'a> {
- pub fn name(&self) -> Result<&str> {
- self.sectname.pread::<&str>(0)
- }
- pub fn segname(&self) -> Result<&str> {
- self.segname.pread::<&str>(0)
- }
-}
-
-impl<'a> ctx::SizeWith for Section<'a> {
- fn size_with(_ctx: &()) -> usize {
- 4
- }
-}
-
-#[repr(C)]
-// renable when scroll_derive Pread/Pwrite matches
-//#[derive(Debug, Clone, Copy, Pread, Pwrite)]
-#[derive(Debug, Clone, Copy)]
-pub struct Section32 {
- pub sectname: [u8; 16],
- pub segname: [u8; 16],
- pub addr: u32,
- pub size: u32,
- pub offset: u32,
- pub align: u32,
- pub reloff: u32,
- pub nreloc: u32,
- pub flags: u32,
- pub reserved1: u32,
- pub reserved2: u32,
-}
-
-impl<'a> ctx::TryFromCtx<'a, ()> for Section<'a> {
- type Error = scroll::Error;
- fn try_from_ctx(
- _bytes: &'a [u8],
- _ctx: (),
- ) -> ::std::result::Result<(Self, usize), Self::Error> {
- let section = Section::default();
- Ok((section, ::std::mem::size_of::<Section>()))
- }
-}
-
-pub struct Segment<'a> {
- pub cmd: u32,
- pub cmdsize: u32,
- pub segname: [u8; 16],
- pub vmaddr: u64,
- pub vmsize: u64,
- pub fileoff: u64,
- pub filesize: u64,
- pub maxprot: u32,
- pub initprot: u32,
- pub nsects: u32,
- pub flags: u32,
- pub data: &'a [u8],
- offset: usize,
- raw_data: &'a [u8],
-}
-
-impl<'a> Segment<'a> {
- pub fn name(&self) -> Result<&str> {
- Ok(self.segname.pread::<&str>(0)?)
- }
- pub fn sections(&self) -> Result<Vec<Section<'a>>> {
- let nsects = self.nsects as usize;
- let mut sections = Vec::with_capacity(nsects);
- let offset = &mut (self.offset + Self::size_with(&()));
- let _size = Section::size_with(&());
- let raw_data: &'a [u8] = self.raw_data;
- for _ in 0..nsects {
- let section = raw_data.gread_with::<Section<'a>>(offset, ())?;
- sections.push(section);
- //offset += size;
- }
- Ok(sections)
- }
-}
-
-impl<'a> ctx::SizeWith for Segment<'a> {
- fn size_with(_ctx: &()) -> usize {
- 4
- }
-}
-
-pub struct Segments<'a> {
- pub segments: Vec<Segment<'a>>,
-}
-
-impl<'a> Deref for Segments<'a> {
- type Target = Vec<Segment<'a>>;
- fn deref(&self) -> &Self::Target {
- &self.segments
- }
-}
-
-impl<'a> DerefMut for Segments<'a> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.segments
- }
-}
-
-impl<'a> Segments<'a> {
- pub fn new() -> Self {
- Segments {
- segments: Vec::new(),
- }
- }
- pub fn sections(&self) -> Result<Vec<Vec<Section<'a>>>> {
- let mut sections = Vec::new();
- for segment in &self.segments {
- sections.push(segment.sections()?);
- }
- Ok(sections)
- }
-}
-
-fn lifetime_passthrough_<'a>(segments: &Segments<'a>, section_name: &str) -> Option<&'a [u8]> {
- let segment_name = "__TEXT";
- for segment in &segments.segments {
- if let Ok(name) = segment.name() {
- println!("segment.name: {}", name);
- if name == segment_name {
- if let Ok(sections) = segment.sections() {
- for section in sections {
- let sname = section.name().unwrap();
- println!("section.name: {}", sname);
- if section_name == sname {
- return Some(section.data);
- }
- }
- }
- }
- }
- }
- None
-}
-
-#[test]
-fn lifetime_passthrough() {
- let segments = Segments::new();
- let _res = lifetime_passthrough_(&segments, "__text");
- assert!(true)
-}
-
-#[derive(Default)]
-#[repr(packed)]
-struct Foo {
- foo: i64,
- bar: u32,
-}
-
-impl scroll::ctx::FromCtx<scroll::Endian> for Foo {
- fn from_ctx(bytes: &[u8], ctx: scroll::Endian) -> Self {
- Foo {
- foo: bytes.cread_with::<i64>(0, ctx),
- bar: bytes.cread_with::<u32>(8, ctx),
- }
- }
-}
-
-impl scroll::ctx::SizeWith<scroll::Endian> for Foo {
- fn size_with(_: &scroll::Endian) -> usize {
- ::std::mem::size_of::<Foo>()
- }
-}
-
-#[test]
-fn ioread_api() {
- use scroll::{IOread, LE};
- use std::io::Cursor;
- let bytes_ = [
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0x00, 0x00,
- ];
- let mut bytes = Cursor::new(bytes_);
- let foo = bytes.ioread_with::<i64>(LE).unwrap();
- let bar = bytes.ioread_with::<u32>(LE).unwrap();
- assert_eq!(foo, 1);
- assert_eq!(bar, 0xbeef);
- let error = bytes.ioread_with::<f64>(LE);
- assert!(error.is_err());
- let mut bytes = Cursor::new(bytes_);
- let foo_ = bytes.ioread_with::<Foo>(LE).unwrap();
- assert_eq!({ foo_.foo }, foo);
- assert_eq!({ foo_.bar }, bar);
-}
-
-#[repr(packed)]
-struct Bar {
- foo: i32,
- bar: u32,
-}
-
-impl scroll::ctx::FromCtx<scroll::Endian> for Bar {
- fn from_ctx(bytes: &[u8], ctx: scroll::Endian) -> Self {
- Bar {
- foo: bytes.cread_with(0, ctx),
- bar: bytes.cread_with(4, ctx),
- }
- }
-}
-
-#[test]
-fn cread_api() {
- use scroll::{Cread, LE};
- let bytes = [
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0x00, 0x00,
- ];
- let foo = bytes.cread_with::<u64>(0, LE);
- let bar = bytes.cread_with::<u32>(8, LE);
- assert_eq!(foo, 1);
- assert_eq!(bar, 0xbeef);
-}
-
-#[test]
-fn cread_api_customtype() {
- use scroll::{Cread, LE};
- let bytes = [0xff, 0xff, 0xff, 0xff, 0xef, 0xbe, 0xad, 0xde];
- let bar = &bytes[..].cread_with::<Bar>(0, LE);
- assert_eq!({ bar.foo }, -1);
- assert_eq!({ bar.bar }, 0xdeadbeef);
-}
-
-#[test]
-#[should_panic]
-fn cread_api_badindex() {
- use scroll::Cread;
- let bytes = [
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0xbe, 0xad, 0xde,
- ];
- let _foo = bytes.cread::<i64>(1_000_000);
-}
-
-#[test]
-fn cwrite_api() {
- use scroll::Cread;
- use scroll::Cwrite;
- let mut bytes = [0x0; 16];
- bytes.cwrite::<u64>(42, 0);
- bytes.cwrite::<u32>(0xdeadbeef, 8);
- assert_eq!(bytes.cread::<u64>(0), 42);
- assert_eq!(bytes.cread::<u32>(8), 0xdeadbeef);
-}
-
-impl scroll::ctx::IntoCtx<scroll::Endian> for Bar {
- fn into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) {
- use scroll::Cwrite;
- bytes.cwrite_with(self.foo, 0, ctx);
- bytes.cwrite_with(self.bar, 4, ctx);
- }
-}
-
-#[test]
-fn cwrite_api_customtype() {
- use scroll::{Cread, Cwrite};
- let bar = Bar {
- foo: -1,
- bar: 0xdeadbeef,
- };
- let mut bytes = [0x0; 16];
- let _ = &bytes[..].cwrite::<Bar>(bar, 0);
- let bar = bytes.cread::<Bar>(0);
- assert_eq!({ bar.foo }, -1);
- assert_eq!({ bar.bar }, 0xdeadbeef);
-}
diff --git a/third_party/rust/scroll_derive/.cargo-checksum.json b/third_party/rust/scroll_derive/.cargo-checksum.json
index 8c6b3b87c4..b881da6c6a 100644
--- a/third_party/rust/scroll_derive/.cargo-checksum.json
+++ b/third_party/rust/scroll_derive/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"9fbb5068c3ffbf2c357f4068f854f439bae4999e04527e2dedc6758fa37a9807","LICENSE":"afb11426e09da40a1ae4f8fa17ddcc6b6a52d14df04c29bc5bcd06eb8730624d","README.md":"f89c7768454b0d2b9db816afe05db3a4cea1125bef87f08ed3eefd65e9e2b180","src/lib.rs":"a9cabe3c0b373f352357745b817f188ab841e9445056014dee9cc83c4d167483"},"package":"1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae"} \ No newline at end of file
+{"files":{"Cargo.toml":"57ee02784903ef6f506e87164230e0bf543cf9f9bcd1546e123158c7ab98b648","LICENSE":"afb11426e09da40a1ae4f8fa17ddcc6b6a52d14df04c29bc5bcd06eb8730624d","README.md":"0ed9b8c8ec7dd75f14aab9b7e54769f81b86e68960658356e260e5ec8ccac206","src/lib.rs":"a9cabe3c0b373f352357745b817f188ab841e9445056014dee9cc83c4d167483"},"package":"7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932"} \ No newline at end of file
diff --git a/third_party/rust/scroll_derive/Cargo.toml b/third_party/rust/scroll_derive/Cargo.toml
index 71f40a7e6c..9e2e33bd78 100644
--- a/third_party/rust/scroll_derive/Cargo.toml
+++ b/third_party/rust/scroll_derive/Cargo.toml
@@ -12,7 +12,7 @@
[package]
edition = "2018"
name = "scroll_derive"
-version = "0.11.1"
+version = "0.12.0"
authors = [
"m4b <m4b.github.io@gmail.com>",
"Ted Mielczarek <ted@mielczarek.org>",
diff --git a/third_party/rust/scroll_derive/README.md b/third_party/rust/scroll_derive/README.md
index a7f7e85f0e..675b6d7803 100644
--- a/third_party/rust/scroll_derive/README.md
+++ b/third_party/rust/scroll_derive/README.md
@@ -21,7 +21,7 @@ use scroll::{Pread, Pwrite, Cread, LE};
fn main (){
let bytes = [0xefu8, 0xbe, 0xad, 0xde, 0, 0, 0, 0, 0, 0, 224, 63, 0xad, 0xde, 0xef, 0xbe];
let data: Data = bytes.pread_with(0, LE).unwrap();
- println!("data: {:?}", &data);
+ println!("data: {data:?}");
assert_eq!(data.id, 0xdeadbeefu32);
let mut bytes2 = vec![0; ::std::mem::size_of::<Data>()];
bytes2.pwrite_with(data, 0, LE).unwrap();
diff --git a/third_party/rust/smawk/.cargo-checksum.json b/third_party/rust/smawk/.cargo-checksum.json
new file mode 100644
index 0000000000..c553a4a2c6
--- /dev/null
+++ b/third_party/rust/smawk/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"4581638d3c628d22826bde37114048c825ffb354f17f21645d8d49f9ebd64689","LICENSE":"0173035e025d60b1d19197840a93a887f6da8b075c01dd10601fcb6414a0043b","README.md":"c27297df61be8dd14e47dc30a80ae1d443f5acea82932139637543bc6d860631","dprint.json":"aacd5ec32db8741fbdea4ac916e61f0011485a51e8ec7a660f849be60cc7b512","rustfmt.toml":"6819baea67831b8a8b2a7ad33af1128dd2774a900c804635c912bb6545a4e922","src/brute_force.rs":"02edda18441ea5d6cc89d2fdfb9ab32a361e2598de74a71fb930fb630288ce35","src/lib.rs":"b312e4855945cfe27f4b1e9949b1c6ffea8f248ad80ac8fc49e72f0cc38df219","src/monge.rs":"f6c475f4d094b70b5e45d0c8a94112d42eaafa0ab41b2d3d96d06a38f1bac32d","src/recursive.rs":"e585286fe6c885dcac8001d0f484718aa8f73f3f85a452f8b4c1cb36d4fbfcf6","tests/agreement.rs":"764406a5d8c9a322bab8787764d780832cfc3962722ed01efda99684a619d543","tests/complexity.rs":"e2e850d38529f171eb6005807c2a86a3f95a907052253eaa8e24a834200cda0b","tests/monge.rs":"fe418373f89904cd40e2ed1d539bccd2d9be50c1f3f9ab2d93806ff3bce6b7ea","tests/random_monge/mod.rs":"83cf1dd0c7b0b511ad754c19857a5d830ed54e8fef3c31235cd70b709687534b","tests/version-numbers.rs":"73301b7bfe500eada5ede66f0dce89bd3e354af50a8e7a123b02931cd5eb8e16"},"package":"b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"} \ No newline at end of file
diff --git a/third_party/rust/smawk/Cargo.toml b/third_party/rust/smawk/Cargo.toml
new file mode 100644
index 0000000000..44fc03df30
--- /dev/null
+++ b/third_party/rust/smawk/Cargo.toml
@@ -0,0 +1,53 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "smawk"
+version = "0.3.2"
+authors = ["Martin Geisler <martin@geisler.net>"]
+exclude = [
+ ".github/",
+ ".gitignore",
+ "benches/",
+ "examples/",
+]
+description = "Functions for finding row-minima in a totally monotone matrix."
+readme = "README.md"
+keywords = [
+ "smawk",
+ "matrix",
+ "optimization",
+ "dynamic-programming",
+]
+categories = [
+ "algorithms",
+ "mathematics",
+ "science",
+]
+license = "MIT"
+repository = "https://github.com/mgeisler/smawk"
+
+[dependencies.ndarray]
+version = "0.15.4"
+optional = true
+
+[dev-dependencies.num-traits]
+version = "0.2.14"
+
+[dev-dependencies.rand]
+version = "0.8.4"
+
+[dev-dependencies.rand_chacha]
+version = "0.3.1"
+
+[dev-dependencies.version-sync]
+version = "0.9.4"
diff --git a/third_party/rust/smawk/LICENSE b/third_party/rust/smawk/LICENSE
new file mode 100644
index 0000000000..124067f7ae
--- /dev/null
+++ b/third_party/rust/smawk/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Martin Geisler
+
+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/third_party/rust/smawk/README.md b/third_party/rust/smawk/README.md
new file mode 100644
index 0000000000..7d45acfeef
--- /dev/null
+++ b/third_party/rust/smawk/README.md
@@ -0,0 +1,151 @@
+# SMAWK Algorithm in Rust
+
+[![](https://github.com/mgeisler/smawk/workflows/build/badge.svg)][build-status]
+[![](https://codecov.io/gh/mgeisler/smawk/branch/master/graph/badge.svg)][codecov]
+[![](https://img.shields.io/crates/v/smawk.svg)][crates-io]
+[![](https://docs.rs/smawk/badge.svg)][api-docs]
+
+This crate contains an implementation of the [SMAWK algorithm][smawk] for
+finding the smallest element per row in a totally monotone matrix.
+
+The SMAWK algorithm allows you to lower the running time of some algorithms from
+O(_n_²) to just O(_n_). In other words, you can turn a quadratic time complexity
+(which is often too expensive) into linear time complexity.
+
+Finding optimal line breaks in a paragraph of text is an example of an algorithm
+which would normally take O(_n_²) time for _n_ words. With this crate, the
+running time becomes linear. Please see the [textwrap crate][textwrap] for an
+example of this.
+
+## Usage
+
+Add this to your `Cargo.toml`:
+
+```toml
+[dependencies]
+smawk = "0.3"
+```
+
+You can now efficiently find row and column minima. Here is an example where we
+find the column minima:
+
+```rust
+use smawk::Matrix;
+
+let matrix = vec![
+ vec![3, 2, 4, 5, 6],
+ vec![2, 1, 3, 3, 4],
+ vec![2, 1, 3, 3, 4],
+ vec![3, 2, 4, 3, 4],
+ vec![4, 3, 2, 1, 1],
+];
+let minima = vec![1, 1, 4, 4, 4];
+assert_eq!(smawk::column_minima(&matrix), minima);
+```
+
+The `minima` vector gives the index of the minimum value per column, so
+`minima[0] == 1` since the minimum value in the first column is 2 (row 1). Note
+that the smallest row index is returned.
+
+### Cargo Features
+
+This crate has an optional dependency on the
+[`ndarray` crate](https://docs.rs/ndarray/), which provides an efficient matrix
+implementation. Enable the `ndarray` Cargo feature to use it.
+
+## Documentation
+
+**[API documentation][api-docs]**
+
+## Changelog
+
+### Version 0.3.2 (2023-09-17)
+
+This release adds more documentation and renames the top-level SMAWK functions.
+The old names have been kept for now to ensure backwards compatibility, but they
+will be removed in a future release.
+
+- [#65](https://github.com/mgeisler/smawk/pull/65): Forbid the use of unsafe
+ code.
+- [#69](https://github.com/mgeisler/smawk/pull/69): Migrate to the Rust 2021
+ edition.
+- [#73](https://github.com/mgeisler/smawk/pull/73): Add examples to all
+ functions.
+- [#74](https://github.com/mgeisler/smawk/pull/74): Add “mathematics” as a crate
+ category.
+- [#75](https://github.com/mgeisler/smawk/pull/75): Remove `smawk_` prefix from
+ optimized functions.
+
+### Version 0.3.1 (2021-01-30)
+
+This release relaxes the bounds on the `smawk_row_minima`,
+`smawk_column_minima`, and `online_column_minima` functions so that they work on
+matrices containing floating point numbers.
+
+- [#55](https://github.com/mgeisler/smawk/pull/55): Relax bounds to `PartialOrd`
+ instead of `Ord`.
+- [#56](https://github.com/mgeisler/smawk/pull/56): Update dependencies to their
+ latest versions.
+- [#59](https://github.com/mgeisler/smawk/pull/59): Give an example of what
+ SMAWK does in the README.
+
+### Version 0.3.0 (2020-09-02)
+
+This release slims down the crate significantly by making `ndarray` an optional
+dependency.
+
+- [#45](https://github.com/mgeisler/smawk/pull/45): Move non-SMAWK code and unit
+ tests out of lib and into separate modules.
+- [#46](https://github.com/mgeisler/smawk/pull/46): Switch `smawk_row_minima`
+ and `smawk_column_minima` functions to a new `Matrix` trait.
+- [#47](https://github.com/mgeisler/smawk/pull/47): Make the dependency on the
+ `ndarray` crate optional.
+- [#48](https://github.com/mgeisler/smawk/pull/48): Let `is_monge` take a
+ `Matrix` argument instead of `ndarray::Array2`.
+- [#50](https://github.com/mgeisler/smawk/pull/50): Remove mandatory
+ dependencies on `rand` and `num-traits` crates.
+
+### Version 0.2.0 (2020-07-29)
+
+This release updates the code to Rust 2018.
+
+- [#18](https://github.com/mgeisler/smawk/pull/18): Make `online_column_minima`
+ generic in matrix type.
+- [#23](https://github.com/mgeisler/smawk/pull/23): Switch to the
+ [Rust 2018][rust-2018] edition. We test against the latest stable and nightly
+ version of Rust.
+- [#29](https://github.com/mgeisler/smawk/pull/29): Drop strict Rust 2018
+ compatibility by not testing with Rust 1.31.0.
+- [#32](https://github.com/mgeisler/smawk/pull/32): Fix crash on overflow in
+ `is_monge`.
+- [#33](https://github.com/mgeisler/smawk/pull/33): Update `rand` dependency to
+ latest version and get rid of `rand_derive`.
+- [#34](https://github.com/mgeisler/smawk/pull/34): Bump `num-traits` and
+ `version-sync` dependencies to latest versions.
+- [#35](https://github.com/mgeisler/smawk/pull/35): Drop unnecessary Windows
+ tests. The assumption is that the numeric computations we do are
+ cross-platform.
+- [#36](https://github.com/mgeisler/smawk/pull/36): Update `ndarray` dependency
+ to the latest version.
+- [#37](https://github.com/mgeisler/smawk/pull/37): Automate publishing new
+ releases to crates.io.
+
+### Version 0.1.0 — August 7th, 2018
+
+First release with the classical offline SMAWK algorithm as well as a newer
+online version where the matrix entries can depend on previously computed column
+minima.
+
+## License
+
+SMAWK can be distributed according to the [MIT license][mit]. Contributions will
+be accepted under the same license.
+
+[build-status]: https://github.com/mgeisler/smawk/actions?query=branch%3Amaster+workflow%3Abuild
+[crates-io]: https://crates.io/crates/smawk
+[codecov]: https://codecov.io/gh/mgeisler/smawk
+[textwrap]: https://crates.io/crates/textwrap
+[smawk]: https://en.wikipedia.org/wiki/SMAWK_algorithm
+[api-docs]: https://docs.rs/smawk/
+[rust-2018]: https://doc.rust-lang.org/edition-guide/rust-2018/
+[mit]: LICENSE
diff --git a/third_party/rust/smawk/dprint.json b/third_party/rust/smawk/dprint.json
new file mode 100644
index 0000000000..e48af5fe9d
--- /dev/null
+++ b/third_party/rust/smawk/dprint.json
@@ -0,0 +1,19 @@
+{
+ "markdown": {
+ "textWrap": "always"
+ },
+ "exec": {
+ "commands": [{
+ "command": "rustfmt",
+ "exts": ["rs"]
+ }]
+ },
+ "excludes": ["target/"],
+ "plugins": [
+ "https://plugins.dprint.dev/json-0.17.4.wasm",
+ "https://plugins.dprint.dev/markdown-0.16.1.wasm",
+ "https://plugins.dprint.dev/toml-0.5.4.wasm",
+ "https://plugins.dprint.dev/exec-0.4.3.json@42343548b8022c99b1d750be6b894fe6b6c7ee25f72ae9f9082226dd2e515072",
+ "https://plugins.dprint.dev/prettier-0.27.0.json@3557a62b4507c55a47d8cde0683195b14d13c41dda66d0f0b0e111aed107e2fe"
+ ]
+}
diff --git a/third_party/rust/smawk/rustfmt.toml b/third_party/rust/smawk/rustfmt.toml
new file mode 100644
index 0000000000..24e4ea784e
--- /dev/null
+++ b/third_party/rust/smawk/rustfmt.toml
@@ -0,0 +1,2 @@
+# Use rustfmt from the nightly channel for this:
+imports_granularity = "Module"
diff --git a/third_party/rust/smawk/src/brute_force.rs b/third_party/rust/smawk/src/brute_force.rs
new file mode 100644
index 0000000000..1ec0ca35a7
--- /dev/null
+++ b/third_party/rust/smawk/src/brute_force.rs
@@ -0,0 +1,150 @@
+//! Brute-force algorithm for finding column minima.
+//!
+//! The functions here are mostly meant to be used for testing
+//! correctness of the SMAWK implementation.
+//!
+//! **Note: this module is only available if you enable the `ndarray`
+//! Cargo feature.**
+
+use ndarray::{Array2, ArrayView1};
+
+/// Compute lane minimum by brute force.
+///
+/// This does a simple scan through the lane (row or column).
+#[inline]
+pub fn lane_minimum<T: Ord>(lane: ArrayView1<'_, T>) -> usize {
+ lane.iter()
+ .enumerate()
+ .min_by_key(|&(idx, elem)| (elem, idx))
+ .map(|(idx, _)| idx)
+ .expect("empty lane in matrix")
+}
+
+/// Compute row minima by brute force in O(*mn*) time.
+///
+/// This function implements a simple brute-force approach where each
+/// matrix row is scanned completely. This means that the function
+/// works on all matrices, not just Monge matrices.
+///
+/// # Examples
+///
+/// ```
+/// let matrix = ndarray::arr2(&[[4, 2, 4, 3],
+/// [5, 3, 5, 3],
+/// [5, 3, 3, 1]]);
+/// assert_eq!(smawk::brute_force::row_minima(&matrix),
+/// vec![1, 1, 3]);
+/// ```
+///
+/// # Panics
+///
+/// It is an error to call this on a matrix with zero columns.
+pub fn row_minima<T: Ord>(matrix: &Array2<T>) -> Vec<usize> {
+ matrix.rows().into_iter().map(lane_minimum).collect()
+}
+
+/// Compute column minima by brute force in O(*mn*) time.
+///
+/// This function implements a simple brute-force approach where each
+/// matrix column is scanned completely. This means that the function
+/// works on all matrices, not just Monge matrices.
+///
+/// # Examples
+///
+/// ```
+/// let matrix = ndarray::arr2(&[[4, 2, 4, 3],
+/// [5, 3, 5, 3],
+/// [5, 3, 3, 1]]);
+/// assert_eq!(smawk::brute_force::column_minima(&matrix),
+/// vec![0, 0, 2, 2]);
+/// ```
+///
+/// # Panics
+///
+/// It is an error to call this on a matrix with zero rows.
+pub fn column_minima<T: Ord>(matrix: &Array2<T>) -> Vec<usize> {
+ matrix.columns().into_iter().map(lane_minimum).collect()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use ndarray::arr2;
+
+ #[test]
+ fn brute_force_1x1() {
+ let matrix = arr2(&[[2]]);
+ let minima = vec![0];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+
+ #[test]
+ fn brute_force_2x1() {
+ let matrix = arr2(&[
+ [3], //
+ [2],
+ ]);
+ let minima = vec![0, 0];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+
+ #[test]
+ fn brute_force_1x2() {
+ let matrix = arr2(&[[2, 1]]);
+ let minima = vec![1];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+
+ #[test]
+ fn brute_force_2x2() {
+ let matrix = arr2(&[
+ [3, 2], //
+ [2, 1],
+ ]);
+ let minima = vec![1, 1];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+
+ #[test]
+ fn brute_force_3x3() {
+ let matrix = arr2(&[
+ [3, 4, 4], //
+ [3, 4, 4],
+ [2, 3, 3],
+ ]);
+ let minima = vec![0, 0, 0];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+
+ #[test]
+ fn brute_force_4x4() {
+ let matrix = arr2(&[
+ [4, 5, 5, 5], //
+ [2, 3, 3, 3],
+ [2, 3, 3, 3],
+ [2, 2, 2, 2],
+ ]);
+ let minima = vec![0, 0, 0, 0];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+
+ #[test]
+ fn brute_force_5x5() {
+ let matrix = arr2(&[
+ [3, 2, 4, 5, 6],
+ [2, 1, 3, 3, 4],
+ [2, 1, 3, 3, 4],
+ [3, 2, 4, 3, 4],
+ [4, 3, 2, 1, 1],
+ ]);
+ let minima = vec![1, 1, 1, 1, 3];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+}
diff --git a/third_party/rust/smawk/src/lib.rs b/third_party/rust/smawk/src/lib.rs
new file mode 100644
index 0000000000..367d0337dc
--- /dev/null
+++ b/third_party/rust/smawk/src/lib.rs
@@ -0,0 +1,570 @@
+//! This crate implements various functions that help speed up dynamic
+//! programming, most importantly the SMAWK algorithm for finding row
+//! or column minima in a totally monotone matrix with *m* rows and
+//! *n* columns in time O(*m* + *n*). This is much better than the
+//! brute force solution which would take O(*mn*). When *m* and *n*
+//! are of the same order, this turns a quadratic function into a
+//! linear function.
+//!
+//! # Examples
+//!
+//! Computing the column minima of an *m* ✕ *n* Monge matrix can be
+//! done efficiently with `smawk::column_minima`:
+//!
+//! ```
+//! use smawk::Matrix;
+//!
+//! let matrix = vec![
+//! vec![3, 2, 4, 5, 6],
+//! vec![2, 1, 3, 3, 4],
+//! vec![2, 1, 3, 3, 4],
+//! vec![3, 2, 4, 3, 4],
+//! vec![4, 3, 2, 1, 1],
+//! ];
+//! let minima = vec![1, 1, 4, 4, 4];
+//! assert_eq!(smawk::column_minima(&matrix), minima);
+//! ```
+//!
+//! The `minima` vector gives the index of the minimum value per
+//! column, so `minima[0] == 1` since the minimum value in the first
+//! column is 2 (row 1). Note that the smallest row index is returned.
+//!
+//! # Definitions
+//!
+//! Some of the functions in this crate only work on matrices that are
+//! *totally monotone*, which we will define below.
+//!
+//! ## Monotone Matrices
+//!
+//! We start with a helper definition. Given an *m* ✕ *n* matrix `M`,
+//! we say that `M` is *monotone* when the minimum value of row `i` is
+//! found to the left of the minimum value in row `i'` where `i < i'`.
+//!
+//! More formally, if we let `rm(i)` denote the column index of the
+//! left-most minimum value in row `i`, then we have
+//!
+//! ```text
+//! rm(0) ≤ rm(1) ≤ ... ≤ rm(m - 1)
+//! ```
+//!
+//! This means that as you go down the rows from top to bottom, the
+//! row-minima proceed from left to right.
+//!
+//! The algorithms in this crate deal with finding such row- and
+//! column-minima.
+//!
+//! ## Totally Monotone Matrices
+//!
+//! We say that a matrix `M` is *totally monotone* when every
+//! sub-matrix is monotone. A sub-matrix is formed by the intersection
+//! of any two rows `i < i'` and any two columns `j < j'`.
+//!
+//! This is often expressed as via this equivalent condition:
+//!
+//! ```text
+//! M[i, j] > M[i, j'] => M[i', j] > M[i', j']
+//! ```
+//!
+//! for all `i < i'` and `j < j'`.
+//!
+//! ## Monge Property for Matrices
+//!
+//! A matrix `M` is said to fulfill the *Monge property* if
+//!
+//! ```text
+//! M[i, j] + M[i', j'] ≤ M[i, j'] + M[i', j]
+//! ```
+//!
+//! for all `i < i'` and `j < j'`. This says that given any rectangle
+//! in the matrix, the sum of the top-left and bottom-right corners is
+//! less than or equal to the sum of the bottom-left and upper-right
+//! corners.
+//!
+//! All Monge matrices are totally monotone, so it is enough to
+//! establish that the Monge property holds in order to use a matrix
+//! with the functions in this crate. If your program is dealing with
+//! unknown inputs, it can use [`monge::is_monge`] to verify that a
+//! matrix is a Monge matrix.
+
+#![doc(html_root_url = "https://docs.rs/smawk/0.3.2")]
+// The s! macro from ndarray uses unsafe internally, so we can only
+// forbid unsafe code when building with the default features.
+#![cfg_attr(not(feature = "ndarray"), forbid(unsafe_code))]
+
+#[cfg(feature = "ndarray")]
+pub mod brute_force;
+pub mod monge;
+#[cfg(feature = "ndarray")]
+pub mod recursive;
+
+/// Minimal matrix trait for two-dimensional arrays.
+///
+/// This provides the functionality needed to represent a read-only
+/// numeric matrix. You can query the size of the matrix and access
+/// elements. Modeled after [`ndarray::Array2`] from the [ndarray
+/// crate](https://crates.io/crates/ndarray).
+///
+/// Enable the `ndarray` Cargo feature if you want to use it with
+/// `ndarray::Array2`.
+pub trait Matrix<T: Copy> {
+ /// Return the number of rows.
+ fn nrows(&self) -> usize;
+ /// Return the number of columns.
+ fn ncols(&self) -> usize;
+ /// Return a matrix element.
+ fn index(&self, row: usize, column: usize) -> T;
+}
+
+/// Simple and inefficient matrix representation used for doctest
+/// examples and simple unit tests.
+///
+/// You should prefer implementing it yourself, or you can enable the
+/// `ndarray` Cargo feature and use the provided implementation for
+/// [`ndarray::Array2`].
+impl<T: Copy> Matrix<T> for Vec<Vec<T>> {
+ fn nrows(&self) -> usize {
+ self.len()
+ }
+ fn ncols(&self) -> usize {
+ self[0].len()
+ }
+ fn index(&self, row: usize, column: usize) -> T {
+ self[row][column]
+ }
+}
+
+/// Adapting [`ndarray::Array2`] to the `Matrix` trait.
+///
+/// **Note: this implementation is only available if you enable the
+/// `ndarray` Cargo feature.**
+#[cfg(feature = "ndarray")]
+impl<T: Copy> Matrix<T> for ndarray::Array2<T> {
+ #[inline]
+ fn nrows(&self) -> usize {
+ self.nrows()
+ }
+ #[inline]
+ fn ncols(&self) -> usize {
+ self.ncols()
+ }
+ #[inline]
+ fn index(&self, row: usize, column: usize) -> T {
+ self[[row, column]]
+ }
+}
+
+/// Compute row minima in O(*m* + *n*) time.
+///
+/// This implements the [SMAWK algorithm] for efficiently finding row
+/// minima in a totally monotone matrix.
+///
+/// The SMAWK algorithm is from Agarwal, Klawe, Moran, Shor, and
+/// Wilbur, *Geometric applications of a matrix searching algorithm*,
+/// Algorithmica 2, pp. 195-208 (1987) and the code here is a
+/// translation [David Eppstein's Python code][pads].
+///
+/// Running time on an *m* ✕ *n* matrix: O(*m* + *n*).
+///
+/// # Examples
+///
+/// ```
+/// use smawk::Matrix;
+/// let matrix = vec![vec![4, 2, 4, 3],
+/// vec![5, 3, 5, 3],
+/// vec![5, 3, 3, 1]];
+/// assert_eq!(smawk::row_minima(&matrix),
+/// vec![1, 1, 3]);
+/// ```
+///
+/// # Panics
+///
+/// It is an error to call this on a matrix with zero columns.
+///
+/// [pads]: https://github.com/jfinkels/PADS/blob/master/pads/smawk.py
+/// [SMAWK algorithm]: https://en.wikipedia.org/wiki/SMAWK_algorithm
+pub fn row_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
+ // Benchmarking shows that SMAWK performs roughly the same on row-
+ // and column-major matrices.
+ let mut minima = vec![0; matrix.nrows()];
+ smawk_inner(
+ &|j, i| matrix.index(i, j),
+ &(0..matrix.ncols()).collect::<Vec<_>>(),
+ &(0..matrix.nrows()).collect::<Vec<_>>(),
+ &mut minima,
+ );
+ minima
+}
+
+#[deprecated(since = "0.3.2", note = "Please use `row_minima` instead.")]
+pub fn smawk_row_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
+ row_minima(matrix)
+}
+
+/// Compute column minima in O(*m* + *n*) time.
+///
+/// This implements the [SMAWK algorithm] for efficiently finding
+/// column minima in a totally monotone matrix.
+///
+/// The SMAWK algorithm is from Agarwal, Klawe, Moran, Shor, and
+/// Wilbur, *Geometric applications of a matrix searching algorithm*,
+/// Algorithmica 2, pp. 195-208 (1987) and the code here is a
+/// translation [David Eppstein's Python code][pads].
+///
+/// Running time on an *m* ✕ *n* matrix: O(*m* + *n*).
+///
+/// # Examples
+///
+/// ```
+/// use smawk::Matrix;
+/// let matrix = vec![vec![4, 2, 4, 3],
+/// vec![5, 3, 5, 3],
+/// vec![5, 3, 3, 1]];
+/// assert_eq!(smawk::column_minima(&matrix),
+/// vec![0, 0, 2, 2]);
+/// ```
+///
+/// # Panics
+///
+/// It is an error to call this on a matrix with zero rows.
+///
+/// [SMAWK algorithm]: https://en.wikipedia.org/wiki/SMAWK_algorithm
+/// [pads]: https://github.com/jfinkels/PADS/blob/master/pads/smawk.py
+pub fn column_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
+ let mut minima = vec![0; matrix.ncols()];
+ smawk_inner(
+ &|i, j| matrix.index(i, j),
+ &(0..matrix.nrows()).collect::<Vec<_>>(),
+ &(0..matrix.ncols()).collect::<Vec<_>>(),
+ &mut minima,
+ );
+ minima
+}
+
+#[deprecated(since = "0.3.2", note = "Please use `column_minima` instead.")]
+pub fn smawk_column_minima<T: PartialOrd + Copy, M: Matrix<T>>(matrix: &M) -> Vec<usize> {
+ column_minima(matrix)
+}
+
+/// Compute column minima in the given area of the matrix. The
+/// `minima` slice is updated inplace.
+fn smawk_inner<T: PartialOrd + Copy, M: Fn(usize, usize) -> T>(
+ matrix: &M,
+ rows: &[usize],
+ cols: &[usize],
+ minima: &mut [usize],
+) {
+ if cols.is_empty() {
+ return;
+ }
+
+ let mut stack = Vec::with_capacity(cols.len());
+ for r in rows {
+ // TODO: use stack.last() instead of stack.is_empty() etc
+ while !stack.is_empty()
+ && matrix(stack[stack.len() - 1], cols[stack.len() - 1])
+ > matrix(*r, cols[stack.len() - 1])
+ {
+ stack.pop();
+ }
+ if stack.len() != cols.len() {
+ stack.push(*r);
+ }
+ }
+ let rows = &stack;
+
+ let mut odd_cols = Vec::with_capacity(1 + cols.len() / 2);
+ for (idx, c) in cols.iter().enumerate() {
+ if idx % 2 == 1 {
+ odd_cols.push(*c);
+ }
+ }
+
+ smawk_inner(matrix, rows, &odd_cols, minima);
+
+ let mut r = 0;
+ for (c, &col) in cols.iter().enumerate().filter(|(c, _)| c % 2 == 0) {
+ let mut row = rows[r];
+ let last_row = if c == cols.len() - 1 {
+ rows[rows.len() - 1]
+ } else {
+ minima[cols[c + 1]]
+ };
+ let mut pair = (matrix(row, col), row);
+ while row != last_row {
+ r += 1;
+ row = rows[r];
+ if (matrix(row, col), row) < pair {
+ pair = (matrix(row, col), row);
+ }
+ }
+ minima[col] = pair.1;
+ }
+}
+
+/// Compute upper-right column minima in O(*m* + *n*) time.
+///
+/// The input matrix must be totally monotone.
+///
+/// The function returns a vector of `(usize, T)`. The `usize` in the
+/// tuple at index `j` tells you the row of the minimum value in
+/// column `j` and the `T` value is minimum value itself.
+///
+/// The algorithm only considers values above the main diagonal, which
+/// means that it computes values `v(j)` where:
+///
+/// ```text
+/// v(0) = initial
+/// v(j) = min { M[i, j] | i < j } for j > 0
+/// ```
+///
+/// If we let `r(j)` denote the row index of the minimum value in
+/// column `j`, the tuples in the result vector become `(r(j), M[r(j),
+/// j])`.
+///
+/// The algorithm is an *online* algorithm, in the sense that `matrix`
+/// function can refer back to previously computed column minima when
+/// determining an entry in the matrix. The guarantee is that we only
+/// call `matrix(i, j)` after having computed `v(i)`. This is
+/// reflected in the `&[(usize, T)]` argument to `matrix`, which grows
+/// as more and more values are computed.
+pub fn online_column_minima<T: Copy + PartialOrd, M: Fn(&[(usize, T)], usize, usize) -> T>(
+ initial: T,
+ size: usize,
+ matrix: M,
+) -> Vec<(usize, T)> {
+ let mut result = vec![(0, initial)];
+
+ // State used by the algorithm.
+ let mut finished = 0;
+ let mut base = 0;
+ let mut tentative = 0;
+
+ // Shorthand for evaluating the matrix. We need a macro here since
+ // we don't want to borrow the result vector.
+ macro_rules! m {
+ ($i:expr, $j:expr) => {{
+ assert!($i < $j, "(i, j) not above diagonal: ({}, {})", $i, $j);
+ assert!(
+ $i < size && $j < size,
+ "(i, j) out of bounds: ({}, {}), size: {}",
+ $i,
+ $j,
+ size
+ );
+ matrix(&result[..finished + 1], $i, $j)
+ }};
+ }
+
+ // Keep going until we have finished all size columns. Since the
+ // columns are zero-indexed, we're done when finished == size - 1.
+ while finished < size - 1 {
+ // First case: we have already advanced past the previous
+ // tentative value. We make a new tentative value by applying
+ // smawk_inner to the largest square submatrix that fits under
+ // the base.
+ let i = finished + 1;
+ if i > tentative {
+ let rows = (base..finished + 1).collect::<Vec<_>>();
+ tentative = std::cmp::min(finished + rows.len(), size - 1);
+ let cols = (finished + 1..tentative + 1).collect::<Vec<_>>();
+ let mut minima = vec![0; tentative + 1];
+ smawk_inner(&|i, j| m![i, j], &rows, &cols, &mut minima);
+ for col in cols {
+ let row = minima[col];
+ let v = m![row, col];
+ if col >= result.len() {
+ result.push((row, v));
+ } else if v < result[col].1 {
+ result[col] = (row, v);
+ }
+ }
+ finished = i;
+ continue;
+ }
+
+ // Second case: the new column minimum is on the diagonal. All
+ // subsequent ones will be at least as low, so we can clear
+ // out all our work from higher rows. As in the fourth case,
+ // the loss of tentative is amortized against the increase in
+ // base.
+ let diag = m![i - 1, i];
+ if diag < result[i].1 {
+ result[i] = (i - 1, diag);
+ base = i - 1;
+ tentative = i;
+ finished = i;
+ continue;
+ }
+
+ // Third case: row i-1 does not supply a column minimum in any
+ // column up to tentative. We simply advance finished while
+ // maintaining the invariant.
+ if m![i - 1, tentative] >= result[tentative].1 {
+ finished = i;
+ continue;
+ }
+
+ // Fourth and final case: a new column minimum at tentative.
+ // This allows us to make progress by incorporating rows prior
+ // to finished into the base. The base invariant holds because
+ // these rows cannot supply any later column minima. The work
+ // done when we last advanced tentative (and undone by this
+ // step) can be amortized against the increase in base.
+ base = i - 1;
+ tentative = i;
+ finished = i;
+ }
+
+ result
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn smawk_1x1() {
+ let matrix = vec![vec![2]];
+ assert_eq!(row_minima(&matrix), vec![0]);
+ assert_eq!(column_minima(&matrix), vec![0]);
+ }
+
+ #[test]
+ fn smawk_2x1() {
+ let matrix = vec![
+ vec![3], //
+ vec![2],
+ ];
+ assert_eq!(row_minima(&matrix), vec![0, 0]);
+ assert_eq!(column_minima(&matrix), vec![1]);
+ }
+
+ #[test]
+ fn smawk_1x2() {
+ let matrix = vec![vec![2, 1]];
+ assert_eq!(row_minima(&matrix), vec![1]);
+ assert_eq!(column_minima(&matrix), vec![0, 0]);
+ }
+
+ #[test]
+ fn smawk_2x2() {
+ let matrix = vec![
+ vec![3, 2], //
+ vec![2, 1],
+ ];
+ assert_eq!(row_minima(&matrix), vec![1, 1]);
+ assert_eq!(column_minima(&matrix), vec![1, 1]);
+ }
+
+ #[test]
+ fn smawk_3x3() {
+ let matrix = vec![
+ vec![3, 4, 4], //
+ vec![3, 4, 4],
+ vec![2, 3, 3],
+ ];
+ assert_eq!(row_minima(&matrix), vec![0, 0, 0]);
+ assert_eq!(column_minima(&matrix), vec![2, 2, 2]);
+ }
+
+ #[test]
+ fn smawk_4x4() {
+ let matrix = vec![
+ vec![4, 5, 5, 5], //
+ vec![2, 3, 3, 3],
+ vec![2, 3, 3, 3],
+ vec![2, 2, 2, 2],
+ ];
+ assert_eq!(row_minima(&matrix), vec![0, 0, 0, 0]);
+ assert_eq!(column_minima(&matrix), vec![1, 3, 3, 3]);
+ }
+
+ #[test]
+ fn smawk_5x5() {
+ let matrix = vec![
+ vec![3, 2, 4, 5, 6],
+ vec![2, 1, 3, 3, 4],
+ vec![2, 1, 3, 3, 4],
+ vec![3, 2, 4, 3, 4],
+ vec![4, 3, 2, 1, 1],
+ ];
+ assert_eq!(row_minima(&matrix), vec![1, 1, 1, 1, 3]);
+ assert_eq!(column_minima(&matrix), vec![1, 1, 4, 4, 4]);
+ }
+
+ #[test]
+ fn online_1x1() {
+ let matrix = vec![vec![0]];
+ let minima = vec![(0, 0)];
+ assert_eq!(online_column_minima(0, 1, |_, i, j| matrix[i][j]), minima);
+ }
+
+ #[test]
+ fn online_2x2() {
+ let matrix = vec![
+ vec![0, 2], //
+ vec![0, 0],
+ ];
+ let minima = vec![(0, 0), (0, 2)];
+ assert_eq!(online_column_minima(0, 2, |_, i, j| matrix[i][j]), minima);
+ }
+
+ #[test]
+ fn online_3x3() {
+ let matrix = vec![
+ vec![0, 4, 4], //
+ vec![0, 0, 4],
+ vec![0, 0, 0],
+ ];
+ let minima = vec![(0, 0), (0, 4), (0, 4)];
+ assert_eq!(online_column_minima(0, 3, |_, i, j| matrix[i][j]), minima);
+ }
+
+ #[test]
+ fn online_4x4() {
+ let matrix = vec![
+ vec![0, 5, 5, 5], //
+ vec![0, 0, 3, 3],
+ vec![0, 0, 0, 3],
+ vec![0, 0, 0, 0],
+ ];
+ let minima = vec![(0, 0), (0, 5), (1, 3), (1, 3)];
+ assert_eq!(online_column_minima(0, 4, |_, i, j| matrix[i][j]), minima);
+ }
+
+ #[test]
+ fn online_5x5() {
+ let matrix = vec![
+ vec![0, 2, 4, 6, 7],
+ vec![0, 0, 3, 4, 5],
+ vec![0, 0, 0, 3, 4],
+ vec![0, 0, 0, 0, 4],
+ vec![0, 0, 0, 0, 0],
+ ];
+ let minima = vec![(0, 0), (0, 2), (1, 3), (2, 3), (2, 4)];
+ assert_eq!(online_column_minima(0, 5, |_, i, j| matrix[i][j]), minima);
+ }
+
+ #[test]
+ fn smawk_works_with_partial_ord() {
+ let matrix = vec![
+ vec![3.0, 2.0], //
+ vec![2.0, 1.0],
+ ];
+ assert_eq!(row_minima(&matrix), vec![1, 1]);
+ assert_eq!(column_minima(&matrix), vec![1, 1]);
+ }
+
+ #[test]
+ fn online_works_with_partial_ord() {
+ let matrix = vec![
+ vec![0.0, 2.0], //
+ vec![0.0, 0.0],
+ ];
+ let minima = vec![(0, 0.0), (0, 2.0)];
+ assert_eq!(
+ online_column_minima(0.0, 2, |_, i: usize, j: usize| matrix[i][j]),
+ minima
+ );
+ }
+}
diff --git a/third_party/rust/smawk/src/monge.rs b/third_party/rust/smawk/src/monge.rs
new file mode 100644
index 0000000000..dbc80e1517
--- /dev/null
+++ b/third_party/rust/smawk/src/monge.rs
@@ -0,0 +1,121 @@
+//! Functions for generating and checking Monge arrays.
+//!
+//! The functions here are mostly meant to be used for testing
+//! correctness of the SMAWK implementation.
+
+use crate::Matrix;
+use std::num::Wrapping;
+use std::ops::Add;
+
+/// Verify that a matrix is a Monge matrix.
+///
+/// A [Monge matrix] \(or array) is a matrix where the following
+/// inequality holds:
+///
+/// ```text
+/// M[i, j] + M[i', j'] <= M[i, j'] + M[i', j] for all i < i', j < j'
+/// ```
+///
+/// The inequality says that the sum of the main diagonal is less than
+/// the sum of the antidiagonal. Checking this condition is done by
+/// checking *n* ✕ *m* submatrices, so the running time is O(*mn*).
+///
+/// [Monge matrix]: https://en.wikipedia.org/wiki/Monge_array
+pub fn is_monge<T: Ord + Copy, M: Matrix<T>>(matrix: &M) -> bool
+where
+ Wrapping<T>: Add<Output = Wrapping<T>>,
+{
+ /// Returns `Ok(a + b)` if the computation can be done without
+ /// overflow, otherwise `Err(a + b - T::MAX - 1)` is returned.
+ fn checked_add<T: Ord + Copy>(a: Wrapping<T>, b: Wrapping<T>) -> Result<T, T>
+ where
+ Wrapping<T>: Add<Output = Wrapping<T>>,
+ {
+ let sum = a + b;
+ if sum < a {
+ Err(sum.0)
+ } else {
+ Ok(sum.0)
+ }
+ }
+
+ (0..matrix.nrows() - 1)
+ .flat_map(|row| (0..matrix.ncols() - 1).map(move |col| (row, col)))
+ .all(|(row, col)| {
+ let top_left = Wrapping(matrix.index(row, col));
+ let top_right = Wrapping(matrix.index(row, col + 1));
+ let bot_left = Wrapping(matrix.index(row + 1, col));
+ let bot_right = Wrapping(matrix.index(row + 1, col + 1));
+
+ match (
+ checked_add(top_left, bot_right),
+ checked_add(bot_left, top_right),
+ ) {
+ (Ok(a), Ok(b)) => a <= b, // No overflow.
+ (Err(a), Err(b)) => a <= b, // Double overflow.
+ (Ok(_), Err(_)) => true, // Anti-diagonal overflow.
+ (Err(_), Ok(_)) => false, // Main diagonal overflow.
+ }
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn is_monge_handles_overflow() {
+ // The x + y <= z + w computations will overflow for an u8
+ // matrix unless is_monge is careful.
+ let matrix: Vec<Vec<u8>> = vec![
+ vec![200, 200, 200, 200],
+ vec![200, 200, 200, 200],
+ vec![200, 200, 200, 200],
+ ];
+ assert!(is_monge(&matrix));
+ }
+
+ #[test]
+ fn monge_constant_rows() {
+ let matrix = vec![
+ vec![42, 42, 42, 42],
+ vec![0, 0, 0, 0],
+ vec![100, 100, 100, 100],
+ vec![1000, 1000, 1000, 1000],
+ ];
+ assert!(is_monge(&matrix));
+ }
+
+ #[test]
+ fn monge_constant_cols() {
+ let matrix = vec![
+ vec![42, 0, 100, 1000],
+ vec![42, 0, 100, 1000],
+ vec![42, 0, 100, 1000],
+ vec![42, 0, 100, 1000],
+ ];
+ assert!(is_monge(&matrix));
+ }
+
+ #[test]
+ fn monge_upper_right() {
+ let matrix = vec![
+ vec![10, 10, 42, 42, 42],
+ vec![10, 10, 42, 42, 42],
+ vec![10, 10, 10, 10, 10],
+ vec![10, 10, 10, 10, 10],
+ ];
+ assert!(is_monge(&matrix));
+ }
+
+ #[test]
+ fn monge_lower_left() {
+ let matrix = vec![
+ vec![10, 10, 10, 10, 10],
+ vec![10, 10, 10, 10, 10],
+ vec![42, 42, 42, 10, 10],
+ vec![42, 42, 42, 10, 10],
+ ];
+ assert!(is_monge(&matrix));
+ }
+}
diff --git a/third_party/rust/smawk/src/recursive.rs b/third_party/rust/smawk/src/recursive.rs
new file mode 100644
index 0000000000..9df8b9c824
--- /dev/null
+++ b/third_party/rust/smawk/src/recursive.rs
@@ -0,0 +1,191 @@
+//! Recursive algorithm for finding column minima.
+//!
+//! The functions here are mostly meant to be used for testing
+//! correctness of the SMAWK implementation.
+//!
+//! **Note: this module is only available if you enable the `ndarray`
+//! Cargo feature.**
+
+use ndarray::{s, Array2, ArrayView2, Axis};
+
+/// Compute row minima in O(*m* + *n* log *m*) time.
+///
+/// This function computes row minima in a totally monotone matrix
+/// using a recursive algorithm.
+///
+/// Running time on an *m* ✕ *n* matrix: O(*m* + *n* log *m*).
+///
+/// # Examples
+///
+/// ```
+/// let matrix = ndarray::arr2(&[[4, 2, 4, 3],
+/// [5, 3, 5, 3],
+/// [5, 3, 3, 1]]);
+/// assert_eq!(smawk::recursive::row_minima(&matrix),
+/// vec![1, 1, 3]);
+/// ```
+///
+/// # Panics
+///
+/// It is an error to call this on a matrix with zero columns.
+pub fn row_minima<T: Ord>(matrix: &Array2<T>) -> Vec<usize> {
+ let mut minima = vec![0; matrix.nrows()];
+ recursive_inner(matrix.view(), &|| Direction::Row, 0, &mut minima);
+ minima
+}
+
+/// Compute column minima in O(*n* + *m* log *n*) time.
+///
+/// This function computes column minima in a totally monotone matrix
+/// using a recursive algorithm.
+///
+/// Running time on an *m* ✕ *n* matrix: O(*n* + *m* log *n*).
+///
+/// # Examples
+///
+/// ```
+/// let matrix = ndarray::arr2(&[[4, 2, 4, 3],
+/// [5, 3, 5, 3],
+/// [5, 3, 3, 1]]);
+/// assert_eq!(smawk::recursive::column_minima(&matrix),
+/// vec![0, 0, 2, 2]);
+/// ```
+///
+/// # Panics
+///
+/// It is an error to call this on a matrix with zero rows.
+pub fn column_minima<T: Ord>(matrix: &Array2<T>) -> Vec<usize> {
+ let mut minima = vec![0; matrix.ncols()];
+ recursive_inner(matrix.view(), &|| Direction::Column, 0, &mut minima);
+ minima
+}
+
+/// The type of minima (row or column) we compute.
+enum Direction {
+ Row,
+ Column,
+}
+
+/// Compute the minima along the given direction (`Direction::Row` for
+/// row minima and `Direction::Column` for column minima).
+///
+/// The direction is given as a generic function argument to allow
+/// monomorphization to kick in. The function calls will be inlined
+/// and optimized away and the result is that the compiler generates
+/// differnet code for finding row and column minima.
+fn recursive_inner<T: Ord, F: Fn() -> Direction>(
+ matrix: ArrayView2<'_, T>,
+ dir: &F,
+ offset: usize,
+ minima: &mut [usize],
+) {
+ if matrix.is_empty() {
+ return;
+ }
+
+ let axis = match dir() {
+ Direction::Row => Axis(0),
+ Direction::Column => Axis(1),
+ };
+ let mid = matrix.len_of(axis) / 2;
+ let min_idx = crate::brute_force::lane_minimum(matrix.index_axis(axis, mid));
+ minima[mid] = offset + min_idx;
+
+ if mid == 0 {
+ return; // Matrix has a single row or column, so we're done.
+ }
+
+ let top_left = match dir() {
+ Direction::Row => matrix.slice(s![..mid, ..(min_idx + 1)]),
+ Direction::Column => matrix.slice(s![..(min_idx + 1), ..mid]),
+ };
+ let bot_right = match dir() {
+ Direction::Row => matrix.slice(s![(mid + 1).., min_idx..]),
+ Direction::Column => matrix.slice(s![min_idx.., (mid + 1)..]),
+ };
+ recursive_inner(top_left, dir, offset, &mut minima[..mid]);
+ recursive_inner(bot_right, dir, offset + min_idx, &mut minima[mid + 1..]);
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use ndarray::arr2;
+
+ #[test]
+ fn recursive_1x1() {
+ let matrix = arr2(&[[2]]);
+ let minima = vec![0];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+
+ #[test]
+ fn recursive_2x1() {
+ let matrix = arr2(&[
+ [3], //
+ [2],
+ ]);
+ let minima = vec![0, 0];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+
+ #[test]
+ fn recursive_1x2() {
+ let matrix = arr2(&[[2, 1]]);
+ let minima = vec![1];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+
+ #[test]
+ fn recursive_2x2() {
+ let matrix = arr2(&[
+ [3, 2], //
+ [2, 1],
+ ]);
+ let minima = vec![1, 1];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+
+ #[test]
+ fn recursive_3x3() {
+ let matrix = arr2(&[
+ [3, 4, 4], //
+ [3, 4, 4],
+ [2, 3, 3],
+ ]);
+ let minima = vec![0, 0, 0];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+
+ #[test]
+ fn recursive_4x4() {
+ let matrix = arr2(&[
+ [4, 5, 5, 5], //
+ [2, 3, 3, 3],
+ [2, 3, 3, 3],
+ [2, 2, 2, 2],
+ ]);
+ let minima = vec![0, 0, 0, 0];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+
+ #[test]
+ fn recursive_5x5() {
+ let matrix = arr2(&[
+ [3, 2, 4, 5, 6],
+ [2, 1, 3, 3, 4],
+ [2, 1, 3, 3, 4],
+ [3, 2, 4, 3, 4],
+ [4, 3, 2, 1, 1],
+ ]);
+ let minima = vec![1, 1, 1, 1, 3];
+ assert_eq!(row_minima(&matrix), minima);
+ assert_eq!(column_minima(&matrix.reversed_axes()), minima);
+ }
+}
diff --git a/third_party/rust/smawk/tests/agreement.rs b/third_party/rust/smawk/tests/agreement.rs
new file mode 100644
index 0000000000..2e0343a59a
--- /dev/null
+++ b/third_party/rust/smawk/tests/agreement.rs
@@ -0,0 +1,104 @@
+#![cfg(feature = "ndarray")]
+
+use ndarray::{s, Array2};
+use rand::SeedableRng;
+use rand_chacha::ChaCha20Rng;
+use smawk::{brute_force, online_column_minima, recursive};
+
+mod random_monge;
+use random_monge::random_monge_matrix;
+
+/// Check that the brute force, recursive, and SMAWK functions
+/// give identical results on a large number of randomly generated
+/// Monge matrices.
+#[test]
+fn column_minima_agree() {
+ let sizes = vec![1, 2, 3, 4, 5, 10, 15, 20, 30];
+ let mut rng = ChaCha20Rng::seed_from_u64(0);
+ for _ in 0..4 {
+ for m in sizes.clone().iter() {
+ for n in sizes.clone().iter() {
+ let matrix: Array2<i32> = random_monge_matrix(*m, *n, &mut rng);
+
+ // Compute and test row minima.
+ let brute_force = brute_force::row_minima(&matrix);
+ let recursive = recursive::row_minima(&matrix);
+ let smawk = smawk::row_minima(&matrix);
+ assert_eq!(
+ brute_force, recursive,
+ "recursive and brute force differs on:\n{:?}",
+ matrix
+ );
+ assert_eq!(
+ brute_force, smawk,
+ "SMAWK and brute force differs on:\n{:?}",
+ matrix
+ );
+
+ // Do the same for the column minima.
+ let brute_force = brute_force::column_minima(&matrix);
+ let recursive = recursive::column_minima(&matrix);
+ let smawk = smawk::column_minima(&matrix);
+ assert_eq!(
+ brute_force, recursive,
+ "recursive and brute force differs on:\n{:?}",
+ matrix
+ );
+ assert_eq!(
+ brute_force, smawk,
+ "SMAWK and brute force differs on:\n{:?}",
+ matrix
+ );
+ }
+ }
+ }
+}
+
+/// Check that the brute force and online SMAWK functions give
+/// identical results on a large number of randomly generated
+/// Monge matrices.
+#[test]
+fn online_agree() {
+ let sizes = vec![1, 2, 3, 4, 5, 10, 15, 20, 30, 50];
+ let mut rng = ChaCha20Rng::seed_from_u64(0);
+ for _ in 0..5 {
+ for &size in &sizes {
+ // Random totally monotone square matrix of the
+ // desired size.
+ let mut matrix: Array2<i32> = random_monge_matrix(size, size, &mut rng);
+
+ // Adjust matrix so the column minima are above the
+ // diagonal. The brute_force::column_minima will still
+ // work just fine on such a mangled Monge matrix.
+ let max = *matrix.iter().max().unwrap_or(&0);
+ for idx in 0..(size as isize) {
+ // Using the maximum value of the matrix instead
+ // of i32::max_value() makes for prettier matrices
+ // in case we want to print them.
+ matrix.slice_mut(s![idx..idx + 1, ..idx + 1]).fill(max);
+ }
+
+ // The online algorithm always returns the initial
+ // value for the left-most column -- without
+ // inspecting the column at all. So we fill the
+ // left-most column with this value to have the brute
+ // force algorithm do the same.
+ let initial = 42;
+ matrix.slice_mut(s![0.., ..1]).fill(initial);
+
+ // Brute-force computation of column minima, returned
+ // in the same form as online_column_minima.
+ let brute_force = brute_force::column_minima(&matrix)
+ .iter()
+ .enumerate()
+ .map(|(j, &i)| (i, matrix[[i, j]]))
+ .collect::<Vec<_>>();
+ let online = online_column_minima(initial, size, |_, i, j| matrix[[i, j]]);
+ assert_eq!(
+ brute_force, online,
+ "brute force and online differ on:\n{:3?}",
+ matrix
+ );
+ }
+ }
+}
diff --git a/third_party/rust/smawk/tests/complexity.rs b/third_party/rust/smawk/tests/complexity.rs
new file mode 100644
index 0000000000..c9881eaeac
--- /dev/null
+++ b/third_party/rust/smawk/tests/complexity.rs
@@ -0,0 +1,83 @@
+#![cfg(feature = "ndarray")]
+
+use ndarray::{Array1, Array2};
+use rand::SeedableRng;
+use rand_chacha::ChaCha20Rng;
+use smawk::online_column_minima;
+
+mod random_monge;
+use random_monge::random_monge_matrix;
+
+#[derive(Debug)]
+struct LinRegression {
+ alpha: f64,
+ beta: f64,
+ r_squared: f64,
+}
+
+/// Square an expression. Works equally well for floats and matrices.
+macro_rules! squared {
+ ($x:expr) => {
+ $x * $x
+ };
+}
+
+/// Compute the mean of a 1-dimensional array.
+macro_rules! mean {
+ ($a:expr) => {
+ $a.mean().expect("Mean of empty array")
+ };
+}
+
+/// Compute a simple linear regression from the list of values.
+///
+/// See <https://en.wikipedia.org/wiki/Simple_linear_regression>.
+fn linear_regression(values: &[(usize, i32)]) -> LinRegression {
+ let xs = values.iter().map(|&(x, _)| x as f64).collect::<Array1<_>>();
+ let ys = values.iter().map(|&(_, y)| y as f64).collect::<Array1<_>>();
+
+ let xs_mean = mean!(&xs);
+ let ys_mean = mean!(&ys);
+ let xs_ys_mean = mean!(&xs * &ys);
+
+ let cov_xs_ys = ((&xs - xs_mean) * (&ys - ys_mean)).sum();
+ let var_xs = squared!(&xs - xs_mean).sum();
+
+ let beta = cov_xs_ys / var_xs;
+ let alpha = ys_mean - beta * xs_mean;
+ let r_squared = squared!(xs_ys_mean - xs_mean * ys_mean)
+ / ((mean!(&xs * &xs) - squared!(xs_mean)) * (mean!(&ys * &ys) - squared!(ys_mean)));
+
+ LinRegression {
+ alpha: alpha,
+ beta: beta,
+ r_squared: r_squared,
+ }
+}
+
+/// Check that the number of matrix accesses in `online_column_minima`
+/// grows as O(*n*) for *n* ✕ *n* matrix.
+#[test]
+fn online_linear_complexity() {
+ let mut rng = ChaCha20Rng::seed_from_u64(0);
+ let mut data = vec![];
+
+ for &size in &[1, 2, 3, 4, 5, 10, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100] {
+ let matrix: Array2<i32> = random_monge_matrix(size, size, &mut rng);
+ let count = std::cell::RefCell::new(0);
+ online_column_minima(0, size, |_, i, j| {
+ *count.borrow_mut() += 1;
+ matrix[[i, j]]
+ });
+ data.push((size, count.into_inner()));
+ }
+
+ let lin_reg = linear_regression(&data);
+ assert!(
+ lin_reg.r_squared > 0.95,
+ "r² = {:.4} is lower than expected for a linear fit\nData points: {:?}\n{:?}",
+ lin_reg.r_squared,
+ data,
+ lin_reg
+ );
+}
diff --git a/third_party/rust/smawk/tests/monge.rs b/third_party/rust/smawk/tests/monge.rs
new file mode 100644
index 0000000000..67058a75a5
--- /dev/null
+++ b/third_party/rust/smawk/tests/monge.rs
@@ -0,0 +1,83 @@
+#![cfg(feature = "ndarray")]
+
+use ndarray::{arr2, Array, Array2};
+use rand::SeedableRng;
+use rand_chacha::ChaCha20Rng;
+use smawk::monge::is_monge;
+
+mod random_monge;
+use random_monge::{random_monge_matrix, MongePrim};
+
+#[test]
+fn random_monge() {
+ let mut rng = ChaCha20Rng::seed_from_u64(0);
+ let matrix: Array2<u8> = random_monge_matrix(5, 5, &mut rng);
+
+ assert!(is_monge(&matrix));
+ assert_eq!(
+ matrix,
+ arr2(&[
+ [2, 3, 4, 4, 5],
+ [5, 5, 6, 6, 7],
+ [3, 3, 4, 4, 5],
+ [5, 2, 3, 3, 4],
+ [5, 2, 3, 3, 4]
+ ])
+ );
+}
+
+#[test]
+fn monge_constant_rows() {
+ let mut rng = ChaCha20Rng::seed_from_u64(0);
+ let matrix: Array2<u8> = MongePrim::ConstantRows.to_matrix(5, 4, &mut rng);
+ assert!(is_monge(&matrix));
+ for row in matrix.rows() {
+ let elem = row[0];
+ assert_eq!(row, Array::from_elem(matrix.ncols(), elem));
+ }
+}
+
+#[test]
+fn monge_constant_cols() {
+ let mut rng = ChaCha20Rng::seed_from_u64(0);
+ let matrix: Array2<u8> = MongePrim::ConstantCols.to_matrix(5, 4, &mut rng);
+ assert!(is_monge(&matrix));
+ for column in matrix.columns() {
+ let elem = column[0];
+ assert_eq!(column, Array::from_elem(matrix.nrows(), elem));
+ }
+}
+
+#[test]
+fn monge_upper_right_ones() {
+ let mut rng = ChaCha20Rng::seed_from_u64(1);
+ let matrix: Array2<u8> = MongePrim::UpperRightOnes.to_matrix(5, 4, &mut rng);
+ assert!(is_monge(&matrix));
+ assert_eq!(
+ matrix,
+ arr2(&[
+ [0, 0, 1, 1],
+ [0, 0, 1, 1],
+ [0, 0, 1, 1],
+ [0, 0, 0, 0],
+ [0, 0, 0, 0]
+ ])
+ );
+}
+
+#[test]
+fn monge_lower_left_ones() {
+ let mut rng = ChaCha20Rng::seed_from_u64(1);
+ let matrix: Array2<u8> = MongePrim::LowerLeftOnes.to_matrix(5, 4, &mut rng);
+ assert!(is_monge(&matrix));
+ assert_eq!(
+ matrix,
+ arr2(&[
+ [0, 0, 0, 0],
+ [0, 0, 0, 0],
+ [1, 1, 0, 0],
+ [1, 1, 0, 0],
+ [1, 1, 0, 0]
+ ])
+ );
+}
diff --git a/third_party/rust/smawk/tests/random_monge/mod.rs b/third_party/rust/smawk/tests/random_monge/mod.rs
new file mode 100644
index 0000000000..50a932fbeb
--- /dev/null
+++ b/third_party/rust/smawk/tests/random_monge/mod.rs
@@ -0,0 +1,83 @@
+//! Test functionality for generating random Monge matrices.
+
+// The code is put here so we can reuse it in different integration
+// tests, without Cargo finding it when `cargo test` is run. See the
+// section on "Submodules in Integration Tests" in
+// https://doc.rust-lang.org/book/ch11-03-test-organization.html
+
+use ndarray::{s, Array2};
+use num_traits::PrimInt;
+use rand::distributions::{Distribution, Standard};
+use rand::Rng;
+
+/// A Monge matrix can be decomposed into one of these primitive
+/// building blocks.
+#[derive(Copy, Clone)]
+pub enum MongePrim {
+ ConstantRows,
+ ConstantCols,
+ UpperRightOnes,
+ LowerLeftOnes,
+}
+
+impl MongePrim {
+ /// Generate a Monge matrix from a primitive.
+ pub fn to_matrix<T: PrimInt, R: Rng>(&self, m: usize, n: usize, rng: &mut R) -> Array2<T>
+ where
+ Standard: Distribution<T>,
+ {
+ let mut matrix = Array2::from_elem((m, n), T::zero());
+ // Avoid panic in UpperRightOnes and LowerLeftOnes below.
+ if m == 0 || n == 0 {
+ return matrix;
+ }
+
+ match *self {
+ MongePrim::ConstantRows => {
+ for mut row in matrix.rows_mut() {
+ if rng.gen::<bool>() {
+ row.fill(T::one())
+ }
+ }
+ }
+ MongePrim::ConstantCols => {
+ for mut col in matrix.columns_mut() {
+ if rng.gen::<bool>() {
+ col.fill(T::one())
+ }
+ }
+ }
+ MongePrim::UpperRightOnes => {
+ let i = rng.gen_range(0..(m + 1) as isize);
+ let j = rng.gen_range(0..(n + 1) as isize);
+ matrix.slice_mut(s![..i, -j..]).fill(T::one());
+ }
+ MongePrim::LowerLeftOnes => {
+ let i = rng.gen_range(0..(m + 1) as isize);
+ let j = rng.gen_range(0..(n + 1) as isize);
+ matrix.slice_mut(s![-i.., ..j]).fill(T::one());
+ }
+ }
+
+ matrix
+ }
+}
+
+/// Generate a random Monge matrix.
+pub fn random_monge_matrix<R: Rng, T: PrimInt>(m: usize, n: usize, rng: &mut R) -> Array2<T>
+where
+ Standard: Distribution<T>,
+{
+ let monge_primitives = [
+ MongePrim::ConstantRows,
+ MongePrim::ConstantCols,
+ MongePrim::LowerLeftOnes,
+ MongePrim::UpperRightOnes,
+ ];
+ let mut matrix = Array2::from_elem((m, n), T::zero());
+ for _ in 0..(m + n) {
+ let monge = monge_primitives[rng.gen_range(0..monge_primitives.len())];
+ matrix = matrix + monge.to_matrix(m, n, rng);
+ }
+ matrix
+}
diff --git a/third_party/rust/smawk/tests/version-numbers.rs b/third_party/rust/smawk/tests/version-numbers.rs
new file mode 100644
index 0000000000..288592d02f
--- /dev/null
+++ b/third_party/rust/smawk/tests/version-numbers.rs
@@ -0,0 +1,9 @@
+#[test]
+fn test_readme_deps() {
+ version_sync::assert_markdown_deps_updated!("README.md");
+}
+
+#[test]
+fn test_html_root_url() {
+ version_sync::assert_html_root_url_updated!("src/lib.rs");
+}
diff --git a/third_party/rust/sql-support/.cargo-checksum.json b/third_party/rust/sql-support/.cargo-checksum.json
index bbfb487f7f..93cef6e94d 100644
--- a/third_party/rust/sql-support/.cargo-checksum.json
+++ b/third_party/rust/sql-support/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"812811e5a8e00abe3ec345cd8fd435e27fec7cb8f2e45a0e93e5becf564c46ad","src/conn_ext.rs":"e48e862e47c000c545dcc766fc1889498a8709bee00e240ed68d247b0fbef577","src/debug_tools.rs":"bece2bc3d35379b81ea2f942a0a3e909e0ab0553656505904745548eacaf402a","src/each_chunk.rs":"8aaba842e43b002fbc0fee95d14ce08faa7187b1979c765b2e270cd4802607a5","src/lib.rs":"af704ec04beb6c2c388d4566710e1167b18fb64acb248ccf37a67679daffddb6","src/maybe_cached.rs":"0b18425595055883a98807fbd62ff27a79c18af34e7cb3439f8c3438463ef2dd","src/open_database.rs":"40ad2da7d5559f0e5180e35d403c307ce230fe9d0d2a3fec7c9481ce13acda64","src/repeat.rs":"b4c5ff5d083afba7f9f153f54aba2e6859b78b85c82d48dbd6bd58f67da9e6b9"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"812811e5a8e00abe3ec345cd8fd435e27fec7cb8f2e45a0e93e5becf564c46ad","src/conn_ext.rs":"e48e862e47c000c545dcc766fc1889498a8709bee00e240ed68d247b0fbef577","src/debug_tools.rs":"bece2bc3d35379b81ea2f942a0a3e909e0ab0553656505904745548eacaf402a","src/each_chunk.rs":"8aaba842e43b002fbc0fee95d14ce08faa7187b1979c765b2e270cd4802607a5","src/lib.rs":"af704ec04beb6c2c388d4566710e1167b18fb64acb248ccf37a67679daffddb6","src/maybe_cached.rs":"0b18425595055883a98807fbd62ff27a79c18af34e7cb3439f8c3438463ef2dd","src/open_database.rs":"ba290bfb39468e96f9b3ea865e0c13c2cc5a731ea8877a9feb6b1de4f7d666c4","src/repeat.rs":"b4c5ff5d083afba7f9f153f54aba2e6859b78b85c82d48dbd6bd58f67da9e6b9"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/sql-support/src/open_database.rs b/third_party/rust/sql-support/src/open_database.rs
index 6c49471d29..d92a94a9ed 100644
--- a/third_party/rust/sql-support/src/open_database.rs
+++ b/third_party/rust/sql-support/src/open_database.rs
@@ -278,6 +278,9 @@ pub mod test_utils {
}
}
+ /// Attempt to run all upgrades up to a specific version.
+ ///
+ /// This will result in a panic if an upgrade fails to run.
pub fn upgrade_to(&self, version: u32) {
let mut conn = self.open();
let tx = conn.transaction().unwrap();
@@ -293,6 +296,9 @@ pub mod test_utils {
tx.commit().unwrap();
}
+ /// Attempt to run all upgrades
+ ///
+ /// This will result in a panic if an upgrade fails to run.
pub fn run_all_upgrades(&self) {
let current_version = get_schema_version(&self.open()).unwrap();
for version in current_version..CI::END_VERSION {
diff --git a/third_party/rust/suggest/.cargo-checksum.json b/third_party/rust/suggest/.cargo-checksum.json
index c8c5fa2566..120a503f29 100644
--- a/third_party/rust/suggest/.cargo-checksum.json
+++ b/third_party/rust/suggest/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"4aa81cff67e67b08ba3348c1acddaa5aee887df3c35006754c9cda4273a94458","README.md":"8d7457893194e255b87e5a2667ee25c87bd470f5338d7078506f866a67a3fdbd","build.rs":"78780c5cccfe22c3ff4198624b9e188559c437c3e6fa1c8bb66548eee6aa66bf","src/config.rs":"03630b2219b6674e332a1f96f44db74def17f985c850a800299b815fa72241c2","src/db.rs":"d373ad097edac2bbcc6e1b14f51c21b6e2cab2289d27667332798c9cde4dcbef","src/error.rs":"f563210a6c050d98ec85e0f6d9401e7373bfb816e865e8edabbabb23d848ba13","src/keyword.rs":"988d0ab021c0df19cfd3c519df7d37f606bf984cd14d0efca4e5a7aff88344dd","src/lib.rs":"65a035dbfb17e2d2d9f237ad52dc03982ae28c70e3dcf3d96cc9f2d7af79efe3","src/pocket.rs":"c4dda43390d1c39dc795933596b3c1e4e282932cac6c69da53c6e05d39e9ef29","src/provider.rs":"4fe662587efc5a80d000c217ce124506c6800293c50ff460ef95e9e659c764b9","src/rs.rs":"0910368f9e7c4703b00d0de86902d647d70c1f75a256fbeb2126c91f0499a083","src/schema.rs":"8fad4cc624f48946676adbc3de7d061f05fe82531523008f417d6130a2132e34","src/store.rs":"a869971d5593bec2dd40822ba63d0e5a5def96a870ff5a7c33afbcbf5869946b","src/suggest.udl":"d941662596d48793d1570e5b8432b7fd7b4fb1b4550fb38d4e14224fcf4195bc","src/suggestion.rs":"7ee407949f40d88e5d3d4c0da400b987e85ace9f34c648f010cd7f5f2aba0506","src/yelp.rs":"37e77900c12c68cca292a84c6dd6c67d16628c68f4612d8d9bedb1bddf985229","uniffi.toml":"f26317442ddb5b3281245bef6e60ffcb78bb95d29fe4a351a56dbb88d4ec8aab"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"05e4d7f7b3649a3e3fa441c4af53a633d18f20bb04fd761ed33fc9d461fd0dee","README.md":"fb72d0028586cab1421b853ef529d7ce78ad7316818b7733a4f3488b0fba67f7","benches/benchmark_all.rs":"c2343c9197b6d9ccb0798d7701b1b0d2569d494dd31a975d21d7ec6f26e32879","build.rs":"78780c5cccfe22c3ff4198624b9e188559c437c3e6fa1c8bb66548eee6aa66bf","src/benchmarks/README.md":"ee6d50df2c31cfd80a5bc047011b518dcf57f1ef928a811bb770f1a09f41b3de","src/benchmarks/client.rs":"5d5db3f6e132654c06532feba15f98576122f6b9572ab5fa27b0c67d5b39ecb6","src/benchmarks/ingest.rs":"1ffdc403fb945ea0b58353df9773ba45ab0e9082d61dd5330ad49fad8cbb5d9f","src/benchmarks/mod.rs":"fe1898ba4d783213525da10d92858ee84cebfd22749bad7aeb461d338fe5504a","src/bin/debug_ingestion_sizes.rs":"ce6e810be7b3fc19e826d75b622b82cfab5a1a99397a6d0833c2c4eebff2d364","src/config.rs":"206ae9dc768c755649cb0c88a7b1fc3c926c715441784f61e9dc06a8a02fc568","src/db.rs":"734f5fd9f36f03c07a508a9a353872b81107f5fe09f27294ba27d7e1249e3988","src/error.rs":"f563210a6c050d98ec85e0f6d9401e7373bfb816e865e8edabbabb23d848ba13","src/keyword.rs":"988d0ab021c0df19cfd3c519df7d37f606bf984cd14d0efca4e5a7aff88344dd","src/lib.rs":"91ebbe0e1ffb99eefde204f81bc6bb199b4941976347baf1f132fd0ede20479c","src/pocket.rs":"1316668840ec9b4ea886223921dc9d3b5a1731d1a5206c0b1089f2a6c45c1b7b","src/provider.rs":"fe76f19a223f5cac056c7d48525087ca2c26bf0629b0e11b1f8dc98d165c8bb2","src/rs.rs":"e3eabde58c859ebe1154bf8da56ca134ace135934e3f280acc8186b4204399b3","src/schema.rs":"8b21006940e872658d722b52ba171280c96789eecf614b837d8cdbc9153ab576","src/store.rs":"413779074db3ce4589c31cd4fb0a050d44d1cbad1df3c94101d03e98efdf09cb","src/suggest.udl":"de50ea5c7ece0ae0ff4798979e0e12a5227b42bf024d48b6f585ea30a5133eb3","src/suggestion.rs":"f31227779d13d1b03a622e08a417ceba4afb161885a01c2bc87a6a652b5e8be5","src/yelp.rs":"9c0dc02a994cc05df524aa4ef337d10f575d1891259193b6419fed6fe279cb54","uniffi.toml":"f26317442ddb5b3281245bef6e60ffcb78bb95d29fe4a351a56dbb88d4ec8aab"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/suggest/Cargo.toml b/third_party/rust/suggest/Cargo.toml
index 17ce1af26d..cec02ceadf 100644
--- a/third_party/rust/suggest/Cargo.toml
+++ b/third_party/rust/suggest/Cargo.toml
@@ -21,6 +21,15 @@ description = "Manages sponsored and web suggestions for Firefox Suggest"
readme = "README.md"
license = "MPL-2.0"
+[[bin]]
+name = "debug_ingestion_sizes"
+required-features = ["benchmark_api"]
+
+[[bench]]
+name = "benchmark_all"
+harness = false
+required-features = ["benchmark_api"]
+
[dependencies]
anyhow = "1.0"
chrono = "0.4"
@@ -28,7 +37,7 @@ once_cell = "1.5"
parking_lot = ">=0.11,<=0.12"
serde_json = "1"
thiserror = "1"
-uniffi = "0.25.2"
+uniffi = "0.27.1"
[dependencies.error-support]
path = "../support/error"
@@ -53,6 +62,10 @@ features = ["derive"]
[dependencies.sql-support]
path = "../support/sql"
+[dependencies.tempfile]
+version = "3.2.0"
+optional = true
+
[dependencies.url]
version = "2.1"
features = ["serde"]
@@ -60,7 +73,12 @@ features = ["serde"]
[dependencies.viaduct]
path = "../viaduct"
+[dependencies.viaduct-reqwest]
+path = "../support/viaduct-reqwest"
+optional = true
+
[dev-dependencies]
+criterion = "0.5"
expect-test = "1.4"
hex = "0.4"
@@ -72,5 +90,11 @@ default-features = false
path = "../support/rc_crypto"
[build-dependencies.uniffi]
-version = "0.25.2"
+version = "0.27.1"
features = ["build"]
+
+[features]
+benchmark_api = [
+ "tempfile",
+ "viaduct-reqwest",
+]
diff --git a/third_party/rust/suggest/README.md b/third_party/rust/suggest/README.md
index 74716d2ebb..bd68f0d7c4 100644
--- a/third_party/rust/suggest/README.md
+++ b/third_party/rust/suggest/README.md
@@ -1,7 +1,111 @@
# Suggest
-The **Suggest Rust component** powers the [Firefox Suggest](https://support.mozilla.org/en-US/kb/firefox-suggest-faq) feature.
+The **Suggest Rust component** provides address bar search suggestions from Mozilla. This includes suggestions from sponsors, as well as non-sponsored suggestions for other web destinations. These suggestions are part of the [Firefox Suggest](https://support.mozilla.org/en-US/kb/firefox-suggest-faq) feature.
-This component currently supports the basic Suggest experience only. The basic experience shows suggestions for sponsored and web content from a canned dataset. The component downloads the dataset from [Remote Settings](https://remote-settings.readthedocs.io/en/latest/), stores the suggestions in a local database, and makes them available to the Firefox address bar. Because matching is done locally, Mozilla never sees the user's query.
+This component is integrated into Firefox Desktop, Android, and iOS.
-The opt-in "Improved Firefox Suggest Experience", which sends user queries to a [Mozilla-owned proxy server](https://mozilla-services.github.io/merino/intro.html) for server-side matching, is not currently supported.
+## Architecture
+
+Search suggestions from Mozilla are stored in a [Remote Settings](https://remote-settings.readthedocs.io/en/latest/) collection. The Suggest component downloads these suggestions from Remote Settings, stores them in a local SQLite database, and makes them available to the Firefox address bar. Because these suggestions are stored and matched locally, Mozilla never sees the user's search queries.
+
+This component follows the architecture of the other [Application Services Rust components](https://mozilla.github.io/application-services/book/index.html): a cross-platform Rust core, and platform-specific bindings for Firefox Desktop, Android, and iOS. These bindings are generated automatically using the [UniFFI](https://mozilla.github.io/uniffi-rs/) tool.
+
+### For consumers
+
+This section is for application developers. It describes how Firefox Desktop, Android, and iOS consume the Suggest Rust component.
+
+The cornerstone of the component is the `SuggestStore` interface, which is the **store**. The store _ingests_ (downloads and persists) suggestions from Remote Settings, and returns matching suggestions as the user types. This is the main interface that applications use to interact with the component.
+
+While the store provides most of the functionality, the application has a few responsibilities:
+
+**1. Create and manage a `SuggestStore` as a singleton.** Under the hood, the store holds multiple connections to the database: a read-write connection for ingestion, and a read-only connection for queries. The store uses the right connection for each operation, so applications shouldn't create multiple stores. The application is responsible for specifying the correct platform-specific storage directory for the database. The database contains _cached data_, like suggestions, and _user data_, like which suggestions have been dismissed. For this reason, applications should persist the database in their durable storage or "profile" directory. Applications specify the storage directory, and create a store, using the `SuggestStoreBuilder` interface.
+
+**2. Periodically call the store's `ingest()` method to ingest new suggestions.** While the store takes care of efficiently downloading and persisting suggestions from Remote Settings, the application is still responsible for scheduling this work. This is because the Suggest component doesn't have access to the various platform-specific background work scheduling mechanisms, like `nsIUpdateTimerManager` on Desktop, `WorkManager` on Android, or `BGTaskScheduler` on iOS. These are three different APIs with different constraints, written in three different languages. Instead of trying to bind to these different mechanisms, the component leaves it up to the application to use the right one on each platform. Ingestion is network- and disk I/O-bound, and should be done in the background.
+
+**3. Use the store's `query()` and `interrupt()` methods to query for fresh suggestions as the user types.** The application passes the user's input, and additional options like which suggestion types to return, to `query()`. Querying the database is disk I/O-bound, so applications should use their respective platforms' facilities for asynchronous work—Kotlin coroutines on Android, and Swift actors on iOS; the UniFFI bindings for Desktop take care of dispatching work to a background thread pool—to avoid blocking the main thread. Running `query()` off-main-thread also lets applications `interrupt()` those queries from the main thread when the user's input changes. This avoids waiting for a query to return suggestions that are now stale.
+
+### For contributors
+
+This section is a primer for engineers contributing code to the Suggest Rust component.
+
+`suggest.udl` describes the component's interface for foreign language consumers. UniFFI uses this file to generate the language bindings for each platform. If you're adding a new suggestion type, you'll want to update the declarations of `Suggestion` and `SuggestionProvider` in this file, as well as the definitions of those types in `suggestion.rs` and `provider.rs`, respectively.
+
+`store.rs` contains the implementation of `SuggestStore` and most of the Suggest component's tests.
+
+`schema.rs` manages the database schema. **Remember to bump the schema version and add a migration whenever you change the schema.**
+
+`db.rs` interacts with the Suggest database. The `SuggestDao` type in this file is a ["data access object"](https://en.wikipedia.org/wiki/Data_access_object) (DAO) that contains all the SQL statements for querying and updating the SQLite database. By convention, `SuggestDao` methods that can write to the database take `&mut self`, and methods that only read take `&self`. The `SuggestDb::read()` and `SuggestDb::write()` methods take a closure that receives either `&SuggestDao` or `&mut SuggestDao`; this is how the DAO enforces that all writes are done in a transaction. If you're curious to learn about how we use SQLite, or you're diagnosing a slow query or adding a new suggestion type, you'll almost certainly want to look at this file.
+
+`rs.rs` defines all the Remote Settings [record](https://docs.kinto-storage.org/en/stable/concepts.html#buckets-collections-and-records) and [attachment](https://docs.kinto-storage.org/en/stable/faq.html#can-i-store-files-inside-kinto) types. The records in the `quicksuggest` Remote Settings collection don't store the suggestions themselves. Instead, each record has a type, and a pointer to a JSON attachment that contains multiple suggestions of that type. This file defines [Serde](https://serde.rs/)-compatible types for all these records and attachments.
+
+`errors.rs` contains all the errors that this component returns. We use the crate-internal `Error` type for all fallible operations within the component, and the public `SuggestApiError` type for errors that applications should handle.
+
+There are other suggestion provider-specific files, like `yelp.rs`, `pocket.rs`, and `keyword.rs`, that aren't covered in this primer. If you're new to the component, we recommend starting with the highest-level interface in `store.rs` first, and jumping to the other files and types as you encounter them in the code.
+
+## Documentation
+
+Each Rust file contains [inline documentation](https://doc.rust-lang.org/rustdoc/what-is-rustdoc.html) for types, traits, functions, and methods.
+
+Documentation for `pub` symbols is written with application developers in mind: even if you're a Desktop, Android or iOS developer, the Rust documentation is meant to give you an understanding of how the Gecko, Kotlin and Swift bindings work.
+
+You can see the documentation for all public symbols by running from the command line:
+
+```shell
+cargo doc --open
+```
+
+By convention, symbols that are exported as part of the component's foreign language interface have `pub` visibility, and symbols that are only used within the component have `pub(crate)` or private visibility. As an exception to this convention, symbols with [doctests](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html) are also `pub`, because doctests [can only link against public symbols](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#include-items-only-when-collecting-doctests).
+
+If you're working on the component, you can see the documentation for `pub(crate)` and private symbols using:
+
+```shell
+cargo doc --document-private-items --open
+```
+
+💡 If you're adding a new suggestion type, the documentation for the `rs` module is a great place to start.
+
+Please help us keep our documentation useful for everyone, and feel free to file bugs for anything that looks unclear or out-of-date!
+
+## Tests
+
+We use a technique called ["snapshot testing"](https://notlaura.com/what-is-a-snapshot-test/) with the [`expect-test`](https://docs.rs/expect-test/latest/expect_test/) crate for the Suggest component's tests. This technique makes it easier to compare and update all the expected outputs when adding, removing, and changing suggestion types.
+
+The snapshot tests in `store.rs` look like this:
+
+```rs
+expect![["{expected-output}"]].assert_debug_eq(&actual_output);
+```
+
+The `expect-test` crate generates the `{expected-output}` string, and can update it automatically. If you add, remove, or change a suggestion type, or update the schema, and run `cargo test`, you'll likely see a few failures. `expect-test` will print a readable diff in the `cargo test` output, which you can audit for accuracy.
+
+If the diff looks good, you can update the expectations in-place from the command line using:
+
+```shell
+env UPDATE_EXPECT=1 cargo test
+```
+
+Most of the tests in `store.rs` are integration-style tests that use a fake Remote Settings interface.
+
+## ⚠️ Breaking Changes ⚠️
+
+A "breaking change" is any code change that breaks the build, tests, or behavior of a consuming application.
+
+These are some common changes that can break consumers:
+
+* **Changing the signature of a method or function that's currently in use.** Adding, removing, or changing the type of an argument or a return value, or reordering arguments, is a breaking change on Desktop, Android, and iOS.
+* **Removing or renaming a method, function, or type that's currently in use.**
+* **Adding or removing a `[Throws]` attribute**. Changing a non-throwing function to throw an error, or vice versa, is a build breaking change on iOS. On Desktop and Android, changing a non-throwing function to a throwing function won't break the build, but can cause crashes if the consumer doesn't handle the new error.
+* **Changing the fields of a dictionary or an enum.** Adding, removing, reordering, or changing the type of a dictionary or an `[Enum]` interface field is a guaranteed build breaking change on iOS. It may be a breaking change on Desktop and Android if the consumer creates new instances of the dictionary or enum.
+
+When working on the Suggest component, this means:
+
+* Adding a new suggestion type is generally _not_ breaking.
+* Adding a new method to `SuggestStore` is _not_ breaking.
+* Renaming or removing a `SuggestStore` method that's currently in use _is_ breaking.
+* Adding a new field to an existing suggestion type _is_ breaking.
+
+If you need to make a breaking change, don't panic! We have a [process for landing them in Application Services](https://mozilla.github.io/application-services/book/howtos/breaking-changes.html), and you can use a [branch build](https://mozilla.github.io/application-services/book/howtos/branch-builds.html) to verify that Android and iOS build and run their tests with your change.
+
+## Bugs
+
+We use Bugzilla to track bugs and feature work. You can use [this link](https://bugzilla.mozilla.org/enter_bug.cgi?product=Application+Services&component=Suggest) to file bugs in the `Application Services :: Suggest` bug component.
diff --git a/third_party/rust/suggest/benches/benchmark_all.rs b/third_party/rust/suggest/benches/benchmark_all.rs
new file mode 100644
index 0000000000..2e328e5804
--- /dev/null
+++ b/third_party/rust/suggest/benches/benchmark_all.rs
@@ -0,0 +1,25 @@
+use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
+use suggest::benchmarks::{ingest, BenchmarkWithInput};
+
+pub fn ingest_single_provider(c: &mut Criterion) {
+ let mut group = c.benchmark_group("ingest");
+ viaduct_reqwest::use_reqwest_backend();
+ // This needs to be 10 for now, or else the `ingest-amp-wikipedia` benchmark would take around
+ // 100s to run which feels like too long. `ingest-amp-mobile` also would take a around 50s.
+ group.sample_size(10);
+ for (name, benchmark) in ingest::all_benchmarks() {
+ group.bench_function(format!("ingest-{name}"), |b| {
+ b.iter_batched(
+ || benchmark.generate_input(),
+ |input| benchmark.benchmarked_code(input),
+ // See https://docs.rs/criterion/latest/criterion/enum.BatchSize.html#variants for
+ // a discussion of this. PerIteration is chosen for these benchmarks because the
+ // input holds a database file handle
+ BatchSize::PerIteration,
+ );
+ });
+ }
+}
+
+criterion_group!(benches, ingest_single_provider);
+criterion_main!(benches);
diff --git a/third_party/rust/suggest/src/benchmarks/README.md b/third_party/rust/suggest/src/benchmarks/README.md
new file mode 100644
index 0000000000..45150d8413
--- /dev/null
+++ b/third_party/rust/suggest/src/benchmarks/README.md
@@ -0,0 +1,28 @@
+# Suggest benchmarking code
+
+Use `cargo suggest-bench` to run these benchmarks.
+
+The main benchmarking code lives here, while the criterion integration code lives in the `benches/`
+directory.
+
+## Benchmarks
+
+### ingest-[provider-type]
+
+Time it takes to ingest all suggestions for a provider type on an empty database.
+The bechmark downloads network resources in advance in order to exclude the network request time
+from these measurements.
+
+### Benchmarks it would be nice to have
+
+- Ingestion with synthetic data. This would isolate the benchmark from changes to the RS database.
+- Fetching suggestions
+
+## cargo suggest-debug-ingestion-sizes
+
+Run this to get row counts for all database tables. This can be very useful for improving
+benchmarks, since targeting the tables with the largest number of rows will usually lead to the
+largest improvements.
+
+The command also prints out the size of all remote-settings attachments, which can be good to
+optimize on its own since it represents the amount of data user's need to download.
diff --git a/third_party/rust/suggest/src/benchmarks/client.rs b/third_party/rust/suggest/src/benchmarks/client.rs
new file mode 100644
index 0000000000..f5a21fd9cc
--- /dev/null
+++ b/third_party/rust/suggest/src/benchmarks/client.rs
@@ -0,0 +1,97 @@
+/* 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/. */
+
+use crate::{rs::SuggestRemoteSettingsClient, Result};
+use parking_lot::Mutex;
+use remote_settings::{Client, GetItemsOptions, RemoteSettingsConfig, RemoteSettingsResponse};
+use std::collections::HashMap;
+
+/// Remotes settings client that runs during the benchmark warm-up phase.
+///
+/// This should be used to run a full ingestion.
+/// Then it can be converted into a [RemoteSettingsBenchmarkClient], which allows benchmark code to exclude the network request time.
+/// [RemoteSettingsBenchmarkClient] implements [SuggestRemoteSettingsClient] by getting data from a HashMap rather than hitting the network.
+pub struct RemoteSettingsWarmUpClient {
+ client: Client,
+ pub get_records_responses: Mutex<HashMap<GetItemsOptions, RemoteSettingsResponse>>,
+ pub get_attachment_responses: Mutex<HashMap<String, Vec<u8>>>,
+}
+
+impl RemoteSettingsWarmUpClient {
+ pub fn new() -> Self {
+ Self {
+ client: Client::new(RemoteSettingsConfig {
+ server_url: None,
+ bucket_name: None,
+ collection_name: crate::rs::REMOTE_SETTINGS_COLLECTION.into(),
+ })
+ .unwrap(),
+ get_records_responses: Mutex::new(HashMap::new()),
+ get_attachment_responses: Mutex::new(HashMap::new()),
+ }
+ }
+}
+
+impl Default for RemoteSettingsWarmUpClient {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl SuggestRemoteSettingsClient for RemoteSettingsWarmUpClient {
+ fn get_records_with_options(
+ &self,
+ options: &GetItemsOptions,
+ ) -> Result<RemoteSettingsResponse> {
+ let response = self.client.get_records_with_options(options)?;
+ self.get_records_responses
+ .lock()
+ .insert(options.clone(), response.clone());
+ Ok(response)
+ }
+
+ fn get_attachment(&self, location: &str) -> Result<Vec<u8>> {
+ let response = self.client.get_attachment(location)?;
+ self.get_attachment_responses
+ .lock()
+ .insert(location.to_string(), response.clone());
+ Ok(response)
+ }
+}
+
+#[derive(Clone)]
+pub struct RemoteSettingsBenchmarkClient {
+ pub get_records_responses: HashMap<GetItemsOptions, RemoteSettingsResponse>,
+ pub get_attachment_responses: HashMap<String, Vec<u8>>,
+}
+
+impl SuggestRemoteSettingsClient for RemoteSettingsBenchmarkClient {
+ fn get_records_with_options(
+ &self,
+ options: &GetItemsOptions,
+ ) -> Result<RemoteSettingsResponse> {
+ Ok(self
+ .get_records_responses
+ .get(options)
+ .unwrap_or_else(|| panic!("options not found: {options:?}"))
+ .clone())
+ }
+
+ fn get_attachment(&self, location: &str) -> Result<Vec<u8>> {
+ Ok(self
+ .get_attachment_responses
+ .get(location)
+ .unwrap_or_else(|| panic!("location not found: {location:?}"))
+ .clone())
+ }
+}
+
+impl From<RemoteSettingsWarmUpClient> for RemoteSettingsBenchmarkClient {
+ fn from(warm_up_client: RemoteSettingsWarmUpClient) -> Self {
+ Self {
+ get_records_responses: warm_up_client.get_records_responses.into_inner(),
+ get_attachment_responses: warm_up_client.get_attachment_responses.into_inner(),
+ }
+ }
+}
diff --git a/third_party/rust/suggest/src/benchmarks/ingest.rs b/third_party/rust/suggest/src/benchmarks/ingest.rs
new file mode 100644
index 0000000000..bbefc6a00a
--- /dev/null
+++ b/third_party/rust/suggest/src/benchmarks/ingest.rs
@@ -0,0 +1,116 @@
+/* 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/. */
+
+use crate::{
+ benchmarks::{
+ client::{RemoteSettingsBenchmarkClient, RemoteSettingsWarmUpClient},
+ BenchmarkWithInput,
+ },
+ rs::SuggestRecordType,
+ store::SuggestStoreInner,
+ SuggestIngestionConstraints,
+};
+use std::sync::atomic::{AtomicU32, Ordering};
+
+static DB_FILE_COUNTER: AtomicU32 = AtomicU32::new(0);
+
+pub struct IngestBenchmark {
+ temp_dir: tempfile::TempDir,
+ client: RemoteSettingsBenchmarkClient,
+ record_type: SuggestRecordType,
+}
+
+impl IngestBenchmark {
+ pub fn new(record_type: SuggestRecordType) -> Self {
+ let temp_dir = tempfile::tempdir().unwrap();
+ let store = SuggestStoreInner::new(
+ temp_dir.path().join("warmup.sqlite"),
+ RemoteSettingsWarmUpClient::new(),
+ );
+ store.benchmark_ingest_records_by_type(record_type);
+ Self {
+ client: RemoteSettingsBenchmarkClient::from(store.into_settings_client()),
+ temp_dir,
+ record_type,
+ }
+ }
+}
+
+// The input for each benchmark is `SuggestStoreInner` with a fresh database.
+//
+// This is wrapped in a newtype so that it can be exposed in the public trait
+pub struct InputType(SuggestStoreInner<RemoteSettingsBenchmarkClient>);
+
+impl BenchmarkWithInput for IngestBenchmark {
+ type Input = InputType;
+
+ fn generate_input(&self) -> Self::Input {
+ let data_path = self.temp_dir.path().join(format!(
+ "db{}.sqlite",
+ DB_FILE_COUNTER.fetch_add(1, Ordering::Relaxed)
+ ));
+ let store = SuggestStoreInner::new(data_path, self.client.clone());
+ store.ensure_db_initialized();
+ InputType(store)
+ }
+
+ fn benchmarked_code(&self, input: Self::Input) {
+ let InputType(store) = input;
+ store.benchmark_ingest_records_by_type(self.record_type);
+ }
+}
+
+/// Get IngestBenchmark instances for all record types
+pub fn all_benchmarks() -> Vec<(&'static str, IngestBenchmark)> {
+ vec![
+ ("icon", IngestBenchmark::new(SuggestRecordType::Icon)),
+ (
+ "amp-wikipedia",
+ IngestBenchmark::new(SuggestRecordType::AmpWikipedia),
+ ),
+ ("amo", IngestBenchmark::new(SuggestRecordType::Amo)),
+ ("pocket", IngestBenchmark::new(SuggestRecordType::Pocket)),
+ ("yelp", IngestBenchmark::new(SuggestRecordType::Yelp)),
+ ("mdn", IngestBenchmark::new(SuggestRecordType::Mdn)),
+ ("weather", IngestBenchmark::new(SuggestRecordType::Weather)),
+ (
+ "global-config",
+ IngestBenchmark::new(SuggestRecordType::GlobalConfig),
+ ),
+ (
+ "amp-mobile",
+ IngestBenchmark::new(SuggestRecordType::AmpMobile),
+ ),
+ ]
+}
+
+pub fn print_debug_ingestion_sizes() {
+ viaduct_reqwest::use_reqwest_backend();
+ let store = SuggestStoreInner::new(
+ "file:debug_ingestion_sizes?mode=memory&cache=shared",
+ RemoteSettingsWarmUpClient::new(),
+ );
+ store
+ .ingest(SuggestIngestionConstraints::default())
+ .unwrap();
+ let table_row_counts = store.table_row_counts();
+ let client = store.into_settings_client();
+ let total_attachment_size: usize = client
+ .get_attachment_responses
+ .lock()
+ .values()
+ .map(|data| data.len())
+ .sum();
+
+ println!(
+ "Total attachment size: {}kb",
+ (total_attachment_size + 500) / 1000
+ );
+ println!();
+ println!("Database table row counts");
+ println!("-------------------------");
+ for (name, count) in table_row_counts {
+ println!("{name:30}: {count}");
+ }
+}
diff --git a/third_party/rust/suggest/src/benchmarks/mod.rs b/third_party/rust/suggest/src/benchmarks/mod.rs
new file mode 100644
index 0000000000..eb3b2e8abe
--- /dev/null
+++ b/third_party/rust/suggest/src/benchmarks/mod.rs
@@ -0,0 +1,40 @@
+/* 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/. */
+
+//! Benchmarking support
+//!
+//! Benchmarks are split up into two parts: the functions to be benchmarked live here, which the benchmarking code itself lives in `benches/bench.rs`.
+//! It's easier to write benchmarking code inside the main crate, where we have access to private items.
+//! However, it's easier to integrate with Cargo and criterion if benchmarks live in a separate crate.
+//!
+//! All benchmarks are defined as structs that implement either the [Benchmark] or [BenchmarkWithInput]
+
+pub mod client;
+pub mod ingest;
+
+/// Trait for simple benchmarks
+///
+/// This supports simple benchmarks that don't require any input. Note: global setup can be done
+/// in the `new()` method for the struct.
+pub trait Benchmark {
+ /// Perform the operations that we're benchmarking.
+ fn benchmarked_code(&self);
+}
+
+/// Trait for benchmarks that require input
+///
+/// This will run using Criterion's `iter_batched` function. Criterion will create a batch of
+/// inputs, then pass each one to benchmark.
+///
+/// This supports simple benchmarks that don't require any input. Note: global setup can be done
+/// in the `new()` method for the struct.
+pub trait BenchmarkWithInput {
+ type Input;
+
+ /// Generate the input (this is not included in the benchmark time)
+ fn generate_input(&self) -> Self::Input;
+
+ /// Perform the operations that we're benchmarking.
+ fn benchmarked_code(&self, input: Self::Input);
+}
diff --git a/third_party/rust/suggest/src/bin/debug_ingestion_sizes.rs b/third_party/rust/suggest/src/bin/debug_ingestion_sizes.rs
new file mode 100644
index 0000000000..14ca3d9462
--- /dev/null
+++ b/third_party/rust/suggest/src/bin/debug_ingestion_sizes.rs
@@ -0,0 +1,9 @@
+/* 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/. */
+
+use suggest::benchmarks::ingest;
+
+fn main() {
+ ingest::print_debug_ingestion_sizes()
+}
diff --git a/third_party/rust/suggest/src/config.rs b/third_party/rust/suggest/src/config.rs
index fcb3c2e256..532ac5b504 100644
--- a/third_party/rust/suggest/src/config.rs
+++ b/third_party/rust/suggest/src/config.rs
@@ -1,3 +1,8 @@
+/* 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/.
+ */
+
use serde::{Deserialize, Serialize};
use crate::rs::{DownloadedGlobalConfig, DownloadedWeatherData};
diff --git a/third_party/rust/suggest/src/db.rs b/third_party/rust/suggest/src/db.rs
index 07fc3ab4a2..0412c50d8f 100644
--- a/third_party/rust/suggest/src/db.rs
+++ b/third_party/rust/suggest/src/db.rs
@@ -23,17 +23,17 @@ use crate::{
rs::{
DownloadedAmoSuggestion, DownloadedAmpSuggestion, DownloadedAmpWikipediaSuggestion,
DownloadedMdnSuggestion, DownloadedPocketSuggestion, DownloadedWeatherData,
- SuggestRecordId,
+ DownloadedWikipediaSuggestion, SuggestRecordId,
},
- schema::{SuggestConnectionInitializer, VERSION},
+ schema::{clear_database, SuggestConnectionInitializer, VERSION},
store::{UnparsableRecord, UnparsableRecords},
suggestion::{cook_raw_suggestion_url, AmpSuggestionType, Suggestion},
Result, SuggestionQuery,
};
-/// The metadata key whose value is the timestamp of the last record ingested
-/// from the Suggest Remote Settings collection.
-pub const LAST_INGEST_META_KEY: &str = "last_quicksuggest_ingest";
+/// The metadata key prefix for the last ingested unparsable record. These are
+/// records that were not parsed properly, or were not of the "approved" types.
+pub const LAST_INGEST_META_UNPARSABLE: &str = "last_quicksuggest_ingest_unparsable";
/// The metadata key whose value keeps track of records of suggestions
/// that aren't parsable and which schema version it was first seen in.
pub const UNPARSABLE_RECORDS_META_KEY: &str = "unparsable_records";
@@ -148,20 +148,28 @@ impl<'a> SuggestDao<'a> {
self.put_unparsable_record_id(&record_id)?;
// Advance the last fetch time, so that we can resume
// fetching after this record if we're interrupted.
- self.put_last_ingest_if_newer(record.last_modified)
+ self.put_last_ingest_if_newer(LAST_INGEST_META_UNPARSABLE, record.last_modified)
}
- pub fn handle_ingested_record(&mut self, record: &RemoteSettingsRecord) -> Result<()> {
+ pub fn handle_ingested_record(
+ &mut self,
+ last_ingest_key: &str,
+ record: &RemoteSettingsRecord,
+ ) -> Result<()> {
let record_id = SuggestRecordId::from(&record.id);
// Remove this record's ID from the list of unparsable
// records, since we understand it now.
self.drop_unparsable_record_id(&record_id)?;
// Advance the last fetch time, so that we can resume
// fetching after this record if we're interrupted.
- self.put_last_ingest_if_newer(record.last_modified)
+ self.put_last_ingest_if_newer(last_ingest_key, record.last_modified)
}
- pub fn handle_deleted_record(&mut self, record: &RemoteSettingsRecord) -> Result<()> {
+ pub fn handle_deleted_record(
+ &mut self,
+ last_ingest_key: &str,
+ record: &RemoteSettingsRecord,
+ ) -> Result<()> {
let record_id = SuggestRecordId::from(&record.id);
// Drop either the icon or suggestions, records only contain one or the other
match record_id.as_icon_id() {
@@ -173,7 +181,7 @@ impl<'a> SuggestDao<'a> {
self.drop_unparsable_record_id(&record_id)?;
// Advance the last fetch time, so that we can resume
// fetching after this record if we're interrupted.
- self.put_last_ingest_if_newer(record.last_modified)
+ self.put_last_ingest_if_newer(last_ingest_key, record.last_modified)
}
// =============== Low level API ===============
@@ -231,15 +239,20 @@ impl<'a> SuggestDao<'a> {
s.title,
s.url,
s.provider,
- s.score
+ s.score,
+ fk.full_keyword
FROM
suggestions s
JOIN
keywords k
ON k.suggestion_id = s.id
+ LEFT JOIN
+ full_keywords fk
+ ON k.full_keyword_id = fk.id
WHERE
s.provider = :provider
AND k.keyword = :keyword
+ AND NOT EXISTS (SELECT 1 FROM dismissed_suggestions WHERE url=s.url)
"#,
named_params! {
":keyword": keyword_lowercased,
@@ -248,8 +261,9 @@ impl<'a> SuggestDao<'a> {
|row| -> Result<Suggestion> {
let suggestion_id: i64 = row.get("id")?;
let title = row.get("title")?;
- let raw_url = row.get::<_, String>("url")?;
- let score = row.get::<_, f64>("score")?;
+ let raw_url: String = row.get("url")?;
+ let score: f64 = row.get("score")?;
+ let full_keyword_from_db: Option<String> = row.get("full_keyword")?;
let keywords: Vec<String> = self.conn.query_rows_and_then_cached(
r#"
@@ -277,9 +291,12 @@ impl<'a> SuggestDao<'a> {
amp.iab_category,
amp.impression_url,
amp.click_url,
- (SELECT i.data FROM icons i WHERE i.id = amp.icon_id) AS icon
+ i.data AS icon,
+ i.mimetype AS icon_mimetype
FROM
amp_custom_details amp
+ LEFT JOIN
+ icons i ON amp.icon_id = i.id
WHERE
amp.suggestion_id = :suggestion_id
"#,
@@ -298,8 +315,10 @@ impl<'a> SuggestDao<'a> {
title,
url: cooked_url,
raw_url,
- full_keyword: full_keyword(keyword_lowercased, &keywords),
+ full_keyword: full_keyword_from_db
+ .unwrap_or_else(|| full_keyword(keyword_lowercased, &keywords)),
icon: row.get("icon")?,
+ icon_mimetype: row.get("icon_mimetype")?,
impression_url: row.get("impression_url")?,
click_url: cooked_click_url,
raw_click_url,
@@ -330,6 +349,7 @@ impl<'a> SuggestDao<'a> {
WHERE
s.provider = :provider
AND k.keyword = :keyword
+ AND NOT EXISTS (SELECT 1 FROM dismissed_suggestions WHERE url=s.url)
"#,
named_params! {
":keyword": keyword_lowercased,
@@ -350,36 +370,51 @@ impl<'a> SuggestDao<'a> {
},
|row| row.get(0),
)?;
- let icon = self.conn.try_query_one(
- "SELECT i.data
+ let (icon, icon_mimetype) = self
+ .conn
+ .try_query_row(
+ "SELECT i.data, i.mimetype
FROM icons i
JOIN wikipedia_custom_details s ON s.icon_id = i.id
- WHERE s.suggestion_id = :suggestion_id",
- named_params! {
- ":suggestion_id": suggestion_id
- },
- true,
- )?;
+ WHERE s.suggestion_id = :suggestion_id
+ LIMIT 1",
+ named_params! {
+ ":suggestion_id": suggestion_id
+ },
+ |row| -> Result<_> {
+ Ok((
+ row.get::<_, Option<Vec<u8>>>(0)?,
+ row.get::<_, Option<String>>(1)?,
+ ))
+ },
+ true,
+ )?
+ .unwrap_or((None, None));
+
Ok(Suggestion::Wikipedia {
title,
url: raw_url,
full_keyword: full_keyword(keyword_lowercased, &keywords),
icon,
+ icon_mimetype,
})
},
)?;
Ok(suggestions)
}
- /// Fetches Suggestions of type Amo provider that match the given query
- pub fn fetch_amo_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> {
+ /// Query for suggestions using the keyword prefix and provider
+ fn map_prefix_keywords<T>(
+ &self,
+ query: &SuggestionQuery,
+ provider: &SuggestionProvider,
+ mut mapper: impl FnMut(&rusqlite::Row, &str) -> Result<T>,
+ ) -> Result<Vec<T>> {
let keyword_lowercased = &query.keyword.to_lowercase();
let (keyword_prefix, keyword_suffix) = split_keyword(keyword_lowercased);
- let suggestions_limit = &query.limit.unwrap_or(-1);
- let suggestions = self
- .conn
- .query_rows_and_then_cached(
- r#"
+ let suggestions_limit = query.limit.unwrap_or(-1);
+ self.conn.query_rows_and_then_cached(
+ r#"
SELECT
s.id,
MAX(k.rank) AS rank,
@@ -397,6 +432,7 @@ impl<'a> SuggestDao<'a> {
k.keyword_prefix = :keyword_prefix
AND (k.keyword_suffix BETWEEN :keyword_suffix AND :keyword_suffix || x'FFFF')
AND s.provider = :provider
+ AND NOT EXISTS (SELECT 1 FROM dismissed_suggestions WHERE url=s.url)
GROUP BY
s.id
ORDER BY
@@ -405,13 +441,23 @@ impl<'a> SuggestDao<'a> {
LIMIT
:suggestions_limit
"#,
- named_params! {
- ":keyword_prefix": keyword_prefix,
- ":keyword_suffix": keyword_suffix,
- ":provider": SuggestionProvider::Amo,
- ":suggestions_limit": suggestions_limit,
- },
- |row| -> Result<Option<Suggestion>> {
+ &[
+ (":keyword_prefix", &keyword_prefix as &dyn ToSql),
+ (":keyword_suffix", &keyword_suffix as &dyn ToSql),
+ (":provider", provider as &dyn ToSql),
+ (":suggestions_limit", &suggestions_limit as &dyn ToSql),
+ ],
+ |row| mapper(row, keyword_suffix),
+ )
+ }
+
+ /// Fetches Suggestions of type Amo provider that match the given query
+ pub fn fetch_amo_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> {
+ let suggestions = self
+ .map_prefix_keywords(
+ query,
+ &SuggestionProvider::Amo,
+ |row, keyword_suffix| -> Result<Option<Suggestion>> {
let suggestion_id: i64 = row.get("id")?;
let title = row.get("title")?;
let raw_url = row.get::<_, String>("url")?;
@@ -486,6 +532,7 @@ impl<'a> SuggestDao<'a> {
k.keyword_prefix = :keyword_prefix
AND (k.keyword_suffix BETWEEN :keyword_suffix AND :keyword_suffix || x'FFFF')
AND s.provider = :provider
+ AND NOT EXISTS (SELECT 1 FROM dismissed_suggestions WHERE url=s.url)
GROUP BY
s.id,
k.confidence
@@ -534,45 +581,11 @@ impl<'a> SuggestDao<'a> {
/// Fetches suggestions for MDN
pub fn fetch_mdn_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> {
- let keyword_lowercased = &query.keyword.to_lowercase();
- let (keyword_prefix, keyword_suffix) = split_keyword(keyword_lowercased);
- let suggestions_limit = &query.limit.unwrap_or(-1);
let suggestions = self
- .conn
- .query_rows_and_then_cached(
- r#"
- SELECT
- s.id,
- MAX(k.rank) AS rank,
- s.title,
- s.url,
- s.provider,
- s.score,
- k.keyword_suffix
- FROM
- suggestions s
- JOIN
- prefix_keywords k
- ON k.suggestion_id = s.id
- WHERE
- k.keyword_prefix = :keyword_prefix
- AND (k.keyword_suffix BETWEEN :keyword_suffix AND :keyword_suffix || x'FFFF')
- AND s.provider = :provider
- GROUP BY
- s.id
- ORDER BY
- s.score DESC,
- rank DESC
- LIMIT
- :suggestions_limit
- "#,
- named_params! {
- ":keyword_prefix": keyword_prefix,
- ":keyword_suffix": keyword_suffix,
- ":provider": SuggestionProvider::Mdn,
- ":suggestions_limit": suggestions_limit,
- },
- |row| -> Result<Option<Suggestion>> {
+ .map_prefix_keywords(
+ query,
+ &SuggestionProvider::Mdn,
+ |row, keyword_suffix| -> Result<Option<Suggestion>> {
let suggestion_id: i64 = row.get("id")?;
let title = row.get("title")?;
let raw_url = row.get::<_, String>("url")?;
@@ -657,35 +670,15 @@ impl<'a> SuggestDao<'a> {
record_id: &SuggestRecordId,
suggestions: &[DownloadedAmoSuggestion],
) -> Result<()> {
+ let mut suggestion_insert = SuggestionInsertStatement::new(self.conn)?;
for suggestion in suggestions {
self.scope.err_if_interrupted()?;
- let suggestion_id: i64 = self.conn.query_row_and_then_cachable(
- &format!(
- "INSERT INTO suggestions(
- record_id,
- provider,
- title,
- url,
- score
- )
- VALUES(
- :record_id,
- {},
- :title,
- :url,
- :score
- )
- RETURNING id",
- SuggestionProvider::Amo as u8
- ),
- named_params! {
- ":record_id": record_id.as_str(),
- ":title": suggestion.title,
- ":url": suggestion.url,
- ":score": suggestion.score,
- },
- |row| row.get(0),
- true,
+ let suggestion_id = suggestion_insert.execute(
+ record_id,
+ &suggestion.title,
+ &suggestion.url,
+ suggestion.score,
+ SuggestionProvider::Amo,
)?;
self.conn.execute(
"INSERT INTO amo_custom_details(
@@ -747,105 +740,48 @@ impl<'a> SuggestDao<'a> {
record_id: &SuggestRecordId,
suggestions: &[DownloadedAmpWikipediaSuggestion],
) -> Result<()> {
+ // Prepare statements outside of the loop. This results in a large performance
+ // improvement on a fresh ingest, since there are so many rows.
+ let mut suggestion_insert = SuggestionInsertStatement::new(self.conn)?;
+ let mut amp_insert = AmpInsertStatement::new(self.conn)?;
+ let mut wiki_insert = WikipediaInsertStatement::new(self.conn)?;
+ let mut keyword_insert = KeywordInsertStatement::new(self.conn)?;
for suggestion in suggestions {
self.scope.err_if_interrupted()?;
let common_details = suggestion.common_details();
let provider = suggestion.provider();
- let suggestion_id: i64 = self.conn.query_row_and_then_cachable(
- &format!(
- "INSERT INTO suggestions(
- record_id,
- provider,
- title,
- url,
- score
- )
- VALUES(
- :record_id,
- {},
- :title,
- :url,
- :score
- )
- RETURNING id",
- provider as u8
- ),
- named_params! {
- ":record_id": record_id.as_str(),
- ":title": common_details.title,
- ":url": common_details.url,
- ":score": common_details.score.unwrap_or(DEFAULT_SUGGESTION_SCORE)
- },
- |row| row.get(0),
- true,
+ let suggestion_id = suggestion_insert.execute(
+ record_id,
+ &common_details.title,
+ &common_details.url,
+ common_details.score.unwrap_or(DEFAULT_SUGGESTION_SCORE),
+ provider,
)?;
match suggestion {
DownloadedAmpWikipediaSuggestion::Amp(amp) => {
- self.conn.execute(
- "INSERT INTO amp_custom_details(
- suggestion_id,
- advertiser,
- block_id,
- iab_category,
- impression_url,
- click_url,
- icon_id
- )
- VALUES(
- :suggestion_id,
- :advertiser,
- :block_id,
- :iab_category,
- :impression_url,
- :click_url,
- :icon_id
- )",
- named_params! {
- ":suggestion_id": suggestion_id,
- ":advertiser": amp.advertiser,
- ":block_id": amp.block_id,
- ":iab_category": amp.iab_category,
- ":impression_url": amp.impression_url,
- ":click_url": amp.click_url,
- ":icon_id": amp.icon_id,
- },
- )?;
+ amp_insert.execute(suggestion_id, amp)?;
}
DownloadedAmpWikipediaSuggestion::Wikipedia(wikipedia) => {
- self.conn.execute(
- "INSERT INTO wikipedia_custom_details(
- suggestion_id,
- icon_id
- )
- VALUES(
- :suggestion_id,
- :icon_id
- )",
- named_params! {
- ":suggestion_id": suggestion_id,
- ":icon_id": wikipedia.icon_id,
- },
- )?;
+ wiki_insert.execute(suggestion_id, wikipedia)?;
}
}
- for (index, keyword) in common_details.keywords.iter().enumerate() {
- self.conn.execute(
- "INSERT INTO keywords(
- keyword,
- suggestion_id,
- rank
- )
- VALUES(
- :keyword,
- :suggestion_id,
- :rank
- )",
- named_params! {
- ":keyword": keyword,
- ":rank": index,
- ":suggestion_id": suggestion_id,
- },
+ let mut full_keyword_inserter = FullKeywordInserter::new(self.conn, suggestion_id);
+ for keyword in common_details.keywords() {
+ let full_keyword_id = match (suggestion, keyword.full_keyword) {
+ // Try to associate full keyword data. Only do this for AMP, we decided to
+ // skip it for Wikipedia in https://bugzilla.mozilla.org/show_bug.cgi?id=1876217
+ (DownloadedAmpWikipediaSuggestion::Amp(_), Some(full_keyword)) => {
+ Some(full_keyword_inserter.maybe_insert(full_keyword)?)
+ }
+ _ => None,
+ };
+
+ keyword_insert.execute(
+ suggestion_id,
+ keyword.keyword,
+ full_keyword_id,
+ keyword.rank,
)?;
}
}
@@ -859,84 +795,32 @@ impl<'a> SuggestDao<'a> {
record_id: &SuggestRecordId,
suggestions: &[DownloadedAmpSuggestion],
) -> Result<()> {
+ let mut suggestion_insert = SuggestionInsertStatement::new(self.conn)?;
+ let mut amp_insert = AmpInsertStatement::new(self.conn)?;
+ let mut keyword_insert = KeywordInsertStatement::new(self.conn)?;
for suggestion in suggestions {
self.scope.err_if_interrupted()?;
let common_details = &suggestion.common_details;
- let suggestion_id: i64 = self.conn.query_row_and_then_cachable(
- &format!(
- "INSERT INTO suggestions(
- record_id,
- provider,
- title,
- url,
- score
- )
- VALUES(
- :record_id,
- {},
- :title,
- :url,
- :score
- )
- RETURNING id",
- SuggestionProvider::AmpMobile as u8
- ),
- named_params! {
- ":record_id": record_id.as_str(),
- ":title": common_details.title,
- ":url": common_details.url,
- ":score": common_details.score.unwrap_or(DEFAULT_SUGGESTION_SCORE)
- },
- |row| row.get(0),
- true,
- )?;
- self.conn.execute(
- "INSERT INTO amp_custom_details(
- suggestion_id,
- advertiser,
- block_id,
- iab_category,
- impression_url,
- click_url,
- icon_id
- )
- VALUES(
- :suggestion_id,
- :advertiser,
- :block_id,
- :iab_category,
- :impression_url,
- :click_url,
- :icon_id
- )",
- named_params! {
- ":suggestion_id": suggestion_id,
- ":advertiser": suggestion.advertiser,
- ":block_id": suggestion.block_id,
- ":iab_category": suggestion.iab_category,
- ":impression_url": suggestion.impression_url,
- ":click_url": suggestion.click_url,
- ":icon_id": suggestion.icon_id,
- },
+ let suggestion_id = suggestion_insert.execute(
+ record_id,
+ &common_details.title,
+ &common_details.url,
+ common_details.score.unwrap_or(DEFAULT_SUGGESTION_SCORE),
+ SuggestionProvider::AmpMobile,
)?;
+ amp_insert.execute(suggestion_id, suggestion)?;
- for (index, keyword) in common_details.keywords.iter().enumerate() {
- self.conn.execute(
- "INSERT INTO keywords(
- keyword,
- suggestion_id,
- rank
- )
- VALUES(
- :keyword,
- :suggestion_id,
- :rank
- )",
- named_params! {
- ":keyword": keyword,
- ":rank": index,
- ":suggestion_id": suggestion_id,
- },
+ let mut full_keyword_inserter = FullKeywordInserter::new(self.conn, suggestion_id);
+ for keyword in common_details.keywords() {
+ let full_keyword_id = keyword
+ .full_keyword
+ .map(|full_keyword| full_keyword_inserter.maybe_insert(full_keyword))
+ .transpose()?;
+ keyword_insert.execute(
+ suggestion_id,
+ keyword.keyword,
+ full_keyword_id,
+ keyword.rank,
)?;
}
}
@@ -950,37 +834,16 @@ impl<'a> SuggestDao<'a> {
record_id: &SuggestRecordId,
suggestions: &[DownloadedPocketSuggestion],
) -> Result<()> {
+ let mut suggestion_insert = SuggestionInsertStatement::new(self.conn)?;
for suggestion in suggestions {
self.scope.err_if_interrupted()?;
- let suggestion_id: i64 = self.conn.query_row_and_then_cachable(
- &format!(
- "INSERT INTO suggestions(
- record_id,
- provider,
- title,
- url,
- score
- )
- VALUES(
- :record_id,
- {},
- :title,
- :url,
- :score
- )
- RETURNING id",
- SuggestionProvider::Pocket as u8
- ),
- named_params! {
- ":record_id": record_id.as_str(),
- ":title": suggestion.title,
- ":url": suggestion.url,
- ":score": suggestion.score,
- },
- |row| row.get(0),
- true,
+ let suggestion_id = suggestion_insert.execute(
+ record_id,
+ &suggestion.title,
+ &suggestion.url,
+ suggestion.score,
+ SuggestionProvider::Pocket,
)?;
-
for ((rank, keyword), confidence) in suggestion
.high_confidence_keywords
.iter()
@@ -1030,35 +893,15 @@ impl<'a> SuggestDao<'a> {
record_id: &SuggestRecordId,
suggestions: &[DownloadedMdnSuggestion],
) -> Result<()> {
+ let mut suggestion_insert = SuggestionInsertStatement::new(self.conn)?;
for suggestion in suggestions {
self.scope.err_if_interrupted()?;
- let suggestion_id: i64 = self.conn.query_row_and_then_cachable(
- &format!(
- "INSERT INTO suggestions(
- record_id,
- provider,
- title,
- url,
- score
- )
- VALUES(
- :record_id,
- {},
- :title,
- :url,
- :score
- )
- RETURNING id",
- SuggestionProvider::Mdn as u8
- ),
- named_params! {
- ":record_id": record_id.as_str(),
- ":title": suggestion.title,
- ":url": suggestion.url,
- ":score": suggestion.score,
- },
- |row| row.get(0),
- true,
+ let suggestion_id = suggestion_insert.execute(
+ record_id,
+ &suggestion.title,
+ &suggestion.url,
+ suggestion.score,
+ SuggestionProvider::Mdn,
)?;
self.conn.execute_cached(
"INSERT INTO mdn_custom_details(
@@ -1107,20 +950,14 @@ impl<'a> SuggestDao<'a> {
record_id: &SuggestRecordId,
data: &DownloadedWeatherData,
) -> Result<()> {
+ let mut suggestion_insert = SuggestionInsertStatement::new(self.conn)?;
self.scope.err_if_interrupted()?;
- let suggestion_id: i64 = self.conn.query_row_and_then_cachable(
- &format!(
- "INSERT INTO suggestions(record_id, provider, title, url, score)
- VALUES(:record_id, {}, '', '', :score)
- RETURNING id",
- SuggestionProvider::Weather as u8
- ),
- named_params! {
- ":record_id": record_id.as_str(),
- ":score": data.weather.score.unwrap_or(DEFAULT_SUGGESTION_SCORE),
- },
- |row| row.get(0),
- true,
+ let suggestion_id = suggestion_insert.execute(
+ record_id,
+ "",
+ "",
+ data.weather.score.unwrap_or(DEFAULT_SUGGESTION_SCORE),
+ SuggestionProvider::Weather,
)?;
for (index, keyword) in data.weather.keywords.iter().enumerate() {
self.conn.execute(
@@ -1141,24 +978,43 @@ impl<'a> SuggestDao<'a> {
}
/// Inserts or replaces an icon for a suggestion into the database.
- pub fn put_icon(&mut self, icon_id: &str, data: &[u8]) -> Result<()> {
+ pub fn put_icon(&mut self, icon_id: &str, data: &[u8], mimetype: &str) -> Result<()> {
self.conn.execute(
"INSERT OR REPLACE INTO icons(
id,
- data
+ data,
+ mimetype
)
VALUES(
:id,
- :data
+ :data,
+ :mimetype
)",
named_params! {
":id": icon_id,
":data": data,
+ ":mimetype": mimetype,
},
)?;
Ok(())
}
+ pub fn insert_dismissal(&self, url: &str) -> Result<()> {
+ self.conn.execute(
+ "INSERT OR IGNORE INTO dismissed_suggestions(url)
+ VALUES(:url)",
+ named_params! {
+ ":url": url,
+ },
+ )?;
+ Ok(())
+ }
+
+ pub fn clear_dismissals(&self) -> Result<()> {
+ self.conn.execute("DELETE FROM dismissed_suggestions", ())?;
+ Ok(())
+ }
+
/// Deletes all suggestions associated with a Remote Settings record from
/// the database.
pub fn drop_suggestions(&mut self, record_id: &SuggestRecordId) -> Result<()> {
@@ -1196,12 +1052,7 @@ impl<'a> SuggestDao<'a> {
/// Clears the database, removing all suggestions, icons, and metadata.
pub fn clear(&mut self) -> Result<()> {
- self.conn.execute_batch(
- "DELETE FROM suggestions;
- DELETE FROM icons;
- DELETE FROM meta;",
- )?;
- Ok(())
+ Ok(clear_database(self.conn)?)
}
/// Returns the value associated with a metadata key.
@@ -1224,12 +1075,14 @@ impl<'a> SuggestDao<'a> {
/// Updates the last ingest timestamp if the given last modified time is
/// newer than the existing one recorded.
- pub fn put_last_ingest_if_newer(&mut self, record_last_modified: u64) -> Result<()> {
- let last_ingest = self
- .get_meta::<u64>(LAST_INGEST_META_KEY)?
- .unwrap_or_default();
+ pub fn put_last_ingest_if_newer(
+ &mut self,
+ last_ingest_key: &str,
+ record_last_modified: u64,
+ ) -> Result<()> {
+ let last_ingest = self.get_meta::<u64>(last_ingest_key)?.unwrap_or_default();
if record_last_modified > last_ingest {
- self.put_meta(LAST_INGEST_META_KEY, record_last_modified)?;
+ self.put_meta(last_ingest_key, record_last_modified)?;
}
Ok(())
@@ -1310,6 +1163,185 @@ impl<'a> SuggestDao<'a> {
}
}
+/// Helper struct to get full_keyword_ids for a suggestion
+///
+/// `FullKeywordInserter` handles repeated full keywords efficiently. The first instance will
+/// cause a row to be inserted into the database. Subsequent instances will return the same
+/// full_keyword_id.
+struct FullKeywordInserter<'a> {
+ conn: &'a Connection,
+ suggestion_id: i64,
+ last_inserted: Option<(&'a str, i64)>,
+}
+
+impl<'a> FullKeywordInserter<'a> {
+ fn new(conn: &'a Connection, suggestion_id: i64) -> Self {
+ Self {
+ conn,
+ suggestion_id,
+ last_inserted: None,
+ }
+ }
+
+ fn maybe_insert(&mut self, full_keyword: &'a str) -> rusqlite::Result<i64> {
+ match self.last_inserted {
+ Some((s, id)) if s == full_keyword => Ok(id),
+ _ => {
+ let full_keyword_id = self.conn.query_row_and_then(
+ "INSERT INTO full_keywords(
+ suggestion_id,
+ full_keyword
+ )
+ VALUES(
+ :suggestion_id,
+ :keyword
+ )
+ RETURNING id",
+ named_params! {
+ ":keyword": full_keyword,
+ ":suggestion_id": self.suggestion_id,
+ },
+ |row| row.get(0),
+ )?;
+ self.last_inserted = Some((full_keyword, full_keyword_id));
+ Ok(full_keyword_id)
+ }
+ }
+ }
+}
+
+// ======================== Statement types ========================
+//
+// During ingestion we can insert hundreds of thousands of rows. These types enable speedups by
+// allowing us to prepare a statement outside a loop and use it many times inside the loop.
+//
+// Each type wraps [Connection::prepare] and [Statement] to provide a simplified interface,
+// tailored to a specific query.
+//
+// This pattern is applicable for whenever we execute the same query repeatedly in a loop.
+// The impact scales with the number of loop iterations, which is why we currently don't do this
+// for providers like Mdn, Pocket, and Weather, which have relatively small number of records
+// compared to Amp/Wikipedia.
+
+struct SuggestionInsertStatement<'conn>(rusqlite::Statement<'conn>);
+
+impl<'conn> SuggestionInsertStatement<'conn> {
+ fn new(conn: &'conn Connection) -> Result<Self> {
+ Ok(Self(conn.prepare(
+ "INSERT INTO suggestions(
+ record_id,
+ title,
+ url,
+ score,
+ provider
+ )
+ VALUES(?, ?, ?, ?, ?)
+ RETURNING id",
+ )?))
+ }
+
+ /// Execute the insert and return the `suggestion_id` for the new row
+ fn execute(
+ &mut self,
+ record_id: &SuggestRecordId,
+ title: &str,
+ url: &str,
+ score: f64,
+ provider: SuggestionProvider,
+ ) -> Result<i64> {
+ Ok(self.0.query_row(
+ (record_id.as_str(), title, url, score, provider as u8),
+ |row| row.get(0),
+ )?)
+ }
+}
+
+struct AmpInsertStatement<'conn>(rusqlite::Statement<'conn>);
+
+impl<'conn> AmpInsertStatement<'conn> {
+ fn new(conn: &'conn Connection) -> Result<Self> {
+ Ok(Self(conn.prepare(
+ "INSERT INTO amp_custom_details(
+ suggestion_id,
+ advertiser,
+ block_id,
+ iab_category,
+ impression_url,
+ click_url,
+ icon_id
+ )
+ VALUES(?, ?, ?, ?, ?, ?, ?)
+ ",
+ )?))
+ }
+
+ fn execute(&mut self, suggestion_id: i64, amp: &DownloadedAmpSuggestion) -> Result<()> {
+ self.0.execute((
+ suggestion_id,
+ &amp.advertiser,
+ amp.block_id,
+ &amp.iab_category,
+ &amp.impression_url,
+ &amp.click_url,
+ &amp.icon_id,
+ ))?;
+ Ok(())
+ }
+}
+
+struct WikipediaInsertStatement<'conn>(rusqlite::Statement<'conn>);
+
+impl<'conn> WikipediaInsertStatement<'conn> {
+ fn new(conn: &'conn Connection) -> Result<Self> {
+ Ok(Self(conn.prepare(
+ "INSERT INTO wikipedia_custom_details(
+ suggestion_id,
+ icon_id
+ )
+ VALUES(?, ?)
+ ",
+ )?))
+ }
+
+ fn execute(
+ &mut self,
+ suggestion_id: i64,
+ wikipedia: &DownloadedWikipediaSuggestion,
+ ) -> Result<()> {
+ self.0.execute((suggestion_id, &wikipedia.icon_id))?;
+ Ok(())
+ }
+}
+
+struct KeywordInsertStatement<'conn>(rusqlite::Statement<'conn>);
+
+impl<'conn> KeywordInsertStatement<'conn> {
+ fn new(conn: &'conn Connection) -> Result<Self> {
+ Ok(Self(conn.prepare(
+ "INSERT INTO keywords(
+ suggestion_id,
+ keyword,
+ full_keyword_id,
+ rank
+ )
+ VALUES(?, ?, ?, ?)
+ ",
+ )?))
+ }
+
+ fn execute(
+ &mut self,
+ suggestion_id: i64,
+ keyword: &str,
+ full_keyword_id: Option<i64>,
+ rank: usize,
+ ) -> Result<()> {
+ self.0
+ .execute((suggestion_id, keyword, full_keyword_id, rank))?;
+ Ok(())
+ }
+}
+
fn provider_config_meta_key(provider: SuggestionProvider) -> String {
format!("{}{}", PROVIDER_CONFIG_META_KEY_PREFIX, provider as u8)
}
diff --git a/third_party/rust/suggest/src/lib.rs b/third_party/rust/suggest/src/lib.rs
index 23775b7dec..15746614d0 100644
--- a/third_party/rust/suggest/src/lib.rs
+++ b/third_party/rust/suggest/src/lib.rs
@@ -4,6 +4,8 @@
*/
use remote_settings::RemoteSettingsConfig;
+#[cfg(feature = "benchmark_api")]
+pub mod benchmarks;
mod config;
mod db;
mod error;
@@ -26,7 +28,7 @@ pub(crate) type Result<T> = std::result::Result<T, error::Error>;
pub type SuggestApiResult<T> = std::result::Result<T, error::SuggestApiError>;
/// A query for suggestions to show in the address bar.
-#[derive(Debug, Default)]
+#[derive(Clone, Debug, Default)]
pub struct SuggestionQuery {
pub keyword: String,
pub providers: Vec<SuggestionProvider>,
diff --git a/third_party/rust/suggest/src/pocket.rs b/third_party/rust/suggest/src/pocket.rs
index cf7070c62a..06fd4b012d 100644
--- a/third_party/rust/suggest/src/pocket.rs
+++ b/third_party/rust/suggest/src/pocket.rs
@@ -12,7 +12,7 @@ use rusqlite::{Result as RusqliteResult, ToSql};
/// substring for the suffix.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[repr(u8)]
-pub enum KeywordConfidence {
+pub(crate) enum KeywordConfidence {
Low = 0,
High = 1,
}
diff --git a/third_party/rust/suggest/src/provider.rs b/third_party/rust/suggest/src/provider.rs
index 1449c35c8a..2c0b6674cb 100644
--- a/third_party/rust/suggest/src/provider.rs
+++ b/third_party/rust/suggest/src/provider.rs
@@ -8,6 +8,8 @@ use rusqlite::{
Result as RusqliteResult,
};
+use crate::rs::SuggestRecordType;
+
/// A provider is a source of search suggestions.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[repr(u8)]
@@ -46,6 +48,52 @@ impl SuggestionProvider {
_ => None,
}
}
+
+ pub(crate) fn records_for_provider(&self) -> Vec<SuggestRecordType> {
+ match self {
+ SuggestionProvider::Amp => {
+ vec![
+ SuggestRecordType::AmpWikipedia,
+ SuggestRecordType::Icon,
+ SuggestRecordType::GlobalConfig,
+ ]
+ }
+ SuggestionProvider::Wikipedia => {
+ vec![
+ SuggestRecordType::AmpWikipedia,
+ SuggestRecordType::Icon,
+ SuggestRecordType::GlobalConfig,
+ ]
+ }
+ SuggestionProvider::Amo => {
+ vec![SuggestRecordType::Amo, SuggestRecordType::GlobalConfig]
+ }
+ SuggestionProvider::Pocket => {
+ vec![SuggestRecordType::Pocket, SuggestRecordType::GlobalConfig]
+ }
+ SuggestionProvider::Yelp => {
+ vec![
+ SuggestRecordType::Yelp,
+ SuggestRecordType::Icon,
+ SuggestRecordType::GlobalConfig,
+ ]
+ }
+ SuggestionProvider::Mdn => {
+ vec![SuggestRecordType::Mdn, SuggestRecordType::GlobalConfig]
+ }
+ SuggestionProvider::Weather => {
+ vec![SuggestRecordType::Weather, SuggestRecordType::GlobalConfig]
+ }
+ SuggestionProvider::AmpMobile => {
+ vec![
+ SuggestRecordType::AmpMobile,
+ SuggestRecordType::AmpWikipedia,
+ SuggestRecordType::Icon,
+ SuggestRecordType::GlobalConfig,
+ ]
+ }
+ }
+ }
}
impl ToSql for SuggestionProvider {
diff --git a/third_party/rust/suggest/src/rs.rs b/third_party/rust/suggest/src/rs.rs
index 198a8c43f6..4a733ece9d 100644
--- a/third_party/rust/suggest/src/rs.rs
+++ b/third_party/rust/suggest/src/rs.rs
@@ -31,7 +31,7 @@
//! the new suggestion in their results, and return `Suggestion::T` variants
//! as needed.
-use std::borrow::Cow;
+use std::{borrow::Cow, fmt};
use remote_settings::{GetItemsOptions, RemoteSettingsResponse};
use serde::{Deserialize, Deserializer};
@@ -47,6 +47,20 @@ pub(crate) const REMOTE_SETTINGS_COLLECTION: &str = "quicksuggest";
/// `mozilla-services/quicksuggest-rs` repo.
pub(crate) const SUGGESTIONS_PER_ATTACHMENT: u64 = 200;
+/// A list of default record types to download if nothing is specified.
+/// This currently defaults to all of the record types.
+pub(crate) const DEFAULT_RECORDS_TYPES: [SuggestRecordType; 9] = [
+ SuggestRecordType::Icon,
+ SuggestRecordType::AmpWikipedia,
+ SuggestRecordType::Amo,
+ SuggestRecordType::Pocket,
+ SuggestRecordType::Yelp,
+ SuggestRecordType::Mdn,
+ SuggestRecordType::Weather,
+ SuggestRecordType::GlobalConfig,
+ SuggestRecordType::AmpMobile,
+];
+
/// A trait for a client that downloads suggestions from Remote Settings.
///
/// This trait lets tests use a mock client.
@@ -102,6 +116,61 @@ pub(crate) enum SuggestRecord {
AmpMobile,
}
+/// Enum for the different record types that can be consumed.
+/// Extracting this from the serialization enum so that we can
+/// extend it to get type metadata.
+#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord)]
+pub enum SuggestRecordType {
+ Icon,
+ AmpWikipedia,
+ Amo,
+ Pocket,
+ Yelp,
+ Mdn,
+ Weather,
+ GlobalConfig,
+ AmpMobile,
+}
+
+impl From<SuggestRecord> for SuggestRecordType {
+ fn from(suggest_record: SuggestRecord) -> Self {
+ match suggest_record {
+ SuggestRecord::Amo => Self::Amo,
+ SuggestRecord::AmpWikipedia => Self::AmpWikipedia,
+ SuggestRecord::Icon => Self::Icon,
+ SuggestRecord::Mdn => Self::Mdn,
+ SuggestRecord::Pocket => Self::Pocket,
+ SuggestRecord::Weather(_) => Self::Weather,
+ SuggestRecord::Yelp => Self::Yelp,
+ SuggestRecord::GlobalConfig(_) => Self::GlobalConfig,
+ SuggestRecord::AmpMobile => Self::AmpMobile,
+ }
+ }
+}
+
+impl fmt::Display for SuggestRecordType {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Icon => write!(f, "icon"),
+ Self::AmpWikipedia => write!(f, "data"),
+ Self::Amo => write!(f, "amo-suggestions"),
+ Self::Pocket => write!(f, "pocket-suggestions"),
+ Self::Yelp => write!(f, "yelp-suggestions"),
+ Self::Mdn => write!(f, "mdn-suggestions"),
+ Self::Weather => write!(f, "weather"),
+ Self::GlobalConfig => write!(f, "configuration"),
+ Self::AmpMobile => write!(f, "amp-mobile-suggestions"),
+ }
+ }
+}
+
+impl SuggestRecordType {
+ /// Return the meta key for the last ingested record.
+ pub fn last_ingest_meta_key(&self) -> String {
+ format!("last_quicksuggest_ingest_{}", self)
+ }
+}
+
/// Represents either a single value, or a list of values. This is used to
/// deserialize downloaded attachments.
#[derive(Clone, Debug, Deserialize)]
@@ -156,16 +225,18 @@ where
}
/// Fields that are common to all downloaded suggestions.
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, Default, Deserialize)]
pub(crate) struct DownloadedSuggestionCommonDetails {
pub keywords: Vec<String>,
pub title: String,
pub url: String,
pub score: Option<f64>,
+ #[serde(default)]
+ pub full_keywords: Vec<(String, usize)>,
}
/// An AMP suggestion to ingest from an AMP-Wikipedia attachment.
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, Default, Deserialize)]
pub(crate) struct DownloadedAmpSuggestion {
#[serde(flatten)]
pub common_details: DownloadedSuggestionCommonDetails,
@@ -180,7 +251,7 @@ pub(crate) struct DownloadedAmpSuggestion {
}
/// A Wikipedia suggestion to ingest from an AMP-Wikipedia attachment.
-#[derive(Clone, Debug, Deserialize)]
+#[derive(Clone, Debug, Default, Deserialize)]
pub(crate) struct DownloadedWikipediaSuggestion {
#[serde(flatten)]
pub common_details: DownloadedSuggestionCommonDetails,
@@ -214,6 +285,34 @@ impl DownloadedAmpWikipediaSuggestion {
}
}
+impl DownloadedSuggestionCommonDetails {
+ /// Iterate over all keywords for this suggestion
+ pub fn keywords(&self) -> impl Iterator<Item = AmpKeyword<'_>> {
+ let full_keywords = self
+ .full_keywords
+ .iter()
+ .flat_map(|(full_keyword, repeat_for)| {
+ std::iter::repeat(Some(full_keyword.as_str())).take(*repeat_for)
+ })
+ .chain(std::iter::repeat(None)); // In case of insufficient full keywords, just fill in with infinite `None`s
+ //
+ self.keywords.iter().zip(full_keywords).enumerate().map(
+ move |(i, (keyword, full_keyword))| AmpKeyword {
+ rank: i,
+ keyword,
+ full_keyword,
+ },
+ )
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub(crate) struct AmpKeyword<'a> {
+ pub rank: usize,
+ pub keyword: &'a str,
+ pub full_keyword: Option<&'a str>,
+}
+
impl<'de> Deserialize<'de> for DownloadedAmpWikipediaSuggestion {
fn deserialize<D>(
deserializer: D,
@@ -344,3 +443,119 @@ where
{
String::deserialize(deserializer).map(|s| s.parse().ok())
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_full_keywords() {
+ let suggestion = DownloadedAmpWikipediaSuggestion::Amp(DownloadedAmpSuggestion {
+ common_details: DownloadedSuggestionCommonDetails {
+ keywords: vec![
+ String::from("f"),
+ String::from("fo"),
+ String::from("foo"),
+ String::from("foo b"),
+ String::from("foo ba"),
+ String::from("foo bar"),
+ ],
+ full_keywords: vec![(String::from("foo"), 3), (String::from("foo bar"), 3)],
+ ..DownloadedSuggestionCommonDetails::default()
+ },
+ ..DownloadedAmpSuggestion::default()
+ });
+
+ assert_eq!(
+ Vec::from_iter(suggestion.common_details().keywords()),
+ vec![
+ AmpKeyword {
+ rank: 0,
+ keyword: "f",
+ full_keyword: Some("foo"),
+ },
+ AmpKeyword {
+ rank: 1,
+ keyword: "fo",
+ full_keyword: Some("foo"),
+ },
+ AmpKeyword {
+ rank: 2,
+ keyword: "foo",
+ full_keyword: Some("foo"),
+ },
+ AmpKeyword {
+ rank: 3,
+ keyword: "foo b",
+ full_keyword: Some("foo bar"),
+ },
+ AmpKeyword {
+ rank: 4,
+ keyword: "foo ba",
+ full_keyword: Some("foo bar"),
+ },
+ AmpKeyword {
+ rank: 5,
+ keyword: "foo bar",
+ full_keyword: Some("foo bar"),
+ },
+ ],
+ );
+ }
+
+ #[test]
+ fn test_missing_full_keywords() {
+ let suggestion = DownloadedAmpWikipediaSuggestion::Amp(DownloadedAmpSuggestion {
+ common_details: DownloadedSuggestionCommonDetails {
+ keywords: vec![
+ String::from("f"),
+ String::from("fo"),
+ String::from("foo"),
+ String::from("foo b"),
+ String::from("foo ba"),
+ String::from("foo bar"),
+ ],
+ // Only the first 3 keywords have full keywords associated with them
+ full_keywords: vec![(String::from("foo"), 3)],
+ ..DownloadedSuggestionCommonDetails::default()
+ },
+ ..DownloadedAmpSuggestion::default()
+ });
+
+ assert_eq!(
+ Vec::from_iter(suggestion.common_details().keywords()),
+ vec![
+ AmpKeyword {
+ rank: 0,
+ keyword: "f",
+ full_keyword: Some("foo"),
+ },
+ AmpKeyword {
+ rank: 1,
+ keyword: "fo",
+ full_keyword: Some("foo"),
+ },
+ AmpKeyword {
+ rank: 2,
+ keyword: "foo",
+ full_keyword: Some("foo"),
+ },
+ AmpKeyword {
+ rank: 3,
+ keyword: "foo b",
+ full_keyword: None,
+ },
+ AmpKeyword {
+ rank: 4,
+ keyword: "foo ba",
+ full_keyword: None,
+ },
+ AmpKeyword {
+ rank: 5,
+ keyword: "foo bar",
+ full_keyword: None,
+ },
+ ],
+ );
+ }
+}
diff --git a/third_party/rust/suggest/src/schema.rs b/third_party/rust/suggest/src/schema.rs
index 95d987c09e..b304363de5 100644
--- a/third_party/rust/suggest/src/schema.rs
+++ b/third_party/rust/suggest/src/schema.rs
@@ -13,7 +13,9 @@ use sql_support::open_database::{self, ConnectionInitializer};
/// 1. Bump this version.
/// 2. Add a migration from the old version to the new version in
/// [`SuggestConnectionInitializer::upgrade_from`].
-pub const VERSION: u32 = 14;
+/// a. If suggestions should be re-ingested after the migration, call `clear_database()` inside
+/// the migration.
+pub const VERSION: u32 = 18;
/// The current Suggest database schema.
pub const SQL: &str = "
@@ -25,10 +27,19 @@ pub const SQL: &str = "
CREATE TABLE keywords(
keyword TEXT NOT NULL,
suggestion_id INTEGER NOT NULL REFERENCES suggestions(id) ON DELETE CASCADE,
+ full_keyword_id INTEGER NULL REFERENCES full_keywords(id) ON DELETE SET NULL,
rank INTEGER NOT NULL,
PRIMARY KEY (keyword, suggestion_id)
) WITHOUT ROWID;
+ -- full keywords are what we display to the user when a (partial) keyword matches
+ -- The FK to suggestion_id makes it so full keywords get deleted when the parent suggestion is deleted.
+ CREATE TABLE full_keywords(
+ id INTEGER PRIMARY KEY,
+ suggestion_id INTEGER NOT NULL REFERENCES suggestions(id) ON DELETE CASCADE,
+ full_keyword TEXT NOT NULL
+ );
+
CREATE TABLE prefix_keywords(
keyword_prefix TEXT NOT NULL,
keyword_suffix TEXT NOT NULL DEFAULT '',
@@ -79,7 +90,8 @@ pub const SQL: &str = "
CREATE TABLE icons(
id TEXT PRIMARY KEY,
- data BLOB NOT NULL
+ data BLOB NOT NULL,
+ mimetype TEXT NOT NULL
) WITHOUT ROWID;
CREATE TABLE yelp_subjects(
@@ -111,6 +123,10 @@ pub const SQL: &str = "
description TEXT NOT NULL,
FOREIGN KEY(suggestion_id) REFERENCES suggestions(id) ON DELETE CASCADE
);
+
+ CREATE TABLE dismissed_suggestions (
+ url TEXT PRIMARY KEY
+ ) WITHOUT ROWID;
";
/// Initializes an SQLite connection to the Suggest database, performing
@@ -139,15 +155,178 @@ impl ConnectionInitializer for SuggestConnectionInitializer {
Ok(db.execute_batch(SQL)?)
}
- fn upgrade_from(&self, _db: &Transaction<'_>, version: u32) -> open_database::Result<()> {
+ fn upgrade_from(&self, tx: &Transaction<'_>, version: u32) -> open_database::Result<()> {
match version {
- 1..=13 => {
+ 1..=15 => {
// Treat databases with these older schema versions as corrupt,
// so that they'll be replaced by a fresh, empty database with
// the current schema.
Err(open_database::Error::Corrupt)
}
+ 16 => {
+ tx.execute(
+ "
+ CREATE TABLE dismissed_suggestions (
+ url_hash INTEGER PRIMARY KEY
+ ) WITHOUT ROWID;",
+ (),
+ )?;
+ Ok(())
+ }
+ 17 => {
+ tx.execute(
+ "
+ DROP TABLE dismissed_suggestions;
+ CREATE TABLE dismissed_suggestions (
+ url TEXT PRIMARY KEY
+ ) WITHOUT ROWID;",
+ (),
+ )?;
+ Ok(())
+ }
_ => Err(open_database::Error::IncompatibleVersion(version)),
}
}
}
+
+/// Clears the database, removing all suggestions, icons, and metadata.
+pub fn clear_database(db: &Connection) -> rusqlite::Result<()> {
+ db.execute_batch(
+ "
+ DELETE FROM meta;
+ DELETE FROM suggestions;
+ DELETE FROM icons;
+ DELETE FROM yelp_subjects;
+ DELETE FROM yelp_modifiers;
+ DELETE FROM yelp_location_signs;
+ DELETE FROM yelp_custom_details;
+ ",
+ )
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use sql_support::open_database::test_utils::MigratedDatabaseFile;
+
+ // Snapshot of the v16 schema. We use this to test that we can migrate from there to the
+ // current schema.
+ const V16_SCHEMA: &str = r#"
+ CREATE TABLE meta(
+ key TEXT PRIMARY KEY,
+ value NOT NULL
+ ) WITHOUT ROWID;
+
+ CREATE TABLE keywords(
+ keyword TEXT NOT NULL,
+ suggestion_id INTEGER NOT NULL REFERENCES suggestions(id) ON DELETE CASCADE,
+ full_keyword_id INTEGER NULL REFERENCES full_keywords(id) ON DELETE SET NULL,
+ rank INTEGER NOT NULL,
+ PRIMARY KEY (keyword, suggestion_id)
+ ) WITHOUT ROWID;
+
+ -- full keywords are what we display to the user when a (partial) keyword matches
+ -- The FK to suggestion_id makes it so full keywords get deleted when the parent suggestion is deleted.
+ CREATE TABLE full_keywords(
+ id INTEGER PRIMARY KEY,
+ suggestion_id INTEGER NOT NULL REFERENCES suggestions(id) ON DELETE CASCADE,
+ full_keyword TEXT NOT NULL
+ );
+
+ CREATE TABLE prefix_keywords(
+ keyword_prefix TEXT NOT NULL,
+ keyword_suffix TEXT NOT NULL DEFAULT '',
+ confidence INTEGER NOT NULL DEFAULT 0,
+ rank INTEGER NOT NULL,
+ suggestion_id INTEGER NOT NULL REFERENCES suggestions(id) ON DELETE CASCADE,
+ PRIMARY KEY (keyword_prefix, keyword_suffix, suggestion_id)
+ ) WITHOUT ROWID;
+
+ CREATE UNIQUE INDEX keywords_suggestion_id_rank ON keywords(suggestion_id, rank);
+
+ CREATE TABLE suggestions(
+ id INTEGER PRIMARY KEY,
+ record_id TEXT NOT NULL,
+ provider INTEGER NOT NULL,
+ title TEXT NOT NULL,
+ url TEXT NOT NULL,
+ score REAL NOT NULL
+ );
+
+ CREATE TABLE amp_custom_details(
+ suggestion_id INTEGER PRIMARY KEY,
+ advertiser TEXT NOT NULL,
+ block_id INTEGER NOT NULL,
+ iab_category TEXT NOT NULL,
+ impression_url TEXT NOT NULL,
+ click_url TEXT NOT NULL,
+ icon_id TEXT NOT NULL,
+ FOREIGN KEY(suggestion_id) REFERENCES suggestions(id) ON DELETE CASCADE
+ );
+
+ CREATE TABLE wikipedia_custom_details(
+ suggestion_id INTEGER PRIMARY KEY REFERENCES suggestions(id) ON DELETE CASCADE,
+ icon_id TEXT NOT NULL
+ );
+
+ CREATE TABLE amo_custom_details(
+ suggestion_id INTEGER PRIMARY KEY,
+ description TEXT NOT NULL,
+ guid TEXT NOT NULL,
+ icon_url TEXT NOT NULL,
+ rating TEXT,
+ number_of_ratings INTEGER NOT NULL,
+ FOREIGN KEY(suggestion_id) REFERENCES suggestions(id) ON DELETE CASCADE
+ );
+
+ CREATE INDEX suggestions_record_id ON suggestions(record_id);
+
+ CREATE TABLE icons(
+ id TEXT PRIMARY KEY,
+ data BLOB NOT NULL,
+ mimetype TEXT NOT NULL
+ ) WITHOUT ROWID;
+
+ CREATE TABLE yelp_subjects(
+ keyword TEXT PRIMARY KEY,
+ record_id TEXT NOT NULL
+ ) WITHOUT ROWID;
+
+ CREATE TABLE yelp_modifiers(
+ type INTEGER NOT NULL,
+ keyword TEXT NOT NULL,
+ record_id TEXT NOT NULL,
+ PRIMARY KEY (type, keyword)
+ ) WITHOUT ROWID;
+
+ CREATE TABLE yelp_location_signs(
+ keyword TEXT PRIMARY KEY,
+ need_location INTEGER NOT NULL,
+ record_id TEXT NOT NULL
+ ) WITHOUT ROWID;
+
+ CREATE TABLE yelp_custom_details(
+ icon_id TEXT PRIMARY KEY,
+ score REAL NOT NULL,
+ record_id TEXT NOT NULL
+ ) WITHOUT ROWID;
+
+ CREATE TABLE mdn_custom_details(
+ suggestion_id INTEGER PRIMARY KEY,
+ description TEXT NOT NULL,
+ FOREIGN KEY(suggestion_id) REFERENCES suggestions(id) ON DELETE CASCADE
+ );
+
+ PRAGMA user_version=16;
+"#;
+
+ /// Test running all schema upgrades from V16, which was the first schema with a "real"
+ /// migration.
+ ///
+ /// If an upgrade fails, then this test will fail with a panic.
+ #[test]
+ fn test_all_upgrades() {
+ let db_file = MigratedDatabaseFile::new(SuggestConnectionInitializer, V16_SCHEMA);
+ db_file.run_all_upgrades();
+ }
+}
diff --git a/third_party/rust/suggest/src/store.rs b/third_party/rust/suggest/src/store.rs
index e1f437e8c5..c55cffc7f5 100644
--- a/third_party/rust/suggest/src/store.rs
+++ b/third_party/rust/suggest/src/store.rs
@@ -4,7 +4,7 @@
*/
use std::{
- collections::BTreeMap,
+ collections::{BTreeMap, BTreeSet},
path::{Path, PathBuf},
sync::Arc,
};
@@ -24,13 +24,15 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
use crate::{
config::{SuggestGlobalConfig, SuggestProviderConfig},
db::{
- ConnectionType, SuggestDao, SuggestDb, LAST_INGEST_META_KEY, UNPARSABLE_RECORDS_META_KEY,
+ ConnectionType, SuggestDao, SuggestDb, LAST_INGEST_META_UNPARSABLE,
+ UNPARSABLE_RECORDS_META_KEY,
},
error::Error,
provider::SuggestionProvider,
rs::{
- SuggestAttachment, SuggestRecord, SuggestRecordId, SuggestRemoteSettingsClient,
- REMOTE_SETTINGS_COLLECTION, SUGGESTIONS_PER_ATTACHMENT,
+ SuggestAttachment, SuggestRecord, SuggestRecordId, SuggestRecordType,
+ SuggestRemoteSettingsClient, DEFAULT_RECORDS_TYPES, REMOTE_SETTINGS_COLLECTION,
+ SUGGESTIONS_PER_ATTACHMENT,
},
schema::VERSION,
Result, SuggestApiResult, Suggestion, SuggestionQuery,
@@ -48,7 +50,6 @@ pub struct SuggestStoreBuilder(Mutex<SuggestStoreBuilderInner>);
#[derive(Default)]
struct SuggestStoreBuilderInner {
data_path: Option<String>,
- cache_path: Option<String>,
remote_settings_config: Option<RemoteSettingsConfig>,
}
@@ -68,8 +69,8 @@ impl SuggestStoreBuilder {
self
}
- pub fn cache_path(self: Arc<Self>, path: String) -> Arc<Self> {
- self.0.lock().cache_path = Some(path);
+ pub fn cache_path(self: Arc<Self>, _path: String) -> Arc<Self> {
+ // We used to use this, but we're not using it anymore, just ignore the call
self
}
@@ -85,10 +86,6 @@ impl SuggestStoreBuilder {
.data_path
.clone()
.ok_or_else(|| Error::SuggestStoreBuilder("data_path not specified".to_owned()))?;
- let cache_path = inner
- .cache_path
- .clone()
- .ok_or_else(|| Error::SuggestStoreBuilder("cache_path not specified".to_owned()))?;
let settings_client =
remote_settings::Client::new(inner.remote_settings_config.clone().unwrap_or_else(
|| RemoteSettingsConfig {
@@ -98,7 +95,7 @@ impl SuggestStoreBuilder {
},
))?;
Ok(Arc::new(SuggestStore {
- inner: SuggestStoreInner::new(data_path, cache_path, settings_client),
+ inner: SuggestStoreInner::new(data_path, settings_client),
}))
}
}
@@ -182,7 +179,7 @@ impl SuggestStore {
)?)
}()?;
Ok(Self {
- inner: SuggestStoreInner::new("".to_owned(), path.to_owned(), settings_client),
+ inner: SuggestStoreInner::new(path.to_owned(), settings_client),
})
}
@@ -192,6 +189,22 @@ impl SuggestStore {
self.inner.query(query)
}
+ /// Dismiss a suggestion
+ ///
+ /// Dismissed suggestions will not be returned again
+ ///
+ /// In the case of AMP suggestions this should be the raw URL.
+ #[handle_error(Error)]
+ pub fn dismiss_suggestion(&self, suggestion_url: String) -> SuggestApiResult<()> {
+ self.inner.dismiss_suggestion(suggestion_url)
+ }
+
+ /// Clear dismissed suggestions
+ #[handle_error(Error)]
+ pub fn clear_dismissed_suggestions(&self) -> SuggestApiResult<()> {
+ self.inner.clear_dismissed_suggestions()
+ }
+
/// Interrupts any ongoing queries.
///
/// This should be called when the user types new input into the address
@@ -238,6 +251,7 @@ pub struct SuggestIngestionConstraints {
/// Because of how suggestions are partitioned in Remote Settings, this is a
/// soft limit, and the store might ingest more than requested.
pub max_suggestions: Option<u64>,
+ pub providers: Option<Vec<SuggestionProvider>>,
}
/// The implementation of the store. This is generic over the Remote Settings
@@ -250,23 +264,14 @@ pub(crate) struct SuggestStoreInner<S> {
/// It's not currently used because not all consumers pass this in yet.
#[allow(unused)]
data_path: PathBuf,
- /// Path to the temporary SQL database.
- ///
- /// This stores things that should be deleted when the user clears their cache.
- cache_path: PathBuf,
dbs: OnceCell<SuggestStoreDbs>,
settings_client: S,
}
impl<S> SuggestStoreInner<S> {
- fn new(
- data_path: impl Into<PathBuf>,
- cache_path: impl Into<PathBuf>,
- settings_client: S,
- ) -> Self {
+ pub fn new(data_path: impl Into<PathBuf>, settings_client: S) -> Self {
Self {
data_path: data_path.into(),
- cache_path: cache_path.into(),
dbs: OnceCell::new(),
settings_client,
}
@@ -276,7 +281,7 @@ impl<S> SuggestStoreInner<S> {
/// they're not already open.
fn dbs(&self) -> Result<&SuggestStoreDbs> {
self.dbs
- .get_or_try_init(|| SuggestStoreDbs::open(&self.cache_path))
+ .get_or_try_init(|| SuggestStoreDbs::open(&self.data_path))
}
fn query(&self, query: SuggestionQuery) -> Result<Vec<Suggestion>> {
@@ -286,6 +291,17 @@ impl<S> SuggestStoreInner<S> {
self.dbs()?.reader.read(|dao| dao.fetch_suggestions(&query))
}
+ fn dismiss_suggestion(&self, suggestion_url: String) -> Result<()> {
+ self.dbs()?
+ .writer
+ .write(|dao| dao.insert_dismissal(&suggestion_url))
+ }
+
+ fn clear_dismissed_suggestions(&self) -> Result<()> {
+ self.dbs()?.writer.write(|dao| dao.clear_dismissals())?;
+ Ok(())
+ }
+
fn interrupt(&self) {
if let Some(dbs) = self.dbs.get() {
// Only interrupt if the databases are already open.
@@ -315,7 +331,7 @@ impl<S> SuggestStoreInner<S>
where
S: SuggestRemoteSettingsClient,
{
- fn ingest(&self, constraints: SuggestIngestionConstraints) -> Result<()> {
+ pub fn ingest(&self, constraints: SuggestIngestionConstraints) -> Result<()> {
let writer = &self.dbs()?.writer;
if let Some(unparsable_records) =
@@ -330,26 +346,58 @@ where
for unparsable_ids in all_unparsable_ids.chunks(UNPARSABLE_IDS_PER_REQUEST) {
let mut options = GetItemsOptions::new();
for unparsable_id in unparsable_ids {
- options.eq("id", *unparsable_id);
+ options.filter_eq("id", *unparsable_id);
}
let records_chunk = self
.settings_client
.get_records_with_options(&options)?
.records;
- self.ingest_records(writer, &records_chunk)?;
+ self.ingest_records(LAST_INGEST_META_UNPARSABLE, writer, &records_chunk)?;
}
}
+ // use std::collections::BTreeSet;
+ let ingest_record_types = if let Some(rt) = &constraints.providers {
+ rt.iter()
+ .flat_map(|x| x.records_for_provider())
+ .collect::<BTreeSet<_>>()
+ .into_iter()
+ .collect()
+ } else {
+ DEFAULT_RECORDS_TYPES.to_vec()
+ };
+
+ for ingest_record_type in ingest_record_types {
+ self.ingest_records_by_type(ingest_record_type, writer, &constraints)?;
+ }
+
+ Ok(())
+ }
+
+ fn ingest_records_by_type(
+ &self,
+ ingest_record_type: SuggestRecordType,
+ writer: &SuggestDb,
+ constraints: &SuggestIngestionConstraints,
+ ) -> Result<()> {
let mut options = GetItemsOptions::new();
+
// Remote Settings returns records in descending modification order
// (newest first), but we want them in ascending order (oldest first),
// so that we can eventually resume downloading where we left off.
options.sort("last_modified", SortOrder::Ascending);
- if let Some(last_ingest) = writer.read(|dao| dao.get_meta::<u64>(LAST_INGEST_META_KEY))? {
+
+ options.filter_eq("type", ingest_record_type.to_string());
+
+ // Get the last ingest value. This is the max of the last_ingest_keys
+ // that are in the database.
+ if let Some(last_ingest) = writer
+ .read(|dao| dao.get_meta::<u64>(ingest_record_type.last_ingest_meta_key().as_str()))?
+ {
// Only download changes since our last ingest. If our last ingest
// was interrupted, we'll pick up where we left off.
- options.gt("last_modified", last_ingest.to_string());
+ options.filter_gt("last_modified", last_ingest.to_string());
}
if let Some(max_suggestions) = constraints.max_suggestions {
@@ -363,39 +411,56 @@ where
.settings_client
.get_records_with_options(&options)?
.records;
- self.ingest_records(writer, &records)?;
-
+ self.ingest_records(&ingest_record_type.last_ingest_meta_key(), writer, &records)?;
Ok(())
}
- fn ingest_records(&self, writer: &SuggestDb, records: &[RemoteSettingsRecord]) -> Result<()> {
+ fn ingest_records(
+ &self,
+ last_ingest_key: &str,
+ writer: &SuggestDb,
+ records: &[RemoteSettingsRecord],
+ ) -> Result<()> {
for record in records {
let record_id = SuggestRecordId::from(&record.id);
if record.deleted {
// If the entire record was deleted, drop all its suggestions
// and advance the last ingest time.
- writer.write(|dao| dao.handle_deleted_record(record))?;
+ writer.write(|dao| dao.handle_deleted_record(last_ingest_key, record))?;
continue;
}
let Ok(fields) =
serde_json::from_value(serde_json::Value::Object(record.fields.clone()))
else {
// We don't recognize this record's type, so we don't know how
- // to ingest its suggestions. Record this in the meta table.
+ // to ingest its suggestions. Skip processing this record.
writer.write(|dao| dao.handle_unparsable_record(record))?;
continue;
};
match fields {
SuggestRecord::AmpWikipedia => {
- self.ingest_attachment(writer, record, |dao, record_id, suggestions| {
- dao.insert_amp_wikipedia_suggestions(record_id, suggestions)
- })?;
+ self.ingest_attachment(
+ // TODO: Currently re-creating the last_ingest_key because using last_ingest_meta
+ // breaks the tests (particularly the unparsable functionality). So, keeping
+ // a direct reference until we remove the "unparsable" functionality.
+ &SuggestRecordType::AmpWikipedia.last_ingest_meta_key(),
+ writer,
+ record,
+ |dao, record_id, suggestions| {
+ dao.insert_amp_wikipedia_suggestions(record_id, suggestions)
+ },
+ )?;
}
SuggestRecord::AmpMobile => {
- self.ingest_attachment(writer, record, |dao, record_id, suggestions| {
- dao.insert_amp_mobile_suggestions(record_id, suggestions)
- })?;
+ self.ingest_attachment(
+ &SuggestRecordType::AmpMobile.last_ingest_meta_key(),
+ writer,
+ record,
+ |dao, record_id, suggestions| {
+ dao.insert_amp_mobile_suggestions(record_id, suggestions)
+ },
+ )?;
}
SuggestRecord::Icon => {
let (Some(icon_id), Some(attachment)) =
@@ -404,47 +469,79 @@ where
// An icon record should have an icon ID and an
// attachment. Icons that don't have these are
// malformed, so skip to the next record.
- writer.write(|dao| dao.put_last_ingest_if_newer(record.last_modified))?;
+ writer.write(|dao| {
+ dao.put_last_ingest_if_newer(
+ &SuggestRecordType::Icon.last_ingest_meta_key(),
+ record.last_modified,
+ )
+ })?;
continue;
};
let data = self.settings_client.get_attachment(&attachment.location)?;
writer.write(|dao| {
- dao.put_icon(icon_id, &data)?;
- dao.handle_ingested_record(record)
+ dao.put_icon(icon_id, &data, &attachment.mimetype)?;
+ dao.handle_ingested_record(
+ &SuggestRecordType::Icon.last_ingest_meta_key(),
+ record,
+ )
})?;
}
SuggestRecord::Amo => {
- self.ingest_attachment(writer, record, |dao, record_id, suggestions| {
- dao.insert_amo_suggestions(record_id, suggestions)
- })?;
+ self.ingest_attachment(
+ &SuggestRecordType::Amo.last_ingest_meta_key(),
+ writer,
+ record,
+ |dao, record_id, suggestions| {
+ dao.insert_amo_suggestions(record_id, suggestions)
+ },
+ )?;
}
SuggestRecord::Pocket => {
- self.ingest_attachment(writer, record, |dao, record_id, suggestions| {
- dao.insert_pocket_suggestions(record_id, suggestions)
- })?;
+ self.ingest_attachment(
+ &SuggestRecordType::Pocket.last_ingest_meta_key(),
+ writer,
+ record,
+ |dao, record_id, suggestions| {
+ dao.insert_pocket_suggestions(record_id, suggestions)
+ },
+ )?;
}
SuggestRecord::Yelp => {
- self.ingest_attachment(writer, record, |dao, record_id, suggestions| {
- match suggestions.first() {
+ self.ingest_attachment(
+ &SuggestRecordType::Yelp.last_ingest_meta_key(),
+ writer,
+ record,
+ |dao, record_id, suggestions| match suggestions.first() {
Some(suggestion) => dao.insert_yelp_suggestions(record_id, suggestion),
None => Ok(()),
- }
- })?;
+ },
+ )?;
}
SuggestRecord::Mdn => {
- self.ingest_attachment(writer, record, |dao, record_id, suggestions| {
- dao.insert_mdn_suggestions(record_id, suggestions)
- })?;
+ self.ingest_attachment(
+ &SuggestRecordType::Mdn.last_ingest_meta_key(),
+ writer,
+ record,
+ |dao, record_id, suggestions| {
+ dao.insert_mdn_suggestions(record_id, suggestions)
+ },
+ )?;
}
SuggestRecord::Weather(data) => {
- self.ingest_record(writer, record, |dao, record_id| {
- dao.insert_weather_data(record_id, &data)
- })?;
+ self.ingest_record(
+ &SuggestRecordType::Weather.last_ingest_meta_key(),
+ writer,
+ record,
+ |dao, record_id| dao.insert_weather_data(record_id, &data),
+ )?;
}
SuggestRecord::GlobalConfig(config) => {
- self.ingest_record(writer, record, |dao, _| {
- dao.put_global_config(&SuggestGlobalConfig::from(&config))
- })?;
+ self.ingest_record(
+ &SuggestRecordType::GlobalConfig.last_ingest_meta_key(),
+ writer,
+ record,
+ |dao, _| dao.put_global_config(&SuggestGlobalConfig::from(&config)),
+ )?;
}
}
}
@@ -453,6 +550,7 @@ where
fn ingest_record(
&self,
+ last_ingest_key: &str,
writer: &SuggestDb,
record: &RemoteSettingsRecord,
ingestion_handler: impl FnOnce(&mut SuggestDao<'_>, &SuggestRecordId) -> Result<()>,
@@ -469,12 +567,13 @@ where
// Ingest (or re-ingest) all data in the record.
ingestion_handler(dao, &record_id)?;
- dao.handle_ingested_record(record)
+ dao.handle_ingested_record(last_ingest_key, record)
})
}
fn ingest_attachment<T>(
&self,
+ last_ingest_key: &str,
writer: &SuggestDb,
record: &RemoteSettingsRecord,
ingestion_handler: impl FnOnce(&mut SuggestDao<'_>, &SuggestRecordId, &[T]) -> Result<()>,
@@ -486,20 +585,72 @@ where
// This method should be called only when a record is expected to
// have an attachment. If it doesn't have one, it's malformed, so
// skip to the next record.
- writer.write(|dao| dao.put_last_ingest_if_newer(record.last_modified))?;
+ writer
+ .write(|dao| dao.put_last_ingest_if_newer(last_ingest_key, record.last_modified))?;
return Ok(());
};
let attachment_data = self.settings_client.get_attachment(&attachment.location)?;
match serde_json::from_slice::<SuggestAttachment<T>>(&attachment_data) {
- Ok(attachment) => self.ingest_record(writer, record, |dao, record_id| {
- ingestion_handler(dao, record_id, attachment.suggestions())
- }),
+ Ok(attachment) => {
+ self.ingest_record(last_ingest_key, writer, record, |dao, record_id| {
+ ingestion_handler(dao, record_id, attachment.suggestions())
+ })
+ }
Err(_) => writer.write(|dao| dao.handle_unparsable_record(record)),
}
}
}
+#[cfg(feature = "benchmark_api")]
+impl<S> SuggestStoreInner<S>
+where
+ S: SuggestRemoteSettingsClient,
+{
+ pub fn into_settings_client(self) -> S {
+ self.settings_client
+ }
+
+ pub fn ensure_db_initialized(&self) {
+ self.dbs().unwrap();
+ }
+
+ pub fn benchmark_ingest_records_by_type(&self, ingest_record_type: SuggestRecordType) {
+ self.ingest_records_by_type(
+ ingest_record_type,
+ &self.dbs().unwrap().writer,
+ &SuggestIngestionConstraints::default(),
+ )
+ .unwrap()
+ }
+
+ pub fn table_row_counts(&self) -> Vec<(String, u32)> {
+ use sql_support::ConnExt;
+
+ // Note: since this is just used for debugging, use unwrap to simplify the error handling.
+ let reader = &self.dbs().unwrap().reader;
+ let conn = reader.conn.lock();
+ let table_names: Vec<String> = conn
+ .query_rows_and_then(
+ "SELECT name FROM sqlite_master where type = 'table'",
+ (),
+ |row| row.get(0),
+ )
+ .unwrap();
+ let mut table_names_with_counts: Vec<(String, u32)> = table_names
+ .into_iter()
+ .map(|name| {
+ let count: u32 = conn
+ .query_one(&format!("SELECT COUNT(*) FROM {name}"))
+ .unwrap();
+ (name, count)
+ })
+ .collect();
+ table_names_with_counts.sort_by(|a, b| (b.1.cmp(&a.1)));
+ table_names_with_counts
+ }
+}
+
/// Holds a store's open connections to the Suggest database.
struct SuggestStoreDbs {
/// A read-write connection used to update the database with new data.
@@ -549,10 +700,6 @@ mod tests {
"file:test_store_data_{}?mode=memory&cache=shared",
hex::encode(unique_suffix),
),
- format!(
- "file:test_store_cache_{}?mode=memory&cache=shared",
- hex::encode(unique_suffix),
- ),
settings_client,
)
}
@@ -721,7 +868,14 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta::<u64>(LAST_INGEST_META_KEY)?, Some(15));
+ assert_eq!(
+ dao.get_meta::<u64>(
+ SuggestRecordType::AmpWikipedia
+ .last_ingest_meta_key()
+ .as_str()
+ )?,
+ Some(15)
+ );
expect![[r#"
[
Amp {
@@ -729,6 +883,7 @@ mod tests {
url: "https://www.lph-nm.biz",
raw_url: "https://www.lph-nm.biz",
icon: None,
+ icon_mimetype: None,
full_keyword: "los",
block_id: 0,
advertiser: "Los Pollos Hermanos",
@@ -834,6 +989,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "lasagna",
block_id: 0,
advertiser: "Good Place Eats",
@@ -872,6 +1030,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "penne",
block_id: 0,
advertiser: "Good Place Eats",
@@ -895,6 +1056,267 @@ mod tests {
Ok(())
}
+ #[test]
+ fn ingest_full_keywords() -> anyhow::Result<()> {
+ before_each();
+
+ let snapshot = Snapshot::with_records(json!([{
+ "id": "1",
+ "type": "data",
+ "last_modified": 15,
+ "attachment": {
+ "filename": "data-1.json",
+ "mimetype": "application/json",
+ "location": "data-1.json",
+ "hash": "",
+ "size": 0,
+ },
+ }, {
+ "id": "2",
+ "type": "data",
+ "last_modified": 15,
+ "attachment": {
+ "filename": "data-2.json",
+ "mimetype": "application/json",
+ "location": "data-2.json",
+ "hash": "",
+ "size": 0,
+ },
+ }, {
+ "id": "3",
+ "type": "data",
+ "last_modified": 15,
+ "attachment": {
+ "filename": "data-3.json",
+ "mimetype": "application/json",
+ "location": "data-3.json",
+ "hash": "",
+ "size": 0,
+ },
+ }, {
+ "id": "4",
+ "type": "amp-mobile-suggestions",
+ "last_modified": 15,
+ "attachment": {
+ "filename": "data-4.json",
+ "mimetype": "application/json",
+ "location": "data-4.json",
+ "hash": "",
+ "size": 0,
+ },
+ }]))?
+ // AMP attachment with full keyword data
+ .with_data(
+ "data-1.json",
+ json!([{
+ "id": 0,
+ "advertiser": "Los Pollos Hermanos",
+ "iab_category": "8 - Food & Drink",
+ "keywords": ["lo", "los", "los p", "los pollos", "los pollos h", "los pollos hermanos"],
+ "full_keywords": [
+ // Full keyword for the first 4 keywords
+ ("los pollos", 4),
+ // Full keyword for the next 2 keywords
+ ("los pollos hermanos (restaurant)", 2),
+ ],
+ "title": "Los Pollos Hermanos - Albuquerque - 1",
+ "url": "https://www.lph-nm.biz",
+ "icon": "5678",
+ "impression_url": "https://example.com/impression_url",
+ "click_url": "https://example.com/click_url",
+ "score": 0.3
+ }]),
+ )?
+ // AMP attachment without a full keyword
+ .with_data(
+ "data-2.json",
+ json!([{
+ "id": 1,
+ "advertiser": "Los Pollos Hermanos",
+ "iab_category": "8 - Food & Drink",
+ "keywords": ["lo", "los", "los p", "los pollos", "los pollos h", "los pollos hermanos"],
+ "title": "Los Pollos Hermanos - Albuquerque - 2",
+ "url": "https://www.lph-nm.biz",
+ "icon": "5678",
+ "impression_url": "https://example.com/impression_url",
+ "click_url": "https://example.com/click_url",
+ "score": 0.3
+ }]),
+ )?
+ // Wikipedia attachment with full keyword data. We should ignore the full
+ // keyword data for Wikipedia suggestions
+ .with_data(
+ "data-3.json",
+ json!([{
+ "id": 2,
+ "advertiser": "Wikipedia",
+ "keywords": ["lo", "los", "los p", "los pollos", "los pollos h", "los pollos hermanos"],
+ "title": "Los Pollos Hermanos - Albuquerque - Wiki",
+ "full_keywords": [
+ ("Los Pollos Hermanos - Albuquerque", 6),
+ ],
+ "url": "https://www.lph-nm.biz",
+ "icon": "5678",
+ "score": 0.3,
+ }]),
+ )?
+ // Amp mobile suggestion, this is essentially the same as 1, except for the SuggestionProvider
+ .with_data(
+ "data-4.json",
+ json!([{
+ "id": 0,
+ "advertiser": "Los Pollos Hermanos",
+ "iab_category": "8 - Food & Drink",
+ "keywords": ["lo", "los", "los p", "los pollos", "los pollos h", "los pollos hermanos"],
+ "full_keywords": [
+ // Full keyword for the first 4 keywords
+ ("los pollos", 4),
+ // Full keyword for the next 2 keywords
+ ("los pollos hermanos (restaurant)", 2),
+ ],
+ "title": "Los Pollos Hermanos - Albuquerque - 4",
+ "url": "https://www.lph-nm.biz",
+ "icon": "5678",
+ "impression_url": "https://example.com/impression_url",
+ "click_url": "https://example.com/click_url",
+ "score": 0.3
+ }]),
+ )?;
+
+ let store = unique_test_store(SnapshotSettingsClient::with_snapshot(snapshot));
+
+ store.ingest(SuggestIngestionConstraints::default())?;
+
+ store.dbs()?.reader.read(|dao| {
+ // This one should match the first full keyword for the first AMP item.
+ expect![[r#"
+ [
+ Amp {
+ title: "Los Pollos Hermanos - Albuquerque - 1",
+ url: "https://www.lph-nm.biz",
+ raw_url: "https://www.lph-nm.biz",
+ icon: None,
+ icon_mimetype: None,
+ full_keyword: "los pollos",
+ block_id: 0,
+ advertiser: "Los Pollos Hermanos",
+ iab_category: "8 - Food & Drink",
+ impression_url: "https://example.com/impression_url",
+ click_url: "https://example.com/click_url",
+ raw_click_url: "https://example.com/click_url",
+ score: 0.3,
+ },
+ Amp {
+ title: "Los Pollos Hermanos - Albuquerque - 2",
+ url: "https://www.lph-nm.biz",
+ raw_url: "https://www.lph-nm.biz",
+ icon: None,
+ icon_mimetype: None,
+ full_keyword: "los",
+ block_id: 1,
+ advertiser: "Los Pollos Hermanos",
+ iab_category: "8 - Food & Drink",
+ impression_url: "https://example.com/impression_url",
+ click_url: "https://example.com/click_url",
+ raw_click_url: "https://example.com/click_url",
+ score: 0.3,
+ },
+ ]
+ "#]]
+ .assert_debug_eq(&dao.fetch_suggestions(&SuggestionQuery {
+ keyword: "lo".into(),
+ providers: vec![SuggestionProvider::Amp],
+ limit: None,
+ })?);
+ // This one should match the second full keyword for the first AMP item.
+ expect![[r#"
+ [
+ Amp {
+ title: "Los Pollos Hermanos - Albuquerque - 1",
+ url: "https://www.lph-nm.biz",
+ raw_url: "https://www.lph-nm.biz",
+ icon: None,
+ icon_mimetype: None,
+ full_keyword: "los pollos hermanos (restaurant)",
+ block_id: 0,
+ advertiser: "Los Pollos Hermanos",
+ iab_category: "8 - Food & Drink",
+ impression_url: "https://example.com/impression_url",
+ click_url: "https://example.com/click_url",
+ raw_click_url: "https://example.com/click_url",
+ score: 0.3,
+ },
+ Amp {
+ title: "Los Pollos Hermanos - Albuquerque - 2",
+ url: "https://www.lph-nm.biz",
+ raw_url: "https://www.lph-nm.biz",
+ icon: None,
+ icon_mimetype: None,
+ full_keyword: "los pollos hermanos",
+ block_id: 1,
+ advertiser: "Los Pollos Hermanos",
+ iab_category: "8 - Food & Drink",
+ impression_url: "https://example.com/impression_url",
+ click_url: "https://example.com/click_url",
+ raw_click_url: "https://example.com/click_url",
+ score: 0.3,
+ },
+ ]
+ "#]]
+ .assert_debug_eq(&dao.fetch_suggestions(&SuggestionQuery {
+ keyword: "los pollos h".into(),
+ providers: vec![SuggestionProvider::Amp],
+ limit: None,
+ })?);
+ // This one matches a Wikipedia suggestion, so the full keyword should be ignored
+ expect![[r#"
+ [
+ Wikipedia {
+ title: "Los Pollos Hermanos - Albuquerque - Wiki",
+ url: "https://www.lph-nm.biz",
+ icon: None,
+ icon_mimetype: None,
+ full_keyword: "los",
+ },
+ ]
+ "#]]
+ .assert_debug_eq(&dao.fetch_suggestions(&SuggestionQuery {
+ keyword: "los".into(),
+ providers: vec![SuggestionProvider::Wikipedia],
+ limit: None,
+ })?);
+ // This one matches a Wikipedia suggestion, so the full keyword should be ignored
+ expect![[r#"
+ [
+ Amp {
+ title: "Los Pollos Hermanos - Albuquerque - 4",
+ url: "https://www.lph-nm.biz",
+ raw_url: "https://www.lph-nm.biz",
+ icon: None,
+ icon_mimetype: None,
+ full_keyword: "los pollos hermanos (restaurant)",
+ block_id: 0,
+ advertiser: "Los Pollos Hermanos",
+ iab_category: "8 - Food & Drink",
+ impression_url: "https://example.com/impression_url",
+ click_url: "https://example.com/click_url",
+ raw_click_url: "https://example.com/click_url",
+ score: 0.3,
+ },
+ ]
+ "#]]
+ .assert_debug_eq(&dao.fetch_suggestions(&SuggestionQuery {
+ keyword: "los pollos h".into(),
+ providers: vec![SuggestionProvider::AmpMobile],
+ limit: None,
+ })?);
+
+ Ok(())
+ })?;
+
+ Ok(())
+ }
+
/// Tests ingesting a data attachment containing a single suggestion,
/// instead of an array of suggestions.
#[test]
@@ -941,6 +1363,7 @@ mod tests {
url: "https://www.lasagna.restaurant",
raw_url: "https://www.lasagna.restaurant",
icon: None,
+ icon_mimetype: None,
full_keyword: "lasagna",
block_id: 0,
advertiser: "Good Place Eats",
@@ -1014,7 +1437,14 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta(LAST_INGEST_META_KEY)?, Some(15u64));
+ assert_eq!(
+ dao.get_meta(
+ SuggestRecordType::AmpWikipedia
+ .last_ingest_meta_key()
+ .as_str()
+ )?,
+ Some(15u64)
+ );
expect![[r#"
[
Amp {
@@ -1022,6 +1452,7 @@ mod tests {
url: "https://www.lasagna.restaurant",
raw_url: "https://www.lasagna.restaurant",
icon: None,
+ icon_mimetype: None,
full_keyword: "lasagna",
block_id: 0,
advertiser: "Good Place Eats",
@@ -1084,8 +1515,15 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
- store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta(LAST_INGEST_META_KEY)?, Some(30u64));
+ store.dbs()?.reader.read(|dao: &SuggestDao<'_>| {
+ assert_eq!(
+ dao.get_meta(
+ SuggestRecordType::AmpWikipedia
+ .last_ingest_meta_key()
+ .as_str()
+ )?,
+ Some(30u64)
+ );
assert!(dao
.fetch_suggestions(&SuggestionQuery {
keyword: "la".into(),
@@ -1100,6 +1538,7 @@ mod tests {
url: "https://www.lph-nm.biz",
raw_url: "https://www.lph-nm.biz",
icon: None,
+ icon_mimetype: None,
full_keyword: "los pollos",
block_id: 0,
advertiser: "Los Pollos Hermanos",
@@ -1123,6 +1562,7 @@ mod tests {
url: "https://penne.biz",
raw_url: "https://penne.biz",
icon: None,
+ icon_mimetype: None,
full_keyword: "penne",
block_id: 0,
advertiser: "Good Place Eats",
@@ -1219,7 +1659,10 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta(LAST_INGEST_META_KEY)?, Some(25u64));
+ assert_eq!(
+ dao.get_meta(SuggestRecordType::Icon.last_ingest_meta_key().as_str())?,
+ Some(25u64)
+ );
assert_eq!(
dao.conn
.query_one::<i64>("SELECT count(*) FROM suggestions")?,
@@ -1259,7 +1702,10 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta(LAST_INGEST_META_KEY)?, Some(35u64));
+ assert_eq!(
+ dao.get_meta(SuggestRecordType::Icon.last_ingest_meta_key().as_str())?,
+ Some(35u64)
+ );
expect![[r#"
[
Amp {
@@ -1286,6 +1732,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "lasagna",
block_id: 0,
advertiser: "Good Place Eats",
@@ -1327,6 +1776,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "los",
block_id: 0,
advertiser: "Los Pollos Hermanos",
@@ -1422,7 +1874,10 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta(LAST_INGEST_META_KEY)?, Some(15u64));
+ assert_eq!(
+ dao.get_meta(SuggestRecordType::Amo.last_ingest_meta_key().as_str())?,
+ Some(15u64)
+ );
expect![[r#"
[
@@ -1513,7 +1968,10 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta(LAST_INGEST_META_KEY)?, Some(30u64));
+ assert_eq!(
+ dao.get_meta(SuggestRecordType::Amo.last_ingest_meta_key().as_str())?,
+ Some(30u64)
+ );
expect![[r#"
[
@@ -1648,13 +2106,24 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta::<u64>(LAST_INGEST_META_KEY)?, Some(20));
assert_eq!(
dao.conn
.query_one::<i64>("SELECT count(*) FROM suggestions")?,
1
);
assert_eq!(dao.conn.query_one::<i64>("SELECT count(*) FROM icons")?, 1);
+ assert_eq!(
+ dao.get_meta(
+ SuggestRecordType::AmpWikipedia
+ .last_ingest_meta_key()
+ .as_str()
+ )?,
+ Some(15)
+ );
+ assert_eq!(
+ dao.get_meta(SuggestRecordType::Icon.last_ingest_meta_key().as_str())?,
+ Some(20)
+ );
Ok(())
})?;
@@ -1674,14 +2143,16 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta::<u64>(LAST_INGEST_META_KEY)?, Some(30));
assert_eq!(
dao.conn
.query_one::<i64>("SELECT count(*) FROM suggestions")?,
0
);
assert_eq!(dao.conn.query_one::<i64>("SELECT count(*) FROM icons")?, 0);
-
+ assert_eq!(
+ dao.get_meta(SuggestRecordType::Icon.last_ingest_meta_key().as_str())?,
+ Some(30)
+ );
Ok(())
})?;
@@ -1717,6 +2188,7 @@ mod tests {
for (max_suggestions, expected_limit) in table {
store.ingest(SuggestIngestionConstraints {
max_suggestions: Some(max_suggestions),
+ providers: Some(vec![SuggestionProvider::Amp]),
})?;
let actual_limit = store
.settings_client
@@ -1772,7 +2244,14 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta::<u64>(LAST_INGEST_META_KEY)?, Some(15));
+ assert_eq!(
+ dao.get_meta::<u64>(
+ SuggestRecordType::AmpWikipedia
+ .last_ingest_meta_key()
+ .as_str()
+ )?,
+ Some(15)
+ );
assert_eq!(
dao.conn
.query_one::<i64>("SELECT count(*) FROM suggestions")?,
@@ -1789,7 +2268,14 @@ mod tests {
store.clear()?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta::<u64>(LAST_INGEST_META_KEY)?, None);
+ assert_eq!(
+ dao.get_meta::<u64>(
+ SuggestRecordType::AmpWikipedia
+ .last_ingest_meta_key()
+ .as_str()
+ )?,
+ None
+ );
assert_eq!(
dao.conn
.query_one::<i64>("SELECT count(*) FROM suggestions")?,
@@ -2080,6 +2566,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "lasagna",
block_id: 0,
advertiser: "Good Place Eats",
@@ -2143,6 +2632,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "multimatch",
},
]
@@ -2199,6 +2691,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "multimatch",
},
]
@@ -2268,6 +2763,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "lasagna",
block_id: 0,
advertiser: "Good Place Eats",
@@ -2349,6 +2847,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "california",
},
Wikipedia {
@@ -2370,6 +2871,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "california",
},
]
@@ -2403,6 +2907,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "california",
},
]
@@ -2614,6 +3121,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: true,
subject_exact_match: true,
@@ -2647,6 +3157,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: true,
subject_exact_match: true,
@@ -2680,6 +3193,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: true,
subject_exact_match: true,
@@ -2735,6 +3251,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: true,
subject_exact_match: true,
@@ -2779,6 +3298,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: true,
subject_exact_match: true,
@@ -2812,6 +3334,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: true,
subject_exact_match: true,
@@ -2856,6 +3381,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: true,
subject_exact_match: true,
@@ -2889,6 +3417,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: true,
subject_exact_match: true,
@@ -2933,6 +3464,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: true,
subject_exact_match: true,
@@ -2966,6 +3500,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: true,
subject_exact_match: true,
@@ -2999,6 +3536,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: true,
@@ -3032,6 +3572,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: true,
@@ -3076,6 +3619,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: true,
@@ -3109,6 +3655,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: true,
@@ -3186,6 +3735,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: true,
@@ -3219,6 +3771,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: true,
@@ -3252,6 +3807,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: true,
subject_exact_match: true,
@@ -3285,6 +3843,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: true,
subject_exact_match: true,
@@ -3318,6 +3879,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: true,
@@ -3362,6 +3926,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: false,
@@ -3395,6 +3962,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: true,
@@ -3428,6 +3998,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: false,
@@ -3483,6 +4056,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: false,
@@ -3516,6 +4092,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: false,
@@ -3549,6 +4128,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: false,
@@ -3593,6 +4175,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/svg+xml",
+ ),
score: 0.5,
has_location_sign: false,
subject_exact_match: false,
@@ -3734,6 +4319,7 @@ mod tests {
url: "https://www.lasagna.restaurant",
raw_url: "https://www.lasagna.restaurant",
icon: None,
+ icon_mimetype: None,
full_keyword: "amp wiki match",
block_id: 0,
advertiser: "Good Place Eats",
@@ -3762,6 +4348,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "amp wiki match",
},
Amp {
@@ -3769,6 +4358,7 @@ mod tests {
url: "https://penne.biz",
raw_url: "https://penne.biz",
icon: None,
+ icon_mimetype: None,
full_keyword: "amp wiki match",
block_id: 0,
advertiser: "Good Place Eats",
@@ -3801,6 +4391,7 @@ mod tests {
url: "https://www.lasagna.restaurant",
raw_url: "https://www.lasagna.restaurant",
icon: None,
+ icon_mimetype: None,
full_keyword: "amp wiki match",
block_id: 0,
advertiser: "Good Place Eats",
@@ -3829,6 +4420,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "amp wiki match",
},
]
@@ -3873,6 +4467,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "pocket wiki match",
},
Pocket {
@@ -4193,6 +4790,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "lasagna",
block_id: 0,
advertiser: "Good Place Eats",
@@ -4234,6 +4834,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "lasagna",
block_id: 0,
advertiser: "Good Place Eats",
@@ -4275,6 +4878,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "lasagna",
block_id: 0,
advertiser: "Good Place Eats",
@@ -4304,6 +4910,9 @@ mod tests {
110,
],
),
+ icon_mimetype: Some(
+ "image/png",
+ ),
full_keyword: "lasagna",
block_id: 0,
advertiser: "Good Place Eats",
@@ -4365,7 +4974,10 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta::<u64>(LAST_INGEST_META_KEY)?, Some(45));
+ assert_eq!(
+ dao.get_meta::<u64>(SuggestRecordType::Icon.last_ingest_meta_key().as_str())?,
+ Some(45)
+ );
assert_eq!(
dao.conn
.query_one::<i64>("SELECT count(*) FROM suggestions")?,
@@ -4400,16 +5012,19 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta::<u64>(LAST_INGEST_META_KEY)?, Some(30));
+ assert_eq!(
+ dao.get_meta("last_quicksuggest_ingest_unparsable")?,
+ Some(30)
+ );
expect![[r#"
Some(
UnparsableRecords(
{
"clippy-2": UnparsableRecord {
- schema_version: 14,
+ schema_version: 18,
},
"fancy-new-suggestions-1": UnparsableRecord {
- schema_version: 14,
+ schema_version: 18,
},
},
),
@@ -4469,16 +5084,19 @@ mod tests {
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta::<u64>(LAST_INGEST_META_KEY)?, Some(30));
+ assert_eq!(
+ dao.get_meta("last_quicksuggest_ingest_unparsable")?,
+ Some(30)
+ );
expect![[r#"
Some(
UnparsableRecords(
{
"clippy-2": UnparsableRecord {
- schema_version: 14,
+ schema_version: 18,
},
"fancy-new-suggestions-1": UnparsableRecord {
- schema_version: 14,
+ schema_version: 18,
},
},
),
@@ -4505,13 +5123,105 @@ mod tests {
let store = unique_test_store(SnapshotSettingsClient::with_snapshot(snapshot));
store.dbs()?.writer.write(|dao| {
- dao.put_meta(LAST_INGEST_META_KEY, 30)?;
+ dao.put_meta(
+ SuggestRecordType::AmpWikipedia
+ .last_ingest_meta_key()
+ .as_str(),
+ 30,
+ )?;
Ok(())
})?;
store.ingest(SuggestIngestionConstraints::default())?;
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta::<u64>(LAST_INGEST_META_KEY)?, Some(30));
+ assert_eq!(
+ dao.get_meta::<u64>(
+ SuggestRecordType::AmpWikipedia
+ .last_ingest_meta_key()
+ .as_str()
+ )?,
+ Some(30)
+ );
+ Ok(())
+ })?;
+
+ Ok(())
+ }
+
+ /// Tests that we only ingest providers that we're concerned with.
+ #[test]
+ fn ingest_constraints_provider() -> anyhow::Result<()> {
+ before_each();
+
+ let snapshot = Snapshot::with_records(json!([{
+ "id": "data-1",
+ "type": "data",
+ "last_modified": 15,
+ }, {
+ "id": "icon-1",
+ "type": "icon",
+ "last_modified": 30,
+ }]))?;
+
+ let store = unique_test_store(SnapshotSettingsClient::with_snapshot(snapshot));
+ store.dbs()?.writer.write(|dao| {
+ // Check that existing data is updated properly.
+ dao.put_meta(
+ SuggestRecordType::AmpWikipedia
+ .last_ingest_meta_key()
+ .as_str(),
+ 10,
+ )?;
+ Ok(())
+ })?;
+
+ let constraints = SuggestIngestionConstraints {
+ max_suggestions: Some(100),
+ providers: Some(vec![SuggestionProvider::Amp, SuggestionProvider::Pocket]),
+ };
+ store.ingest(constraints)?;
+
+ store.dbs()?.reader.read(|dao| {
+ assert_eq!(
+ dao.get_meta::<u64>(
+ SuggestRecordType::AmpWikipedia
+ .last_ingest_meta_key()
+ .as_str()
+ )?,
+ Some(15)
+ );
+ assert_eq!(
+ dao.get_meta::<u64>(SuggestRecordType::Icon.last_ingest_meta_key().as_str())?,
+ Some(30)
+ );
+ assert_eq!(
+ dao.get_meta::<u64>(SuggestRecordType::Pocket.last_ingest_meta_key().as_str())?,
+ None
+ );
+ assert_eq!(
+ dao.get_meta::<u64>(SuggestRecordType::Amo.last_ingest_meta_key().as_str())?,
+ None
+ );
+ assert_eq!(
+ dao.get_meta::<u64>(SuggestRecordType::Yelp.last_ingest_meta_key().as_str())?,
+ None
+ );
+ assert_eq!(
+ dao.get_meta::<u64>(SuggestRecordType::Mdn.last_ingest_meta_key().as_str())?,
+ None
+ );
+ assert_eq!(
+ dao.get_meta::<u64>(SuggestRecordType::AmpMobile.last_ingest_meta_key().as_str())?,
+ None
+ );
+ assert_eq!(
+ dao.get_meta::<u64>(
+ SuggestRecordType::GlobalConfig
+ .last_ingest_meta_key()
+ .as_str()
+ )?,
+ None
+ );
Ok(())
})?;
@@ -4582,10 +5292,10 @@ mod tests {
UnparsableRecords(
{
"clippy-2": UnparsableRecord {
- schema_version: 14,
+ schema_version: 18,
},
"fancy-new-suggestions-1": UnparsableRecord {
- schema_version: 14,
+ schema_version: 18,
},
},
),
@@ -4671,7 +5381,7 @@ mod tests {
UnparsableRecords(
{
"invalid-attachment": UnparsableRecord {
- schema_version: 14,
+ schema_version: 18,
},
},
),
@@ -4683,7 +5393,14 @@ mod tests {
// Test that the valid record was read
store.dbs()?.reader.read(|dao| {
- assert_eq!(dao.get_meta::<u64>(LAST_INGEST_META_KEY)?, Some(15));
+ assert_eq!(
+ dao.get_meta(
+ SuggestRecordType::AmpWikipedia
+ .last_ingest_meta_key()
+ .as_str()
+ )?,
+ Some(15)
+ );
expect![[r#"
[
Amp {
@@ -4691,6 +5408,7 @@ mod tests {
url: "https://www.lph-nm.biz",
raw_url: "https://www.lph-nm.biz",
icon: None,
+ icon_mimetype: None,
full_keyword: "los",
block_id: 0,
advertiser: "Los Pollos Hermanos",
@@ -4899,6 +5617,7 @@ mod tests {
url: "https://www.yelp.com/search?find_desc=ramen",
title: "ramen",
icon: None,
+ icon_mimetype: None,
score: 0.5,
has_location_sign: false,
subject_exact_match: true,
@@ -5313,4 +6032,204 @@ mod tests {
Ok(())
}
+
+ #[test]
+ fn remove_dismissed_suggestions() -> anyhow::Result<()> {
+ before_each();
+
+ let snapshot = Snapshot::with_records(json!([{
+ "id": "data-1",
+ "type": "data",
+ "last_modified": 15,
+ "attachment": {
+ "filename": "data-1.json",
+ "mimetype": "application/json",
+ "location": "data-1.json",
+ "hash": "",
+ "size": 0,
+ },
+
+ }, {
+ "id": "data-2",
+ "type": "amo-suggestions",
+ "last_modified": 15,
+ "attachment": {
+ "filename": "data-2.json",
+ "mimetype": "application/json",
+ "location": "data-2.json",
+ "hash": "",
+ "size": 0,
+ },
+ }, {
+ "id": "data-3",
+ "type": "pocket-suggestions",
+ "last_modified": 15,
+ "attachment": {
+ "filename": "data-3.json",
+ "mimetype": "application/json",
+ "location": "data-3.json",
+ "hash": "",
+ "size": 0,
+ },
+ }, {
+ "id": "data-5",
+ "type": "mdn-suggestions",
+ "last_modified": 15,
+ "attachment": {
+ "filename": "data-5.json",
+ "mimetype": "application/json",
+ "location": "data-5.json",
+ "hash": "",
+ "size": 0,
+ },
+ }, {
+ "id": "data-6",
+ "type": "amp-mobile-suggestions",
+ "last_modified": 15,
+ "attachment": {
+ "filename": "data-6.json",
+ "mimetype": "application/json",
+ "location": "data-6.json",
+ "hash": "",
+ "size": 0,
+ },
+ }, {
+ "id": "icon-2",
+ "type": "icon",
+ "last_modified": 20,
+ "attachment": {
+ "filename": "icon-2.png",
+ "mimetype": "image/png",
+ "location": "icon-2.png",
+ "hash": "",
+ "size": 0,
+ },
+ }, {
+ "id": "icon-3",
+ "type": "icon",
+ "last_modified": 25,
+ "attachment": {
+ "filename": "icon-3.png",
+ "mimetype": "image/png",
+ "location": "icon-3.png",
+ "hash": "",
+ "size": 0,
+ },
+ }]))?
+ .with_data(
+ "data-1.json",
+ json!([{
+ "id": 0,
+ "advertiser": "Good Place Eats",
+ "iab_category": "8 - Food & Drink",
+ "keywords": ["cats"],
+ "title": "Lasagna Come Out Tomorrow",
+ "url": "https://www.lasagna.restaurant",
+ "icon": "2",
+ "impression_url": "https://example.com/impression_url",
+ "click_url": "https://example.com/click_url",
+ "score": 0.31
+ }, {
+ "id": 0,
+ "advertiser": "Wikipedia",
+ "iab_category": "5 - Education",
+ "keywords": ["cats"],
+ "title": "California",
+ "url": "https://wikipedia.org/California",
+ "icon": "3"
+ }]),
+ )?
+ .with_data(
+ "data-2.json",
+ json!([
+ {
+ "description": "amo suggestion",
+ "url": "https://addons.mozilla.org/en-US/firefox/addon/example",
+ "guid": "{b9db16a4-6edc-47ec-a1f4-b86292ed211d}",
+ "keywords": ["cats"],
+ "title": "Firefox Relay",
+ "icon": "https://addons.mozilla.org/user-media/addon_icons/2633/2633704-64.png?modified=2c11a80b",
+ "rating": "4.9",
+ "number_of_ratings": 888,
+ "score": 0.32
+ },
+ ]),
+ )?
+ .with_data(
+ "data-3.json",
+ json!([
+ {
+ "description": "pocket suggestion",
+ "url": "https://getpocket.com/collections/its-not-just-burnout-how-grind-culture-failed-women",
+ "lowConfidenceKeywords": [],
+ "highConfidenceKeywords": ["cats"],
+ "title": "‘It’s Not Just Burnout:’ How Grind Culture Fails Women",
+ "score": 0.33
+ },
+ ]),
+ )?
+ .with_data(
+ "data-5.json",
+ json!([
+ {
+ "description": "Javascript Array",
+ "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array",
+ "keywords": ["cats"],
+ "title": "Array",
+ "score": 0.24
+ },
+ ]),
+ )?
+ .with_data(
+ "data-6.json",
+ json!([
+ {
+ "id": 0,
+ "advertiser": "Good Place Eats",
+ "iab_category": "8 - Food & Drink",
+ "keywords": ["cats"],
+ "title": "Mobile - Lasagna Come Out Tomorrow",
+ "url": "https://www.lasagna.restaurant",
+ "icon": "3",
+ "impression_url": "https://example.com/impression_url",
+ "click_url": "https://example.com/click_url",
+ "score": 0.26
+ }
+ ]),
+ )?
+ .with_icon("icon-2.png", "i-am-an-icon".as_bytes().into())
+ .with_icon("icon-3.png", "also-an-icon".as_bytes().into());
+
+ let store = unique_test_store(SnapshotSettingsClient::with_snapshot(snapshot));
+ store.ingest(SuggestIngestionConstraints::default())?;
+
+ // A query for cats should return all suggestions
+ let query = SuggestionQuery {
+ keyword: "cats".into(),
+ providers: vec![
+ SuggestionProvider::Amp,
+ SuggestionProvider::Wikipedia,
+ SuggestionProvider::Amo,
+ SuggestionProvider::Pocket,
+ SuggestionProvider::Mdn,
+ SuggestionProvider::AmpMobile,
+ ],
+ limit: None,
+ };
+ let results = store.query(query.clone())?;
+ assert_eq!(results.len(), 6);
+
+ for result in results {
+ store.dismiss_suggestion(result.raw_url().unwrap().to_string())?;
+ }
+
+ // After dismissing the suggestions, the next query shouldn't return them
+ assert_eq!(store.query(query.clone())?.len(), 0);
+
+ // Clearing the dismissals should cause them to be returned again
+ store.clear_dismissed_suggestions()?;
+ assert_eq!(store.query(query.clone())?.len(), 6);
+
+ Ok(())
+ }
}
diff --git a/third_party/rust/suggest/src/suggest.udl b/third_party/rust/suggest/src/suggest.udl
index 1cd8911a48..4a4e3fe9a0 100644
--- a/third_party/rust/suggest/src/suggest.udl
+++ b/third_party/rust/suggest/src/suggest.udl
@@ -40,6 +40,7 @@ interface Suggestion {
string url,
string raw_url,
sequence<u8>? icon,
+ string? icon_mimetype,
string full_keyword,
i64 block_id,
string advertiser,
@@ -59,6 +60,7 @@ interface Suggestion {
string title,
string url,
sequence<u8>? icon,
+ string? icon_mimetype,
string full_keyword
);
Amo(
@@ -75,6 +77,7 @@ interface Suggestion {
string url,
string title,
sequence<u8>? icon,
+ string? icon_mimetype,
f64 score,
boolean has_location_sign,
boolean subject_exact_match,
@@ -99,6 +102,7 @@ dictionary SuggestionQuery {
dictionary SuggestIngestionConstraints {
u64? max_suggestions = null;
+ sequence<SuggestionProvider>? providers = null;
};
dictionary SuggestGlobalConfig {
@@ -119,6 +123,12 @@ interface SuggestStore {
[Throws=SuggestApiError]
sequence<Suggestion> query(SuggestionQuery query);
+ [Throws=SuggestApiError]
+ void dismiss_suggestion(string raw_suggestion_url);
+
+ [Throws=SuggestApiError]
+ void clear_dismissed_suggestions();
+
void interrupt();
[Throws=SuggestApiError]
@@ -140,6 +150,7 @@ interface SuggestStoreBuilder {
[Self=ByArc]
SuggestStoreBuilder data_path(string path);
+ // Deprecated: this is no longer used by the suggest component.
[Self=ByArc]
SuggestStoreBuilder cache_path(string path);
diff --git a/third_party/rust/suggest/src/suggestion.rs b/third_party/rust/suggest/src/suggestion.rs
index f5425e3c73..c0b45524c7 100644
--- a/third_party/rust/suggest/src/suggestion.rs
+++ b/third_party/rust/suggest/src/suggestion.rs
@@ -29,6 +29,7 @@ pub enum Suggestion {
url: String,
raw_url: String,
icon: Option<Vec<u8>>,
+ icon_mimetype: Option<String>,
full_keyword: String,
block_id: i64,
advertiser: String,
@@ -48,6 +49,7 @@ pub enum Suggestion {
title: String,
url: String,
icon: Option<Vec<u8>>,
+ icon_mimetype: Option<String>,
full_keyword: String,
},
Amo {
@@ -64,6 +66,7 @@ pub enum Suggestion {
url: String,
title: String,
icon: Option<Vec<u8>>,
+ icon_mimetype: Option<String>,
score: f64,
has_location_sign: bool,
subject_exact_match: bool,
@@ -106,6 +109,37 @@ impl Ord for Suggestion {
}
}
+impl Suggestion {
+ /// Get the URL for this suggestion, if present
+ pub fn url(&self) -> Option<&str> {
+ match self {
+ Self::Amp { url, .. }
+ | Self::Pocket { url, .. }
+ | Self::Wikipedia { url, .. }
+ | Self::Amo { url, .. }
+ | Self::Yelp { url, .. }
+ | Self::Mdn { url, .. } => Some(url),
+ _ => None,
+ }
+ }
+
+ /// Get the raw URL for this suggestion, if present
+ ///
+ /// This is the same as `url` except for Amp. In that case, `url` is the URL after being
+ /// "cooked" using template interpolation, while `raw_url` is the URL template.
+ pub fn raw_url(&self) -> Option<&str> {
+ match self {
+ Self::Amp { raw_url: url, .. }
+ | Self::Pocket { url, .. }
+ | Self::Wikipedia { url, .. }
+ | Self::Amo { url, .. }
+ | Self::Yelp { url, .. }
+ | Self::Mdn { url, .. } => Some(url),
+ _ => None,
+ }
+ }
+}
+
impl Eq for Suggestion {}
/// Replaces all template parameters in a "raw" sponsored suggestion URL,
/// producing a "cooked" URL with real values.
diff --git a/third_party/rust/suggest/src/yelp.rs b/third_party/rust/suggest/src/yelp.rs
index 2413709c67..0eb6c10d56 100644
--- a/third_party/rust/suggest/src/yelp.rs
+++ b/third_party/rust/suggest/src/yelp.rs
@@ -52,7 +52,7 @@ const SUBJECT_PREFIX_MATCH_THRESHOLD: usize = 2;
impl<'a> SuggestDao<'a> {
/// Inserts the suggestions for Yelp attachment into the database.
- pub fn insert_yelp_suggestions(
+ pub(crate) fn insert_yelp_suggestions(
&mut self,
record_id: &SuggestRecordId,
suggestion: &DownloadedYelpSuggestion,
@@ -130,7 +130,10 @@ impl<'a> SuggestDao<'a> {
}
/// Fetch Yelp suggestion from given user's query.
- pub fn fetch_yelp_suggestions(&self, query: &SuggestionQuery) -> Result<Vec<Suggestion>> {
+ pub(crate) fn fetch_yelp_suggestions(
+ &self,
+ query: &SuggestionQuery,
+ ) -> Result<Vec<Suggestion>> {
if !query.providers.contains(&SuggestionProvider::Yelp) {
return Ok(vec![]);
}
@@ -144,7 +147,7 @@ impl<'a> SuggestDao<'a> {
let Some((subject, subject_exact_match)) = self.find_subject(query_string)? else {
return Ok(vec![]);
};
- let (icon, score) = self.fetch_custom_details()?;
+ let (icon, icon_mimetype, score) = self.fetch_custom_details()?;
let builder = SuggestionBuilder {
subject: &subject,
subject_exact_match,
@@ -154,6 +157,7 @@ impl<'a> SuggestDao<'a> {
location: None,
need_location: false,
icon,
+ icon_mimetype,
score,
};
return Ok(vec![builder.into()]);
@@ -185,7 +189,7 @@ impl<'a> SuggestDao<'a> {
return Ok(vec![]);
};
- let (icon, score) = self.fetch_custom_details()?;
+ let (icon, icon_mimetype, score) = self.fetch_custom_details()?;
let builder = SuggestionBuilder {
subject: &subject,
subject_exact_match,
@@ -195,6 +199,7 @@ impl<'a> SuggestDao<'a> {
location,
need_location,
icon,
+ icon_mimetype,
score,
};
Ok(vec![builder.into()])
@@ -204,6 +209,7 @@ impl<'a> SuggestDao<'a> {
/// It returns the location tuple as follows:
/// (
/// Option<Vec<u8>>: Icon data. If not found, returns None.
+ /// Option<String>: Mimetype of the icon data. If not found, returns None.
/// f64: Reflects score field in the yelp_custom_details table.
/// )
///
@@ -212,11 +218,11 @@ impl<'a> SuggestDao<'a> {
/// on Remote Settings. The following query will perform a table scan against
/// `yelp_custom_details` followed by an index search against `icons`,
/// which should be fine since there is only one record in the first table.
- fn fetch_custom_details(&self) -> Result<(Option<Vec<u8>>, f64)> {
+ fn fetch_custom_details(&self) -> Result<(Option<Vec<u8>>, Option<String>, f64)> {
let result = self.conn.query_row_and_then_cachable(
r#"
SELECT
- i.data, y.score
+ i.data, i.mimetype, y.score
FROM
yelp_custom_details y
LEFT JOIN
@@ -226,7 +232,13 @@ impl<'a> SuggestDao<'a> {
1
"#,
(),
- |row| -> Result<_> { Ok((row.get::<_, Option<Vec<u8>>>(0)?, row.get::<_, f64>(1)?)) },
+ |row| -> Result<_> {
+ Ok((
+ row.get::<_, Option<Vec<u8>>>(0)?,
+ row.get::<_, Option<String>>(1)?,
+ row.get::<_, f64>(2)?,
+ ))
+ },
true,
)?;
@@ -439,6 +451,7 @@ struct SuggestionBuilder<'a> {
location: Option<String>,
need_location: bool,
icon: Option<Vec<u8>>,
+ icon_mimetype: Option<String>,
score: f64,
}
@@ -488,6 +501,7 @@ impl<'a> From<SuggestionBuilder<'a>> for Suggestion {
url,
title,
icon: builder.icon,
+ icon_mimetype: builder.icon_mimetype,
score: builder.score,
has_location_sign: location_modifier.is_none() && builder.location_sign.is_some(),
subject_exact_match: builder.subject_exact_match,
diff --git a/third_party/rust/sync15/.cargo-checksum.json b/third_party/rust/sync15/.cargo-checksum.json
index 5f995892d5..723c4c4384 100644
--- a/third_party/rust/sync15/.cargo-checksum.json
+++ b/third_party/rust/sync15/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"739abc68b38e8468c5d1eb3f7a66f01e638765f9d8714080e07817f0939a2b66","README.md":"6d4ff5b079ac5340d18fa127f583e7ad793c5a2328b8ecd12c3fc723939804f2","build.rs":"aa971160d67ce8626b26e15c04c34b730f594c45c817aae34cfc9f3ea14ae284","src/bso/content.rs":"92935258745bdf0c3915a555cb6884a7fa69faa1290ec2c1815f6e2f3c0f0562","src/bso/crypto.rs":"27602dcccb37d3a55620ee4e16b705da455d49af575de115c7c79c0178eb1d6d","src/bso/mod.rs":"09e723dc7e99295ecafdcadffaf604d66ea27cf2b7f1fd9ab3cac4f4698ff6a7","src/bso/test_utils.rs":"4ec5a2df5e1c0ec14dc770681e959bdcef6ef04f6fde435999197f46a8ae4831","src/client/coll_state.rs":"13e6ef55273baf5536acc369be522e34a803a32cabf19cce43e426aea9b6223e","src/client/coll_update.rs":"dac04a90c29dd969f8b4250414609c9b6d61daf2dfa4ae77d1c4a165ba970b05","src/client/collection_keys.rs":"c27b2277a3a52033b58ab01490fc2ea7007494195dd5e6dc2c6931a4ca96795a","src/client/mod.rs":"8f588d4a035cf79d96f2500f06d5651c1a7c566127c456ffa5429811ddce3fd6","src/client/request.rs":"8841524e37d8195867bdf6ba98c75f610cf47a4644adeebd6372cc6713f2260a","src/client/state.rs":"4e31193ef2471c1dfabf1c6a391bcb95e14ddb45855786a4194ff187d5c9347c","src/client/status.rs":"f445a8765dac9789444e23b5145148413407bb1d18a15ef56682243997f591bf","src/client/storage_client.rs":"8de72d4ba3ca4f68c8e1898466de83a2b543545a18679800cb4f7fbda2dc3183","src/client/sync.rs":"b29abb512ec9d163f7883b71f78c9202802dcb17cad1fc5dc08087fb0bb66704","src/client/sync_multiple.rs":"6e92571132f89744b553190c596be8aff9b2d031d8f79d82c94cdf78b1683f4a","src/client/token.rs":"b268759d31e0fe17e0e2a428694cd9a317fcfbdd52f023d5d8c7cc6f00f1a102","src/client/util.rs":"71cc70ee41f821f53078675e636e9fad9c6046fa1a989e37f5487e340a2277d6","src/client_types.rs":"3c3cac1540b92482f43660d9e43bdde8481c4cc1a98253a68c80e791231f5976","src/clients_engine/engine.rs":"9e11b47be81fc63214f31879af74075674aa50a8f8989afe20fefa7990fa99b9","src/clients_engine/mod.rs":"461729e6f89b66b2cbd89b041a03d4d6a8ba582284ed4f3015cb13e1a0c6da97","src/clients_engine/record.rs":"b0d84bf420743d7638a45e4836633a45e50257d5548fe7ecd04bff4d724439b8","src/clients_engine/ser.rs":"ef12daeb11faf618fe3cafe91f20a031fe5bb6751369b6ee5aee03f196efe88c","src/device_type.rs":"dc2d4296d25e31471c8e68488f1043ff239b902036cd6aea8a686cf79b4ed335","src/enc_payload.rs":"aa3eea7df49b24cd59831680a47c417b73a3e36e6b0f3f4baf14ca66bd68be6b","src/engine/bridged_engine.rs":"f70f1bfce6e0c04b0c72ec9cbfbb12c82d4009a23fb9768792107d41b2865a4f","src/engine/mod.rs":"90f1f9760f5f712a337aebb04e59c736e4b6fbd89d6a188d969210c7f3f321ae","src/engine/request.rs":"5923025fb9550178339f880a1bf8526d8e853e7a0b2bce6d9d687cc808ac0085","src/engine/sync_engine.rs":"531b35d72ce9e04c3e543c0468c1e450fba2c0dc3d33d68d9b1c0a5c1ad7dd34","src/error.rs":"a45cfe02e6301f473c34678b694943c1a04308b8c292c6e0448bf495194c3b5e","src/key_bundle.rs":"abd0781f3be8c8e7c691f18bb71f3433b633803c48da9794e15ac6301ed60d6c","src/lib.rs":"f59f8817978d943518dfa03ab31fc0f6b1fc72ee9943a97aef1537e2769649f5","src/record_types.rs":"02bb3d352fb808131d298f9b90d9c95b7e9e0138b97c5401f3b9fdacc5562f44","src/server_timestamp.rs":"6272299c92b05b9ec9dc2e18402ebe927b07ccf1dcab5082301a09e0ee56ce24","src/sync15.udl":"005b2b056b93c959a04670f6f489afecb8e17093d8e4be34765a3a4cc0faeb8c","src/telemetry.rs":"e3a7e13e85f5e336526ebf07db04c81b8f1ba89ae1db4159a3a570826cb8cfd2","uniffi.toml":"34488f947497a9b05007445dd816024ef02e6b1696f1056ee868f039722828ee"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"6827fe696bd5e7ef806389493be837c7fa87f3301a7b243890259c5304fda21f","README.md":"6d4ff5b079ac5340d18fa127f583e7ad793c5a2328b8ecd12c3fc723939804f2","build.rs":"aa971160d67ce8626b26e15c04c34b730f594c45c817aae34cfc9f3ea14ae284","src/bso/content.rs":"92935258745bdf0c3915a555cb6884a7fa69faa1290ec2c1815f6e2f3c0f0562","src/bso/crypto.rs":"27602dcccb37d3a55620ee4e16b705da455d49af575de115c7c79c0178eb1d6d","src/bso/mod.rs":"09e723dc7e99295ecafdcadffaf604d66ea27cf2b7f1fd9ab3cac4f4698ff6a7","src/bso/test_utils.rs":"4ec5a2df5e1c0ec14dc770681e959bdcef6ef04f6fde435999197f46a8ae4831","src/client/coll_state.rs":"13e6ef55273baf5536acc369be522e34a803a32cabf19cce43e426aea9b6223e","src/client/coll_update.rs":"dac04a90c29dd969f8b4250414609c9b6d61daf2dfa4ae77d1c4a165ba970b05","src/client/collection_keys.rs":"c27b2277a3a52033b58ab01490fc2ea7007494195dd5e6dc2c6931a4ca96795a","src/client/mod.rs":"8f588d4a035cf79d96f2500f06d5651c1a7c566127c456ffa5429811ddce3fd6","src/client/request.rs":"8841524e37d8195867bdf6ba98c75f610cf47a4644adeebd6372cc6713f2260a","src/client/state.rs":"4e31193ef2471c1dfabf1c6a391bcb95e14ddb45855786a4194ff187d5c9347c","src/client/status.rs":"f445a8765dac9789444e23b5145148413407bb1d18a15ef56682243997f591bf","src/client/storage_client.rs":"8de72d4ba3ca4f68c8e1898466de83a2b543545a18679800cb4f7fbda2dc3183","src/client/sync.rs":"b29abb512ec9d163f7883b71f78c9202802dcb17cad1fc5dc08087fb0bb66704","src/client/sync_multiple.rs":"6e92571132f89744b553190c596be8aff9b2d031d8f79d82c94cdf78b1683f4a","src/client/token.rs":"b268759d31e0fe17e0e2a428694cd9a317fcfbdd52f023d5d8c7cc6f00f1a102","src/client/util.rs":"71cc70ee41f821f53078675e636e9fad9c6046fa1a989e37f5487e340a2277d6","src/client_types.rs":"3c3cac1540b92482f43660d9e43bdde8481c4cc1a98253a68c80e791231f5976","src/clients_engine/engine.rs":"9e11b47be81fc63214f31879af74075674aa50a8f8989afe20fefa7990fa99b9","src/clients_engine/mod.rs":"461729e6f89b66b2cbd89b041a03d4d6a8ba582284ed4f3015cb13e1a0c6da97","src/clients_engine/record.rs":"b0d84bf420743d7638a45e4836633a45e50257d5548fe7ecd04bff4d724439b8","src/clients_engine/ser.rs":"ef12daeb11faf618fe3cafe91f20a031fe5bb6751369b6ee5aee03f196efe88c","src/device_type.rs":"dc2d4296d25e31471c8e68488f1043ff239b902036cd6aea8a686cf79b4ed335","src/enc_payload.rs":"aa3eea7df49b24cd59831680a47c417b73a3e36e6b0f3f4baf14ca66bd68be6b","src/engine/bridged_engine.rs":"f70f1bfce6e0c04b0c72ec9cbfbb12c82d4009a23fb9768792107d41b2865a4f","src/engine/mod.rs":"90f1f9760f5f712a337aebb04e59c736e4b6fbd89d6a188d969210c7f3f321ae","src/engine/request.rs":"5923025fb9550178339f880a1bf8526d8e853e7a0b2bce6d9d687cc808ac0085","src/engine/sync_engine.rs":"531b35d72ce9e04c3e543c0468c1e450fba2c0dc3d33d68d9b1c0a5c1ad7dd34","src/error.rs":"a45cfe02e6301f473c34678b694943c1a04308b8c292c6e0448bf495194c3b5e","src/key_bundle.rs":"abd0781f3be8c8e7c691f18bb71f3433b633803c48da9794e15ac6301ed60d6c","src/lib.rs":"f59f8817978d943518dfa03ab31fc0f6b1fc72ee9943a97aef1537e2769649f5","src/record_types.rs":"02bb3d352fb808131d298f9b90d9c95b7e9e0138b97c5401f3b9fdacc5562f44","src/server_timestamp.rs":"6272299c92b05b9ec9dc2e18402ebe927b07ccf1dcab5082301a09e0ee56ce24","src/sync15.udl":"005b2b056b93c959a04670f6f489afecb8e17093d8e4be34765a3a4cc0faeb8c","src/telemetry.rs":"e3a7e13e85f5e336526ebf07db04c81b8f1ba89ae1db4159a3a570826cb8cfd2","uniffi.toml":"34488f947497a9b05007445dd816024ef02e6b1696f1056ee868f039722828ee"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/sync15/Cargo.toml b/third_party/rust/sync15/Cargo.toml
index 86f7da91f8..943bee58ae 100644
--- a/third_party/rust/sync15/Cargo.toml
+++ b/third_party/rust/sync15/Cargo.toml
@@ -30,7 +30,7 @@ serde_derive = "1"
serde_json = "1"
serde_path_to_error = "0.1"
thiserror = "1.0"
-uniffi = "0.25.2"
+uniffi = "0.27.1"
[dependencies.base16]
version = "0.2"
@@ -72,7 +72,7 @@ version = "0.10"
default-features = false
[build-dependencies.uniffi]
-version = "0.25.2"
+version = "0.27.1"
features = ["build"]
[features]
diff --git a/third_party/rust/tabs/.cargo-checksum.json b/third_party/rust/tabs/.cargo-checksum.json
index 1f1fa20bfa..a9adbf9214 100644
--- a/third_party/rust/tabs/.cargo-checksum.json
+++ b/third_party/rust/tabs/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"c06aa10f3dfa7be35c4fb54cb629704826da5bd9d08eaf09211343bb2b62bf74","README.md":"c48b8f391ef822c4f3971b5f453a1e7b43bea232752d520460d2f04803aead1a","build.rs":"33e61b811b19ed2b58e319cc65d5988bed258d2c4fea2d706301184c59847a0f","src/error.rs":"2694657aeb12f99c4b2fe102ad2b08b79955d209201831b3e071129f0b7d7eda","src/lib.rs":"7208f78955e015ef8bab7916307e551cd3c1bd56d7fe14f8b53cd53bc4b38555","src/schema.rs":"2b7b51f3c2edc0ca603495c10b917603fd9ac791c4a366080e40d090b13b91f2","src/storage.rs":"18f449b6daf1641dc351be451311495b7c05e16c4e2d4eaf12c1fa02fa750b67","src/store.rs":"ab0b6214b30b0f0fa7c6a89098ff3db1a8f76264f6711c4481c0be460afe522b","src/sync/bridge.rs":"18d3a7913a030b598d4b6cbd5b7e2ab4cef4cc7ea964f5bc84d7fb2f28787529","src/sync/engine.rs":"2d14d899a38ac72b9141d505babd94ef7b6fbc5a95be70f324a40bf01935793d","src/sync/mod.rs":"09ba3c87f1174a243bf5aaa481effd18929d54359ceb9b23ccb2c32ee3482f34","src/sync/record.rs":"eef6751c209d039958afbe245ddb006cfdf6b8b6b47f925f69c552b832b87922","src/tabs.udl":"2cefc7f6a27b5619bc536d4a19608cf24153d745199fbeaf192e24b4381dedfb","uniffi.toml":"f9125e8d55b109e86076ee88bfd640372f06b142b7db557e41816c7227dd445c"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"73ac434bafd302d09e8b61742cdb094db767985f0cbae7d00efb5096a33dea4b","README.md":"c48b8f391ef822c4f3971b5f453a1e7b43bea232752d520460d2f04803aead1a","build.rs":"33e61b811b19ed2b58e319cc65d5988bed258d2c4fea2d706301184c59847a0f","src/error.rs":"2694657aeb12f99c4b2fe102ad2b08b79955d209201831b3e071129f0b7d7eda","src/lib.rs":"7208f78955e015ef8bab7916307e551cd3c1bd56d7fe14f8b53cd53bc4b38555","src/schema.rs":"2b7b51f3c2edc0ca603495c10b917603fd9ac791c4a366080e40d090b13b91f2","src/storage.rs":"18f449b6daf1641dc351be451311495b7c05e16c4e2d4eaf12c1fa02fa750b67","src/store.rs":"ab0b6214b30b0f0fa7c6a89098ff3db1a8f76264f6711c4481c0be460afe522b","src/sync/bridge.rs":"18d3a7913a030b598d4b6cbd5b7e2ab4cef4cc7ea964f5bc84d7fb2f28787529","src/sync/engine.rs":"2d14d899a38ac72b9141d505babd94ef7b6fbc5a95be70f324a40bf01935793d","src/sync/mod.rs":"09ba3c87f1174a243bf5aaa481effd18929d54359ceb9b23ccb2c32ee3482f34","src/sync/record.rs":"eef6751c209d039958afbe245ddb006cfdf6b8b6b47f925f69c552b832b87922","src/tabs.udl":"2cefc7f6a27b5619bc536d4a19608cf24153d745199fbeaf192e24b4381dedfb","uniffi.toml":"f9125e8d55b109e86076ee88bfd640372f06b142b7db557e41816c7227dd445c"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/tabs/Cargo.toml b/third_party/rust/tabs/Cargo.toml
index c06ad84ca9..84a5c7ad33 100644
--- a/third_party/rust/tabs/Cargo.toml
+++ b/third_party/rust/tabs/Cargo.toml
@@ -29,7 +29,7 @@ serde = "1"
serde_derive = "1"
serde_json = "1"
thiserror = "1.0"
-uniffi = "0.25.2"
+uniffi = "0.27.1"
url = "2.1"
[dependencies.error-support]
@@ -65,5 +65,5 @@ features = ["humantime"]
default-features = false
[build-dependencies.uniffi]
-version = "0.25.2"
+version = "0.27.1"
features = ["build"]
diff --git a/third_party/rust/textwrap/.cargo-checksum.json b/third_party/rust/textwrap/.cargo-checksum.json
new file mode 100644
index 0000000000..0948683828
--- /dev/null
+++ b/third_party/rust/textwrap/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"2b49672ca15da27844abb6643660faa3a517b1b731c7c5023a55de2e570f35fb","Cargo.lock":"c60631b9ccb2798984507f9e074ce15d9b6e59f34d4dc3418ef40c222ac55725","Cargo.toml":"bccd38e75da62b89a7bb2d741926688f1822ecd5165a703ffaadf9177a34e919","LICENSE":"ce93600c49fbb3e14df32efe752264644f6a2f8e08a735ba981725799e5309ef","README.md":"5dd8128a4e9057aeb6133a073d30a819230243907e717349101b41a11ec23234","rustfmt.toml":"02637ad90caa19885b25b1ce8230657b3703402775d9db83687a3f55de567509","src/columns.rs":"73432251f95ac0b84d5e971989ebc5f867d8b8ca82d5e3fc67fe3a66216fbc38","src/core.rs":"e2cc6b1e5978df0db9b6d0425e7d0ebf65dd188aff90df800f1f2dda7b1c53f2","src/fill.rs":"1fe773dad2d0bb67a7739b3931c1ee3269d677b71a0716dcdb5b01fe2539d7c2","src/fuzzing.rs":"0a77010a555a244ac5e907754b2104912299815009922cfdc0f6b48d92135295","src/indentation.rs":"f41ee8be41e01620c7d88b76f81a01ce6a619939505eaf3fcfe6c8021fae022b","src/lib.rs":"d5d39085faa4527bf6c16a91c5a44b9b894e3f3a2606763bceac22038528c28c","src/line_ending.rs":"bf416f683ab952d4df75d5dc3c199e7ae7740db2c5982ac1a20c3f4b186ded76","src/options.rs":"0d3aec6ab238f3aa14aa57e736384ec208cd3013373941c76d66c0125ca0630f","src/refill.rs":"33ce98ef31c4791893fc2136edd8f8d95cdd38fa54daa59aaf078b359c43d913","src/termwidth.rs":"2e7854e822c435341bc4d467d13614d417df4f2f530cea3c5e49e3b44e754943","src/word_separators.rs":"d3b2b5faf224bf414bf9da48be02eaffb41aec3a91674bedab02ad5748344143","src/word_splitters.rs":"8de2b92eff6d752e321f219136b45b9812267b5be7ace57602a3bb9d3b5cf332","src/wrap.rs":"52c48e2e5155100e4067363e56b180785684bca3109c95c3425ef8051738ff0e","src/wrap_algorithms.rs":"c99498f2e58634f707545ba73c3a99025086d1afb8c12aeceff2ced2887bb8ae","src/wrap_algorithms/optimal_fit.rs":"a9ce8bad61d4fa81df9e292a557fbf5303df78391d63610ec512b9b06f9193b1","tests/indent.rs":"51f977db11632a32fafecf86af88413d51238fe6efcf18ec52fac89133714278","tests/version-numbers.rs":"9e964f58dbdf051fc6fe0d6542ab312d3e95f26c3fd14bce84449bb625e45761"},"package":"23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"} \ No newline at end of file
diff --git a/third_party/rust/textwrap/CHANGELOG.md b/third_party/rust/textwrap/CHANGELOG.md
new file mode 100644
index 0000000000..362c0c0b9d
--- /dev/null
+++ b/third_party/rust/textwrap/CHANGELOG.md
@@ -0,0 +1,616 @@
+# Changelog
+
+This file lists the most important changes made in each release of
+`textwrap`.
+
+## Version 0.16.1 (2024-02-17)
+
+This release fixes `display_width` to ignore inline-hyperlinks. The minimum
+supported version of Rust is now documented to be 1.56.
+
+* [#526](https://github.com/mgeisler/textwrap/pull/526): Ignore ANSI hyperlinks
+ in `display_width`: calculations.
+* [#521](https://github.com/mgeisler/textwrap/pull/521): Add `Options::width`
+ setter method.
+* [#520](https://github.com/mgeisler/textwrap/pull/520): Clarify that
+ `WordSeparator` is an enum rather than a trait.
+* [#518](https://github.com/mgeisler/textwrap/pull/518): Test with latest stable
+ and nightly Rust, but check that we can build with Rust 1.56.
+
+## Version 0.16.0 (2022-10-23)
+
+This release marks `Options` as `non_exhaustive` and extends it to
+make line endings configurable, it adds new fast paths to `fill` and
+`wrap`, and it fixes crashes in `unfill` and `refill`.
+
+* [#480](https://github.com/mgeisler/textwrap/pull/480): Mark
+ `Options` as `non_exhaustive`. This will allow us to extend the
+ struct in the future without breaking backwards compatibility.
+* [#478](https://github.com/mgeisler/textwrap/pull/478): Add fast
+ paths to `fill` and `wrap`. This makes the functions 10-25 times
+ faster when the no wrapping is needed.
+* [#468](https://github.com/mgeisler/textwrap/pull/468): Fix `refill`
+ to add back correct line ending.
+* [#467](https://github.com/mgeisler/textwrap/pull/467): Fix crashes
+ in `unfill` and `refill`.
+* [#458](https://github.com/mgeisler/textwrap/pull/458): Test with
+ Rust 1.56 (first compiler release with support for Rust 2021).
+* [#454](https://github.com/mgeisler/textwrap/pull/454): Make line
+ endings configurable.
+* [#448](https://github.com/mgeisler/textwrap/pull/448): Migrate to
+ the Rust 2021 edition.
+
+## Version 0.15.2 (2022-10-24)
+
+This release is identical to 0.15.0 and is only there to give people a
+way to install crates which depend on the yanked 0.15.1 release. See
+[#484](https://github.com/mgeisler/textwrap/issues/484) for details.
+
+## Version 0.15.1 (2022-09-15)
+
+This release was yanked since it accidentally broke backwards
+compatibility with 0.15.0.
+
+## Version 0.15.0 (2022-02-27)
+
+This is a major feature release with two main changes:
+
+* [#421](https://github.com/mgeisler/textwrap/pull/421): Use `f64`
+ instead of `usize` for fragment widths.
+
+ This fixes problems with overflows in the internal computations of
+ `wrap_optimal_fit` when fragments (words) or line lengths had
+ extreme values, such as `usize::MAX`.
+
+* [#438](https://github.com/mgeisler/textwrap/pull/438): Simplify
+ `Options` by removing generic type parameters.
+
+ This change removes the new generic parameters introduced in version
+ 0.14, as well as the original `WrapSplitter` parameter which has
+ been present since very early versions.
+
+ The result is a simplification of function and struct signatures
+ across the board. So what used to be
+
+ ```rust
+ let options: Options<
+ wrap_algorithms::FirstFit,
+ word_separators::AsciiSpace,
+ word_splitters::HyphenSplitter,
+ > = Options::new(80);
+ ```
+
+ if types are fully written out, is now simply
+
+ ```rust
+ let options: Options<'_> = Options::new(80);
+ ```
+
+ The anonymous lifetime represent the lifetime of the
+ `initial_indent` and `subsequent_indent` strings. The change is
+ nearly performance neutral (a 1-2% regression).
+
+Smaller improvements and changes:
+
+* [#404](https://github.com/mgeisler/textwrap/pull/404): Make
+ documentation for short last-line penalty more precise.
+* [#405](https://github.com/mgeisler/textwrap/pull/405): Cleanup and
+ simplify `Options` docstring.
+* [#411](https://github.com/mgeisler/textwrap/pull/411): Default to
+ `OptimalFit` in interactive example.
+* [#415](https://github.com/mgeisler/textwrap/pull/415): Add demo
+ program to help compute binary sizes.
+* [#423](https://github.com/mgeisler/textwrap/pull/423): Add fuzz
+ tests with fully arbitrary fragments.
+* [#424](https://github.com/mgeisler/textwrap/pull/424): Change
+ `wrap_optimal_fit` penalties to non-negative numbers.
+* [#430](https://github.com/mgeisler/textwrap/pull/430): Add
+ `debug-words` example.
+* [#432](https://github.com/mgeisler/textwrap/pull/432): Use precise
+ dependency versions in Cargo.toml.
+
+## Version 0.14.2 (2021-06-27)
+
+The 0.14.1 release included more changes than intended and has been
+yanked. The change intended for 0.14.1 is now included in 0.14.2.
+
+## Version 0.14.1 (2021-06-26)
+
+This release fixes a panic reported by @Makoto, thanks!
+
+* [#391](https://github.com/mgeisler/textwrap/pull/391): Fix panic in
+ `find_words` due to string access outside of a character boundary.
+
+## Version 0.14.0 (2021-06-05)
+
+This is a major feature release which makes Textwrap more configurable
+and flexible. The high-level API of `textwrap::wrap` and
+`textwrap::fill` remains unchanged, but low-level structs have moved
+around.
+
+The biggest change is the introduction of new generic type parameters
+to the `Options` struct. These parameters lets you statically
+configure the wrapping algorithm, the word separator, and the word
+splitter. If you previously spelled out the full type for `Options`,
+you now need to take the extra type parameters into account. This
+means that
+
+```rust
+let options: Options<HyphenSplitter> = Options::new(80);
+```
+
+changes to
+
+```rust
+let options: Options<
+ wrap_algorithms::FirstFit,
+ word_separators::AsciiSpace,
+ word_splitters::HyphenSplitter,
+> = Options::new(80);
+```
+
+This is quite a mouthful, so we suggest using type inference where
+possible. You won’t see any chance if you call `wrap` directly with a
+width or with an `Options` value constructed on the fly. Please open
+an issue if this causes problems for you!
+
+### New `WordSeparator` Trait
+
+* [#332](https://github.com/mgeisler/textwrap/pull/332): Add
+ `WordSeparator` trait to allow customizing how words are found in a
+ line of text. Until now, Textwrap would always assume that words are
+ separated by ASCII space characters. You can now customize this as
+ needed.
+
+* [#313](https://github.com/mgeisler/textwrap/pull/313): Add support
+ for using the Unicode line breaking algorithm to find words. This is
+ done by adding a second implementation of the new `WordSeparator`
+ trait. The implementation uses the unicode-linebreak crate, which is
+ a new optional dependency.
+
+ With this, Textwrap can be used with East-Asian languages such as
+ Chinese or Japanese where there are no spaces between words.
+ Breaking a long sequence of emojis is another example where line
+ breaks might be wanted even if there are no whitespace to be found.
+ Feedback would be appreciated for this feature.
+
+
+### Indent
+
+* [#353](https://github.com/mgeisler/textwrap/pull/353): Trim trailing
+ whitespace from `prefix` in `indent`.
+
+ Before, empty lines would get no prefix added. Now, empty lines have
+ a trimmed prefix added. This little trick makes `indent` much more
+ useful since you can now safely indent with `"# "` without creating
+ trailing whitespace in the output due to the trailing whitespace in
+ your prefix.
+
+* [#354](https://github.com/mgeisler/textwrap/pull/354): Make `indent`
+ about 20% faster by preallocating the output string.
+
+
+### Documentation
+
+* [#308](https://github.com/mgeisler/textwrap/pull/308): Document
+ handling of leading and trailing whitespace when wrapping text.
+
+### WebAssembly Demo
+
+* [#310](https://github.com/mgeisler/textwrap/pull/310): Thanks to
+ WebAssembly, you can now try out Textwrap directly in your browser.
+ Please try it out: https://mgeisler.github.io/textwrap/.
+
+### New Generic Parameters
+
+* [#331](https://github.com/mgeisler/textwrap/pull/331): Remove outer
+ boxing from `Options`.
+
+* [#357](https://github.com/mgeisler/textwrap/pull/357): Replace
+ `core::WrapAlgorithm` enum with a `wrap_algorithms::WrapAlgorithm`
+ trait. This allows for arbitrary wrapping algorithms to be plugged
+ into the library.
+
+* [#358](https://github.com/mgeisler/textwrap/pull/358): Switch
+ wrapping functions to use a slice for `line_widths`.
+
+* [#368](https://github.com/mgeisler/textwrap/pull/368): Move
+ `WordSeparator` and `WordSplitter` traits to separate modules.
+ Before, Textwrap had several top-level structs such as
+ `NoHyphenation` and `HyphenSplitter`. These implementations of
+ `WordSplitter` now lives in a dedicated `word_splitters` module.
+ Similarly, we have a new `word_separators` module for
+ implementations of `WordSeparator`.
+
+* [#369](https://github.com/mgeisler/textwrap/pull/369): Rename
+ `Options::splitter` to `Options::word_splitter` for consistency with
+ the other fields backed by traits.
+
+## Version 0.13.4 (2021-02-23)
+
+This release removes `println!` statements which was left behind in
+`unfill` by mistake.
+
+* [#296](https://github.com/mgeisler/textwrap/pull/296): Improve house
+ building example with more comments.
+* [#297](https://github.com/mgeisler/textwrap/pull/297): Remove debug
+ prints in the new `unfill` function.
+
+## Version 0.13.3 (2021-02-20)
+
+This release contains a bugfix for `indent` and improved handling of
+emojis. We’ve also added a new function for formatting text in columns
+and functions for reformatting already wrapped text.
+
+* [#276](https://github.com/mgeisler/textwrap/pull/276): Extend
+ `core::display_width` to handle emojis when the unicode-width Cargo
+ feature is disabled.
+* [#279](https://github.com/mgeisler/textwrap/pull/279): Make `indent`
+ preserve existing newlines in the input string. Before,
+ `indent("foo", "")` would return `"foo\n"` by mistake. It now
+ returns `"foo"` instead.
+* [#281](https://github.com/mgeisler/textwrap/pull/281): Ensure all
+ `Options` fields have examples.
+* [#282](https://github.com/mgeisler/textwrap/pull/282): Add a
+ `wrap_columns` function.
+* [#294](https://github.com/mgeisler/textwrap/pull/294): Add new
+ `unfill` and `refill` functions.
+
+## Version 0.13.2 (2020-12-30)
+
+This release primarily makes all dependencies optional. This makes it
+possible to slim down textwrap as needed.
+
+* [#254](https://github.com/mgeisler/textwrap/pull/254): `impl
+ WordSplitter` for `Box<T> where T: WordSplitter`.
+* [#255](https://github.com/mgeisler/textwrap/pull/255): Use command
+ line arguments as initial text in interactive example.
+* [#256](https://github.com/mgeisler/textwrap/pull/256): Introduce
+ fuzz tests for `wrap_optimal_fit` and `wrap_first_fit`.
+* [#260](https://github.com/mgeisler/textwrap/pull/260): Make the
+ unicode-width dependency optional.
+* [#261](https://github.com/mgeisler/textwrap/pull/261): Make the
+ smawk dependency optional.
+
+## Version 0.13.1 (2020-12-10)
+
+This is a bugfix release which fixes a regression in 0.13.0. The bug
+meant that colored text was wrapped incorrectly.
+
+* [#245](https://github.com/mgeisler/textwrap/pull/245): Support
+ deleting a word with Ctrl-Backspace in the interactive demo.
+* [#246](https://github.com/mgeisler/textwrap/pull/246): Show build
+ type (debug/release) in interactive demo.
+* [#249](https://github.com/mgeisler/textwrap/pull/249): Correctly
+ compute width while skipping over ANSI escape sequences.
+
+## Version 0.13.0 (2020-12-05)
+
+This is a major release which rewrites the core logic, adds many new
+features, and fixes a couple of bugs. Most programs which use
+`textwrap` stays the same, incompatibilities and upgrade notes are
+given below.
+
+Clone the repository and run the following to explore the new features
+in an interactive demo (Linux only):
+
+```sh
+$ cargo run --example interactive --all-features
+```
+
+### Bug Fixes
+
+#### Rewritten core wrapping algorithm
+
+* [#221](https://github.com/mgeisler/textwrap/pull/221): Reformulate
+ wrapping in terms of words with whitespace and penalties.
+
+The core wrapping algorithm has been completely rewritten. This fixed
+bugs and simplified the code, while also making it possible to use
+`textwrap` outside the context of the terminal.
+
+As part of this, trailing whitespace is now discarded consistently
+from wrapped lines. Before we would inconsistently remove whitespace
+at the end of wrapped lines, except for the last. Leading whitespace
+is still preserved.
+
+### New Features
+
+#### Optimal-fit wrapping
+
+* [#234](https://github.com/mgeisler/textwrap/pull/234): Introduce
+ wrapping using an optimal-fit algorithm.
+
+This release adds support for new wrapping algorithm which finds a
+globally optimal set of line breaks, taking certain penalties into
+account. As an example, the old algorithm would produce
+
+ "To be, or"
+ "not to be:"
+ "that is"
+ "the"
+ "question"
+
+Notice how the fourth line with “the” is very short. The new algorithm
+shortens the previous lines slightly to produce fewer short lines:
+
+ "To be,"
+ "or not to"
+ "be: that"
+ "is the"
+ "question"
+
+Use the new `textwrap::core::WrapAlgorithm` enum to select between the
+new and old algorithm. By default, the new algorithm is used.
+
+The optimal-fit algorithm is inspired by the line breaking algorithm
+used in TeX, described in the 1981 article [_Breaking Paragraphs into
+Lines_](http://www.eprg.org/G53DOC/pdfs/knuth-plass-breaking.pdf) by
+Knuth and Plass.
+
+#### In-place wrapping
+
+* [#226](https://github.com/mgeisler/textwrap/pull/226): Add a
+ `fill_inplace` function.
+
+When the text you want to fill is already a temporary `String`, you
+can now mutate it in-place with `fill_inplace`:
+
+```rust
+let mut greeting = format!("Greetings {}, welcome to the game! You have {} lives left.",
+ player.name, player.lives);
+fill_inplace(&mut greeting, line_width);
+```
+
+This is faster than calling `fill` and it will reuse the memory
+already allocated for the string.
+
+### Changed Features
+
+#### `Wrapper` is replaced with `Options`
+
+* [#213](https://github.com/mgeisler/textwrap/pull/213): Simplify API
+ with only top-level functions.
+* [#215](https://github.com/mgeisler/textwrap/pull/215): Reintroducing
+ the type parameter on `Options` (previously known as `Wrapper`).
+* [#219](https://github.com/mgeisler/textwrap/pull/219): Allow using
+ trait objects with `fill` & `wrap`.
+* [#227](https://github.com/mgeisler/textwrap/pull/227): Replace
+ `WrapOptions` with `Into<Options>`.
+
+The `Wrapper` struct held the options (line width, indentation, etc)
+for wrapping text. It was also the entry point for actually wrapping
+the text via its methods such as `wrap`, `wrap_iter`,
+`into_wrap_iter`, and `fill` methods.
+
+The struct has been replaced by a simpler `Options` struct which only
+holds options. The `Wrapper` methods are gone, their job has been
+taken over by the top-level `wrap` and `fill` functions. The signature
+of these functions have changed from
+
+```rust
+fn fill(s: &str, width: usize) -> String;
+
+fn wrap(s: &str, width: usize) -> Vec<Cow<'_, str>>;
+```
+
+to the more general
+
+```rust
+fn fill<'a, S, Opt>(text: &str, options: Opt) -> String
+where
+ S: WordSplitter,
+ Opt: Into<Options<'a, S>>;
+
+fn wrap<'a, S, Opt>(text: &str, options: Opt) -> Vec<Cow<'_, str>>
+where
+ S: WordSplitter,
+ Opt: Into<Options<'a, S>>;
+```
+
+The `Into<Options<'a, S>` bound allows you to pass an `usize` (which
+is interpreted as the line width) *and* a full `Options` object. This
+allows the new functions to work like the old, plus you can now fully
+customize the behavior of the wrapping via `Options` when needed.
+
+Code that call `textwrap::wrap` or `textwrap::fill` can remain
+unchanged. Code that calls into `Wrapper::wrap` or `Wrapper::fill`
+will need to be update. This is a mechanical change, please see
+[#213](https://github.com/mgeisler/textwrap/pull/213) for examples.
+
+Thanks to @CryptJar and @Koxiat for their support in the PRs above!
+
+### Removed Features
+
+* The `wrap_iter` and `into_wrap_iter` methods are gone. This means
+ that lazy iteration is no longer supported: you always get all
+ wrapped lines back as a `Vec`. This was done to simplify the code
+ and to support the optimal-fit algorithm.
+
+ The first-fit algorithm could still be implemented in an incremental
+ fashion. Please let us know if this is important to you.
+
+### Other Changes
+
+* [#206](https://github.com/mgeisler/textwrap/pull/206): Change
+ `Wrapper.splitter` from `T: WordSplitter` to `Box<dyn
+ WordSplitter>`.
+* [#216](https://github.com/mgeisler/textwrap/pull/216): Forbid the
+ use of unsafe code.
+
+## Version 0.12.1 (2020-07-03)
+
+This is a bugfix release.
+
+* Fixed [#176][issue-176]: Mention compile-time wrapping by linking to
+ the [`textwrap-macros` crate].
+* Fixed [#193][issue-193]: Wrapping with `break_words(false)` was
+ broken and would cause extra whitespace to be inserted when words
+ were longer than the line width.
+
+## Version 0.12.0 (2020-06-26)
+
+The code has been updated to the [Rust 2018 edition][rust-2018] and
+each new release of `textwrap` will only support the latest stable
+version of Rust. Trying to support older Rust versions is a fool's
+errand: our dependencies keep releasing new patch versions that
+require newer and newer versions of Rust.
+
+The `term_size` feature has been replaced by `terminal_size`. The API
+is unchanged, it is just the name of the Cargo feature that changed.
+
+The `hyphenation` feature now only embeds the hyphenation patterns for
+US-English. This slims down the dependency.
+
+* Fixed [#140][issue-140]: Ignore ANSI escape sequences.
+* Fixed [#158][issue-158]: Unintended wrapping when using external splitter.
+* Fixed [#177][issue-177]: Update examples to the 2018 edition.
+
+## Version 0.11.0 (2018-12-09)
+
+Due to our dependencies bumping their minimum supported version of
+Rust, the minimum version of Rust we test against is now 1.22.0.
+
+* Merged [#141][issue-141]: Fix `dedent` handling of empty lines and
+ trailing newlines. Thanks @bbqsrc!
+* Fixed [#151][issue-151]: Release of version with hyphenation 0.7.
+
+## Version 0.10.0 (2018-04-28)
+
+Due to our dependencies bumping their minimum supported version of
+Rust, the minimum version of Rust we test against is now 1.17.0.
+
+* Fixed [#99][issue-99]: Word broken even though it would fit on line.
+* Fixed [#107][issue-107]: Automatic hyphenation is off by one.
+* Fixed [#122][issue-122]: Take newlines into account when wrapping.
+* Fixed [#129][issue-129]: Panic on string with em-dash.
+
+## Version 0.9.0 (2017-10-05)
+
+The dependency on `term_size` is now optional, and by default this
+feature is not enabled. This is a *breaking change* for users of
+`Wrapper::with_termwidth`. Enable the `term_size` feature to restore
+the old functionality.
+
+Added a regression test for the case where `width` is set to
+`usize::MAX`, thanks @Fraser999! All public structs now implement
+`Debug`, thanks @hcpl!
+
+* Fixed [#101][issue-101]: Make `term_size` an optional dependency.
+
+## Version 0.8.0 (2017-09-04)
+
+The `Wrapper` struct is now generic over the type of word splitter
+being used. This means less boxing and a nicer API. The
+`Wrapper::word_splitter` method has been removed. This is a *breaking
+API change* if you used the method to change the word splitter.
+
+The `Wrapper` struct has two new methods that will wrap the input text
+lazily: `Wrapper::wrap_iter` and `Wrapper::into_wrap_iter`. Use those
+if you will be iterating over the wrapped lines one by one.
+
+* Fixed [#59][issue-59]: `wrap` could return an iterator. Thanks
+ @hcpl!
+* Fixed [#81][issue-81]: Set `html_root_url`.
+
+## Version 0.7.0 (2017-07-20)
+
+Version 0.7.0 changes the return type of `Wrapper::wrap` from
+`Vec<String>` to `Vec<Cow<'a, str>>`. This means that the output lines
+borrow data from the input string. This is a *breaking API change* if
+you relied on the exact return type of `Wrapper::wrap`. Callers of the
+`textwrap::fill` convenience function will see no breakage.
+
+The above change and other optimizations makes version 0.7.0 roughly
+15-30% faster than version 0.6.0.
+
+The `squeeze_whitespace` option has been removed since it was
+complicating the above optimization. Let us know if this option is
+important for you so we can provide a work around.
+
+* Fixed [#58][issue-58]: Add a "fast_wrap" function.
+* Fixed [#61][issue-61]: Documentation errors.
+
+## Version 0.6.0 (2017-05-22)
+
+Version 0.6.0 adds builder methods to `Wrapper` for easy one-line
+initialization and configuration:
+
+```rust
+let wrapper = Wrapper::new(60).break_words(false);
+```
+
+It also add a new `NoHyphenation` word splitter that will never split
+words, not even at existing hyphens.
+
+* Fixed [#28][issue-28]: Support not squeezing whitespace.
+
+## Version 0.5.0 (2017-05-15)
+
+Version 0.5.0 has *breaking API changes*. However, this only affects
+code using the hyphenation feature. The feature is now optional, so
+you will first need to enable the `hyphenation` feature as described
+above. Afterwards, please change your code from
+```rust
+wrapper.corpus = Some(&corpus);
+```
+to
+```rust
+wrapper.splitter = Box::new(corpus);
+```
+
+Other changes include optimizations, so version 0.5.0 is roughly
+10-15% faster than version 0.4.0.
+
+* Fixed [#19][issue-19]: Add support for finding terminal size.
+* Fixed [#25][issue-25]: Handle words longer than `self.width`.
+* Fixed [#26][issue-26]: Support custom indentation.
+* Fixed [#36][issue-36]: Support building without `hyphenation`.
+* Fixed [#39][issue-39]: Respect non-breaking spaces.
+
+## Version 0.4.0 (2017-01-24)
+
+Documented complexities and tested these via `cargo bench`.
+
+* Fixed [#13][issue-13]: Immediatedly add word if it fits.
+* Fixed [#14][issue-14]: Avoid splitting on initial hyphens.
+
+## Version 0.3.0 (2017-01-07)
+
+Added support for automatic hyphenation.
+
+## Version 0.2.0 (2016-12-28)
+
+Introduced `Wrapper` struct. Added support for wrapping on hyphens.
+
+## Version 0.1.0 (2016-12-17)
+
+First public release with support for wrapping strings on whitespace.
+
+[rust-2018]: https://doc.rust-lang.org/edition-guide/rust-2018/
+[`textwrap-macros` crate]: https://crates.io/crates/textwrap-macros
+
+[issue-13]: https://github.com/mgeisler/textwrap/issues/13
+[issue-14]: https://github.com/mgeisler/textwrap/issues/14
+[issue-19]: https://github.com/mgeisler/textwrap/issues/19
+[issue-25]: https://github.com/mgeisler/textwrap/issues/25
+[issue-26]: https://github.com/mgeisler/textwrap/issues/26
+[issue-28]: https://github.com/mgeisler/textwrap/issues/28
+[issue-36]: https://github.com/mgeisler/textwrap/issues/36
+[issue-39]: https://github.com/mgeisler/textwrap/issues/39
+[issue-58]: https://github.com/mgeisler/textwrap/issues/58
+[issue-59]: https://github.com/mgeisler/textwrap/issues/59
+[issue-61]: https://github.com/mgeisler/textwrap/issues/61
+[issue-81]: https://github.com/mgeisler/textwrap/issues/81
+[issue-99]: https://github.com/mgeisler/textwrap/issues/99
+[issue-101]: https://github.com/mgeisler/textwrap/issues/101
+[issue-107]: https://github.com/mgeisler/textwrap/issues/107
+[issue-122]: https://github.com/mgeisler/textwrap/issues/122
+[issue-129]: https://github.com/mgeisler/textwrap/issues/129
+[issue-140]: https://github.com/mgeisler/textwrap/issues/140
+[issue-141]: https://github.com/mgeisler/textwrap/issues/141
+[issue-151]: https://github.com/mgeisler/textwrap/issues/151
+[issue-158]: https://github.com/mgeisler/textwrap/issues/158
+[issue-176]: https://github.com/mgeisler/textwrap/issues/176
+[issue-177]: https://github.com/mgeisler/textwrap/issues/177
+[issue-193]: https://github.com/mgeisler/textwrap/issues/193
diff --git a/third_party/rust/textwrap/Cargo.lock b/third_party/rust/textwrap/Cargo.lock
new file mode 100644
index 0000000000..98b642073e
--- /dev/null
+++ b/third_party/rust/textwrap/Cargo.lock
@@ -0,0 +1,657 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "fst"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd"
+
+[[package]]
+name = "hyphenation"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcf4dd4c44ae85155502a52c48739c8a48185d1449fff1963cffee63c28a50f0"
+dependencies = [
+ "bincode",
+ "fst",
+ "hyphenation_commons",
+ "pocket-resources",
+ "serde",
+]
+
+[[package]]
+name = "hyphenation_commons"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5febe7a2ade5c7d98eb8b75f946c046b335324b06a14ea0998271504134c05bf"
+dependencies = [
+ "fst",
+ "serde",
+]
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.153"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+
+[[package]]
+name = "libredox"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607"
+dependencies = [
+ "bitflags 2.4.2",
+ "libc",
+ "redox_syscall",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "memchr"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+
+[[package]]
+name = "numtoa"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pocket-resources"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c135f38778ad324d9e9ee68690bac2c1a51f340fdf96ca13e2ab3914eb2e51d8"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "pulldown-cmark"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
+dependencies = [
+ "bitflags 2.4.2",
+ "memchr",
+ "unicase",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_termios"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
+
+[[package]]
+name = "regex"
+version = "1.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+
+[[package]]
+name = "rustix"
+version = "0.37.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
+dependencies = [
+ "bitflags 1.3.2",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
+
+[[package]]
+name = "serde"
+version = "1.0.196"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.196"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "smawk"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
+
+[[package]]
+name = "syn"
+version = "2.0.49"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237"
+dependencies = [
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "termion"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4648c7def6f2043b2568617b9f9b75eae88ca185dbc1f1fda30e95a85d49d7d"
+dependencies = [
+ "libc",
+ "libredox",
+ "numtoa",
+ "redox_termios",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.16.1"
+dependencies = [
+ "hyphenation",
+ "smawk",
+ "terminal_size",
+ "termion",
+ "unic-emoji-char",
+ "unicode-linebreak",
+ "unicode-width",
+ "version-sync",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "toml"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-emoji-char"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
+[[package]]
+name = "unicase"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-linebreak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "url"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "version-sync"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "835169da0173ea373ddf5987632aac1f918967fbbe58195e304342282efa6089"
+dependencies = [
+ "proc-macro2",
+ "pulldown-cmark",
+ "regex",
+ "semver",
+ "syn",
+ "toml",
+ "url",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.0",
+ "windows_aarch64_msvc 0.52.0",
+ "windows_i686_gnu 0.52.0",
+ "windows_i686_msvc 0.52.0",
+ "windows_x86_64_gnu 0.52.0",
+ "windows_x86_64_gnullvm 0.52.0",
+ "windows_x86_64_msvc 0.52.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
diff --git a/third_party/rust/textwrap/Cargo.toml b/third_party/rust/textwrap/Cargo.toml
new file mode 100644
index 0000000000..1493f0a8a9
--- /dev/null
+++ b/third_party/rust/textwrap/Cargo.toml
@@ -0,0 +1,91 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.56"
+name = "textwrap"
+version = "0.16.1"
+authors = ["Martin Geisler <martin@geisler.net>"]
+exclude = [
+ ".github/",
+ ".gitignore",
+ "benchmarks/",
+ "examples/",
+ "fuzz/",
+ "images/",
+]
+description = "Library for word wrapping, indenting, and dedenting strings. Has optional support for Unicode and emojis as well as machine hyphenation."
+documentation = "https://docs.rs/textwrap/"
+readme = "README.md"
+keywords = [
+ "text",
+ "formatting",
+ "wrap",
+ "typesetting",
+ "hyphenation",
+]
+categories = [
+ "text-processing",
+ "command-line-interface",
+]
+license = "MIT"
+repository = "https://github.com/mgeisler/textwrap"
+
+[package.metadata.docs.rs]
+all-features = true
+
+[[example]]
+name = "hyphenation"
+path = "examples/hyphenation.rs"
+required-features = ["hyphenation"]
+
+[[example]]
+name = "termwidth"
+path = "examples/termwidth.rs"
+required-features = ["terminal_size"]
+
+[dependencies.hyphenation]
+version = "0.8.4"
+features = ["embed_en-us"]
+optional = true
+
+[dependencies.smawk]
+version = "0.3.1"
+optional = true
+
+[dependencies.terminal_size]
+version = "0.2.1"
+optional = true
+
+[dependencies.unicode-linebreak]
+version = "0.1.4"
+optional = true
+
+[dependencies.unicode-width]
+version = "0.1.10"
+optional = true
+
+[dev-dependencies.unic-emoji-char]
+version = "0.9.0"
+
+[dev-dependencies.version-sync]
+version = "0.9.4"
+
+[features]
+default = [
+ "unicode-linebreak",
+ "unicode-width",
+ "smawk",
+]
+
+[target."cfg(unix)".dev-dependencies.termion]
+version = "2.0.1"
diff --git a/third_party/rust/textwrap/LICENSE b/third_party/rust/textwrap/LICENSE
new file mode 100644
index 0000000000..0d37ec3891
--- /dev/null
+++ b/third_party/rust/textwrap/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 Martin Geisler
+
+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/third_party/rust/textwrap/README.md b/third_party/rust/textwrap/README.md
new file mode 100644
index 0000000000..bf13cd0957
--- /dev/null
+++ b/third_party/rust/textwrap/README.md
@@ -0,0 +1,176 @@
+# Textwrap
+
+[![](https://github.com/mgeisler/textwrap/workflows/build/badge.svg)][build-status]
+[![](https://codecov.io/gh/mgeisler/textwrap/branch/master/graph/badge.svg)][codecov]
+[![](https://img.shields.io/crates/v/textwrap.svg)][crates-io]
+[![](https://docs.rs/textwrap/badge.svg)][api-docs]
+
+Textwrap is a library for wrapping and indenting text. It is most
+often used by command-line programs to format dynamic output nicely so
+it looks good in a terminal. You can also use Textwrap to wrap text
+set in a proportional font—such as text used to generate PDF files, or
+drawn on a [HTML5 canvas using WebAssembly][wasm-demo].
+
+## Usage
+
+To use the textwrap crate, add this to your `Cargo.toml` file:
+```toml
+[dependencies]
+textwrap = "0.16"
+```
+
+By default, this enables word wrapping with support for Unicode
+strings. Extra features can be enabled with Cargo features—and the
+Unicode support can be disabled if needed. This allows you slim down
+the library and so you will only pay for the features you actually
+use.
+
+Please see the [_Cargo Features_ in the crate
+documentation](https://docs.rs/textwrap/#cargo-features) for a full
+list of the available features as well as their impact on the size of
+your binary.
+
+## Documentation
+
+**[API documentation][api-docs]**
+
+## Getting Started
+
+Word wrapping is easy using the `wrap` and `fill` functions:
+
+```rust
+#[cfg(feature = "smawk")] {
+let text = "textwrap: an efficient and powerful library for wrapping text.";
+assert_eq!(
+ textwrap::wrap(text, 28),
+ vec![
+ "textwrap: an efficient",
+ "and powerful library for",
+ "wrapping text.",
+ ]
+);
+}
+```
+
+Sharp-eyed readers will notice that the first line is 22 columns wide.
+So why is the word “and” put in the second line when there is space
+for it in the first line?
+
+The explanation is that textwrap does not just wrap text one line at a
+time. Instead, it uses an optimal-fit algorithm which looks ahead and
+chooses line breaks which minimize the gaps left at ends of lines.
+This is controlled with the `smawk` Cargo feature, which is why the
+example is wrapped in the `cfg`-block.
+
+Without look-ahead, the first line would be longer and the text would
+look like this:
+
+```rust
+#[cfg(not(feature = "smawk"))] {
+let text = "textwrap: an efficient and powerful library for wrapping text.";
+assert_eq!(
+ textwrap::wrap(text, 28),
+ vec![
+ "textwrap: an efficient and",
+ "powerful library for",
+ "wrapping text.",
+ ]
+);
+}
+```
+
+The second line is now shorter and the text is more ragged. The kind
+of wrapping can be configured via `Options::wrap_algorithm`.
+
+If you enable the `hyphenation` Cargo feature, you get support for
+automatic hyphenation for [about 70 languages][patterns] via
+high-quality TeX hyphenation patterns.
+
+Your program must load the hyphenation pattern and configure
+`Options::word_splitter` to use it:
+
+```rust
+#[cfg(feature = "hyphenation")] {
+use hyphenation::{Language, Load, Standard};
+use textwrap::{fill, Options, WordSplitter};
+
+let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
+let options = textwrap::Options::new(28).word_splitter(WordSplitter::Hyphenation(dictionary));
+let text = "textwrap: an efficient and powerful library for wrapping text.";
+
+assert_eq!(
+ textwrap::wrap(text, &options),
+ vec![
+ "textwrap: an efficient and",
+ "powerful library for wrap-",
+ "ping text."
+ ]
+);
+}
+```
+
+The US-English hyphenation patterns are embedded when you enable the
+`hyphenation` feature. They are licensed under a [permissive
+license][en-us license] and take up about 88 KB in your binary. If you
+need hyphenation for other languages, you need to download a
+[precompiled `.bincode` file][bincode] and load it yourself. Please
+see the [`hyphenation` documentation] for details.
+
+## Wrapping Strings at Compile Time
+
+If your strings are known at compile time, please take a look at the
+procedural macros from the [`textwrap-macros` crate].
+
+## Examples
+
+The library comes with [a
+collection](https://github.com/mgeisler/textwrap/tree/master/examples)
+of small example programs that shows various features.
+
+If you want to see Textwrap in action right away, then take a look at
+[`examples/wasm/`], which shows how to wrap sans-serif, serif, and
+monospace text. It uses WebAssembly and is automatically deployed to
+https://mgeisler.github.io/textwrap/.
+
+For the command-line examples, you’re invited to clone the repository
+and try them out for yourself! Of special note is
+[`examples/interactive.rs`]. This is a demo program which demonstrates
+most of the available features: you can enter text and adjust the
+width at which it is wrapped interactively. You can also adjust the
+`Options` used to see the effect of different `WordSplitter`s and wrap
+algorithms.
+
+Run the demo with
+
+```sh
+$ cargo run --example interactive
+```
+
+The demo needs a Linux terminal to function.
+
+## Release History
+
+Please see the [CHANGELOG file] for details on the changes made in
+each release.
+
+## License
+
+Textwrap can be distributed according to the [MIT license][mit].
+Contributions will be accepted under the same license.
+
+[crates-io]: https://crates.io/crates/textwrap
+[build-status]: https://github.com/mgeisler/textwrap/actions?query=workflow%3Abuild+branch%3Amaster
+[codecov]: https://codecov.io/gh/mgeisler/textwrap
+[wasm-demo]: https://mgeisler.github.io/textwrap/
+[`textwrap-macros` crate]: https://crates.io/crates/textwrap-macros
+[`hyphenation` example]: https://github.com/mgeisler/textwrap/blob/master/examples/hyphenation.rs
+[`termwidth` example]: https://github.com/mgeisler/textwrap/blob/master/examples/termwidth.rs
+[patterns]: https://github.com/tapeinosyne/hyphenation/tree/master/patterns
+[en-us license]: https://github.com/hyphenation/tex-hyphen/blob/master/hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-en-us.tex
+[bincode]: https://github.com/tapeinosyne/hyphenation/tree/master/dictionaries
+[`hyphenation` documentation]: http://docs.rs/hyphenation
+[`examples/wasm/`]: https://github.com/mgeisler/textwrap/tree/master/examples/wasm
+[`examples/interactive.rs`]: https://github.com/mgeisler/textwrap/tree/master/examples/interactive.rs
+[api-docs]: https://docs.rs/textwrap/
+[CHANGELOG file]: https://github.com/mgeisler/textwrap/blob/master/CHANGELOG.md
+[mit]: LICENSE
diff --git a/third_party/rust/textwrap/rustfmt.toml b/third_party/rust/textwrap/rustfmt.toml
new file mode 100644
index 0000000000..c1578aafbc
--- /dev/null
+++ b/third_party/rust/textwrap/rustfmt.toml
@@ -0,0 +1 @@
+imports_granularity = "Module"
diff --git a/third_party/rust/textwrap/src/columns.rs b/third_party/rust/textwrap/src/columns.rs
new file mode 100644
index 0000000000..d14d5588fa
--- /dev/null
+++ b/third_party/rust/textwrap/src/columns.rs
@@ -0,0 +1,193 @@
+//! Functionality for wrapping text into columns.
+
+use crate::core::display_width;
+use crate::{wrap, Options};
+
+/// Wrap text into columns with a given total width.
+///
+/// The `left_gap`, `middle_gap` and `right_gap` arguments specify the
+/// strings to insert before, between, and after the columns. The
+/// total width of all columns and all gaps is specified using the
+/// `total_width_or_options` argument. This argument can simply be an
+/// integer if you want to use default settings when wrapping, or it
+/// can be a [`Options`] value if you want to customize the wrapping.
+///
+/// If the columns are narrow, it is recommended to set
+/// [`Options::break_words`] to `true` to prevent words from
+/// protruding into the margins.
+///
+/// The per-column width is computed like this:
+///
+/// ```
+/// # let (left_gap, middle_gap, right_gap) = ("", "", "");
+/// # let columns = 2;
+/// # let options = textwrap::Options::new(80);
+/// let inner_width = options.width
+/// - textwrap::core::display_width(left_gap)
+/// - textwrap::core::display_width(right_gap)
+/// - textwrap::core::display_width(middle_gap) * (columns - 1);
+/// let column_width = inner_width / columns;
+/// ```
+///
+/// The `text` is wrapped using [`wrap()`] and the given `options`
+/// argument, but the width is overwritten to the computed
+/// `column_width`.
+///
+/// # Panics
+///
+/// Panics if `columns` is zero.
+///
+/// # Examples
+///
+/// ```
+/// use textwrap::wrap_columns;
+///
+/// let text = "\
+/// This is an example text, which is wrapped into three columns. \
+/// Notice how the final column can be shorter than the others.";
+///
+/// #[cfg(feature = "smawk")]
+/// assert_eq!(wrap_columns(text, 3, 50, "| ", " | ", " |"),
+/// vec!["| This is | into three | column can be |",
+/// "| an example | columns. | shorter than |",
+/// "| text, which | Notice how | the others. |",
+/// "| is wrapped | the final | |"]);
+///
+/// // Without the `smawk` feature, the middle column is a little more uneven:
+/// #[cfg(not(feature = "smawk"))]
+/// assert_eq!(wrap_columns(text, 3, 50, "| ", " | ", " |"),
+/// vec!["| This is an | three | column can be |",
+/// "| example text, | columns. | shorter than |",
+/// "| which is | Notice how | the others. |",
+/// "| wrapped into | the final | |"]);
+pub fn wrap_columns<'a, Opt>(
+ text: &str,
+ columns: usize,
+ total_width_or_options: Opt,
+ left_gap: &str,
+ middle_gap: &str,
+ right_gap: &str,
+) -> Vec<String>
+where
+ Opt: Into<Options<'a>>,
+{
+ assert!(columns > 0);
+
+ let mut options: Options = total_width_or_options.into();
+
+ let inner_width = options
+ .width
+ .saturating_sub(display_width(left_gap))
+ .saturating_sub(display_width(right_gap))
+ .saturating_sub(display_width(middle_gap) * (columns - 1));
+
+ let column_width = std::cmp::max(inner_width / columns, 1);
+ options.width = column_width;
+ let last_column_padding = " ".repeat(inner_width % column_width);
+ let wrapped_lines = wrap(text, options);
+ let lines_per_column =
+ wrapped_lines.len() / columns + usize::from(wrapped_lines.len() % columns > 0);
+ let mut lines = Vec::new();
+ for line_no in 0..lines_per_column {
+ let mut line = String::from(left_gap);
+ for column_no in 0..columns {
+ match wrapped_lines.get(line_no + column_no * lines_per_column) {
+ Some(column_line) => {
+ line.push_str(column_line);
+ line.push_str(&" ".repeat(column_width - display_width(column_line)));
+ }
+ None => {
+ line.push_str(&" ".repeat(column_width));
+ }
+ }
+ if column_no == columns - 1 {
+ line.push_str(&last_column_padding);
+ } else {
+ line.push_str(middle_gap);
+ }
+ }
+ line.push_str(right_gap);
+ lines.push(line);
+ }
+
+ lines
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn wrap_columns_empty_text() {
+ assert_eq!(wrap_columns("", 1, 10, "| ", "", " |"), vec!["| |"]);
+ }
+
+ #[test]
+ fn wrap_columns_single_column() {
+ assert_eq!(
+ wrap_columns("Foo", 3, 30, "| ", " | ", " |"),
+ vec!["| Foo | | |"]
+ );
+ }
+
+ #[test]
+ fn wrap_columns_uneven_columns() {
+ // The gaps take up a total of 5 columns, so the columns are
+ // (21 - 5)/4 = 4 columns wide:
+ assert_eq!(
+ wrap_columns("Foo Bar Baz Quux", 4, 21, "|", "|", "|"),
+ vec!["|Foo |Bar |Baz |Quux|"]
+ );
+ // As the total width increases, the last column absorbs the
+ // excess width:
+ assert_eq!(
+ wrap_columns("Foo Bar Baz Quux", 4, 24, "|", "|", "|"),
+ vec!["|Foo |Bar |Baz |Quux |"]
+ );
+ // Finally, when the width is 25, the columns can be resized
+ // to a width of (25 - 5)/4 = 5 columns:
+ assert_eq!(
+ wrap_columns("Foo Bar Baz Quux", 4, 25, "|", "|", "|"),
+ vec!["|Foo |Bar |Baz |Quux |"]
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "unicode-width")]
+ fn wrap_columns_with_emojis() {
+ assert_eq!(
+ wrap_columns(
+ "Words and a few emojis 😍 wrapped in ⓶ columns",
+ 2,
+ 30,
+ "✨ ",
+ " ⚽ ",
+ " 👀"
+ ),
+ vec![
+ "✨ Words ⚽ wrapped in 👀",
+ "✨ and a few ⚽ ⓶ columns 👀",
+ "✨ emojis 😍 ⚽ 👀"
+ ]
+ );
+ }
+
+ #[test]
+ fn wrap_columns_big_gaps() {
+ // The column width shrinks to 1 because the gaps take up all
+ // the space.
+ assert_eq!(
+ wrap_columns("xyz", 2, 10, "----> ", " !!! ", " <----"),
+ vec![
+ "----> x !!! z <----", //
+ "----> y !!! <----"
+ ]
+ );
+ }
+
+ #[test]
+ #[should_panic]
+ fn wrap_columns_panic_with_zero_columns() {
+ wrap_columns("", 0, 10, "", "", "");
+ }
+}
diff --git a/third_party/rust/textwrap/src/core.rs b/third_party/rust/textwrap/src/core.rs
new file mode 100644
index 0000000000..6b07f763c8
--- /dev/null
+++ b/third_party/rust/textwrap/src/core.rs
@@ -0,0 +1,461 @@
+//! Building blocks for advanced wrapping functionality.
+//!
+//! The functions and structs in this module can be used to implement
+//! advanced wrapping functionality when [`wrap()`](crate::wrap())
+//! [`fill()`](crate::fill()) don't do what you want.
+//!
+//! In general, you want to follow these steps when wrapping
+//! something:
+//!
+//! 1. Split your input into [`Fragment`]s. These are abstract blocks
+//! of text or content which can be wrapped into lines. See
+//! [`WordSeparator`](crate::word_separators::WordSeparator) for
+//! how to do this for text.
+//!
+//! 2. Potentially split your fragments into smaller pieces. This
+//! allows you to implement things like hyphenation. If you use the
+//! `Word` type, you can use [`WordSplitter`](crate::WordSplitter)
+//! enum for this.
+//!
+//! 3. Potentially break apart fragments that are still too large to
+//! fit on a single line. This is implemented in [`break_words`].
+//!
+//! 4. Finally take your fragments and put them into lines. There are
+//! two algorithms for this in the
+//! [`wrap_algorithms`](crate::wrap_algorithms) module:
+//! [`wrap_optimal_fit`](crate::wrap_algorithms::wrap_optimal_fit)
+//! and [`wrap_first_fit`](crate::wrap_algorithms::wrap_first_fit).
+//! The former produces better line breaks, the latter is faster.
+//!
+//! 5. Iterate through the slices returned by the wrapping functions
+//! and construct your lines of output.
+//!
+//! Please [open an issue](https://github.com/mgeisler/textwrap/) if
+//! the functionality here is not sufficient or if you have ideas for
+//! improving it. We would love to hear from you!
+
+/// The CSI or “Control Sequence Introducer” introduces an ANSI escape
+/// sequence. This is typically used for colored text and will be
+/// ignored when computing the text width.
+const CSI: (char, char) = ('\x1b', '[');
+/// The final bytes of an ANSI escape sequence must be in this range.
+const ANSI_FINAL_BYTE: std::ops::RangeInclusive<char> = '\x40'..='\x7e';
+
+/// Skip ANSI escape sequences.
+///
+/// The `ch` is the current `char`, the `chars` provide the following
+/// characters. The `chars` will be modified if `ch` is the start of
+/// an ANSI escape sequence.
+///
+/// Returns `true` if one or more chars were skipped.
+#[inline]
+pub(crate) fn skip_ansi_escape_sequence<I: Iterator<Item = char>>(ch: char, chars: &mut I) -> bool {
+ if ch != CSI.0 {
+ return false; // Nothing to skip here.
+ }
+
+ let next = chars.next();
+ if next == Some(CSI.1) {
+ // We have found the start of an ANSI escape code, typically
+ // used for colored terminal text. We skip until we find a
+ // "final byte" in the range 0x40–0x7E.
+ for ch in chars {
+ if ANSI_FINAL_BYTE.contains(&ch) {
+ break;
+ }
+ }
+ } else if next == Some(']') {
+ // We have found the start of an Operating System Command,
+ // which extends until the next sequence "\x1b\\" (the String
+ // Terminator sequence) or the BEL character. The BEL
+ // character is non-standard, but it is still used quite
+ // often, for example, by GNU ls.
+ let mut last = ']';
+ for new in chars {
+ if new == '\x07' || (new == '\\' && last == CSI.0) {
+ break;
+ }
+ last = new;
+ }
+ }
+
+ true // Indicate that some chars were skipped.
+}
+
+#[cfg(feature = "unicode-width")]
+#[inline]
+fn ch_width(ch: char) -> usize {
+ unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0)
+}
+
+/// First character which [`ch_width`] will classify as double-width.
+/// Please see [`display_width`].
+#[cfg(not(feature = "unicode-width"))]
+const DOUBLE_WIDTH_CUTOFF: char = '\u{1100}';
+
+#[cfg(not(feature = "unicode-width"))]
+#[inline]
+fn ch_width(ch: char) -> usize {
+ if ch < DOUBLE_WIDTH_CUTOFF {
+ 1
+ } else {
+ 2
+ }
+}
+
+/// Compute the display width of `text` while skipping over ANSI
+/// escape sequences.
+///
+/// # Examples
+///
+/// ```
+/// use textwrap::core::display_width;
+///
+/// assert_eq!(display_width("Café Plain"), 10);
+/// assert_eq!(display_width("\u{1b}[31mCafé Rouge\u{1b}[0m"), 10);
+/// assert_eq!(display_width("\x1b]8;;http://example.com\x1b\\This is a link\x1b]8;;\x1b\\"), 14);
+/// ```
+///
+/// **Note:** When the `unicode-width` Cargo feature is disabled, the
+/// width of a `char` is determined by a crude approximation which
+/// simply counts chars below U+1100 as 1 column wide, and all other
+/// characters as 2 columns wide. With the feature enabled, function
+/// will correctly deal with [combining characters] in their
+/// decomposed form (see [Unicode equivalence]).
+///
+/// An example of a decomposed character is “é”, which can be
+/// decomposed into: “e” followed by a combining acute accent: “◌́”.
+/// Without the `unicode-width` Cargo feature, every `char` below
+/// U+1100 has a width of 1. This includes the combining accent:
+///
+/// ```
+/// use textwrap::core::display_width;
+///
+/// assert_eq!(display_width("Cafe Plain"), 10);
+/// #[cfg(feature = "unicode-width")]
+/// assert_eq!(display_width("Cafe\u{301} Plain"), 10);
+/// #[cfg(not(feature = "unicode-width"))]
+/// assert_eq!(display_width("Cafe\u{301} Plain"), 11);
+/// ```
+///
+/// ## Emojis and CJK Characters
+///
+/// Characters such as emojis and [CJK characters] used in the
+/// Chinese, Japanese, and Korean languages are seen as double-width,
+/// even if the `unicode-width` feature is disabled:
+///
+/// ```
+/// use textwrap::core::display_width;
+///
+/// assert_eq!(display_width("😂😭🥺🤣✨😍🙏🥰😊🔥"), 20);
+/// assert_eq!(display_width("你好"), 4); // “Nǐ hǎo” or “Hello” in Chinese
+/// ```
+///
+/// # Limitations
+///
+/// The displayed width of a string cannot always be computed from the
+/// string alone. This is because the width depends on the rendering
+/// engine used. This is particularly visible with [emoji modifier
+/// sequences] where a base emoji is modified with, e.g., skin tone or
+/// hair color modifiers. It is up to the rendering engine to detect
+/// this and to produce a suitable emoji.
+///
+/// A simple example is “❤️”, which consists of “❤” (U+2764: Black
+/// Heart Symbol) followed by U+FE0F (Variation Selector-16). By
+/// itself, “❤” is a black heart, but if you follow it with the
+/// variant selector, you may get a wider red heart.
+///
+/// A more complex example would be “👨‍🦰” which should depict a man
+/// with red hair. Here the computed width is too large — and the
+/// width differs depending on the use of the `unicode-width` feature:
+///
+/// ```
+/// use textwrap::core::display_width;
+///
+/// assert_eq!("👨‍🦰".chars().collect::<Vec<char>>(), ['\u{1f468}', '\u{200d}', '\u{1f9b0}']);
+/// #[cfg(feature = "unicode-width")]
+/// assert_eq!(display_width("👨‍🦰"), 4);
+/// #[cfg(not(feature = "unicode-width"))]
+/// assert_eq!(display_width("👨‍🦰"), 6);
+/// ```
+///
+/// This happens because the grapheme consists of three code points:
+/// “👨” (U+1F468: Man), Zero Width Joiner (U+200D), and “🦰”
+/// (U+1F9B0: Red Hair). You can see them above in the test. With
+/// `unicode-width` enabled, the ZWJ is correctly seen as having zero
+/// width, without it is counted as a double-width character.
+///
+/// ## Terminal Support
+///
+/// Modern browsers typically do a great job at combining characters
+/// as shown above, but terminals often struggle more. As an example,
+/// Gnome Terminal version 3.38.1, shows “❤️” as a big red heart, but
+/// shows "👨‍🦰" as “👨🦰”.
+///
+/// [combining characters]: https://en.wikipedia.org/wiki/Combining_character
+/// [Unicode equivalence]: https://en.wikipedia.org/wiki/Unicode_equivalence
+/// [CJK characters]: https://en.wikipedia.org/wiki/CJK_characters
+/// [emoji modifier sequences]: https://unicode.org/emoji/charts/full-emoji-modifiers.html
+pub fn display_width(text: &str) -> usize {
+ let mut chars = text.chars();
+ let mut width = 0;
+ while let Some(ch) = chars.next() {
+ if skip_ansi_escape_sequence(ch, &mut chars) {
+ continue;
+ }
+ width += ch_width(ch);
+ }
+ width
+}
+
+/// A (text) fragment denotes the unit which we wrap into lines.
+///
+/// Fragments represent an abstract _word_ plus the _whitespace_
+/// following the word. In case the word falls at the end of the line,
+/// the whitespace is dropped and a so-called _penalty_ is inserted
+/// instead (typically `"-"` if the word was hyphenated).
+///
+/// For wrapping purposes, the precise content of the word, the
+/// whitespace, and the penalty is irrelevant. All we need to know is
+/// the displayed width of each part, which this trait provides.
+pub trait Fragment: std::fmt::Debug {
+ /// Displayed width of word represented by this fragment.
+ fn width(&self) -> f64;
+
+ /// Displayed width of the whitespace that must follow the word
+ /// when the word is not at the end of a line.
+ fn whitespace_width(&self) -> f64;
+
+ /// Displayed width of the penalty that must be inserted if the
+ /// word falls at the end of a line.
+ fn penalty_width(&self) -> f64;
+}
+
+/// A piece of wrappable text, including any trailing whitespace.
+///
+/// A `Word` is an example of a [`Fragment`], so it has a width,
+/// trailing whitespace, and potentially a penalty item.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub struct Word<'a> {
+ /// Word content.
+ pub word: &'a str,
+ /// Whitespace to insert if the word does not fall at the end of a line.
+ pub whitespace: &'a str,
+ /// Penalty string to insert if the word falls at the end of a line.
+ pub penalty: &'a str,
+ // Cached width in columns.
+ pub(crate) width: usize,
+}
+
+impl std::ops::Deref for Word<'_> {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ self.word
+ }
+}
+
+impl<'a> Word<'a> {
+ /// Construct a `Word` from a string.
+ ///
+ /// A trailing stretch of `' '` is automatically taken to be the
+ /// whitespace part of the word.
+ pub fn from(word: &str) -> Word<'_> {
+ let trimmed = word.trim_end_matches(' ');
+ Word {
+ word: trimmed,
+ width: display_width(trimmed),
+ whitespace: &word[trimmed.len()..],
+ penalty: "",
+ }
+ }
+
+ /// Break this word into smaller words with a width of at most
+ /// `line_width`. The whitespace and penalty from this `Word` is
+ /// added to the last piece.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use textwrap::core::Word;
+ /// assert_eq!(
+ /// Word::from("Hello! ").break_apart(3).collect::<Vec<_>>(),
+ /// vec![Word::from("Hel"), Word::from("lo! ")]
+ /// );
+ /// ```
+ pub fn break_apart<'b>(&'b self, line_width: usize) -> impl Iterator<Item = Word<'a>> + 'b {
+ let mut char_indices = self.word.char_indices();
+ let mut offset = 0;
+ let mut width = 0;
+
+ std::iter::from_fn(move || {
+ while let Some((idx, ch)) = char_indices.next() {
+ if skip_ansi_escape_sequence(ch, &mut char_indices.by_ref().map(|(_, ch)| ch)) {
+ continue;
+ }
+
+ if width > 0 && width + ch_width(ch) > line_width {
+ let word = Word {
+ word: &self.word[offset..idx],
+ width: width,
+ whitespace: "",
+ penalty: "",
+ };
+ offset = idx;
+ width = ch_width(ch);
+ return Some(word);
+ }
+
+ width += ch_width(ch);
+ }
+
+ if offset < self.word.len() {
+ let word = Word {
+ word: &self.word[offset..],
+ width: width,
+ whitespace: self.whitespace,
+ penalty: self.penalty,
+ };
+ offset = self.word.len();
+ return Some(word);
+ }
+
+ None
+ })
+ }
+}
+
+impl Fragment for Word<'_> {
+ #[inline]
+ fn width(&self) -> f64 {
+ self.width as f64
+ }
+
+ // We assume the whitespace consist of ' ' only. This allows us to
+ // compute the display width in constant time.
+ #[inline]
+ fn whitespace_width(&self) -> f64 {
+ self.whitespace.len() as f64
+ }
+
+ // We assume the penalty is `""` or `"-"`. This allows us to
+ // compute the display width in constant time.
+ #[inline]
+ fn penalty_width(&self) -> f64 {
+ self.penalty.len() as f64
+ }
+}
+
+/// Forcibly break words wider than `line_width` into smaller words.
+///
+/// This simply calls [`Word::break_apart`] on words that are too
+/// wide. This means that no extra `'-'` is inserted, the word is
+/// simply broken into smaller pieces.
+pub fn break_words<'a, I>(words: I, line_width: usize) -> Vec<Word<'a>>
+where
+ I: IntoIterator<Item = Word<'a>>,
+{
+ let mut shortened_words = Vec::new();
+ for word in words {
+ if word.width() > line_width as f64 {
+ shortened_words.extend(word.break_apart(line_width));
+ } else {
+ shortened_words.push(word);
+ }
+ }
+ shortened_words
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[cfg(feature = "unicode-width")]
+ use unicode_width::UnicodeWidthChar;
+
+ #[test]
+ fn skip_ansi_escape_sequence_works() {
+ let blue_text = "\u{1b}[34mHello\u{1b}[0m";
+ let mut chars = blue_text.chars();
+ let ch = chars.next().unwrap();
+ assert!(skip_ansi_escape_sequence(ch, &mut chars));
+ assert_eq!(chars.next(), Some('H'));
+ }
+
+ #[test]
+ fn emojis_have_correct_width() {
+ use unic_emoji_char::is_emoji;
+
+ // Emojis in the Basic Latin (ASCII) and Latin-1 Supplement
+ // blocks all have a width of 1 column. This includes
+ // characters such as '#' and '©'.
+ for ch in '\u{1}'..'\u{FF}' {
+ if is_emoji(ch) {
+ let desc = format!("{:?} U+{:04X}", ch, ch as u32);
+
+ #[cfg(feature = "unicode-width")]
+ assert_eq!(ch.width().unwrap(), 1, "char: {}", desc);
+
+ #[cfg(not(feature = "unicode-width"))]
+ assert_eq!(ch_width(ch), 1, "char: {}", desc);
+ }
+ }
+
+ // Emojis in the remaining blocks of the Basic Multilingual
+ // Plane (BMP), in the Supplementary Multilingual Plane (SMP),
+ // and in the Supplementary Ideographic Plane (SIP), are all 1
+ // or 2 columns wide when unicode-width is used, and always 2
+ // columns wide otherwise. This includes all of our favorite
+ // emojis such as 😊.
+ for ch in '\u{FF}'..'\u{2FFFF}' {
+ if is_emoji(ch) {
+ let desc = format!("{:?} U+{:04X}", ch, ch as u32);
+
+ #[cfg(feature = "unicode-width")]
+ assert!(ch.width().unwrap() <= 2, "char: {}", desc);
+
+ #[cfg(not(feature = "unicode-width"))]
+ assert_eq!(ch_width(ch), 2, "char: {}", desc);
+ }
+ }
+
+ // The remaining planes contain almost no assigned code points
+ // and thus also no emojis.
+ }
+
+ #[test]
+ fn display_width_works() {
+ assert_eq!("Café Plain".len(), 11); // “é” is two bytes
+ assert_eq!(display_width("Café Plain"), 10);
+ assert_eq!(display_width("\u{1b}[31mCafé Rouge\u{1b}[0m"), 10);
+ assert_eq!(
+ display_width("\x1b]8;;http://example.com\x1b\\This is a link\x1b]8;;\x1b\\"),
+ 14
+ );
+ }
+
+ #[test]
+ fn display_width_narrow_emojis() {
+ #[cfg(feature = "unicode-width")]
+ assert_eq!(display_width("⁉"), 1);
+
+ // The ⁉ character is above DOUBLE_WIDTH_CUTOFF.
+ #[cfg(not(feature = "unicode-width"))]
+ assert_eq!(display_width("⁉"), 2);
+ }
+
+ #[test]
+ fn display_width_narrow_emojis_variant_selector() {
+ #[cfg(feature = "unicode-width")]
+ assert_eq!(display_width("⁉\u{fe0f}"), 1);
+
+ // The variant selector-16 is also counted.
+ #[cfg(not(feature = "unicode-width"))]
+ assert_eq!(display_width("⁉\u{fe0f}"), 4);
+ }
+
+ #[test]
+ fn display_width_emojis() {
+ assert_eq!(display_width("😂😭🥺🤣✨😍🙏🥰😊🔥"), 20);
+ }
+}
diff --git a/third_party/rust/textwrap/src/fill.rs b/third_party/rust/textwrap/src/fill.rs
new file mode 100644
index 0000000000..fbcaab9e21
--- /dev/null
+++ b/third_party/rust/textwrap/src/fill.rs
@@ -0,0 +1,298 @@
+//! Functions for filling text.
+
+use crate::{wrap, wrap_algorithms, Options, WordSeparator};
+
+/// Fill a line of text at a given width.
+///
+/// The result is a [`String`], complete with newlines between each
+/// line. Use [`wrap()`] if you need access to the individual lines.
+///
+/// The easiest way to use this function is to pass an integer for
+/// `width_or_options`:
+///
+/// ```
+/// use textwrap::fill;
+///
+/// assert_eq!(
+/// fill("Memory safety without garbage collection.", 15),
+/// "Memory safety\nwithout garbage\ncollection."
+/// );
+/// ```
+///
+/// If you need to customize the wrapping, you can pass an [`Options`]
+/// instead of an `usize`:
+///
+/// ```
+/// use textwrap::{fill, Options};
+///
+/// let options = Options::new(15)
+/// .initial_indent("- ")
+/// .subsequent_indent(" ");
+/// assert_eq!(
+/// fill("Memory safety without garbage collection.", &options),
+/// "- Memory safety\n without\n garbage\n collection."
+/// );
+/// ```
+pub fn fill<'a, Opt>(text: &str, width_or_options: Opt) -> String
+where
+ Opt: Into<Options<'a>>,
+{
+ let options = width_or_options.into();
+
+ if text.len() < options.width && !text.contains('\n') && options.initial_indent.is_empty() {
+ String::from(text.trim_end_matches(' '))
+ } else {
+ fill_slow_path(text, options)
+ }
+}
+
+/// Slow path for fill.
+///
+/// This is taken when `text` is longer than `options.width`.
+pub(crate) fn fill_slow_path(text: &str, options: Options<'_>) -> String {
+ // This will avoid reallocation in simple cases (no
+ // indentation, no hyphenation).
+ let mut result = String::with_capacity(text.len());
+
+ let line_ending_str = options.line_ending.as_str();
+ for (i, line) in wrap(text, options).iter().enumerate() {
+ if i > 0 {
+ result.push_str(line_ending_str);
+ }
+ result.push_str(line);
+ }
+
+ result
+}
+
+/// Fill `text` in-place without reallocating the input string.
+///
+/// This function works by modifying the input string: some `' '`
+/// characters will be replaced by `'\n'` characters. The rest of the
+/// text remains untouched.
+///
+/// Since we can only replace existing whitespace in the input with
+/// `'\n'` (there is no space for `"\r\n"`), we cannot do hyphenation
+/// nor can we split words longer than the line width. We also need to
+/// use `AsciiSpace` as the word separator since we need `' '`
+/// characters between words in order to replace some of them with a
+/// `'\n'`. Indentation is also ruled out. In other words,
+/// `fill_inplace(width)` behaves as if you had called [`fill()`] with
+/// these options:
+///
+/// ```
+/// # use textwrap::{core, LineEnding, Options, WordSplitter, WordSeparator, WrapAlgorithm};
+/// # let width = 80;
+/// Options::new(width)
+/// .break_words(false)
+/// .line_ending(LineEnding::LF)
+/// .word_separator(WordSeparator::AsciiSpace)
+/// .wrap_algorithm(WrapAlgorithm::FirstFit)
+/// .word_splitter(WordSplitter::NoHyphenation);
+/// ```
+///
+/// The wrap algorithm is
+/// [`WrapAlgorithm::FirstFit`](crate::WrapAlgorithm::FirstFit) since
+/// this is the fastest algorithm — and the main reason to use
+/// `fill_inplace` is to get the string broken into newlines as fast
+/// as possible.
+///
+/// A last difference is that (unlike [`fill()`]) `fill_inplace` can
+/// leave trailing whitespace on lines. This is because we wrap by
+/// inserting a `'\n'` at the final whitespace in the input string:
+///
+/// ```
+/// let mut text = String::from("Hello World!");
+/// textwrap::fill_inplace(&mut text, 10);
+/// assert_eq!(text, "Hello \nWorld!");
+/// ```
+///
+/// If we didn't do this, the word `World!` would end up being
+/// indented. You can avoid this if you make sure that your input text
+/// has no double spaces.
+///
+/// # Performance
+///
+/// In benchmarks, `fill_inplace` is about twice as fast as
+/// [`fill()`]. Please see the [`linear`
+/// benchmark](https://github.com/mgeisler/textwrap/blob/master/benchmarks/linear.rs)
+/// for details.
+pub fn fill_inplace(text: &mut String, width: usize) {
+ let mut indices = Vec::new();
+
+ let mut offset = 0;
+ for line in text.split('\n') {
+ let words = WordSeparator::AsciiSpace
+ .find_words(line)
+ .collect::<Vec<_>>();
+ let wrapped_words = wrap_algorithms::wrap_first_fit(&words, &[width as f64]);
+
+ let mut line_offset = offset;
+ for words in &wrapped_words[..wrapped_words.len() - 1] {
+ let line_len = words
+ .iter()
+ .map(|word| word.len() + word.whitespace.len())
+ .sum::<usize>();
+
+ line_offset += line_len;
+ // We've advanced past all ' ' characters -- want to move
+ // one ' ' backwards and insert our '\n' there.
+ indices.push(line_offset - 1);
+ }
+
+ // Advance past entire line, plus the '\n' which was removed
+ // by the split call above.
+ offset += line.len() + 1;
+ }
+
+ let mut bytes = std::mem::take(text).into_bytes();
+ for idx in indices {
+ bytes[idx] = b'\n';
+ }
+ *text = String::from_utf8(bytes).unwrap();
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::WrapAlgorithm;
+
+ #[test]
+ fn fill_simple() {
+ assert_eq!(fill("foo bar baz", 10), "foo bar\nbaz");
+ }
+
+ #[test]
+ fn fill_unicode_boundary() {
+ // https://github.com/mgeisler/textwrap/issues/390
+ fill("\u{1b}!Ͽ", 10);
+ }
+
+ #[test]
+ fn non_breaking_space() {
+ let options = Options::new(5).break_words(false);
+ assert_eq!(fill("foo bar baz", &options), "foo bar baz");
+ }
+
+ #[test]
+ fn non_breaking_hyphen() {
+ let options = Options::new(5).break_words(false);
+ assert_eq!(fill("foo‑bar‑baz", &options), "foo‑bar‑baz");
+ }
+
+ #[test]
+ fn fill_preserves_line_breaks_trims_whitespace() {
+ assert_eq!(fill(" ", 80), "");
+ assert_eq!(fill(" \n ", 80), "\n");
+ assert_eq!(fill(" \n \n \n ", 80), "\n\n\n");
+ }
+
+ #[test]
+ fn preserve_line_breaks() {
+ assert_eq!(fill("", 80), "");
+ assert_eq!(fill("\n", 80), "\n");
+ assert_eq!(fill("\n\n\n", 80), "\n\n\n");
+ assert_eq!(fill("test\n", 80), "test\n");
+ assert_eq!(fill("test\n\na\n\n", 80), "test\n\na\n\n");
+ assert_eq!(
+ fill(
+ "1 3 5 7\n1 3 5 7",
+ Options::new(7).wrap_algorithm(WrapAlgorithm::FirstFit)
+ ),
+ "1 3 5 7\n1 3 5 7"
+ );
+ assert_eq!(
+ fill(
+ "1 3 5 7\n1 3 5 7",
+ Options::new(5).wrap_algorithm(WrapAlgorithm::FirstFit)
+ ),
+ "1 3 5\n7\n1 3 5\n7"
+ );
+ }
+
+ #[test]
+ fn break_words_line_breaks() {
+ assert_eq!(fill("ab\ncdefghijkl", 5), "ab\ncdefg\nhijkl");
+ assert_eq!(fill("abcdefgh\nijkl", 5), "abcde\nfgh\nijkl");
+ }
+
+ #[test]
+ fn break_words_empty_lines() {
+ assert_eq!(
+ fill("foo\nbar", &Options::new(2).break_words(false)),
+ "foo\nbar"
+ );
+ }
+
+ #[test]
+ fn fill_inplace_empty() {
+ let mut text = String::from("");
+ fill_inplace(&mut text, 80);
+ assert_eq!(text, "");
+ }
+
+ #[test]
+ fn fill_inplace_simple() {
+ let mut text = String::from("foo bar baz");
+ fill_inplace(&mut text, 10);
+ assert_eq!(text, "foo bar\nbaz");
+ }
+
+ #[test]
+ fn fill_inplace_multiple_lines() {
+ let mut text = String::from("Some text to wrap over multiple lines");
+ fill_inplace(&mut text, 12);
+ assert_eq!(text, "Some text to\nwrap over\nmultiple\nlines");
+ }
+
+ #[test]
+ fn fill_inplace_long_word() {
+ let mut text = String::from("Internationalization is hard");
+ fill_inplace(&mut text, 10);
+ assert_eq!(text, "Internationalization\nis hard");
+ }
+
+ #[test]
+ fn fill_inplace_no_hyphen_splitting() {
+ let mut text = String::from("A well-chosen example");
+ fill_inplace(&mut text, 10);
+ assert_eq!(text, "A\nwell-chosen\nexample");
+ }
+
+ #[test]
+ fn fill_inplace_newlines() {
+ let mut text = String::from("foo bar\n\nbaz\n\n\n");
+ fill_inplace(&mut text, 10);
+ assert_eq!(text, "foo bar\n\nbaz\n\n\n");
+ }
+
+ #[test]
+ fn fill_inplace_newlines_reset_line_width() {
+ let mut text = String::from("1 3 5\n1 3 5 7 9\n1 3 5 7 9 1 3");
+ fill_inplace(&mut text, 10);
+ assert_eq!(text, "1 3 5\n1 3 5 7 9\n1 3 5 7 9\n1 3");
+ }
+
+ #[test]
+ fn fill_inplace_leading_whitespace() {
+ let mut text = String::from(" foo bar baz");
+ fill_inplace(&mut text, 10);
+ assert_eq!(text, " foo bar\nbaz");
+ }
+
+ #[test]
+ fn fill_inplace_trailing_whitespace() {
+ let mut text = String::from("foo bar baz ");
+ fill_inplace(&mut text, 10);
+ assert_eq!(text, "foo bar\nbaz ");
+ }
+
+ #[test]
+ fn fill_inplace_interior_whitespace() {
+ // To avoid an unwanted indentation of "baz", it is important
+ // to replace the final ' ' with '\n'.
+ let mut text = String::from("foo bar baz");
+ fill_inplace(&mut text, 10);
+ assert_eq!(text, "foo bar \nbaz");
+ }
+}
diff --git a/third_party/rust/textwrap/src/fuzzing.rs b/third_party/rust/textwrap/src/fuzzing.rs
new file mode 100644
index 0000000000..b7ad4812a2
--- /dev/null
+++ b/third_party/rust/textwrap/src/fuzzing.rs
@@ -0,0 +1,23 @@
+//! Fuzzing helpers.
+
+use super::Options;
+use std::borrow::Cow;
+
+/// Exposed for fuzzing so we can check the slow path is correct.
+pub fn fill_slow_path<'a>(text: &str, options: Options<'_>) -> String {
+ crate::fill::fill_slow_path(text, options)
+}
+
+/// Exposed for fuzzing so we can check the slow path is correct.
+pub fn wrap_single_line<'a>(line: &'a str, options: &Options<'_>, lines: &mut Vec<Cow<'a, str>>) {
+ crate::wrap::wrap_single_line(line, options, lines);
+}
+
+/// Exposed for fuzzing so we can check the slow path is correct.
+pub fn wrap_single_line_slow_path<'a>(
+ line: &'a str,
+ options: &Options<'_>,
+ lines: &mut Vec<Cow<'a, str>>,
+) {
+ crate::wrap::wrap_single_line_slow_path(line, options, lines)
+}
diff --git a/third_party/rust/textwrap/src/indentation.rs b/third_party/rust/textwrap/src/indentation.rs
new file mode 100644
index 0000000000..2f3a853b3c
--- /dev/null
+++ b/third_party/rust/textwrap/src/indentation.rs
@@ -0,0 +1,347 @@
+//! Functions related to adding and removing indentation from lines of
+//! text.
+//!
+//! The functions here can be used to uniformly indent or dedent
+//! (unindent) word wrapped lines of text.
+
+/// Indent each line by the given prefix.
+///
+/// # Examples
+///
+/// ```
+/// use textwrap::indent;
+///
+/// assert_eq!(indent("First line.\nSecond line.\n", " "),
+/// " First line.\n Second line.\n");
+/// ```
+///
+/// When indenting, trailing whitespace is stripped from the prefix.
+/// This means that empty lines remain empty afterwards:
+///
+/// ```
+/// use textwrap::indent;
+///
+/// assert_eq!(indent("First line.\n\n\nSecond line.\n", " "),
+/// " First line.\n\n\n Second line.\n");
+/// ```
+///
+/// Notice how `"\n\n\n"` remained as `"\n\n\n"`.
+///
+/// This feature is useful when you want to indent text and have a
+/// space between your prefix and the text. In this case, you _don't_
+/// want a trailing space on empty lines:
+///
+/// ```
+/// use textwrap::indent;
+///
+/// assert_eq!(indent("foo = 123\n\nprint(foo)\n", "# "),
+/// "# foo = 123\n#\n# print(foo)\n");
+/// ```
+///
+/// Notice how `"\n\n"` became `"\n#\n"` instead of `"\n# \n"` which
+/// would have trailing whitespace.
+///
+/// Leading and trailing whitespace coming from the text itself is
+/// kept unchanged:
+///
+/// ```
+/// use textwrap::indent;
+///
+/// assert_eq!(indent(" \t Foo ", "->"), "-> \t Foo ");
+/// ```
+pub fn indent(s: &str, prefix: &str) -> String {
+ // We know we'll need more than s.len() bytes for the output, but
+ // without counting '\n' characters (which is somewhat slow), we
+ // don't know exactly how much. However, we can preemptively do
+ // the first doubling of the output size.
+ let mut result = String::with_capacity(2 * s.len());
+ let trimmed_prefix = prefix.trim_end();
+ for (idx, line) in s.split_terminator('\n').enumerate() {
+ if idx > 0 {
+ result.push('\n');
+ }
+ if line.trim().is_empty() {
+ result.push_str(trimmed_prefix);
+ } else {
+ result.push_str(prefix);
+ }
+ result.push_str(line);
+ }
+ if s.ends_with('\n') {
+ // split_terminator will have eaten the final '\n'.
+ result.push('\n');
+ }
+ result
+}
+
+/// Removes common leading whitespace from each line.
+///
+/// This function will look at each non-empty line and determine the
+/// maximum amount of whitespace that can be removed from all lines:
+///
+/// ```
+/// use textwrap::dedent;
+///
+/// assert_eq!(dedent("
+/// 1st line
+/// 2nd line
+/// 3rd line
+/// "), "
+/// 1st line
+/// 2nd line
+/// 3rd line
+/// ");
+/// ```
+pub fn dedent(s: &str) -> String {
+ let mut prefix = "";
+ let mut lines = s.lines();
+
+ // We first search for a non-empty line to find a prefix.
+ for line in &mut lines {
+ let mut whitespace_idx = line.len();
+ for (idx, ch) in line.char_indices() {
+ if !ch.is_whitespace() {
+ whitespace_idx = idx;
+ break;
+ }
+ }
+
+ // Check if the line had anything but whitespace
+ if whitespace_idx < line.len() {
+ prefix = &line[..whitespace_idx];
+ break;
+ }
+ }
+
+ // We then continue looking through the remaining lines to
+ // possibly shorten the prefix.
+ for line in &mut lines {
+ let mut whitespace_idx = line.len();
+ for ((idx, a), b) in line.char_indices().zip(prefix.chars()) {
+ if a != b {
+ whitespace_idx = idx;
+ break;
+ }
+ }
+
+ // Check if the line had anything but whitespace and if we
+ // have found a shorter prefix
+ if whitespace_idx < line.len() && whitespace_idx < prefix.len() {
+ prefix = &line[..whitespace_idx];
+ }
+ }
+
+ // We now go over the lines a second time to build the result.
+ let mut result = String::new();
+ for line in s.lines() {
+ if line.starts_with(prefix) && line.chars().any(|c| !c.is_whitespace()) {
+ let (_, tail) = line.split_at(prefix.len());
+ result.push_str(tail);
+ }
+ result.push('\n');
+ }
+
+ if result.ends_with('\n') && !s.ends_with('\n') {
+ let new_len = result.len() - 1;
+ result.truncate(new_len);
+ }
+
+ result
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn indent_empty() {
+ assert_eq!(indent("\n", " "), "\n");
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn indent_nonempty() {
+ let text = [
+ " foo\n",
+ "bar\n",
+ " baz\n",
+ ].join("");
+ let expected = [
+ "// foo\n",
+ "// bar\n",
+ "// baz\n",
+ ].join("");
+ assert_eq!(indent(&text, "// "), expected);
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn indent_empty_line() {
+ let text = [
+ " foo",
+ "bar",
+ "",
+ " baz",
+ ].join("\n");
+ let expected = [
+ "// foo",
+ "// bar",
+ "//",
+ "// baz",
+ ].join("\n");
+ assert_eq!(indent(&text, "// "), expected);
+ }
+
+ #[test]
+ fn dedent_empty() {
+ assert_eq!(dedent(""), "");
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn dedent_multi_line() {
+ let x = [
+ " foo",
+ " bar",
+ " baz",
+ ].join("\n");
+ let y = [
+ " foo",
+ "bar",
+ " baz"
+ ].join("\n");
+ assert_eq!(dedent(&x), y);
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn dedent_empty_line() {
+ let x = [
+ " foo",
+ " bar",
+ " ",
+ " baz"
+ ].join("\n");
+ let y = [
+ " foo",
+ "bar",
+ "",
+ " baz"
+ ].join("\n");
+ assert_eq!(dedent(&x), y);
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn dedent_blank_line() {
+ let x = [
+ " foo",
+ "",
+ " bar",
+ " foo",
+ " bar",
+ " baz",
+ ].join("\n");
+ let y = [
+ "foo",
+ "",
+ " bar",
+ " foo",
+ " bar",
+ " baz",
+ ].join("\n");
+ assert_eq!(dedent(&x), y);
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn dedent_whitespace_line() {
+ let x = [
+ " foo",
+ " ",
+ " bar",
+ " foo",
+ " bar",
+ " baz",
+ ].join("\n");
+ let y = [
+ "foo",
+ "",
+ " bar",
+ " foo",
+ " bar",
+ " baz",
+ ].join("\n");
+ assert_eq!(dedent(&x), y);
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn dedent_mixed_whitespace() {
+ let x = [
+ "\tfoo",
+ " bar",
+ ].join("\n");
+ let y = [
+ "\tfoo",
+ " bar",
+ ].join("\n");
+ assert_eq!(dedent(&x), y);
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn dedent_tabbed_whitespace() {
+ let x = [
+ "\t\tfoo",
+ "\t\t\tbar",
+ ].join("\n");
+ let y = [
+ "foo",
+ "\tbar",
+ ].join("\n");
+ assert_eq!(dedent(&x), y);
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn dedent_mixed_tabbed_whitespace() {
+ let x = [
+ "\t \tfoo",
+ "\t \t\tbar",
+ ].join("\n");
+ let y = [
+ "foo",
+ "\tbar",
+ ].join("\n");
+ assert_eq!(dedent(&x), y);
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn dedent_mixed_tabbed_whitespace2() {
+ let x = [
+ "\t \tfoo",
+ "\t \tbar",
+ ].join("\n");
+ let y = [
+ "\tfoo",
+ " \tbar",
+ ].join("\n");
+ assert_eq!(dedent(&x), y);
+ }
+
+ #[test]
+ #[rustfmt::skip]
+ fn dedent_preserve_no_terminating_newline() {
+ let x = [
+ " foo",
+ " bar",
+ ].join("\n");
+ let y = [
+ "foo",
+ " bar",
+ ].join("\n");
+ assert_eq!(dedent(&x), y);
+ }
+}
diff --git a/third_party/rust/textwrap/src/lib.rs b/third_party/rust/textwrap/src/lib.rs
new file mode 100644
index 0000000000..32611c0938
--- /dev/null
+++ b/third_party/rust/textwrap/src/lib.rs
@@ -0,0 +1,235 @@
+//! The textwrap library provides functions for word wrapping and
+//! indenting text.
+//!
+//! # Wrapping Text
+//!
+//! Wrapping text can be very useful in command-line programs where
+//! you want to format dynamic output nicely so it looks good in a
+//! terminal. A quick example:
+//!
+//! ```
+//! # #[cfg(feature = "smawk")] {
+//! let text = "textwrap: a small library for wrapping text.";
+//! assert_eq!(textwrap::wrap(text, 18),
+//! vec!["textwrap: a",
+//! "small library for",
+//! "wrapping text."]);
+//! # }
+//! ```
+//!
+//! The [`wrap()`] function returns the individual lines, use
+//! [`fill()`] is you want the lines joined with `'\n'` to form a
+//! `String`.
+//!
+//! If you enable the `hyphenation` Cargo feature, you can get
+//! automatic hyphenation for a number of languages:
+//!
+//! ```
+//! #[cfg(feature = "hyphenation")] {
+//! use hyphenation::{Language, Load, Standard};
+//! use textwrap::{wrap, Options, WordSplitter};
+//!
+//! let text = "textwrap: a small library for wrapping text.";
+//! let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
+//! let options = Options::new(18).word_splitter(WordSplitter::Hyphenation(dictionary));
+//! assert_eq!(wrap(text, &options),
+//! vec!["textwrap: a small",
+//! "library for wrap-",
+//! "ping text."]);
+//! }
+//! ```
+//!
+//! See also the [`unfill()`] and [`refill()`] functions which allow
+//! you to manipulate already wrapped text.
+//!
+//! ## Wrapping Strings at Compile Time
+//!
+//! If your strings are known at compile time, please take a look at
+//! the procedural macros from the [textwrap-macros] crate.
+//!
+//! ## Displayed Width vs Byte Size
+//!
+//! To word wrap text, one must know the width of each word so one can
+//! know when to break lines. This library will by default measure the
+//! width of text using the _displayed width_, not the size in bytes.
+//! The `unicode-width` Cargo feature controls this.
+//!
+//! This is important for non-ASCII text. ASCII characters such as `a`
+//! and `!` are simple and take up one column each. This means that
+//! the displayed width is equal to the string length in bytes.
+//! However, non-ASCII characters and symbols take up more than one
+//! byte when UTF-8 encoded: `é` is `0xc3 0xa9` (two bytes) and `⚙` is
+//! `0xe2 0x9a 0x99` (three bytes) in UTF-8, respectively.
+//!
+//! This is why we take care to use the displayed width instead of the
+//! byte count when computing line lengths. All functions in this
+//! library handle Unicode characters like this when the
+//! `unicode-width` Cargo feature is enabled (it is enabled by
+//! default).
+//!
+//! # Indentation and Dedentation
+//!
+//! The textwrap library also offers functions for adding a prefix to
+//! every line of a string and to remove leading whitespace. As an
+//! example, [`indent()`] allows you to turn lines of text into a
+//! bullet list:
+//!
+//! ```
+//! let before = "\
+//! foo
+//! bar
+//! baz
+//! ";
+//! let after = "\
+//! * foo
+//! * bar
+//! * baz
+//! ";
+//! assert_eq!(textwrap::indent(before, "* "), after);
+//! ```
+//!
+//! Removing leading whitespace is done with [`dedent()`]:
+//!
+//! ```
+//! let before = "
+//! Some
+//! indented
+//! text
+//! ";
+//! let after = "
+//! Some
+//! indented
+//! text
+//! ";
+//! assert_eq!(textwrap::dedent(before), after);
+//! ```
+//!
+//! # Cargo Features
+//!
+//! The textwrap library can be slimmed down as needed via a number of
+//! Cargo features. This means you only pay for the features you
+//! actually use.
+//!
+//! The full dependency graph, where dashed lines indicate optional
+//! dependencies, is shown below:
+//!
+//! <img src="https://raw.githubusercontent.com/mgeisler/textwrap/master/images/textwrap-0.16.1.svg">
+//!
+//! ## Default Features
+//!
+//! These features are enabled by default:
+//!
+//! * `unicode-linebreak`: enables finding words using the
+//! [unicode-linebreak] crate, which implements the line breaking
+//! algorithm described in [Unicode Standard Annex
+//! #14](https://www.unicode.org/reports/tr14/).
+//!
+//! This feature can be disabled if you are happy to find words
+//! separated by ASCII space characters only. People wrapping text
+//! with emojis or East-Asian characters will want most likely want
+//! to enable this feature. See [`WordSeparator`] for details.
+//!
+//! * `unicode-width`: enables correct width computation of non-ASCII
+//! characters via the [unicode-width] crate. Without this feature,
+//! every [`char`] is 1 column wide, except for emojis which are 2
+//! columns wide. See [`core::display_width()`] for details.
+//!
+//! This feature can be disabled if you only need to wrap ASCII
+//! text, or if the functions in [`core`] are used directly with
+//! [`core::Fragment`]s for which the widths have been computed in
+//! other ways.
+//!
+//! * `smawk`: enables linear-time wrapping of the whole paragraph via
+//! the [smawk] crate. See [`wrap_algorithms::wrap_optimal_fit()`]
+//! for details on the optimal-fit algorithm.
+//!
+//! This feature can be disabled if you only ever intend to use
+//! [`wrap_algorithms::wrap_first_fit()`].
+//!
+//! <!-- begin binary-sizes -->
+//!
+//! With Rust 1.64.0, the size impact of the above features on your
+//! binary is as follows:
+//!
+//! | Configuration | Binary Size | Delta |
+//! | :--- | ---: | ---: |
+//! | quick-and-dirty implementation | 289 KB | — KB |
+//! | textwrap without default features | 305 KB | 16 KB |
+//! | textwrap with smawk | 317 KB | 28 KB |
+//! | textwrap with unicode-width | 309 KB | 20 KB |
+//! | textwrap with unicode-linebreak | 342 KB | 53 KB |
+//!
+//! <!-- end binary-sizes -->
+//!
+//! The above sizes are the stripped sizes and the binary is compiled
+//! in release mode with this profile:
+//!
+//! ```toml
+//! [profile.release]
+//! lto = true
+//! codegen-units = 1
+//! ```
+//!
+//! See the [binary-sizes demo] if you want to reproduce these
+//! results.
+//!
+//! ## Optional Features
+//!
+//! These Cargo features enable new functionality:
+//!
+//! * `terminal_size`: enables automatic detection of the terminal
+//! width via the [terminal_size] crate. See
+//! [`Options::with_termwidth()`] for details.
+//!
+//! * `hyphenation`: enables language-sensitive hyphenation via the
+//! [hyphenation] crate. See the [`word_splitters::WordSplitter`]
+//! trait for details.
+//!
+//! [unicode-linebreak]: https://docs.rs/unicode-linebreak/
+//! [unicode-width]: https://docs.rs/unicode-width/
+//! [smawk]: https://docs.rs/smawk/
+//! [binary-sizes demo]: https://github.com/mgeisler/textwrap/tree/master/examples/binary-sizes
+//! [textwrap-macros]: https://docs.rs/textwrap-macros/
+//! [terminal_size]: https://docs.rs/terminal_size/
+//! [hyphenation]: https://docs.rs/hyphenation/
+
+#![doc(html_root_url = "https://docs.rs/textwrap/0.16.1")]
+#![forbid(unsafe_code)] // See https://github.com/mgeisler/textwrap/issues/210
+#![deny(missing_docs)]
+#![deny(missing_debug_implementations)]
+#![allow(clippy::redundant_field_names)]
+
+// Make `cargo test` execute the README doctests.
+#[cfg(doctest)]
+#[doc = include_str!("../README.md")]
+mod readme_doctest {}
+
+pub mod core;
+#[cfg(fuzzing)]
+pub mod fuzzing;
+pub mod word_splitters;
+pub mod wrap_algorithms;
+
+mod columns;
+mod fill;
+mod indentation;
+mod line_ending;
+mod options;
+mod refill;
+#[cfg(feature = "terminal_size")]
+mod termwidth;
+mod word_separators;
+mod wrap;
+
+pub use columns::wrap_columns;
+pub use fill::{fill, fill_inplace};
+pub use indentation::{dedent, indent};
+pub use line_ending::LineEnding;
+pub use options::Options;
+pub use refill::{refill, unfill};
+#[cfg(feature = "terminal_size")]
+pub use termwidth::termwidth;
+pub use word_separators::WordSeparator;
+pub use word_splitters::WordSplitter;
+pub use wrap::wrap;
+pub use wrap_algorithms::WrapAlgorithm;
diff --git a/third_party/rust/textwrap/src/line_ending.rs b/third_party/rust/textwrap/src/line_ending.rs
new file mode 100644
index 0000000000..0514fe5fc0
--- /dev/null
+++ b/third_party/rust/textwrap/src/line_ending.rs
@@ -0,0 +1,88 @@
+//! Line ending detection and conversion.
+
+use std::fmt::Debug;
+
+/// Supported line endings. Like in the Rust standard library, two line
+/// endings are supported: `\r\n` and `\n`
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum LineEnding {
+ /// _Carriage return and line feed_ – a line ending sequence
+ /// historically used in Windows. Corresponds to the sequence
+ /// of ASCII control characters `0x0D 0x0A` or `\r\n`
+ CRLF,
+ /// _Line feed_ – a line ending historically used in Unix.
+ /// Corresponds to the ASCII control character `0x0A` or `\n`
+ LF,
+}
+
+impl LineEnding {
+ /// Turns this [`LineEnding`] value into its ASCII representation.
+ #[inline]
+ pub const fn as_str(&self) -> &'static str {
+ match self {
+ Self::CRLF => "\r\n",
+ Self::LF => "\n",
+ }
+ }
+}
+
+/// An iterator over the lines of a string, as tuples of string slice
+/// and [`LineEnding`] value; it only emits non-empty lines (i.e. having
+/// some content before the terminating `\r\n` or `\n`).
+///
+/// This struct is used internally by the library.
+#[derive(Debug, Clone, Copy)]
+pub(crate) struct NonEmptyLines<'a>(pub &'a str);
+
+impl<'a> Iterator for NonEmptyLines<'a> {
+ type Item = (&'a str, Option<LineEnding>);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while let Some(lf) = self.0.find('\n') {
+ if lf == 0 || (lf == 1 && self.0.as_bytes()[lf - 1] == b'\r') {
+ self.0 = &self.0[(lf + 1)..];
+ continue;
+ }
+ let trimmed = match self.0.as_bytes()[lf - 1] {
+ b'\r' => (&self.0[..(lf - 1)], Some(LineEnding::CRLF)),
+ _ => (&self.0[..lf], Some(LineEnding::LF)),
+ };
+ self.0 = &self.0[(lf + 1)..];
+ return Some(trimmed);
+ }
+ if self.0.is_empty() {
+ None
+ } else {
+ let line = std::mem::take(&mut self.0);
+ Some((line, None))
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn non_empty_lines_full_case() {
+ assert_eq!(
+ NonEmptyLines("LF\nCRLF\r\n\r\n\nunterminated")
+ .collect::<Vec<(&str, Option<LineEnding>)>>(),
+ vec![
+ ("LF", Some(LineEnding::LF)),
+ ("CRLF", Some(LineEnding::CRLF)),
+ ("unterminated", None),
+ ]
+ );
+ }
+
+ #[test]
+ fn non_empty_lines_new_lines_only() {
+ assert_eq!(NonEmptyLines("\r\n\n\n\r\n").next(), None);
+ }
+
+ #[test]
+ fn non_empty_lines_no_input() {
+ assert_eq!(NonEmptyLines("").next(), None);
+ }
+}
diff --git a/third_party/rust/textwrap/src/options.rs b/third_party/rust/textwrap/src/options.rs
new file mode 100644
index 0000000000..80e420d195
--- /dev/null
+++ b/third_party/rust/textwrap/src/options.rs
@@ -0,0 +1,300 @@
+//! Options for wrapping text.
+
+use crate::{LineEnding, WordSeparator, WordSplitter, WrapAlgorithm};
+
+/// Holds configuration options for wrapping and filling text.
+#[non_exhaustive]
+#[derive(Debug, Clone)]
+pub struct Options<'a> {
+ /// The width in columns at which the text will be wrapped.
+ pub width: usize,
+ /// Line ending used for breaking lines.
+ pub line_ending: LineEnding,
+ /// Indentation used for the first line of output. See the
+ /// [`Options::initial_indent`] method.
+ pub initial_indent: &'a str,
+ /// Indentation used for subsequent lines of output. See the
+ /// [`Options::subsequent_indent`] method.
+ pub subsequent_indent: &'a str,
+ /// Allow long words to be broken if they cannot fit on a line.
+ /// When set to `false`, some lines may be longer than
+ /// `self.width`. See the [`Options::break_words`] method.
+ pub break_words: bool,
+ /// Wrapping algorithm to use, see the implementations of the
+ /// [`WrapAlgorithm`] trait for details.
+ pub wrap_algorithm: WrapAlgorithm,
+ /// The line breaking algorithm to use, see the [`WordSeparator`]
+ /// trait for an overview and possible implementations.
+ pub word_separator: WordSeparator,
+ /// The method for splitting words. This can be used to prohibit
+ /// splitting words on hyphens, or it can be used to implement
+ /// language-aware machine hyphenation.
+ pub word_splitter: WordSplitter,
+}
+
+impl<'a> From<&'a Options<'a>> for Options<'a> {
+ fn from(options: &'a Options<'a>) -> Self {
+ Self {
+ width: options.width,
+ line_ending: options.line_ending,
+ initial_indent: options.initial_indent,
+ subsequent_indent: options.subsequent_indent,
+ break_words: options.break_words,
+ word_separator: options.word_separator,
+ wrap_algorithm: options.wrap_algorithm,
+ word_splitter: options.word_splitter.clone(),
+ }
+ }
+}
+
+impl<'a> From<usize> for Options<'a> {
+ fn from(width: usize) -> Self {
+ Options::new(width)
+ }
+}
+
+impl<'a> Options<'a> {
+ /// Creates a new [`Options`] with the specified width.
+ ///
+ /// The other fields are given default values as follows:
+ ///
+ /// ```
+ /// # use textwrap::{LineEnding, Options, WordSplitter, WordSeparator, WrapAlgorithm};
+ /// # let width = 80;
+ /// let options = Options::new(width);
+ /// assert_eq!(options.line_ending, LineEnding::LF);
+ /// assert_eq!(options.initial_indent, "");
+ /// assert_eq!(options.subsequent_indent, "");
+ /// assert_eq!(options.break_words, true);
+ ///
+ /// #[cfg(feature = "unicode-linebreak")]
+ /// assert_eq!(options.word_separator, WordSeparator::UnicodeBreakProperties);
+ /// #[cfg(not(feature = "unicode-linebreak"))]
+ /// assert_eq!(options.word_separator, WordSeparator::AsciiSpace);
+ ///
+ /// #[cfg(feature = "smawk")]
+ /// assert_eq!(options.wrap_algorithm, WrapAlgorithm::new_optimal_fit());
+ /// #[cfg(not(feature = "smawk"))]
+ /// assert_eq!(options.wrap_algorithm, WrapAlgorithm::FirstFit);
+ ///
+ /// assert_eq!(options.word_splitter, WordSplitter::HyphenSplitter);
+ /// ```
+ ///
+ /// Note that the default word separator and wrap algorithms
+ /// changes based on the available Cargo features. The best
+ /// available algorithms are used by default.
+ pub const fn new(width: usize) -> Self {
+ Options {
+ width,
+ line_ending: LineEnding::LF,
+ initial_indent: "",
+ subsequent_indent: "",
+ break_words: true,
+ word_separator: WordSeparator::new(),
+ wrap_algorithm: WrapAlgorithm::new(),
+ word_splitter: WordSplitter::HyphenSplitter,
+ }
+ }
+
+ /// Change [`self.line_ending`]. This specifies which of the
+ /// supported line endings should be used to break the lines of the
+ /// input text.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use textwrap::{refill, LineEnding, Options};
+ ///
+ /// let options = Options::new(15).line_ending(LineEnding::CRLF);
+ /// assert_eq!(refill("This is a little example.", options),
+ /// "This is a\r\nlittle example.");
+ /// ```
+ ///
+ /// [`self.line_ending`]: #structfield.line_ending
+ pub fn line_ending(self, line_ending: LineEnding) -> Self {
+ Options {
+ line_ending,
+ ..self
+ }
+ }
+
+ /// Set [`self.width`] to the given value.
+ ///
+ /// [`self.width`]: #structfield.width
+ pub fn width(self, width: usize) -> Self {
+ Options { width, ..self }
+ }
+
+ /// Change [`self.initial_indent`]. The initial indentation is
+ /// used on the very first line of output.
+ ///
+ /// # Examples
+ ///
+ /// Classic paragraph indentation can be achieved by specifying an
+ /// initial indentation and wrapping each paragraph by itself:
+ ///
+ /// ```
+ /// use textwrap::{wrap, Options};
+ ///
+ /// let options = Options::new(16).initial_indent(" ");
+ /// assert_eq!(wrap("This is a little example.", options),
+ /// vec![" This is a",
+ /// "little example."]);
+ /// ```
+ ///
+ /// [`self.initial_indent`]: #structfield.initial_indent
+ pub fn initial_indent(self, initial_indent: &'a str) -> Self {
+ Options {
+ initial_indent,
+ ..self
+ }
+ }
+
+ /// Change [`self.subsequent_indent`]. The subsequent indentation
+ /// is used on lines following the first line of output.
+ ///
+ /// # Examples
+ ///
+ /// Combining initial and subsequent indentation lets you format a
+ /// single paragraph as a bullet list:
+ ///
+ /// ```
+ /// use textwrap::{wrap, Options};
+ ///
+ /// let options = Options::new(12)
+ /// .initial_indent("* ")
+ /// .subsequent_indent(" ");
+ /// #[cfg(feature = "smawk")]
+ /// assert_eq!(wrap("This is a little example.", options),
+ /// vec!["* This is",
+ /// " a little",
+ /// " example."]);
+ ///
+ /// // Without the `smawk` feature, the wrapping is a little different:
+ /// #[cfg(not(feature = "smawk"))]
+ /// assert_eq!(wrap("This is a little example.", options),
+ /// vec!["* This is a",
+ /// " little",
+ /// " example."]);
+ /// ```
+ ///
+ /// [`self.subsequent_indent`]: #structfield.subsequent_indent
+ pub fn subsequent_indent(self, subsequent_indent: &'a str) -> Self {
+ Options {
+ subsequent_indent,
+ ..self
+ }
+ }
+
+ /// Change [`self.break_words`]. This controls if words longer
+ /// than `self.width` can be broken, or if they will be left
+ /// sticking out into the right margin.
+ ///
+ /// See [`Options::word_splitter`] instead if you want to control
+ /// hyphenation.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use textwrap::{wrap, Options};
+ ///
+ /// let options = Options::new(4).break_words(true);
+ /// assert_eq!(wrap("This is a little example.", options),
+ /// vec!["This",
+ /// "is a",
+ /// "litt",
+ /// "le",
+ /// "exam",
+ /// "ple."]);
+ /// ```
+ ///
+ /// [`self.break_words`]: #structfield.break_words
+ pub fn break_words(self, break_words: bool) -> Self {
+ Options {
+ break_words,
+ ..self
+ }
+ }
+
+ /// Change [`self.word_separator`].
+ ///
+ /// See the [`WordSeparator`] trait for details on the choices.
+ ///
+ /// [`self.word_separator`]: #structfield.word_separator
+ pub fn word_separator(self, word_separator: WordSeparator) -> Options<'a> {
+ Options {
+ word_separator,
+ ..self
+ }
+ }
+
+ /// Change [`self.wrap_algorithm`].
+ ///
+ /// See the [`WrapAlgorithm`] trait for details on the choices.
+ ///
+ /// [`self.wrap_algorithm`]: #structfield.wrap_algorithm
+ pub fn wrap_algorithm(self, wrap_algorithm: WrapAlgorithm) -> Options<'a> {
+ Options {
+ wrap_algorithm,
+ ..self
+ }
+ }
+
+ /// Change [`self.word_splitter`]. The [`WordSplitter`] is used to
+ /// fit part of a word into the current line when wrapping text.
+ ///
+ /// See [`Options::break_words`] instead if you want to control the
+ /// handling of words longer than the line width.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use textwrap::{wrap, Options, WordSplitter};
+ ///
+ /// // The default is WordSplitter::HyphenSplitter.
+ /// let options = Options::new(5);
+ /// assert_eq!(wrap("foo-bar-baz", &options),
+ /// vec!["foo-", "bar-", "baz"]);
+ ///
+ /// // The word is now so long that break_words kick in:
+ /// let options = Options::new(5)
+ /// .word_splitter(WordSplitter::NoHyphenation);
+ /// assert_eq!(wrap("foo-bar-baz", &options),
+ /// vec!["foo-b", "ar-ba", "z"]);
+ ///
+ /// // If you want to breaks at all, disable both:
+ /// let options = Options::new(5)
+ /// .break_words(false)
+ /// .word_splitter(WordSplitter::NoHyphenation);
+ /// assert_eq!(wrap("foo-bar-baz", &options),
+ /// vec!["foo-bar-baz"]);
+ /// ```
+ ///
+ /// [`self.word_splitter`]: #structfield.word_splitter
+ pub fn word_splitter(self, word_splitter: WordSplitter) -> Options<'a> {
+ Options {
+ word_splitter,
+ ..self
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn options_agree_with_usize() {
+ let opt_usize = Options::from(42_usize);
+ let opt_options = Options::new(42);
+
+ assert_eq!(opt_usize.width, opt_options.width);
+ assert_eq!(opt_usize.initial_indent, opt_options.initial_indent);
+ assert_eq!(opt_usize.subsequent_indent, opt_options.subsequent_indent);
+ assert_eq!(opt_usize.break_words, opt_options.break_words);
+ assert_eq!(
+ opt_usize.word_splitter.split_points("hello-world"),
+ opt_options.word_splitter.split_points("hello-world")
+ );
+ }
+}
diff --git a/third_party/rust/textwrap/src/refill.rs b/third_party/rust/textwrap/src/refill.rs
new file mode 100644
index 0000000000..1be85f04eb
--- /dev/null
+++ b/third_party/rust/textwrap/src/refill.rs
@@ -0,0 +1,352 @@
+//! Functionality for unfilling and refilling text.
+
+use crate::core::display_width;
+use crate::line_ending::NonEmptyLines;
+use crate::{fill, LineEnding, Options};
+
+/// Unpack a paragraph of already-wrapped text.
+///
+/// This function attempts to recover the original text from a single
+/// paragraph of wrapped text, such as what [`fill()`] would produce.
+/// This means that it turns
+///
+/// ```text
+/// textwrap: a small
+/// library for
+/// wrapping text.
+/// ```
+///
+/// back into
+///
+/// ```text
+/// textwrap: a small library for wrapping text.
+/// ```
+///
+/// In addition, it will recognize a common prefix and a common line
+/// ending among the lines.
+///
+/// The prefix of the first line is returned in
+/// [`Options::initial_indent`] and the prefix (if any) of the the
+/// other lines is returned in [`Options::subsequent_indent`].
+///
+/// Line ending is returned in [`Options::line_ending`]. If line ending
+/// can not be confidently detected (mixed or no line endings in the
+/// input), [`LineEnding::LF`] will be returned.
+///
+/// In addition to `' '`, the prefixes can consist of characters used
+/// for unordered lists (`'-'`, `'+'`, and `'*'`) and block quotes
+/// (`'>'`) in Markdown as well as characters often used for inline
+/// comments (`'#'` and `'/'`).
+///
+/// The text must come from a single wrapped paragraph. This means
+/// that there can be no empty lines (`"\n\n"` or `"\r\n\r\n"`) within
+/// the text. It is unspecified what happens if `unfill` is called on
+/// more than one paragraph of text.
+///
+/// # Examples
+///
+/// ```
+/// use textwrap::{LineEnding, unfill};
+///
+/// let (text, options) = unfill("\
+/// * This is an
+/// example of
+/// a list item.
+/// ");
+///
+/// assert_eq!(text, "This is an example of a list item.\n");
+/// assert_eq!(options.initial_indent, "* ");
+/// assert_eq!(options.subsequent_indent, " ");
+/// assert_eq!(options.line_ending, LineEnding::LF);
+/// ```
+pub fn unfill(text: &str) -> (String, Options<'_>) {
+ let prefix_chars: &[_] = &[' ', '-', '+', '*', '>', '#', '/'];
+
+ let mut options = Options::new(0);
+ for (idx, line) in text.lines().enumerate() {
+ options.width = std::cmp::max(options.width, display_width(line));
+ let without_prefix = line.trim_start_matches(prefix_chars);
+ let prefix = &line[..line.len() - without_prefix.len()];
+
+ if idx == 0 {
+ options.initial_indent = prefix;
+ } else if idx == 1 {
+ options.subsequent_indent = prefix;
+ } else if idx > 1 {
+ for ((idx, x), y) in prefix.char_indices().zip(options.subsequent_indent.chars()) {
+ if x != y {
+ options.subsequent_indent = &prefix[..idx];
+ break;
+ }
+ }
+ if prefix.len() < options.subsequent_indent.len() {
+ options.subsequent_indent = prefix;
+ }
+ }
+ }
+
+ let mut unfilled = String::with_capacity(text.len());
+ let mut detected_line_ending = None;
+
+ for (idx, (line, ending)) in NonEmptyLines(text).enumerate() {
+ if idx == 0 {
+ unfilled.push_str(&line[options.initial_indent.len()..]);
+ } else {
+ unfilled.push(' ');
+ unfilled.push_str(&line[options.subsequent_indent.len()..]);
+ }
+ match (detected_line_ending, ending) {
+ (None, Some(_)) => detected_line_ending = ending,
+ (Some(LineEnding::CRLF), Some(LineEnding::LF)) => detected_line_ending = ending,
+ _ => (),
+ }
+ }
+
+ // Add back a line ending if `text` ends with the one we detect.
+ if let Some(line_ending) = detected_line_ending {
+ if text.ends_with(line_ending.as_str()) {
+ unfilled.push_str(line_ending.as_str());
+ }
+ }
+
+ options.line_ending = detected_line_ending.unwrap_or(LineEnding::LF);
+ (unfilled, options)
+}
+
+/// Refill a paragraph of wrapped text with a new width.
+///
+/// This function will first use [`unfill()`] to remove newlines from
+/// the text. Afterwards the text is filled again using [`fill()`].
+///
+/// The `new_width_or_options` argument specify the new width and can
+/// specify other options as well — except for
+/// [`Options::initial_indent`] and [`Options::subsequent_indent`],
+/// which are deduced from `filled_text`.
+///
+/// # Examples
+///
+/// ```
+/// use textwrap::refill;
+///
+/// // Some loosely wrapped text. The "> " prefix is recognized automatically.
+/// let text = "\
+/// > Memory
+/// > safety without garbage
+/// > collection.
+/// ";
+///
+/// assert_eq!(refill(text, 20), "\
+/// > Memory safety
+/// > without garbage
+/// > collection.
+/// ");
+///
+/// assert_eq!(refill(text, 40), "\
+/// > Memory safety without garbage
+/// > collection.
+/// ");
+///
+/// assert_eq!(refill(text, 60), "\
+/// > Memory safety without garbage collection.
+/// ");
+/// ```
+///
+/// You can also reshape bullet points:
+///
+/// ```
+/// use textwrap::refill;
+///
+/// let text = "\
+/// - This is my
+/// list item.
+/// ";
+///
+/// assert_eq!(refill(text, 20), "\
+/// - This is my list
+/// item.
+/// ");
+/// ```
+pub fn refill<'a, Opt>(filled_text: &str, new_width_or_options: Opt) -> String
+where
+ Opt: Into<Options<'a>>,
+{
+ let mut new_options = new_width_or_options.into();
+ let (text, options) = unfill(filled_text);
+ // The original line ending is kept by `unfill`.
+ let stripped = text.strip_suffix(options.line_ending.as_str());
+ let new_line_ending = new_options.line_ending.as_str();
+
+ new_options.initial_indent = options.initial_indent;
+ new_options.subsequent_indent = options.subsequent_indent;
+ let mut refilled = fill(stripped.unwrap_or(&text), new_options);
+
+ // Add back right line ending if we stripped one off above.
+ if stripped.is_some() {
+ refilled.push_str(new_line_ending);
+ }
+ refilled
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn unfill_simple() {
+ let (text, options) = unfill("foo\nbar");
+ assert_eq!(text, "foo bar");
+ assert_eq!(options.width, 3);
+ assert_eq!(options.line_ending, LineEnding::LF);
+ }
+
+ #[test]
+ fn unfill_no_new_line() {
+ let (text, options) = unfill("foo bar");
+ assert_eq!(text, "foo bar");
+ assert_eq!(options.width, 7);
+ assert_eq!(options.line_ending, LineEnding::LF);
+ }
+
+ #[test]
+ fn unfill_simple_crlf() {
+ let (text, options) = unfill("foo\r\nbar");
+ assert_eq!(text, "foo bar");
+ assert_eq!(options.width, 3);
+ assert_eq!(options.line_ending, LineEnding::CRLF);
+ }
+
+ #[test]
+ fn unfill_mixed_new_lines() {
+ let (text, options) = unfill("foo\r\nbar\nbaz");
+ assert_eq!(text, "foo bar baz");
+ assert_eq!(options.width, 3);
+ assert_eq!(options.line_ending, LineEnding::LF);
+ }
+
+ #[test]
+ fn test_unfill_consecutive_different_prefix() {
+ let (text, options) = unfill("foo\n*\n/");
+ assert_eq!(text, "foo * /");
+ assert_eq!(options.width, 3);
+ assert_eq!(options.line_ending, LineEnding::LF);
+ }
+
+ #[test]
+ fn unfill_trailing_newlines() {
+ let (text, options) = unfill("foo\nbar\n\n\n");
+ assert_eq!(text, "foo bar\n");
+ assert_eq!(options.width, 3);
+ }
+
+ #[test]
+ fn unfill_mixed_trailing_newlines() {
+ let (text, options) = unfill("foo\r\nbar\n\r\n\n");
+ assert_eq!(text, "foo bar\n");
+ assert_eq!(options.width, 3);
+ assert_eq!(options.line_ending, LineEnding::LF);
+ }
+
+ #[test]
+ fn unfill_trailing_crlf() {
+ let (text, options) = unfill("foo bar\r\n");
+ assert_eq!(text, "foo bar\r\n");
+ assert_eq!(options.width, 7);
+ assert_eq!(options.line_ending, LineEnding::CRLF);
+ }
+
+ #[test]
+ fn unfill_initial_indent() {
+ let (text, options) = unfill(" foo\nbar\nbaz");
+ assert_eq!(text, "foo bar baz");
+ assert_eq!(options.width, 5);
+ assert_eq!(options.initial_indent, " ");
+ }
+
+ #[test]
+ fn unfill_differing_indents() {
+ let (text, options) = unfill(" foo\n bar\n baz");
+ assert_eq!(text, "foo bar baz");
+ assert_eq!(options.width, 7);
+ assert_eq!(options.initial_indent, " ");
+ assert_eq!(options.subsequent_indent, " ");
+ }
+
+ #[test]
+ fn unfill_list_item() {
+ let (text, options) = unfill("* foo\n bar\n baz");
+ assert_eq!(text, "foo bar baz");
+ assert_eq!(options.width, 5);
+ assert_eq!(options.initial_indent, "* ");
+ assert_eq!(options.subsequent_indent, " ");
+ }
+
+ #[test]
+ fn unfill_multiple_char_prefix() {
+ let (text, options) = unfill(" // foo bar\n // baz\n // quux");
+ assert_eq!(text, "foo bar baz quux");
+ assert_eq!(options.width, 14);
+ assert_eq!(options.initial_indent, " // ");
+ assert_eq!(options.subsequent_indent, " // ");
+ }
+
+ #[test]
+ fn unfill_block_quote() {
+ let (text, options) = unfill("> foo\n> bar\n> baz");
+ assert_eq!(text, "foo bar baz");
+ assert_eq!(options.width, 5);
+ assert_eq!(options.initial_indent, "> ");
+ assert_eq!(options.subsequent_indent, "> ");
+ }
+
+ #[test]
+ fn unfill_only_prefixes_issue_466() {
+ // Test that we don't crash if the first line has only prefix
+ // chars *and* the second line is shorter than the first line.
+ let (text, options) = unfill("######\nfoo");
+ assert_eq!(text, " foo");
+ assert_eq!(options.width, 6);
+ assert_eq!(options.initial_indent, "######");
+ assert_eq!(options.subsequent_indent, "");
+ }
+
+ #[test]
+ fn unfill_trailing_newlines_issue_466() {
+ // Test that we don't crash on a '\r' following a string of
+ // '\n'. The problem was that we removed both kinds of
+ // characters in one code path, but not in the other.
+ let (text, options) = unfill("foo\n##\n\n\r");
+ // The \n\n changes subsequent_indent to "".
+ assert_eq!(text, "foo ## \r");
+ assert_eq!(options.width, 3);
+ assert_eq!(options.initial_indent, "");
+ assert_eq!(options.subsequent_indent, "");
+ }
+
+ #[test]
+ fn unfill_whitespace() {
+ assert_eq!(unfill("foo bar").0, "foo bar");
+ }
+
+ #[test]
+ fn refill_convert_lf_to_crlf() {
+ let options = Options::new(5).line_ending(LineEnding::CRLF);
+ assert_eq!(refill("foo\nbar\n", options), "foo\r\nbar\r\n",);
+ }
+
+ #[test]
+ fn refill_convert_crlf_to_lf() {
+ let options = Options::new(5).line_ending(LineEnding::LF);
+ assert_eq!(refill("foo\r\nbar\r\n", options), "foo\nbar\n",);
+ }
+
+ #[test]
+ fn refill_convert_mixed_newlines() {
+ let options = Options::new(5).line_ending(LineEnding::CRLF);
+ assert_eq!(refill("foo\r\nbar\n", options), "foo\r\nbar\r\n",);
+ }
+
+ #[test]
+ fn refill_defaults_to_lf() {
+ assert_eq!(refill("foo bar baz", 5), "foo\nbar\nbaz");
+ }
+}
diff --git a/third_party/rust/textwrap/src/termwidth.rs b/third_party/rust/textwrap/src/termwidth.rs
new file mode 100644
index 0000000000..5c66191b77
--- /dev/null
+++ b/third_party/rust/textwrap/src/termwidth.rs
@@ -0,0 +1,52 @@
+//! Functions related to the terminal size.
+
+use crate::Options;
+
+/// Return the current terminal width.
+///
+/// If the terminal width cannot be determined (typically because the
+/// standard output is not connected to a terminal), a default width
+/// of 80 characters will be used.
+///
+/// # Examples
+///
+/// Create an [`Options`] for wrapping at the current terminal width
+/// with a two column margin to the left and the right:
+///
+/// ```no_run
+/// use textwrap::{termwidth, Options};
+///
+/// let width = termwidth() - 4; // Two columns on each side.
+/// let options = Options::new(width)
+/// .initial_indent(" ")
+/// .subsequent_indent(" ");
+/// ```
+///
+/// **Note:** Only available when the `terminal_size` Cargo feature is
+/// enabled.
+pub fn termwidth() -> usize {
+ terminal_size::terminal_size().map_or(80, |(terminal_size::Width(w), _)| w.into())
+}
+
+impl<'a> Options<'a> {
+ /// Creates a new [`Options`] with `width` set to the current
+ /// terminal width. If the terminal width cannot be determined
+ /// (typically because the standard input and output is not
+ /// connected to a terminal), a width of 80 characters will be
+ /// used. Other settings use the same defaults as
+ /// [`Options::new`].
+ ///
+ /// Equivalent to:
+ ///
+ /// ```no_run
+ /// use textwrap::{termwidth, Options};
+ ///
+ /// let options = Options::new(termwidth());
+ /// ```
+ ///
+ /// **Note:** Only available when the `terminal_size` feature is
+ /// enabled.
+ pub fn with_termwidth() -> Self {
+ Self::new(termwidth())
+ }
+}
diff --git a/third_party/rust/textwrap/src/word_separators.rs b/third_party/rust/textwrap/src/word_separators.rs
new file mode 100644
index 0000000000..e06e9b88aa
--- /dev/null
+++ b/third_party/rust/textwrap/src/word_separators.rs
@@ -0,0 +1,481 @@
+//! Functionality for finding words.
+//!
+//! In order to wrap text, we need to know where the legal break
+//! points are, i.e., where the words of the text are. This means that
+//! we need to define what a "word" is.
+//!
+//! A simple approach is to simply split the text on whitespace, but
+//! this does not work for East-Asian languages such as Chinese or
+//! Japanese where there are no spaces between words. Breaking a long
+//! sequence of emojis is another example where line breaks might be
+//! wanted even if there are no whitespace to be found.
+//!
+//! The [`WordSeparator`] enum is responsible for determining where
+//! there words are in a line of text. Please refer to the enum and
+//! its variants for more information.
+
+#[cfg(feature = "unicode-linebreak")]
+use crate::core::skip_ansi_escape_sequence;
+use crate::core::Word;
+
+/// Describes where words occur in a line of text.
+///
+/// The simplest approach is say that words are separated by one or
+/// more ASCII spaces (`' '`). This works for Western languages
+/// without emojis. A more complex approach is to use the Unicode line
+/// breaking algorithm, which finds break points in non-ASCII text.
+///
+/// The line breaks occur between words, please see
+/// [`WordSplitter`](crate::WordSplitter) for options of how to handle
+/// hyphenation of individual words.
+///
+/// # Examples
+///
+/// ```
+/// use textwrap::core::Word;
+/// use textwrap::WordSeparator::AsciiSpace;
+///
+/// let words = AsciiSpace.find_words("Hello World!").collect::<Vec<_>>();
+/// assert_eq!(words, vec![Word::from("Hello "), Word::from("World!")]);
+/// ```
+#[derive(Clone, Copy)]
+pub enum WordSeparator {
+ /// Find words by splitting on runs of `' '` characters.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use textwrap::core::Word;
+ /// use textwrap::WordSeparator::AsciiSpace;
+ ///
+ /// let words = AsciiSpace.find_words("Hello World!").collect::<Vec<_>>();
+ /// assert_eq!(words, vec![Word::from("Hello "),
+ /// Word::from("World!")]);
+ /// ```
+ AsciiSpace,
+
+ /// Split `line` into words using Unicode break properties.
+ ///
+ /// This word separator uses the Unicode line breaking algorithm
+ /// described in [Unicode Standard Annex
+ /// #14](https://www.unicode.org/reports/tr14/) to find legal places
+ /// to break lines. There is a small difference in that the U+002D
+ /// (Hyphen-Minus) and U+00AD (Soft Hyphen) don’t create a line break:
+ /// to allow a line break at a hyphen, use
+ /// [`WordSplitter::HyphenSplitter`](crate::WordSplitter::HyphenSplitter).
+ /// Soft hyphens are not currently supported.
+ ///
+ /// # Examples
+ ///
+ /// Unlike [`WordSeparator::AsciiSpace`], the Unicode line
+ /// breaking algorithm will find line break opportunities between
+ /// some characters with no intervening whitespace:
+ ///
+ /// ```
+ /// #[cfg(feature = "unicode-linebreak")] {
+ /// use textwrap::core::Word;
+ /// use textwrap::WordSeparator::UnicodeBreakProperties;
+ ///
+ /// assert_eq!(UnicodeBreakProperties.find_words("Emojis: 😂😍").collect::<Vec<_>>(),
+ /// vec![Word::from("Emojis: "),
+ /// Word::from("😂"),
+ /// Word::from("😍")]);
+ ///
+ /// assert_eq!(UnicodeBreakProperties.find_words("CJK: 你好").collect::<Vec<_>>(),
+ /// vec![Word::from("CJK: "),
+ /// Word::from("你"),
+ /// Word::from("好")]);
+ /// }
+ /// ```
+ ///
+ /// A U+2060 (Word Joiner) character can be inserted if you want to
+ /// manually override the defaults and keep the characters together:
+ ///
+ /// ```
+ /// #[cfg(feature = "unicode-linebreak")] {
+ /// use textwrap::core::Word;
+ /// use textwrap::WordSeparator::UnicodeBreakProperties;
+ ///
+ /// assert_eq!(UnicodeBreakProperties.find_words("Emojis: 😂\u{2060}😍").collect::<Vec<_>>(),
+ /// vec![Word::from("Emojis: "),
+ /// Word::from("😂\u{2060}😍")]);
+ /// }
+ /// ```
+ ///
+ /// The Unicode line breaking algorithm will also automatically
+ /// suppress break breaks around certain punctuation characters::
+ ///
+ /// ```
+ /// #[cfg(feature = "unicode-linebreak")] {
+ /// use textwrap::core::Word;
+ /// use textwrap::WordSeparator::UnicodeBreakProperties;
+ ///
+ /// assert_eq!(UnicodeBreakProperties.find_words("[ foo ] bar !").collect::<Vec<_>>(),
+ /// vec![Word::from("[ foo ] "),
+ /// Word::from("bar !")]);
+ /// }
+ /// ```
+ #[cfg(feature = "unicode-linebreak")]
+ UnicodeBreakProperties,
+
+ /// Find words using a custom word separator
+ Custom(fn(line: &str) -> Box<dyn Iterator<Item = Word<'_>> + '_>),
+}
+
+impl PartialEq for WordSeparator {
+ /// Compare two word separators.
+ ///
+ /// ```
+ /// use textwrap::WordSeparator;
+ ///
+ /// assert_eq!(WordSeparator::AsciiSpace, WordSeparator::AsciiSpace);
+ /// #[cfg(feature = "unicode-linebreak")] {
+ /// assert_eq!(WordSeparator::UnicodeBreakProperties,
+ /// WordSeparator::UnicodeBreakProperties);
+ /// }
+ /// ```
+ ///
+ /// Note that `WordSeparator::Custom` values never compare equal:
+ ///
+ /// ```
+ /// use textwrap::WordSeparator;
+ /// use textwrap::core::Word;
+ /// fn word_separator(line: &str) -> Box<dyn Iterator<Item = Word<'_>> + '_> {
+ /// Box::new(line.split_inclusive(' ').map(Word::from))
+ /// }
+ /// assert_ne!(WordSeparator::Custom(word_separator),
+ /// WordSeparator::Custom(word_separator));
+ /// ```
+ fn eq(&self, other: &Self) -> bool {
+ match (self, other) {
+ (WordSeparator::AsciiSpace, WordSeparator::AsciiSpace) => true,
+ #[cfg(feature = "unicode-linebreak")]
+ (WordSeparator::UnicodeBreakProperties, WordSeparator::UnicodeBreakProperties) => true,
+ (_, _) => false,
+ }
+ }
+}
+
+impl std::fmt::Debug for WordSeparator {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ WordSeparator::AsciiSpace => f.write_str("AsciiSpace"),
+ #[cfg(feature = "unicode-linebreak")]
+ WordSeparator::UnicodeBreakProperties => f.write_str("UnicodeBreakProperties"),
+ WordSeparator::Custom(_) => f.write_str("Custom(...)"),
+ }
+ }
+}
+
+impl WordSeparator {
+ /// Create a new word separator.
+ ///
+ /// The best available algorithm is used by default, i.e.,
+ /// [`WordSeparator::UnicodeBreakProperties`] if available,
+ /// otherwise [`WordSeparator::AsciiSpace`].
+ pub const fn new() -> Self {
+ #[cfg(feature = "unicode-linebreak")]
+ {
+ WordSeparator::UnicodeBreakProperties
+ }
+
+ #[cfg(not(feature = "unicode-linebreak"))]
+ {
+ WordSeparator::AsciiSpace
+ }
+ }
+
+ // This function should really return impl Iterator<Item = Word>, but
+ // this isn't possible until Rust supports higher-kinded types:
+ // https://github.com/rust-lang/rfcs/blob/master/text/1522-conservative-impl-trait.md
+ /// Find all words in `line`.
+ pub fn find_words<'a>(&self, line: &'a str) -> Box<dyn Iterator<Item = Word<'a>> + 'a> {
+ match self {
+ WordSeparator::AsciiSpace => find_words_ascii_space(line),
+ #[cfg(feature = "unicode-linebreak")]
+ WordSeparator::UnicodeBreakProperties => find_words_unicode_break_properties(line),
+ WordSeparator::Custom(func) => func(line),
+ }
+ }
+}
+
+fn find_words_ascii_space<'a>(line: &'a str) -> Box<dyn Iterator<Item = Word<'a>> + 'a> {
+ let mut start = 0;
+ let mut in_whitespace = false;
+ let mut char_indices = line.char_indices();
+
+ Box::new(std::iter::from_fn(move || {
+ for (idx, ch) in char_indices.by_ref() {
+ if in_whitespace && ch != ' ' {
+ let word = Word::from(&line[start..idx]);
+ start = idx;
+ in_whitespace = ch == ' ';
+ return Some(word);
+ }
+
+ in_whitespace = ch == ' ';
+ }
+
+ if start < line.len() {
+ let word = Word::from(&line[start..]);
+ start = line.len();
+ return Some(word);
+ }
+
+ None
+ }))
+}
+
+// Strip all ANSI escape sequences from `text`.
+#[cfg(feature = "unicode-linebreak")]
+fn strip_ansi_escape_sequences(text: &str) -> String {
+ let mut result = String::with_capacity(text.len());
+
+ let mut chars = text.chars();
+ while let Some(ch) = chars.next() {
+ if skip_ansi_escape_sequence(ch, &mut chars) {
+ continue;
+ }
+ result.push(ch);
+ }
+
+ result
+}
+
+/// Soft hyphen, also knows as a “shy hyphen”. Should show up as ‘-’
+/// if a line is broken at this point, and otherwise be invisible.
+/// Textwrap does not currently support breaking words at soft
+/// hyphens.
+#[cfg(feature = "unicode-linebreak")]
+const SHY: char = '\u{00ad}';
+
+/// Find words in line. ANSI escape sequences are ignored in `line`.
+#[cfg(feature = "unicode-linebreak")]
+fn find_words_unicode_break_properties<'a>(
+ line: &'a str,
+) -> Box<dyn Iterator<Item = Word<'a>> + 'a> {
+ // Construct an iterator over (original index, stripped index)
+ // tuples. We find the Unicode linebreaks on a stripped string,
+ // but we need the original indices so we can form words based on
+ // the original string.
+ let mut last_stripped_idx = 0;
+ let mut char_indices = line.char_indices();
+ let mut idx_map = std::iter::from_fn(move || match char_indices.next() {
+ Some((orig_idx, ch)) => {
+ let stripped_idx = last_stripped_idx;
+ if !skip_ansi_escape_sequence(ch, &mut char_indices.by_ref().map(|(_, ch)| ch)) {
+ last_stripped_idx += ch.len_utf8();
+ }
+ Some((orig_idx, stripped_idx))
+ }
+ None => None,
+ });
+
+ let stripped = strip_ansi_escape_sequences(line);
+ let mut opportunities = unicode_linebreak::linebreaks(&stripped)
+ .filter(|(idx, _)| {
+ #[allow(clippy::match_like_matches_macro)]
+ match &stripped[..*idx].chars().next_back() {
+ // We suppress breaks at ‘-’ since we want to control
+ // this via the WordSplitter.
+ Some('-') => false,
+ // Soft hyphens are currently not supported since we
+ // require all `Word` fragments to be continuous in
+ // the input string.
+ Some(SHY) => false,
+ // Other breaks should be fine!
+ _ => true,
+ }
+ })
+ .collect::<Vec<_>>()
+ .into_iter();
+
+ // Remove final break opportunity, we will add it below using
+ // &line[start..]; This ensures that we correctly include a
+ // trailing ANSI escape sequence.
+ opportunities.next_back();
+
+ let mut start = 0;
+ Box::new(std::iter::from_fn(move || {
+ for (idx, _) in opportunities.by_ref() {
+ if let Some((orig_idx, _)) = idx_map.find(|&(_, stripped_idx)| stripped_idx == idx) {
+ let word = Word::from(&line[start..orig_idx]);
+ start = orig_idx;
+ return Some(word);
+ }
+ }
+
+ if start < line.len() {
+ let word = Word::from(&line[start..]);
+ start = line.len();
+ return Some(word);
+ }
+
+ None
+ }))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::WordSeparator::*;
+ use super::*;
+
+ // Like assert_eq!, but the left expression is an iterator.
+ macro_rules! assert_iter_eq {
+ ($left:expr, $right:expr) => {
+ assert_eq!($left.collect::<Vec<_>>(), $right);
+ };
+ }
+
+ fn to_words(words: Vec<&str>) -> Vec<Word<'_>> {
+ words.into_iter().map(Word::from).collect()
+ }
+
+ macro_rules! test_find_words {
+ ($ascii_name:ident,
+ $unicode_name:ident,
+ $([ $line:expr, $ascii_words:expr, $unicode_words:expr ]),+) => {
+ #[test]
+ fn $ascii_name() {
+ $(
+ let expected_words = to_words($ascii_words.to_vec());
+ let actual_words = WordSeparator::AsciiSpace
+ .find_words($line)
+ .collect::<Vec<_>>();
+ assert_eq!(actual_words, expected_words, "Line: {:?}", $line);
+ )+
+ }
+
+ #[test]
+ #[cfg(feature = "unicode-linebreak")]
+ fn $unicode_name() {
+ $(
+ let expected_words = to_words($unicode_words.to_vec());
+ let actual_words = WordSeparator::UnicodeBreakProperties
+ .find_words($line)
+ .collect::<Vec<_>>();
+ assert_eq!(actual_words, expected_words, "Line: {:?}", $line);
+ )+
+ }
+ };
+ }
+
+ test_find_words!(ascii_space_empty, unicode_empty, ["", [], []]);
+
+ test_find_words!(
+ ascii_single_word,
+ unicode_single_word,
+ ["foo", ["foo"], ["foo"]]
+ );
+
+ test_find_words!(
+ ascii_two_words,
+ unicode_two_words,
+ ["foo bar", ["foo ", "bar"], ["foo ", "bar"]]
+ );
+
+ test_find_words!(
+ ascii_multiple_words,
+ unicode_multiple_words,
+ ["foo bar", ["foo ", "bar"], ["foo ", "bar"]],
+ ["x y z", ["x ", "y ", "z"], ["x ", "y ", "z"]]
+ );
+
+ test_find_words!(
+ ascii_only_whitespace,
+ unicode_only_whitespace,
+ [" ", [" "], [" "]],
+ [" ", [" "], [" "]]
+ );
+
+ test_find_words!(
+ ascii_inter_word_whitespace,
+ unicode_inter_word_whitespace,
+ ["foo bar", ["foo ", "bar"], ["foo ", "bar"]]
+ );
+
+ test_find_words!(
+ ascii_trailing_whitespace,
+ unicode_trailing_whitespace,
+ ["foo ", ["foo "], ["foo "]]
+ );
+
+ test_find_words!(
+ ascii_leading_whitespace,
+ unicode_leading_whitespace,
+ [" foo", [" ", "foo"], [" ", "foo"]]
+ );
+
+ test_find_words!(
+ ascii_multi_column_char,
+ unicode_multi_column_char,
+ ["\u{1f920}", ["\u{1f920}"], ["\u{1f920}"]] // cowboy emoji 🤠
+ );
+
+ test_find_words!(
+ ascii_hyphens,
+ unicode_hyphens,
+ ["foo-bar", ["foo-bar"], ["foo-bar"]],
+ ["foo- bar", ["foo- ", "bar"], ["foo- ", "bar"]],
+ ["foo - bar", ["foo ", "- ", "bar"], ["foo ", "- ", "bar"]],
+ ["foo -bar", ["foo ", "-bar"], ["foo ", "-bar"]]
+ );
+
+ test_find_words!(
+ ascii_newline,
+ unicode_newline,
+ ["foo\nbar", ["foo\nbar"], ["foo\n", "bar"]]
+ );
+
+ test_find_words!(
+ ascii_tab,
+ unicode_tab,
+ ["foo\tbar", ["foo\tbar"], ["foo\t", "bar"]]
+ );
+
+ test_find_words!(
+ ascii_non_breaking_space,
+ unicode_non_breaking_space,
+ ["foo\u{00A0}bar", ["foo\u{00A0}bar"], ["foo\u{00A0}bar"]]
+ );
+
+ #[test]
+ #[cfg(unix)]
+ fn find_words_colored_text() {
+ use termion::color::{Blue, Fg, Green, Reset};
+
+ let green_hello = format!("{}Hello{} ", Fg(Green), Fg(Reset));
+ let blue_world = format!("{}World!{}", Fg(Blue), Fg(Reset));
+ assert_iter_eq!(
+ AsciiSpace.find_words(&format!("{}{}", green_hello, blue_world)),
+ vec![Word::from(&green_hello), Word::from(&blue_world)]
+ );
+
+ #[cfg(feature = "unicode-linebreak")]
+ assert_iter_eq!(
+ UnicodeBreakProperties.find_words(&format!("{}{}", green_hello, blue_world)),
+ vec![Word::from(&green_hello), Word::from(&blue_world)]
+ );
+ }
+
+ #[test]
+ fn find_words_color_inside_word() {
+ let text = "foo\u{1b}[0m\u{1b}[32mbar\u{1b}[0mbaz";
+ assert_iter_eq!(AsciiSpace.find_words(text), vec![Word::from(text)]);
+
+ #[cfg(feature = "unicode-linebreak")]
+ assert_iter_eq!(
+ UnicodeBreakProperties.find_words(text),
+ vec![Word::from(text)]
+ );
+ }
+
+ #[test]
+ fn word_separator_new() {
+ #[cfg(feature = "unicode-linebreak")]
+ assert!(matches!(WordSeparator::new(), UnicodeBreakProperties));
+
+ #[cfg(not(feature = "unicode-linebreak"))]
+ assert!(matches!(WordSeparator::new(), AsciiSpace));
+ }
+}
diff --git a/third_party/rust/textwrap/src/word_splitters.rs b/third_party/rust/textwrap/src/word_splitters.rs
new file mode 100644
index 0000000000..e2dc6aa01f
--- /dev/null
+++ b/third_party/rust/textwrap/src/word_splitters.rs
@@ -0,0 +1,314 @@
+//! Word splitting functionality.
+//!
+//! To wrap text into lines, long words sometimes need to be split
+//! across lines. The [`WordSplitter`] enum defines this
+//! functionality.
+
+use crate::core::{display_width, Word};
+
+/// The `WordSplitter` enum describes where words can be split.
+///
+/// If the textwrap crate has been compiled with the `hyphenation`
+/// Cargo feature enabled, you will find a
+/// [`WordSplitter::Hyphenation`] variant. Use this struct for
+/// language-aware hyphenation:
+///
+/// ```
+/// #[cfg(feature = "hyphenation")] {
+/// use hyphenation::{Language, Load, Standard};
+/// use textwrap::{wrap, Options, WordSplitter};
+///
+/// let text = "Oxidation is the loss of electrons.";
+/// let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
+/// let options = Options::new(8).word_splitter(WordSplitter::Hyphenation(dictionary));
+/// assert_eq!(wrap(text, &options), vec!["Oxida-",
+/// "tion is",
+/// "the loss",
+/// "of elec-",
+/// "trons."]);
+/// }
+/// ```
+///
+/// Please see the documentation for the [hyphenation] crate for more
+/// details.
+///
+/// [hyphenation]: https://docs.rs/hyphenation/
+#[derive(Clone)]
+pub enum WordSplitter {
+ /// Use this as a [`Options.word_splitter`] to avoid any kind of
+ /// hyphenation:
+ ///
+ /// ```
+ /// use textwrap::{wrap, Options, WordSplitter};
+ ///
+ /// let options = Options::new(8).word_splitter(WordSplitter::NoHyphenation);
+ /// assert_eq!(wrap("foo bar-baz", &options),
+ /// vec!["foo", "bar-baz"]);
+ /// ```
+ ///
+ /// [`Options.word_splitter`]: super::Options::word_splitter
+ NoHyphenation,
+
+ /// `HyphenSplitter` is the default `WordSplitter` used by
+ /// [`Options::new`](super::Options::new). It will split words on
+ /// existing hyphens in the word.
+ ///
+ /// It will only use hyphens that are surrounded by alphanumeric
+ /// characters, which prevents a word like `"--foo-bar"` from
+ /// being split into `"--"` and `"foo-bar"`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use textwrap::WordSplitter;
+ ///
+ /// assert_eq!(WordSplitter::HyphenSplitter.split_points("--foo-bar"),
+ /// vec![6]);
+ /// ```
+ HyphenSplitter,
+
+ /// Use a custom function as the word splitter.
+ ///
+ /// This variant lets you implement a custom word splitter using
+ /// your own function.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use textwrap::WordSplitter;
+ ///
+ /// fn split_at_underscore(word: &str) -> Vec<usize> {
+ /// word.match_indices('_').map(|(idx, _)| idx + 1).collect()
+ /// }
+ ///
+ /// let word_splitter = WordSplitter::Custom(split_at_underscore);
+ /// assert_eq!(word_splitter.split_points("a_long_identifier"),
+ /// vec![2, 7]);
+ /// ```
+ Custom(fn(word: &str) -> Vec<usize>),
+
+ /// A hyphenation dictionary can be used to do language-specific
+ /// hyphenation using patterns from the [hyphenation] crate.
+ ///
+ /// **Note:** Only available when the `hyphenation` Cargo feature is
+ /// enabled.
+ ///
+ /// [hyphenation]: https://docs.rs/hyphenation/
+ #[cfg(feature = "hyphenation")]
+ Hyphenation(hyphenation::Standard),
+}
+
+impl std::fmt::Debug for WordSplitter {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ WordSplitter::NoHyphenation => f.write_str("NoHyphenation"),
+ WordSplitter::HyphenSplitter => f.write_str("HyphenSplitter"),
+ WordSplitter::Custom(_) => f.write_str("Custom(...)"),
+ #[cfg(feature = "hyphenation")]
+ WordSplitter::Hyphenation(dict) => write!(f, "Hyphenation({})", dict.language()),
+ }
+ }
+}
+
+impl PartialEq<WordSplitter> for WordSplitter {
+ fn eq(&self, other: &WordSplitter) -> bool {
+ match (self, other) {
+ (WordSplitter::NoHyphenation, WordSplitter::NoHyphenation) => true,
+ (WordSplitter::HyphenSplitter, WordSplitter::HyphenSplitter) => true,
+ #[cfg(feature = "hyphenation")]
+ (WordSplitter::Hyphenation(this_dict), WordSplitter::Hyphenation(other_dict)) => {
+ this_dict.language() == other_dict.language()
+ }
+ (_, _) => false,
+ }
+ }
+}
+
+impl WordSplitter {
+ /// Return all possible indices where `word` can be split.
+ ///
+ /// The indices are in the range `0..word.len()`. They point to
+ /// the index _after_ the split point, i.e., after `-` if
+ /// splitting on hyphens. This way, `word.split_at(idx)` will
+ /// break the word into two well-formed pieces.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use textwrap::WordSplitter;
+ /// assert_eq!(WordSplitter::NoHyphenation.split_points("cannot-be-split"), vec![]);
+ /// assert_eq!(WordSplitter::HyphenSplitter.split_points("can-be-split"), vec![4, 7]);
+ /// assert_eq!(WordSplitter::Custom(|word| vec![word.len()/2]).split_points("middle"), vec![3]);
+ /// ```
+ pub fn split_points(&self, word: &str) -> Vec<usize> {
+ match self {
+ WordSplitter::NoHyphenation => Vec::new(),
+ WordSplitter::HyphenSplitter => {
+ let mut splits = Vec::new();
+
+ for (idx, _) in word.match_indices('-') {
+ // We only use hyphens that are surrounded by alphanumeric
+ // characters. This is to avoid splitting on repeated hyphens,
+ // such as those found in --foo-bar.
+ let prev = word[..idx].chars().next_back();
+ let next = word[idx + 1..].chars().next();
+
+ if prev.filter(|ch| ch.is_alphanumeric()).is_some()
+ && next.filter(|ch| ch.is_alphanumeric()).is_some()
+ {
+ splits.push(idx + 1); // +1 due to width of '-'.
+ }
+ }
+
+ splits
+ }
+ WordSplitter::Custom(splitter_func) => splitter_func(word),
+ #[cfg(feature = "hyphenation")]
+ WordSplitter::Hyphenation(dictionary) => {
+ use hyphenation::Hyphenator;
+ dictionary.hyphenate(word).breaks
+ }
+ }
+ }
+}
+
+/// Split words into smaller words according to the split points given
+/// by `word_splitter`.
+///
+/// Note that we split all words, regardless of their length. This is
+/// to more cleanly separate the business of splitting (including
+/// automatic hyphenation) from the business of word wrapping.
+pub fn split_words<'a, I>(
+ words: I,
+ word_splitter: &'a WordSplitter,
+) -> impl Iterator<Item = Word<'a>>
+where
+ I: IntoIterator<Item = Word<'a>>,
+{
+ words.into_iter().flat_map(move |word| {
+ let mut prev = 0;
+ let mut split_points = word_splitter.split_points(&word).into_iter();
+ std::iter::from_fn(move || {
+ if let Some(idx) = split_points.next() {
+ let need_hyphen = !word[..idx].ends_with('-');
+ let w = Word {
+ word: &word.word[prev..idx],
+ width: display_width(&word[prev..idx]),
+ whitespace: "",
+ penalty: if need_hyphen { "-" } else { "" },
+ };
+ prev = idx;
+ return Some(w);
+ }
+
+ if prev < word.word.len() || prev == 0 {
+ let w = Word {
+ word: &word.word[prev..],
+ width: display_width(&word[prev..]),
+ whitespace: word.whitespace,
+ penalty: word.penalty,
+ };
+ prev = word.word.len() + 1;
+ return Some(w);
+ }
+
+ None
+ })
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // Like assert_eq!, but the left expression is an iterator.
+ macro_rules! assert_iter_eq {
+ ($left:expr, $right:expr) => {
+ assert_eq!($left.collect::<Vec<_>>(), $right);
+ };
+ }
+
+ #[test]
+ fn split_words_no_words() {
+ assert_iter_eq!(split_words(vec![], &WordSplitter::HyphenSplitter), vec![]);
+ }
+
+ #[test]
+ fn split_words_empty_word() {
+ assert_iter_eq!(
+ split_words(vec![Word::from(" ")], &WordSplitter::HyphenSplitter),
+ vec![Word::from(" ")]
+ );
+ }
+
+ #[test]
+ fn split_words_single_word() {
+ assert_iter_eq!(
+ split_words(vec![Word::from("foobar")], &WordSplitter::HyphenSplitter),
+ vec![Word::from("foobar")]
+ );
+ }
+
+ #[test]
+ fn split_words_hyphen_splitter() {
+ assert_iter_eq!(
+ split_words(vec![Word::from("foo-bar")], &WordSplitter::HyphenSplitter),
+ vec![Word::from("foo-"), Word::from("bar")]
+ );
+ }
+
+ #[test]
+ fn split_words_no_hyphenation() {
+ assert_iter_eq!(
+ split_words(vec![Word::from("foo-bar")], &WordSplitter::NoHyphenation),
+ vec![Word::from("foo-bar")]
+ );
+ }
+
+ #[test]
+ fn split_words_adds_penalty() {
+ let fixed_split_point = |_: &str| vec![3];
+
+ assert_iter_eq!(
+ split_words(
+ vec![Word::from("foobar")].into_iter(),
+ &WordSplitter::Custom(fixed_split_point)
+ ),
+ vec![
+ Word {
+ word: "foo",
+ width: 3,
+ whitespace: "",
+ penalty: "-"
+ },
+ Word {
+ word: "bar",
+ width: 3,
+ whitespace: "",
+ penalty: ""
+ }
+ ]
+ );
+
+ assert_iter_eq!(
+ split_words(
+ vec![Word::from("fo-bar")].into_iter(),
+ &WordSplitter::Custom(fixed_split_point)
+ ),
+ vec![
+ Word {
+ word: "fo-",
+ width: 3,
+ whitespace: "",
+ penalty: ""
+ },
+ Word {
+ word: "bar",
+ width: 3,
+ whitespace: "",
+ penalty: ""
+ }
+ ]
+ );
+ }
+}
diff --git a/third_party/rust/textwrap/src/wrap.rs b/third_party/rust/textwrap/src/wrap.rs
new file mode 100644
index 0000000000..a7f2ccf298
--- /dev/null
+++ b/third_party/rust/textwrap/src/wrap.rs
@@ -0,0 +1,686 @@
+//! Functions for wrapping text.
+
+use std::borrow::Cow;
+
+use crate::core::{break_words, display_width, Word};
+use crate::word_splitters::split_words;
+use crate::Options;
+
+/// Wrap a line of text at a given width.
+///
+/// The result is a vector of lines, each line is of type [`Cow<'_,
+/// str>`](Cow), which means that the line will borrow from the input
+/// `&str` if possible. The lines do not have trailing whitespace,
+/// including a final `'\n'`. Please use [`fill()`](crate::fill()) if
+/// you need a [`String`] instead.
+///
+/// The easiest way to use this function is to pass an integer for
+/// `width_or_options`:
+///
+/// ```
+/// use textwrap::wrap;
+///
+/// let lines = wrap("Memory safety without garbage collection.", 15);
+/// assert_eq!(lines, &[
+/// "Memory safety",
+/// "without garbage",
+/// "collection.",
+/// ]);
+/// ```
+///
+/// If you need to customize the wrapping, you can pass an [`Options`]
+/// instead of an `usize`:
+///
+/// ```
+/// use textwrap::{wrap, Options};
+///
+/// let options = Options::new(15)
+/// .initial_indent("- ")
+/// .subsequent_indent(" ");
+/// let lines = wrap("Memory safety without garbage collection.", &options);
+/// assert_eq!(lines, &[
+/// "- Memory safety",
+/// " without",
+/// " garbage",
+/// " collection.",
+/// ]);
+/// ```
+///
+/// # Optimal-Fit Wrapping
+///
+/// By default, `wrap` will try to ensure an even right margin by
+/// finding breaks which avoid short lines. We call this an
+/// “optimal-fit algorithm” since the line breaks are computed by
+/// considering all possible line breaks. The alternative is a
+/// “first-fit algorithm” which simply accumulates words until they no
+/// longer fit on the line.
+///
+/// As an example, using the first-fit algorithm to wrap the famous
+/// Hamlet quote “To be, or not to be: that is the question” in a
+/// narrow column with room for only 10 characters looks like this:
+///
+/// ```
+/// # use textwrap::{WrapAlgorithm::FirstFit, Options, wrap};
+/// #
+/// # let lines = wrap("To be, or not to be: that is the question",
+/// # Options::new(10).wrap_algorithm(FirstFit));
+/// # assert_eq!(lines.join("\n") + "\n", "\
+/// To be, or
+/// not to be:
+/// that is
+/// the
+/// question
+/// # ");
+/// ```
+///
+/// Notice how the second to last line is quite narrow because
+/// “question” was too large to fit? The greedy first-fit algorithm
+/// doesn’t look ahead, so it has no other option than to put
+/// “question” onto its own line.
+///
+/// With the optimal-fit wrapping algorithm, the previous lines are
+/// shortened slightly in order to make the word “is” go into the
+/// second last line:
+///
+/// ```
+/// # #[cfg(feature = "smawk")] {
+/// # use textwrap::{Options, WrapAlgorithm, wrap};
+/// #
+/// # let lines = wrap(
+/// # "To be, or not to be: that is the question",
+/// # Options::new(10).wrap_algorithm(WrapAlgorithm::new_optimal_fit())
+/// # );
+/// # assert_eq!(lines.join("\n") + "\n", "\
+/// To be,
+/// or not to
+/// be: that
+/// is the
+/// question
+/// # "); }
+/// ```
+///
+/// Please see [`WrapAlgorithm`](crate::WrapAlgorithm) for details on
+/// the choices.
+///
+/// # Examples
+///
+/// The returned iterator yields lines of type `Cow<'_, str>`. If
+/// possible, the wrapped lines will borrow from the input string. As
+/// an example, a hanging indentation, the first line can borrow from
+/// the input, but the subsequent lines become owned strings:
+///
+/// ```
+/// use std::borrow::Cow::{Borrowed, Owned};
+/// use textwrap::{wrap, Options};
+///
+/// let options = Options::new(15).subsequent_indent("....");
+/// let lines = wrap("Wrapping text all day long.", &options);
+/// let annotated = lines
+/// .iter()
+/// .map(|line| match line {
+/// Borrowed(text) => format!("[Borrowed] {}", text),
+/// Owned(text) => format!("[Owned] {}", text),
+/// })
+/// .collect::<Vec<_>>();
+/// assert_eq!(
+/// annotated,
+/// &[
+/// "[Borrowed] Wrapping text",
+/// "[Owned] ....all day",
+/// "[Owned] ....long.",
+/// ]
+/// );
+/// ```
+///
+/// ## Leading and Trailing Whitespace
+///
+/// As a rule, leading whitespace (indentation) is preserved and
+/// trailing whitespace is discarded.
+///
+/// In more details, when wrapping words into lines, words are found
+/// by splitting the input text on space characters. One or more
+/// spaces (shown here as “␣”) are attached to the end of each word:
+///
+/// ```text
+/// "Foo␣␣␣bar␣baz" -> ["Foo␣␣␣", "bar␣", "baz"]
+/// ```
+///
+/// These words are then put into lines. The interword whitespace is
+/// preserved, unless the lines are wrapped so that the `"Foo␣␣␣"`
+/// word falls at the end of a line:
+///
+/// ```
+/// use textwrap::wrap;
+///
+/// assert_eq!(wrap("Foo bar baz", 10), vec!["Foo bar", "baz"]);
+/// assert_eq!(wrap("Foo bar baz", 8), vec!["Foo", "bar baz"]);
+/// ```
+///
+/// Notice how the trailing whitespace is removed in both case: in the
+/// first example, `"bar␣"` becomes `"bar"` and in the second case
+/// `"Foo␣␣␣"` becomes `"Foo"`.
+///
+/// Leading whitespace is preserved when the following word fits on
+/// the first line. To understand this, consider how words are found
+/// in a text with leading spaces:
+///
+/// ```text
+/// "␣␣foo␣bar" -> ["␣␣", "foo␣", "bar"]
+/// ```
+///
+/// When put into lines, the indentation is preserved if `"foo"` fits
+/// on the first line, otherwise you end up with an empty line:
+///
+/// ```
+/// use textwrap::wrap;
+///
+/// assert_eq!(wrap(" foo bar", 8), vec![" foo", "bar"]);
+/// assert_eq!(wrap(" foo bar", 4), vec!["", "foo", "bar"]);
+/// ```
+pub fn wrap<'a, Opt>(text: &str, width_or_options: Opt) -> Vec<Cow<'_, str>>
+where
+ Opt: Into<Options<'a>>,
+{
+ let options: Options = width_or_options.into();
+ let line_ending_str = options.line_ending.as_str();
+
+ let mut lines = Vec::new();
+ for line in text.split(line_ending_str) {
+ wrap_single_line(line, &options, &mut lines);
+ }
+
+ lines
+}
+
+pub(crate) fn wrap_single_line<'a>(
+ line: &'a str,
+ options: &Options<'_>,
+ lines: &mut Vec<Cow<'a, str>>,
+) {
+ let indent = if lines.is_empty() {
+ options.initial_indent
+ } else {
+ options.subsequent_indent
+ };
+ if line.len() < options.width && indent.is_empty() {
+ lines.push(Cow::from(line.trim_end_matches(' ')));
+ } else {
+ wrap_single_line_slow_path(line, options, lines)
+ }
+}
+
+/// Wrap a single line of text.
+///
+/// This is taken when `line` is longer than `options.width`.
+pub(crate) fn wrap_single_line_slow_path<'a>(
+ line: &'a str,
+ options: &Options<'_>,
+ lines: &mut Vec<Cow<'a, str>>,
+) {
+ let initial_width = options
+ .width
+ .saturating_sub(display_width(options.initial_indent));
+ let subsequent_width = options
+ .width
+ .saturating_sub(display_width(options.subsequent_indent));
+ let line_widths = [initial_width, subsequent_width];
+
+ let words = options.word_separator.find_words(line);
+ let split_words = split_words(words, &options.word_splitter);
+ let broken_words = if options.break_words {
+ let mut broken_words = break_words(split_words, line_widths[1]);
+ if !options.initial_indent.is_empty() {
+ // Without this, the first word will always go into the
+ // first line. However, since we break words based on the
+ // _second_ line width, it can be wrong to unconditionally
+ // put the first word onto the first line. An empty
+ // zero-width word fixed this.
+ broken_words.insert(0, Word::from(""));
+ }
+ broken_words
+ } else {
+ split_words.collect::<Vec<_>>()
+ };
+
+ let wrapped_words = options.wrap_algorithm.wrap(&broken_words, &line_widths);
+
+ let mut idx = 0;
+ for words in wrapped_words {
+ let last_word = match words.last() {
+ None => {
+ lines.push(Cow::from(""));
+ continue;
+ }
+ Some(word) => word,
+ };
+
+ // We assume here that all words are contiguous in `line`.
+ // That is, the sum of their lengths should add up to the
+ // length of `line`.
+ let len = words
+ .iter()
+ .map(|word| word.len() + word.whitespace.len())
+ .sum::<usize>()
+ - last_word.whitespace.len();
+
+ // The result is owned if we have indentation, otherwise we
+ // can simply borrow an empty string.
+ let mut result = if lines.is_empty() && !options.initial_indent.is_empty() {
+ Cow::Owned(options.initial_indent.to_owned())
+ } else if !lines.is_empty() && !options.subsequent_indent.is_empty() {
+ Cow::Owned(options.subsequent_indent.to_owned())
+ } else {
+ // We can use an empty string here since string
+ // concatenation for `Cow` preserves a borrowed value when
+ // either side is empty.
+ Cow::from("")
+ };
+
+ result += &line[idx..idx + len];
+
+ if !last_word.penalty.is_empty() {
+ result.to_mut().push_str(last_word.penalty);
+ }
+
+ lines.push(result);
+
+ // Advance by the length of `result`, plus the length of
+ // `last_word.whitespace` -- even if we had a penalty, we need
+ // to skip over the whitespace.
+ idx += len + last_word.whitespace.len();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{WordSeparator, WordSplitter, WrapAlgorithm};
+
+ #[cfg(feature = "hyphenation")]
+ use hyphenation::{Language, Load, Standard};
+
+ #[test]
+ fn no_wrap() {
+ assert_eq!(wrap("foo", 10), vec!["foo"]);
+ }
+
+ #[test]
+ fn wrap_simple() {
+ assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]);
+ }
+
+ #[test]
+ fn to_be_or_not() {
+ assert_eq!(
+ wrap(
+ "To be, or not to be, that is the question.",
+ Options::new(10).wrap_algorithm(WrapAlgorithm::FirstFit)
+ ),
+ vec!["To be, or", "not to be,", "that is", "the", "question."]
+ );
+ }
+
+ #[test]
+ fn multiple_words_on_first_line() {
+ assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]);
+ }
+
+ #[test]
+ fn long_word() {
+ assert_eq!(wrap("foo", 0), vec!["f", "o", "o"]);
+ }
+
+ #[test]
+ fn long_words() {
+ assert_eq!(wrap("foo bar", 0), vec!["f", "o", "o", "b", "a", "r"]);
+ }
+
+ #[test]
+ fn max_width() {
+ assert_eq!(wrap("foo bar", usize::MAX), vec!["foo bar"]);
+
+ let text = "Hello there! This is some English text. \
+ It should not be wrapped given the extents below.";
+ assert_eq!(wrap(text, usize::MAX), vec![text]);
+ }
+
+ #[test]
+ fn leading_whitespace() {
+ assert_eq!(wrap(" foo bar", 6), vec![" foo", "bar"]);
+ }
+
+ #[test]
+ fn leading_whitespace_empty_first_line() {
+ // If there is no space for the first word, the first line
+ // will be empty. This is because the string is split into
+ // words like [" ", "foobar ", "baz"], which puts "foobar " on
+ // the second line. We never output trailing whitespace
+ assert_eq!(wrap(" foobar baz", 6), vec!["", "foobar", "baz"]);
+ }
+
+ #[test]
+ fn trailing_whitespace() {
+ // Whitespace is only significant inside a line. After a line
+ // gets too long and is broken, the first word starts in
+ // column zero and is not indented.
+ assert_eq!(wrap("foo bar baz ", 5), vec!["foo", "bar", "baz"]);
+ }
+
+ #[test]
+ fn issue_99() {
+ // We did not reset the in_whitespace flag correctly and did
+ // not handle single-character words after a line break.
+ assert_eq!(
+ wrap("aaabbbccc x yyyzzzwww", 9),
+ vec!["aaabbbccc", "x", "yyyzzzwww"]
+ );
+ }
+
+ #[test]
+ fn issue_129() {
+ // The dash is an em-dash which takes up four bytes. We used
+ // to panic since we tried to index into the character.
+ let options = Options::new(1).word_separator(WordSeparator::AsciiSpace);
+ assert_eq!(wrap("x – x", options), vec!["x", "–", "x"]);
+ }
+
+ #[test]
+ fn wide_character_handling() {
+ assert_eq!(wrap("Hello, World!", 15), vec!["Hello, World!"]);
+ assert_eq!(
+ wrap(
+ "Hello, World!",
+ Options::new(15).word_separator(WordSeparator::AsciiSpace)
+ ),
+ vec!["Hello,", "World!"]
+ );
+
+ // Wide characters are allowed to break if the
+ // unicode-linebreak feature is enabled.
+ #[cfg(feature = "unicode-linebreak")]
+ assert_eq!(
+ wrap(
+ "Hello, World!",
+ Options::new(15).word_separator(WordSeparator::UnicodeBreakProperties),
+ ),
+ vec!["Hello, W", "orld!"]
+ );
+ }
+
+ #[test]
+ fn indent_empty_line() {
+ // Previously, indentation was not applied to empty lines.
+ // However, this is somewhat inconsistent and undesirable if
+ // the indentation is something like a border ("| ") which you
+ // want to apply to all lines, empty or not.
+ let options = Options::new(10).initial_indent("!!!");
+ assert_eq!(wrap("", &options), vec!["!!!"]);
+ }
+
+ #[test]
+ fn indent_single_line() {
+ let options = Options::new(10).initial_indent(">>>"); // No trailing space
+ assert_eq!(wrap("foo", &options), vec![">>>foo"]);
+ }
+
+ #[test]
+ fn indent_first_emoji() {
+ let options = Options::new(10).initial_indent("👉👉");
+ assert_eq!(
+ wrap("x x x x x x x x x x x x x", &options),
+ vec!["👉👉x x x", "x x x x x", "x x x x x"]
+ );
+ }
+
+ #[test]
+ fn indent_multiple_lines() {
+ let options = Options::new(6).initial_indent("* ").subsequent_indent(" ");
+ assert_eq!(
+ wrap("foo bar baz", &options),
+ vec!["* foo", " bar", " baz"]
+ );
+ }
+
+ #[test]
+ fn only_initial_indent_multiple_lines() {
+ let options = Options::new(10).initial_indent(" ");
+ assert_eq!(wrap("foo\nbar\nbaz", &options), vec![" foo", "bar", "baz"]);
+ }
+
+ #[test]
+ fn only_subsequent_indent_multiple_lines() {
+ let options = Options::new(10).subsequent_indent(" ");
+ assert_eq!(
+ wrap("foo\nbar\nbaz", &options),
+ vec!["foo", " bar", " baz"]
+ );
+ }
+
+ #[test]
+ fn indent_break_words() {
+ let options = Options::new(5).initial_indent("* ").subsequent_indent(" ");
+ assert_eq!(wrap("foobarbaz", &options), vec!["* foo", " bar", " baz"]);
+ }
+
+ #[test]
+ fn initial_indent_break_words() {
+ // This is a corner-case showing how the long word is broken
+ // according to the width of the subsequent lines. The first
+ // fragment of the word no longer fits on the first line,
+ // which ends up being pure indentation.
+ let options = Options::new(5).initial_indent("-->");
+ assert_eq!(wrap("foobarbaz", &options), vec!["-->", "fooba", "rbaz"]);
+ }
+
+ #[test]
+ fn hyphens() {
+ assert_eq!(wrap("foo-bar", 5), vec!["foo-", "bar"]);
+ }
+
+ #[test]
+ fn trailing_hyphen() {
+ let options = Options::new(5).break_words(false);
+ assert_eq!(wrap("foobar-", &options), vec!["foobar-"]);
+ }
+
+ #[test]
+ fn multiple_hyphens() {
+ assert_eq!(wrap("foo-bar-baz", 5), vec!["foo-", "bar-", "baz"]);
+ }
+
+ #[test]
+ fn hyphens_flag() {
+ let options = Options::new(5).break_words(false);
+ assert_eq!(
+ wrap("The --foo-bar flag.", &options),
+ vec!["The", "--foo-", "bar", "flag."]
+ );
+ }
+
+ #[test]
+ fn repeated_hyphens() {
+ let options = Options::new(4).break_words(false);
+ assert_eq!(wrap("foo--bar", &options), vec!["foo--bar"]);
+ }
+
+ #[test]
+ fn hyphens_alphanumeric() {
+ assert_eq!(wrap("Na2-CH4", 5), vec!["Na2-", "CH4"]);
+ }
+
+ #[test]
+ fn hyphens_non_alphanumeric() {
+ let options = Options::new(5).break_words(false);
+ assert_eq!(wrap("foo(-)bar", &options), vec!["foo(-)bar"]);
+ }
+
+ #[test]
+ fn multiple_splits() {
+ assert_eq!(wrap("foo-bar-baz", 9), vec!["foo-bar-", "baz"]);
+ }
+
+ #[test]
+ fn forced_split() {
+ let options = Options::new(5).break_words(false);
+ assert_eq!(wrap("foobar-baz", &options), vec!["foobar-", "baz"]);
+ }
+
+ #[test]
+ fn multiple_unbroken_words_issue_193() {
+ let options = Options::new(3).break_words(false);
+ assert_eq!(
+ wrap("small large tiny", &options),
+ vec!["small", "large", "tiny"]
+ );
+ assert_eq!(
+ wrap("small large tiny", &options),
+ vec!["small", "large", "tiny"]
+ );
+ }
+
+ #[test]
+ fn very_narrow_lines_issue_193() {
+ let options = Options::new(1).break_words(false);
+ assert_eq!(wrap("fooo x y", &options), vec!["fooo", "x", "y"]);
+ assert_eq!(wrap("fooo x y", &options), vec!["fooo", "x", "y"]);
+ }
+
+ #[test]
+ fn simple_hyphens() {
+ let options = Options::new(8).word_splitter(WordSplitter::HyphenSplitter);
+ assert_eq!(wrap("foo bar-baz", &options), vec!["foo bar-", "baz"]);
+ }
+
+ #[test]
+ fn no_hyphenation() {
+ let options = Options::new(8).word_splitter(WordSplitter::NoHyphenation);
+ assert_eq!(wrap("foo bar-baz", &options), vec!["foo", "bar-baz"]);
+ }
+
+ #[test]
+ #[cfg(feature = "hyphenation")]
+ fn auto_hyphenation_double_hyphenation() {
+ let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
+ let options = Options::new(10);
+ assert_eq!(
+ wrap("Internationalization", &options),
+ vec!["Internatio", "nalization"]
+ );
+
+ let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary));
+ assert_eq!(
+ wrap("Internationalization", &options),
+ vec!["Interna-", "tionaliza-", "tion"]
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "hyphenation")]
+ fn auto_hyphenation_issue_158() {
+ let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
+ let options = Options::new(10);
+ assert_eq!(
+ wrap("participation is the key to success", &options),
+ vec!["participat", "ion is", "the key to", "success"]
+ );
+
+ let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary));
+ assert_eq!(
+ wrap("participation is the key to success", &options),
+ vec!["partici-", "pation is", "the key to", "success"]
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "hyphenation")]
+ fn split_len_hyphenation() {
+ // Test that hyphenation takes the width of the whitespace
+ // into account.
+ let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
+ let options = Options::new(15).word_splitter(WordSplitter::Hyphenation(dictionary));
+ assert_eq!(
+ wrap("garbage collection", &options),
+ vec!["garbage col-", "lection"]
+ );
+ }
+
+ #[test]
+ #[cfg(feature = "hyphenation")]
+ fn borrowed_lines() {
+ // Lines that end with an extra hyphen are owned, the final
+ // line is borrowed.
+ use std::borrow::Cow::{Borrowed, Owned};
+ let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
+ let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary));
+ let lines = wrap("Internationalization", &options);
+ assert_eq!(lines, vec!["Interna-", "tionaliza-", "tion"]);
+ if let Borrowed(s) = lines[0] {
+ assert!(false, "should not have been borrowed: {:?}", s);
+ }
+ if let Borrowed(s) = lines[1] {
+ assert!(false, "should not have been borrowed: {:?}", s);
+ }
+ if let Owned(ref s) = lines[2] {
+ assert!(false, "should not have been owned: {:?}", s);
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "hyphenation")]
+ fn auto_hyphenation_with_hyphen() {
+ let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
+ let options = Options::new(8).break_words(false);
+ assert_eq!(
+ wrap("over-caffinated", &options),
+ vec!["over-", "caffinated"]
+ );
+
+ let options = options.word_splitter(WordSplitter::Hyphenation(dictionary));
+ assert_eq!(
+ wrap("over-caffinated", &options),
+ vec!["over-", "caffi-", "nated"]
+ );
+ }
+
+ #[test]
+ fn break_words() {
+ assert_eq!(wrap("foobarbaz", 3), vec!["foo", "bar", "baz"]);
+ }
+
+ #[test]
+ fn break_words_wide_characters() {
+ // Even the poor man's version of `ch_width` counts these
+ // characters as wide.
+ let options = Options::new(5).word_separator(WordSeparator::AsciiSpace);
+ assert_eq!(wrap("Hello", options), vec!["He", "ll", "o"]);
+ }
+
+ #[test]
+ fn break_words_zero_width() {
+ assert_eq!(wrap("foobar", 0), vec!["f", "o", "o", "b", "a", "r"]);
+ }
+
+ #[test]
+ fn break_long_first_word() {
+ assert_eq!(wrap("testx y", 4), vec!["test", "x y"]);
+ }
+
+ #[test]
+ fn wrap_preserves_line_breaks_trims_whitespace() {
+ assert_eq!(wrap(" ", 80), vec![""]);
+ assert_eq!(wrap(" \n ", 80), vec!["", ""]);
+ assert_eq!(wrap(" \n \n \n ", 80), vec!["", "", "", ""]);
+ }
+
+ #[test]
+ fn wrap_colored_text() {
+ // The words are much longer than 6 bytes, but they remain
+ // intact after filling the text.
+ let green_hello = "\u{1b}[0m\u{1b}[32mHello\u{1b}[0m";
+ let blue_world = "\u{1b}[0m\u{1b}[34mWorld!\u{1b}[0m";
+ assert_eq!(
+ wrap(&format!("{} {}", green_hello, blue_world), 6),
+ vec![green_hello, blue_world],
+ );
+ }
+}
diff --git a/third_party/rust/textwrap/src/wrap_algorithms.rs b/third_party/rust/textwrap/src/wrap_algorithms.rs
new file mode 100644
index 0000000000..7737e08f99
--- /dev/null
+++ b/third_party/rust/textwrap/src/wrap_algorithms.rs
@@ -0,0 +1,413 @@
+//! Word wrapping algorithms.
+//!
+//! After a text has been broken into words (or [`Fragment`]s), one
+//! now has to decide how to break the fragments into lines. The
+//! simplest algorithm for this is implemented by
+//! [`wrap_first_fit()`]: it uses no look-ahead and simply adds
+//! fragments to the line as long as they fit. However, this can lead
+//! to poor line breaks if a large fragment almost-but-not-quite fits
+//! on a line. When that happens, the fragment is moved to the next
+//! line and it will leave behind a large gap.
+//!
+//! A more advanced algorithm, implemented by [`wrap_optimal_fit()`],
+//! will take this into account. The optimal-fit algorithm considers
+//! all possible line breaks and will attempt to minimize the gaps
+//! left behind by overly short lines.
+//!
+//! While both algorithms run in linear time, the first-fit algorithm
+//! is about 4 times faster than the optimal-fit algorithm.
+
+#[cfg(feature = "smawk")]
+mod optimal_fit;
+#[cfg(feature = "smawk")]
+pub use optimal_fit::{wrap_optimal_fit, OverflowError, Penalties};
+
+use crate::core::{Fragment, Word};
+
+/// Describes how to wrap words into lines.
+///
+/// The simplest approach is to wrap words one word at a time and
+/// accept the first way of wrapping which fit
+/// ([`WrapAlgorithm::FirstFit`]). If the `smawk` Cargo feature is
+/// enabled, a more complex algorithm is available which will look at
+/// an entire paragraph at a time in order to find optimal line breaks
+/// ([`WrapAlgorithm::OptimalFit`]).
+#[derive(Clone, Copy)]
+pub enum WrapAlgorithm {
+ /// Wrap words using a fast and simple algorithm.
+ ///
+ /// This algorithm uses no look-ahead when finding line breaks.
+ /// Implemented by [`wrap_first_fit()`], please see that function
+ /// for details and examples.
+ FirstFit,
+
+ /// Wrap words using an advanced algorithm with look-ahead.
+ ///
+ /// This wrapping algorithm considers the entire paragraph to find
+ /// optimal line breaks. When wrapping text, "penalties" are
+ /// assigned to line breaks based on the gaps left at the end of
+ /// lines. See [`Penalties`] for details.
+ ///
+ /// The underlying wrapping algorithm is implemented by
+ /// [`wrap_optimal_fit()`], please see that function for examples.
+ ///
+ /// **Note:** Only available when the `smawk` Cargo feature is
+ /// enabled.
+ #[cfg(feature = "smawk")]
+ OptimalFit(Penalties),
+
+ /// Custom wrapping function.
+ ///
+ /// Use this if you want to implement your own wrapping algorithm.
+ /// The function can freely decide how to turn a slice of
+ /// [`Word`]s into lines.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use textwrap::core::Word;
+ /// use textwrap::{wrap, Options, WrapAlgorithm};
+ ///
+ /// fn stair<'a, 'b>(words: &'b [Word<'a>], _: &'b [usize]) -> Vec<&'b [Word<'a>]> {
+ /// let mut lines = Vec::new();
+ /// let mut step = 1;
+ /// let mut start_idx = 0;
+ /// while start_idx + step <= words.len() {
+ /// lines.push(&words[start_idx .. start_idx+step]);
+ /// start_idx += step;
+ /// step += 1;
+ /// }
+ /// lines
+ /// }
+ ///
+ /// let options = Options::new(10).wrap_algorithm(WrapAlgorithm::Custom(stair));
+ /// assert_eq!(wrap("First, second, third, fourth, fifth, sixth", options),
+ /// vec!["First,",
+ /// "second, third,",
+ /// "fourth, fifth, sixth"]);
+ /// ```
+ Custom(for<'a, 'b> fn(words: &'b [Word<'a>], line_widths: &'b [usize]) -> Vec<&'b [Word<'a>]>),
+}
+
+impl PartialEq for WrapAlgorithm {
+ /// Compare two wrap algorithms.
+ ///
+ /// ```
+ /// use textwrap::WrapAlgorithm;
+ ///
+ /// assert_eq!(WrapAlgorithm::FirstFit, WrapAlgorithm::FirstFit);
+ /// #[cfg(feature = "smawk")] {
+ /// assert_eq!(WrapAlgorithm::new_optimal_fit(), WrapAlgorithm::new_optimal_fit());
+ /// }
+ /// ```
+ ///
+ /// Note that `WrapAlgorithm::Custom` values never compare equal:
+ ///
+ /// ```
+ /// use textwrap::WrapAlgorithm;
+ ///
+ /// assert_ne!(WrapAlgorithm::Custom(|words, line_widths| vec![words]),
+ /// WrapAlgorithm::Custom(|words, line_widths| vec![words]));
+ /// ```
+ fn eq(&self, other: &Self) -> bool {
+ match (self, other) {
+ (WrapAlgorithm::FirstFit, WrapAlgorithm::FirstFit) => true,
+ #[cfg(feature = "smawk")]
+ (WrapAlgorithm::OptimalFit(a), WrapAlgorithm::OptimalFit(b)) => a == b,
+ (_, _) => false,
+ }
+ }
+}
+
+impl std::fmt::Debug for WrapAlgorithm {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ WrapAlgorithm::FirstFit => f.write_str("FirstFit"),
+ #[cfg(feature = "smawk")]
+ WrapAlgorithm::OptimalFit(penalties) => write!(f, "OptimalFit({:?})", penalties),
+ WrapAlgorithm::Custom(_) => f.write_str("Custom(...)"),
+ }
+ }
+}
+
+impl WrapAlgorithm {
+ /// Create new wrap algorithm.
+ ///
+ /// The best wrapping algorithm is used by default, i.e.,
+ /// [`WrapAlgorithm::OptimalFit`] if available, otherwise
+ /// [`WrapAlgorithm::FirstFit`].
+ pub const fn new() -> Self {
+ #[cfg(not(feature = "smawk"))]
+ {
+ WrapAlgorithm::FirstFit
+ }
+
+ #[cfg(feature = "smawk")]
+ {
+ WrapAlgorithm::new_optimal_fit()
+ }
+ }
+
+ /// New [`WrapAlgorithm::OptimalFit`] with default penalties. This
+ /// works well for monospace text.
+ ///
+ /// **Note:** Only available when the `smawk` Cargo feature is
+ /// enabled.
+ #[cfg(feature = "smawk")]
+ pub const fn new_optimal_fit() -> Self {
+ WrapAlgorithm::OptimalFit(Penalties::new())
+ }
+
+ /// Wrap words according to line widths.
+ ///
+ /// The `line_widths` slice gives the target line width for each
+ /// line (the last slice element is repeated as necessary). This
+ /// can be used to implement hanging indentation.
+ #[inline]
+ pub fn wrap<'a, 'b>(
+ &self,
+ words: &'b [Word<'a>],
+ line_widths: &'b [usize],
+ ) -> Vec<&'b [Word<'a>]> {
+ // Every integer up to 2u64.pow(f64::MANTISSA_DIGITS) = 2**53
+ // = 9_007_199_254_740_992 can be represented without loss by
+ // a f64. Larger line widths will be rounded to the nearest
+ // representable number.
+ let f64_line_widths = line_widths.iter().map(|w| *w as f64).collect::<Vec<_>>();
+
+ match self {
+ WrapAlgorithm::FirstFit => wrap_first_fit(words, &f64_line_widths),
+
+ #[cfg(feature = "smawk")]
+ WrapAlgorithm::OptimalFit(penalties) => {
+ // The computation cannot overflow when the line
+ // widths are restricted to usize.
+ wrap_optimal_fit(words, &f64_line_widths, penalties).unwrap()
+ }
+
+ WrapAlgorithm::Custom(func) => func(words, line_widths),
+ }
+ }
+}
+
+impl Default for WrapAlgorithm {
+ fn default() -> Self {
+ WrapAlgorithm::new()
+ }
+}
+
+/// Wrap abstract fragments into lines with a first-fit algorithm.
+///
+/// The `line_widths` slice gives the target line width for each line
+/// (the last slice element is repeated as necessary). This can be
+/// used to implement hanging indentation.
+///
+/// The fragments must already have been split into the desired
+/// widths, this function will not (and cannot) attempt to split them
+/// further when arranging them into lines.
+///
+/// # First-Fit Algorithm
+///
+/// This implements a simple “greedy” algorithm: accumulate fragments
+/// one by one and when a fragment no longer fits, start a new line.
+/// There is no look-ahead, we simply take first fit of the fragments
+/// we find.
+///
+/// While fast and predictable, this algorithm can produce poor line
+/// breaks when a long fragment is moved to a new line, leaving behind
+/// a large gap:
+///
+/// ```
+/// use textwrap::core::Word;
+/// use textwrap::wrap_algorithms::wrap_first_fit;
+/// use textwrap::WordSeparator;
+///
+/// // Helper to convert wrapped lines to a Vec<String>.
+/// fn lines_to_strings(lines: Vec<&[Word<'_>]>) -> Vec<String> {
+/// lines.iter().map(|line| {
+/// line.iter().map(|word| &**word).collect::<Vec<_>>().join(" ")
+/// }).collect::<Vec<_>>()
+/// }
+///
+/// let text = "These few words will unfortunately not wrap nicely.";
+/// let words = WordSeparator::AsciiSpace.find_words(text).collect::<Vec<_>>();
+/// assert_eq!(lines_to_strings(wrap_first_fit(&words, &[15.0])),
+/// vec!["These few words",
+/// "will", // <-- short line
+/// "unfortunately",
+/// "not wrap",
+/// "nicely."]);
+///
+/// // We can avoid the short line if we look ahead:
+/// #[cfg(feature = "smawk")]
+/// use textwrap::wrap_algorithms::{wrap_optimal_fit, Penalties};
+/// #[cfg(feature = "smawk")]
+/// assert_eq!(lines_to_strings(wrap_optimal_fit(&words, &[15.0], &Penalties::new()).unwrap()),
+/// vec!["These few",
+/// "words will",
+/// "unfortunately",
+/// "not wrap",
+/// "nicely."]);
+/// ```
+///
+/// The [`wrap_optimal_fit()`] function was used above to get better
+/// line breaks. It uses an advanced algorithm which tries to avoid
+/// short lines. This function is about 4 times faster than
+/// [`wrap_optimal_fit()`].
+///
+/// # Examples
+///
+/// Imagine you're building a house site and you have a number of
+/// tasks you need to execute. Things like pour foundation, complete
+/// framing, install plumbing, electric cabling, install insulation.
+///
+/// The construction workers can only work during daytime, so they
+/// need to pack up everything at night. Because they need to secure
+/// their tools and move machines back to the garage, this process
+/// takes much more time than the time it would take them to simply
+/// switch to another task.
+///
+/// You would like to make a list of tasks to execute every day based
+/// on your estimates. You can model this with a program like this:
+///
+/// ```
+/// use textwrap::core::{Fragment, Word};
+/// use textwrap::wrap_algorithms::wrap_first_fit;
+///
+/// #[derive(Debug)]
+/// struct Task<'a> {
+/// name: &'a str,
+/// hours: f64, // Time needed to complete task.
+/// sweep: f64, // Time needed for a quick sweep after task during the day.
+/// cleanup: f64, // Time needed for full cleanup if day ends with this task.
+/// }
+///
+/// impl Fragment for Task<'_> {
+/// fn width(&self) -> f64 { self.hours }
+/// fn whitespace_width(&self) -> f64 { self.sweep }
+/// fn penalty_width(&self) -> f64 { self.cleanup }
+/// }
+///
+/// // The morning tasks
+/// let tasks = vec![
+/// Task { name: "Foundation", hours: 4.0, sweep: 2.0, cleanup: 3.0 },
+/// Task { name: "Framing", hours: 3.0, sweep: 1.0, cleanup: 2.0 },
+/// Task { name: "Plumbing", hours: 2.0, sweep: 2.0, cleanup: 2.0 },
+/// Task { name: "Electrical", hours: 2.0, sweep: 1.0, cleanup: 2.0 },
+/// Task { name: "Insulation", hours: 2.0, sweep: 1.0, cleanup: 2.0 },
+/// Task { name: "Drywall", hours: 3.0, sweep: 1.0, cleanup: 2.0 },
+/// Task { name: "Floors", hours: 3.0, sweep: 1.0, cleanup: 2.0 },
+/// Task { name: "Countertops", hours: 1.0, sweep: 1.0, cleanup: 2.0 },
+/// Task { name: "Bathrooms", hours: 2.0, sweep: 1.0, cleanup: 2.0 },
+/// ];
+///
+/// // Fill tasks into days, taking `day_length` into account. The
+/// // output shows the hours worked per day along with the names of
+/// // the tasks for that day.
+/// fn assign_days<'a>(tasks: &[Task<'a>], day_length: f64) -> Vec<(f64, Vec<&'a str>)> {
+/// let mut days = Vec::new();
+/// // Assign tasks to days. The assignment is a vector of slices,
+/// // with a slice per day.
+/// let assigned_days: Vec<&[Task<'a>]> = wrap_first_fit(&tasks, &[day_length]);
+/// for day in assigned_days.iter() {
+/// let last = day.last().unwrap();
+/// let work_hours: f64 = day.iter().map(|t| t.hours + t.sweep).sum();
+/// let names = day.iter().map(|t| t.name).collect::<Vec<_>>();
+/// days.push((work_hours - last.sweep + last.cleanup, names));
+/// }
+/// days
+/// }
+///
+/// // With a single crew working 8 hours a day:
+/// assert_eq!(
+/// assign_days(&tasks, 8.0),
+/// [
+/// (7.0, vec!["Foundation"]),
+/// (8.0, vec!["Framing", "Plumbing"]),
+/// (7.0, vec!["Electrical", "Insulation"]),
+/// (5.0, vec!["Drywall"]),
+/// (7.0, vec!["Floors", "Countertops"]),
+/// (4.0, vec!["Bathrooms"]),
+/// ]
+/// );
+///
+/// // With two crews working in shifts, 16 hours a day:
+/// assert_eq!(
+/// assign_days(&tasks, 16.0),
+/// [
+/// (14.0, vec!["Foundation", "Framing", "Plumbing"]),
+/// (15.0, vec!["Electrical", "Insulation", "Drywall", "Floors"]),
+/// (6.0, vec!["Countertops", "Bathrooms"]),
+/// ]
+/// );
+/// ```
+///
+/// Apologies to anyone who actually knows how to build a house and
+/// knows how long each step takes :-)
+pub fn wrap_first_fit<'a, T: Fragment>(
+ fragments: &'a [T],
+ line_widths: &[f64],
+) -> Vec<&'a [T]> {
+ // The final line width is used for all remaining lines.
+ let default_line_width = line_widths.last().copied().unwrap_or(0.0);
+ let mut lines = Vec::new();
+ let mut start = 0;
+ let mut width = 0.0;
+
+ for (idx, fragment) in fragments.iter().enumerate() {
+ let line_width = line_widths
+ .get(lines.len())
+ .copied()
+ .unwrap_or(default_line_width);
+ if width + fragment.width() + fragment.penalty_width() > line_width && idx > start {
+ lines.push(&fragments[start..idx]);
+ start = idx;
+ width = 0.0;
+ }
+ width += fragment.width() + fragment.whitespace_width();
+ }
+ lines.push(&fragments[start..]);
+ lines
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[derive(Debug, PartialEq)]
+ struct Word(f64);
+
+ #[rustfmt::skip]
+ impl Fragment for Word {
+ fn width(&self) -> f64 { self.0 }
+ fn whitespace_width(&self) -> f64 { 1.0 }
+ fn penalty_width(&self) -> f64 { 0.0 }
+ }
+
+ #[test]
+ fn wrap_string_longer_than_f64() {
+ let words = vec![
+ Word(1e307),
+ Word(2e307),
+ Word(3e307),
+ Word(4e307),
+ Word(5e307),
+ Word(6e307),
+ ];
+ // Wrap at just under f64::MAX (~19e307). The tiny
+ // whitespace_widths disappear because of loss of precision.
+ assert_eq!(
+ wrap_first_fit(&words, &[15e307]),
+ &[
+ vec![
+ Word(1e307),
+ Word(2e307),
+ Word(3e307),
+ Word(4e307),
+ Word(5e307)
+ ],
+ vec![Word(6e307)]
+ ]
+ );
+ }
+}
diff --git a/third_party/rust/textwrap/src/wrap_algorithms/optimal_fit.rs b/third_party/rust/textwrap/src/wrap_algorithms/optimal_fit.rs
new file mode 100644
index 0000000000..bdc0334539
--- /dev/null
+++ b/third_party/rust/textwrap/src/wrap_algorithms/optimal_fit.rs
@@ -0,0 +1,433 @@
+use std::cell::RefCell;
+
+use crate::core::Fragment;
+
+/// Penalties for
+/// [`WrapAlgorithm::OptimalFit`](crate::WrapAlgorithm::OptimalFit)
+/// and [`wrap_optimal_fit`].
+///
+/// This wrapping algorithm in [`wrap_optimal_fit`] considers the
+/// entire paragraph to find optimal line breaks. When wrapping text,
+/// "penalties" are assigned to line breaks based on the gaps left at
+/// the end of lines. The penalties are given by this struct, with
+/// [`Penalties::default`] assigning penalties that work well for
+/// monospace text.
+///
+/// If you are wrapping proportional text, you are advised to assign
+/// your own penalties according to your font size. See the individual
+/// penalties below for details.
+///
+/// **Note:** Only available when the `smawk` Cargo feature is
+/// enabled.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Penalties {
+ /// Per-line penalty. This is added for every line, which makes it
+ /// expensive to output more lines than the minimum required.
+ pub nline_penalty: usize,
+
+ /// Per-character cost for lines that overflow the target line width.
+ ///
+ /// With a default value of 50², every single character costs as
+ /// much as leaving a gap of 50 characters behind. This is because
+ /// we assign as cost of `gap * gap` to a short line. When
+ /// wrapping monospace text, we can overflow the line by 1
+ /// character in extreme cases:
+ ///
+ /// ```
+ /// use textwrap::core::Word;
+ /// use textwrap::wrap_algorithms::{wrap_optimal_fit, Penalties};
+ ///
+ /// let short = "foo ";
+ /// let long = "x".repeat(50);
+ /// let length = (short.len() + long.len()) as f64;
+ /// let fragments = vec![Word::from(short), Word::from(&long)];
+ /// let penalties = Penalties::new();
+ ///
+ /// // Perfect fit, both words are on a single line with no overflow.
+ /// let wrapped = wrap_optimal_fit(&fragments, &[length], &penalties).unwrap();
+ /// assert_eq!(wrapped, vec![&[Word::from(short), Word::from(&long)]]);
+ ///
+ /// // The words no longer fit, yet we get a single line back. While
+ /// // the cost of overflow (`1 * 2500`) is the same as the cost of the
+ /// // gap (`50 * 50 = 2500`), the tie is broken by `nline_penalty`
+ /// // which makes it cheaper to overflow than to use two lines.
+ /// let wrapped = wrap_optimal_fit(&fragments, &[length - 1.0], &penalties).unwrap();
+ /// assert_eq!(wrapped, vec![&[Word::from(short), Word::from(&long)]]);
+ ///
+ /// // The cost of overflow would be 2 * 2500, whereas the cost of
+ /// // the gap is only `49 * 49 + nline_penalty = 2401 + 1000 =
+ /// // 3401`. We therefore get two lines.
+ /// let wrapped = wrap_optimal_fit(&fragments, &[length - 2.0], &penalties).unwrap();
+ /// assert_eq!(wrapped, vec![&[Word::from(short)],
+ /// &[Word::from(&long)]]);
+ /// ```
+ ///
+ /// This only happens if the overflowing word is 50 characters
+ /// long _and_ if the word overflows the line by exactly one
+ /// character. If it overflows by more than one character, the
+ /// overflow penalty will quickly outgrow the cost of the gap, as
+ /// seen above.
+ pub overflow_penalty: usize,
+
+ /// When should the a single word on the last line be considered
+ /// "too short"?
+ ///
+ /// If the last line of the text consist of a single word and if
+ /// this word is shorter than `1 / short_last_line_fraction` of
+ /// the line width, then the final line will be considered "short"
+ /// and `short_last_line_penalty` is added as an extra penalty.
+ ///
+ /// The effect of this is to avoid a final line consisting of a
+ /// single small word. For example, with a
+ /// `short_last_line_penalty` of 25 (the default), a gap of up to
+ /// 5 columns will be seen as more desirable than having a final
+ /// short line.
+ ///
+ /// ## Examples
+ ///
+ /// ```
+ /// use textwrap::{wrap, wrap_algorithms, Options, WrapAlgorithm};
+ ///
+ /// let text = "This is a demo of the short last line penalty.";
+ ///
+ /// // The first-fit algorithm leaves a single short word on the last line:
+ /// assert_eq!(wrap(text, Options::new(37).wrap_algorithm(WrapAlgorithm::FirstFit)),
+ /// vec!["This is a demo of the short last line",
+ /// "penalty."]);
+ ///
+ /// #[cfg(feature = "smawk")] {
+ /// let mut penalties = wrap_algorithms::Penalties::new();
+ ///
+ /// // Since "penalty." is shorter than 25% of the line width, the
+ /// // optimal-fit algorithm adds a penalty of 25. This is enough
+ /// // to move "line " down:
+ /// assert_eq!(wrap(text, Options::new(37).wrap_algorithm(WrapAlgorithm::OptimalFit(penalties))),
+ /// vec!["This is a demo of the short last",
+ /// "line penalty."]);
+ ///
+ /// // We can change the meaning of "short" lines. Here, only words
+ /// // shorter than 1/10th of the line width will be considered short:
+ /// penalties.short_last_line_fraction = 10;
+ /// assert_eq!(wrap(text, Options::new(37).wrap_algorithm(WrapAlgorithm::OptimalFit(penalties))),
+ /// vec!["This is a demo of the short last line",
+ /// "penalty."]);
+ ///
+ /// // If desired, the penalty can also be disabled:
+ /// penalties.short_last_line_fraction = 4;
+ /// penalties.short_last_line_penalty = 0;
+ /// assert_eq!(wrap(text, Options::new(37).wrap_algorithm(WrapAlgorithm::OptimalFit(penalties))),
+ /// vec!["This is a demo of the short last line",
+ /// "penalty."]);
+ /// }
+ /// ```
+ pub short_last_line_fraction: usize,
+
+ /// Penalty for a last line with a single short word.
+ ///
+ /// Set this to zero if you do not want to penalize short last lines.
+ pub short_last_line_penalty: usize,
+
+ /// Penalty for lines ending with a hyphen.
+ pub hyphen_penalty: usize,
+}
+
+impl Penalties {
+ /// Default penalties for monospace text.
+ ///
+ /// The penalties here work well for monospace text. This is
+ /// because they expect the gaps at the end of lines to be roughly
+ /// in the range `0..100`. If the gaps are larger, the
+ /// `overflow_penalty` and `hyphen_penalty` become insignificant.
+ pub const fn new() -> Self {
+ Penalties {
+ nline_penalty: 1000,
+ overflow_penalty: 50 * 50,
+ short_last_line_fraction: 4,
+ short_last_line_penalty: 25,
+ hyphen_penalty: 25,
+ }
+ }
+}
+
+impl Default for Penalties {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// Cache for line numbers. This is necessary to avoid a O(n**2)
+/// behavior when computing line numbers in [`wrap_optimal_fit`].
+struct LineNumbers {
+ line_numbers: RefCell<Vec<usize>>,
+}
+
+impl LineNumbers {
+ fn new(size: usize) -> Self {
+ let mut line_numbers = Vec::with_capacity(size);
+ line_numbers.push(0);
+ LineNumbers {
+ line_numbers: RefCell::new(line_numbers),
+ }
+ }
+
+ fn get<T>(&self, i: usize, minima: &[(usize, T)]) -> usize {
+ while self.line_numbers.borrow_mut().len() < i + 1 {
+ let pos = self.line_numbers.borrow().len();
+ let line_number = 1 + self.get(minima[pos].0, minima);
+ self.line_numbers.borrow_mut().push(line_number);
+ }
+
+ self.line_numbers.borrow()[i]
+ }
+}
+
+/// Overflow error during the [`wrap_optimal_fit`] computation.
+#[derive(Debug, PartialEq, Eq)]
+pub struct OverflowError;
+
+impl std::fmt::Display for OverflowError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "wrap_optimal_fit cost computation overflowed")
+ }
+}
+
+impl std::error::Error for OverflowError {}
+
+/// Wrap abstract fragments into lines with an optimal-fit algorithm.
+///
+/// The `line_widths` slice gives the target line width for each line
+/// (the last slice element is repeated as necessary). This can be
+/// used to implement hanging indentation.
+///
+/// The fragments must already have been split into the desired
+/// widths, this function will not (and cannot) attempt to split them
+/// further when arranging them into lines.
+///
+/// # Optimal-Fit Algorithm
+///
+/// The algorithm considers all possible break points and picks the
+/// breaks which minimizes the gaps at the end of each line. More
+/// precisely, the algorithm assigns a cost or penalty to each break
+/// point, determined by `cost = gap * gap` where `gap = target_width -
+/// line_width`. Shorter lines are thus penalized more heavily since
+/// they leave behind a larger gap.
+///
+/// We can illustrate this with the text “To be, or not to be: that is
+/// the question”. We will be wrapping it in a narrow column with room
+/// for only 10 characters. The [greedy
+/// algorithm](super::wrap_first_fit) will produce these lines, each
+/// annotated with the corresponding penalty:
+///
+/// ```text
+/// "To be, or" 1² = 1
+/// "not to be:" 0² = 0
+/// "that is" 3² = 9
+/// "the" 7² = 49
+/// "question" 2² = 4
+/// ```
+///
+/// We see that line four with “the” leaves a gap of 7 columns, which
+/// gives it a penalty of 49. The sum of the penalties is 63.
+///
+/// There are 10 words, which means that there are `2_u32.pow(9)` or
+/// 512 different ways to typeset it. We can compute
+/// the sum of the penalties for each possible line break and search
+/// for the one with the lowest sum:
+///
+/// ```text
+/// "To be," 4² = 16
+/// "or not to" 1² = 1
+/// "be: that" 2² = 4
+/// "is the" 4² = 16
+/// "question" 2² = 4
+/// ```
+///
+/// The sum of the penalties is 41, which is better than what the
+/// greedy algorithm produced.
+///
+/// Searching through all possible combinations would normally be
+/// prohibitively slow. However, it turns out that the problem can be
+/// formulated as the task of finding column minima in a cost matrix.
+/// This matrix has a special form (totally monotone) which lets us
+/// use a [linear-time algorithm called
+/// SMAWK](https://lib.rs/crates/smawk) to find the optimal break
+/// points.
+///
+/// This means that the time complexity remains O(_n_) where _n_ is
+/// the number of words. Compared to
+/// [`wrap_first_fit()`](super::wrap_first_fit), this function is
+/// about 4 times slower.
+///
+/// The optimization of per-line costs over the entire paragraph is
+/// inspired by the line breaking algorithm used in TeX, as described
+/// in the 1981 article [_Breaking Paragraphs into
+/// Lines_](http://www.eprg.org/G53DOC/pdfs/knuth-plass-breaking.pdf)
+/// by Knuth and Plass. The implementation here is based on [Python
+/// code by David
+/// Eppstein](https://github.com/jfinkels/PADS/blob/master/pads/wrap.py).
+///
+/// # Errors
+///
+/// In case of an overflow during the cost computation, an `Err` is
+/// returned. Overflows happens when fragments or lines have infinite
+/// widths (`f64::INFINITY`) or if the widths are so large that the
+/// gaps at the end of lines have sizes larger than `f64::MAX.sqrt()`
+/// (approximately 1e154):
+///
+/// ```
+/// use textwrap::core::Fragment;
+/// use textwrap::wrap_algorithms::{wrap_optimal_fit, OverflowError, Penalties};
+///
+/// #[derive(Debug, PartialEq)]
+/// struct Word(f64);
+///
+/// impl Fragment for Word {
+/// fn width(&self) -> f64 { self.0 }
+/// fn whitespace_width(&self) -> f64 { 1.0 }
+/// fn penalty_width(&self) -> f64 { 0.0 }
+/// }
+///
+/// // Wrapping overflows because 1e155 * 1e155 = 1e310, which is
+/// // larger than f64::MAX:
+/// assert_eq!(wrap_optimal_fit(&[Word(0.0), Word(0.0)], &[1e155], &Penalties::default()),
+/// Err(OverflowError));
+/// ```
+///
+/// When using fragment widths and line widths which fit inside an
+/// `u64`, overflows cannot happen. This means that fragments derived
+/// from a `&str` cannot cause overflows.
+///
+/// **Note:** Only available when the `smawk` Cargo feature is
+/// enabled.
+pub fn wrap_optimal_fit<'a, 'b, T: Fragment>(
+ fragments: &'a [T],
+ line_widths: &'b [f64],
+ penalties: &'b Penalties,
+) -> Result<Vec<&'a [T]>, OverflowError> {
+ // The final line width is used for all remaining lines.
+ let default_line_width = line_widths.last().copied().unwrap_or(0.0);
+ let mut widths = Vec::with_capacity(fragments.len() + 1);
+ let mut width = 0.0;
+ widths.push(width);
+ for fragment in fragments {
+ width += fragment.width() + fragment.whitespace_width();
+ widths.push(width);
+ }
+
+ let line_numbers = LineNumbers::new(fragments.len());
+
+ let minima = smawk::online_column_minima(0.0, widths.len(), |minima, i, j| {
+ // Line number for fragment `i`.
+ let line_number = line_numbers.get(i, minima);
+ let line_width = line_widths
+ .get(line_number)
+ .copied()
+ .unwrap_or(default_line_width);
+ let target_width = line_width.max(1.0);
+
+ // Compute the width of a line spanning fragments[i..j] in
+ // constant time. We need to adjust widths[j] by subtracting
+ // the whitespace of fragment[j-1] and then add the penalty.
+ let line_width = widths[j] - widths[i] - fragments[j - 1].whitespace_width()
+ + fragments[j - 1].penalty_width();
+
+ // We compute cost of the line containing fragments[i..j]. We
+ // start with values[i].1, which is the optimal cost for
+ // breaking before fragments[i].
+ //
+ // First, every extra line cost NLINE_PENALTY.
+ let mut cost = minima[i].1 + penalties.nline_penalty as f64;
+
+ // Next, we add a penalty depending on the line length.
+ if line_width > target_width {
+ // Lines that overflow get a hefty penalty.
+ let overflow = line_width - target_width;
+ cost += overflow * penalties.overflow_penalty as f64;
+ } else if j < fragments.len() {
+ // Other lines (except for the last line) get a milder
+ // penalty which depend on the size of the gap.
+ let gap = target_width - line_width;
+ cost += gap * gap;
+ } else if i + 1 == j
+ && line_width < target_width / penalties.short_last_line_fraction as f64
+ {
+ // The last line can have any size gap, but we do add a
+ // penalty if the line is very short (typically because it
+ // contains just a single word).
+ cost += penalties.short_last_line_penalty as f64;
+ }
+
+ // Finally, we discourage hyphens.
+ if fragments[j - 1].penalty_width() > 0.0 {
+ // TODO: this should use a penalty value from the fragment
+ // instead.
+ cost += penalties.hyphen_penalty as f64;
+ }
+
+ cost
+ });
+
+ for (_, cost) in &minima {
+ if cost.is_infinite() {
+ return Err(OverflowError);
+ }
+ }
+
+ let mut lines = Vec::with_capacity(line_numbers.get(fragments.len(), &minima));
+ let mut pos = fragments.len();
+ loop {
+ let prev = minima[pos].0;
+ lines.push(&fragments[prev..pos]);
+ pos = prev;
+ if pos == 0 {
+ break;
+ }
+ }
+
+ lines.reverse();
+ Ok(lines)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[derive(Debug, PartialEq)]
+ struct Word(f64);
+
+ #[rustfmt::skip]
+ impl Fragment for Word {
+ fn width(&self) -> f64 { self.0 }
+ fn whitespace_width(&self) -> f64 { 1.0 }
+ fn penalty_width(&self) -> f64 { 0.0 }
+ }
+
+ #[test]
+ fn wrap_fragments_with_infinite_widths() {
+ let words = vec![Word(f64::INFINITY)];
+ assert_eq!(
+ wrap_optimal_fit(&words, &[0.0], &Penalties::default()),
+ Err(OverflowError)
+ );
+ }
+
+ #[test]
+ fn wrap_fragments_with_huge_widths() {
+ let words = vec![Word(1e200), Word(1e250), Word(1e300)];
+ assert_eq!(
+ wrap_optimal_fit(&words, &[1e300], &Penalties::default()),
+ Err(OverflowError)
+ );
+ }
+
+ #[test]
+ fn wrap_fragments_with_large_widths() {
+ // The gaps will be of the sizes between 1e25 and 1e75. This
+ // makes the `gap * gap` cost fit comfortably in a f64.
+ let words = vec![Word(1e25), Word(1e50), Word(1e75)];
+ assert_eq!(
+ wrap_optimal_fit(&words, &[1e100], &Penalties::default()),
+ Ok(vec![&vec![Word(1e25), Word(1e50), Word(1e75)][..]])
+ );
+ }
+}
diff --git a/third_party/rust/textwrap/tests/indent.rs b/third_party/rust/textwrap/tests/indent.rs
new file mode 100644
index 0000000000..9dd5ad2642
--- /dev/null
+++ b/third_party/rust/textwrap/tests/indent.rs
@@ -0,0 +1,88 @@
+/// tests cases ported over from python standard library
+use textwrap::{dedent, indent};
+
+const ROUNDTRIP_CASES: [&str; 3] = [
+ // basic test case
+ "Hi.\nThis is a test.\nTesting.",
+ // include a blank line
+ "Hi.\nThis is a test.\n\nTesting.",
+ // include leading and trailing blank lines
+ "\nHi.\nThis is a test.\nTesting.\n",
+];
+
+const WINDOWS_CASES: [&str; 2] = [
+ // use windows line endings
+ "Hi.\r\nThis is a test.\r\nTesting.",
+ // pathological case
+ "Hi.\r\nThis is a test.\n\r\nTesting.\r\n\n",
+];
+
+#[test]
+fn test_indent_nomargin_default() {
+ // indent should do nothing if 'prefix' is empty.
+ for text in ROUNDTRIP_CASES.iter() {
+ assert_eq!(&indent(text, ""), text);
+ }
+ for text in WINDOWS_CASES.iter() {
+ assert_eq!(&indent(text, ""), text);
+ }
+}
+
+#[test]
+fn test_roundtrip_spaces() {
+ // A whitespace prefix should roundtrip with dedent
+ for text in ROUNDTRIP_CASES.iter() {
+ assert_eq!(&dedent(&indent(text, " ")), text);
+ }
+}
+
+#[test]
+fn test_roundtrip_tabs() {
+ // A whitespace prefix should roundtrip with dedent
+ for text in ROUNDTRIP_CASES.iter() {
+ assert_eq!(&dedent(&indent(text, "\t\t")), text);
+ }
+}
+
+#[test]
+fn test_roundtrip_mixed() {
+ // A whitespace prefix should roundtrip with dedent
+ for text in ROUNDTRIP_CASES.iter() {
+ assert_eq!(&dedent(&indent(text, " \t \t ")), text);
+ }
+}
+
+#[test]
+fn test_indent_default() {
+ // Test default indenting of lines that are not whitespace only
+ let prefix = " ";
+ let expected = [
+ // Basic test case
+ " Hi.\n This is a test.\n Testing.",
+ // Include a blank line
+ " Hi.\n This is a test.\n\n Testing.",
+ // Include leading and trailing blank lines
+ "\n Hi.\n This is a test.\n Testing.\n",
+ ];
+ for (text, expect) in ROUNDTRIP_CASES.iter().zip(expected.iter()) {
+ assert_eq!(&indent(text, prefix), expect)
+ }
+ let expected = [
+ // Use Windows line endings
+ " Hi.\r\n This is a test.\r\n Testing.",
+ // Pathological case
+ " Hi.\r\n This is a test.\n\r\n Testing.\r\n\n",
+ ];
+ for (text, expect) in WINDOWS_CASES.iter().zip(expected.iter()) {
+ assert_eq!(&indent(text, prefix), expect)
+ }
+}
+
+#[test]
+fn indented_text_should_have_the_same_number_of_lines_as_the_original_text() {
+ let texts = ["foo\nbar", "foo\nbar\n", "foo\nbar\nbaz"];
+ for original in texts.iter() {
+ let indented = indent(original, "");
+ assert_eq!(&indented, original);
+ }
+}
diff --git a/third_party/rust/textwrap/tests/version-numbers.rs b/third_party/rust/textwrap/tests/version-numbers.rs
new file mode 100644
index 0000000000..3f429b187a
--- /dev/null
+++ b/third_party/rust/textwrap/tests/version-numbers.rs
@@ -0,0 +1,22 @@
+#[test]
+fn test_readme_deps() {
+ version_sync::assert_markdown_deps_updated!("README.md");
+}
+
+#[test]
+fn test_changelog() {
+ version_sync::assert_contains_regex!(
+ "CHANGELOG.md",
+ r"^## Version {version} \(20\d\d-\d\d-\d\d\)"
+ );
+}
+
+#[test]
+fn test_html_root_url() {
+ version_sync::assert_html_root_url_updated!("src/lib.rs");
+}
+
+#[test]
+fn test_dependency_graph() {
+ version_sync::assert_contains_regex!("src/lib.rs", "master/images/textwrap-{version}.svg");
+}
diff --git a/third_party/rust/unicode-linebreak/.cargo-checksum.json b/third_party/rust/unicode-linebreak/.cargo-checksum.json
new file mode 100644
index 0000000000..2b4a10002a
--- /dev/null
+++ b/third_party/rust/unicode-linebreak/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"7f8e0b5d66e9c5621c5fb57d4b16b801a24061e37ee337e06f8a004e6895a8dc","LICENSE":"c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4","src/lib.rs":"ef4f143f99e9ef4f17dab02e1b80ed3ac484ae58e0bb64164920720e0ca9425f","src/shared.rs":"e0c98ea10f78491f567e2a18bcb41b3f0ae01ce587d8b3a16ce0dc1097919109","src/tables.rs":"1821b437dfb31164ce8180af3937ca42270f1edf963a2d2e41cbaaf999553c94"},"package":"3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"} \ No newline at end of file
diff --git a/third_party/rust/unicode-linebreak/Cargo.toml b/third_party/rust/unicode-linebreak/Cargo.toml
new file mode 100644
index 0000000000..857c779d67
--- /dev/null
+++ b/third_party/rust/unicode-linebreak/Cargo.toml
@@ -0,0 +1,32 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.56"
+name = "unicode-linebreak"
+version = "0.1.5"
+authors = ["Axel Forsman <axelsfor@gmail.com>"]
+include = [
+ "src/**/*",
+ "LICENSE",
+]
+description = "Implementation of the Unicode Line Breaking Algorithm"
+homepage = "https://github.com/axelf4/unicode-linebreak"
+readme = "README.md"
+keywords = [
+ "unicode",
+ "text",
+ "layout",
+]
+categories = ["internationalization"]
+license = "Apache-2.0"
+repository = "https://github.com/axelf4/unicode-linebreak"
diff --git a/third_party/rust/unicode-linebreak/LICENSE b/third_party/rust/unicode-linebreak/LICENSE
new file mode 100644
index 0000000000..261eeb9e9f
--- /dev/null
+++ b/third_party/rust/unicode-linebreak/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/third_party/rust/unicode-linebreak/src/lib.rs b/third_party/rust/unicode-linebreak/src/lib.rs
new file mode 100644
index 0000000000..ca473d8494
--- /dev/null
+++ b/third_party/rust/unicode-linebreak/src/lib.rs
@@ -0,0 +1,160 @@
+//! Implementation of the Line Breaking Algorithm described in [Unicode Standard Annex #14][UAX14].
+//!
+//! Given an input text, locates "line break opportunities", or positions appropriate for wrapping
+//! lines when displaying text.
+//!
+//! # Example
+//!
+//! ```
+//! use unicode_linebreak::{linebreaks, BreakOpportunity::{Mandatory, Allowed}};
+//!
+//! let text = "a b \nc";
+//! assert!(linebreaks(text).eq([
+//! (2, Allowed), // May break after first space
+//! (5, Mandatory), // Must break after line feed
+//! (6, Mandatory) // Must break at end of text, so that there always is at least one LB
+//! ]));
+//! ```
+//!
+//! [UAX14]: https://www.unicode.org/reports/tr14/
+
+#![no_std]
+#![deny(missing_docs, missing_debug_implementations)]
+
+use core::iter::once;
+
+/// The [Unicode version](https://www.unicode.org/versions/) conformed to.
+pub const UNICODE_VERSION: (u8, u8, u8) = (15, 0, 0);
+
+include!("shared.rs");
+include!("tables.rs");
+
+/// Returns the line break property of the specified code point.
+///
+/// # Examples
+///
+/// ```
+/// use unicode_linebreak::{BreakClass, break_property};
+/// assert_eq!(break_property(0x2CF3), BreakClass::Alphabetic);
+/// ```
+#[inline(always)]
+pub fn break_property(codepoint: u32) -> BreakClass {
+ const BMP_INDEX_LENGTH: u32 = BMP_LIMIT >> BMP_SHIFT;
+ const OMITTED_BMP_INDEX_1_LENGTH: u32 = BMP_LIMIT >> SHIFT_1;
+
+ let data_pos = if codepoint < BMP_LIMIT {
+ let i = codepoint >> BMP_SHIFT;
+ BREAK_PROP_TRIE_INDEX[i as usize] + (codepoint & (BMP_DATA_BLOCK_LENGTH - 1)) as u16
+ } else if codepoint < BREAK_PROP_TRIE_HIGH_START {
+ let i1 = codepoint >> SHIFT_1;
+ let i2 = BREAK_PROP_TRIE_INDEX
+ [(i1 + BMP_INDEX_LENGTH - OMITTED_BMP_INDEX_1_LENGTH) as usize]
+ + ((codepoint >> SHIFT_2) & (INDEX_2_BLOCK_LENGTH - 1)) as u16;
+ let i3_block = BREAK_PROP_TRIE_INDEX[i2 as usize];
+ let i3_pos = ((codepoint >> SHIFT_3) & (INDEX_3_BLOCK_LENGTH - 1)) as u16;
+
+ debug_assert!(i3_block & 0x8000 == 0, "18-bit indices are unexpected");
+ let data_block = BREAK_PROP_TRIE_INDEX[(i3_block + i3_pos) as usize];
+ data_block + (codepoint & (SMALL_DATA_BLOCK_LENGTH - 1)) as u16
+ } else {
+ return XX;
+ };
+ BREAK_PROP_TRIE_DATA[data_pos as usize]
+}
+
+/// Break opportunity type.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum BreakOpportunity {
+ /// A line must break at this spot.
+ Mandatory,
+ /// A line is allowed to end at this spot.
+ Allowed,
+}
+
+/// Returns an iterator over line break opportunities in the specified string.
+///
+/// Break opportunities are given as tuples of the byte index of the character succeeding the break
+/// and the type.
+///
+/// Uses the default Line Breaking Algorithm with the tailoring that Complex-Context Dependent
+/// (SA) characters get resolved to Ordinary Alphabetic and Symbol Characters (AL) regardless of
+/// General_Category.
+///
+/// # Examples
+///
+/// ```
+/// use unicode_linebreak::{linebreaks, BreakOpportunity::{Mandatory, Allowed}};
+/// assert!(linebreaks("Hello world!").eq(vec![(6, Allowed), (12, Mandatory)]));
+/// ```
+pub fn linebreaks(s: &str) -> impl Iterator<Item = (usize, BreakOpportunity)> + Clone + '_ {
+ use BreakOpportunity::{Allowed, Mandatory};
+
+ s.char_indices()
+ .map(|(i, c)| (i, break_property(c as u32) as u8))
+ .chain(once((s.len(), eot)))
+ .scan((sot, false), |state, (i, cls)| {
+ // ZWJ is handled outside the table to reduce its size
+ let val = PAIR_TABLE[state.0 as usize][cls as usize];
+ let is_mandatory = val & MANDATORY_BREAK_BIT != 0;
+ let is_break = val & ALLOWED_BREAK_BIT != 0 && (!state.1 || is_mandatory);
+ *state = (
+ val & !(ALLOWED_BREAK_BIT | MANDATORY_BREAK_BIT),
+ cls == BreakClass::ZeroWidthJoiner as u8,
+ );
+
+ Some((i, is_break, is_mandatory))
+ })
+ .filter_map(|(i, is_break, is_mandatory)| {
+ if is_break {
+ Some((i, if is_mandatory { Mandatory } else { Allowed }))
+ } else {
+ None
+ }
+ })
+}
+
+/// Divides the string at the last index where further breaks do not depend on prior context.
+///
+/// The trivial index at `eot` is excluded.
+///
+/// A common optimization is to determine only the nearest line break opportunity before the first
+/// character that would cause the line to become overfull, requiring backward traversal, of which
+/// there are two approaches:
+///
+/// * Cache breaks from forward traversals
+/// * Step backward and with `split_at_safe` find a pos to safely search forward from, repeatedly
+///
+/// # Examples
+///
+/// ```
+/// use unicode_linebreak::{linebreaks, split_at_safe};
+/// let s = "Not allowed to break within em dashes: — —";
+/// let (prev, safe) = split_at_safe(s);
+/// let n = prev.len();
+/// assert!(linebreaks(safe).eq(linebreaks(s).filter_map(|(i, x)| i.checked_sub(n).map(|i| (i, x)))));
+/// ```
+pub fn split_at_safe(s: &str) -> (&str, &str) {
+ let mut chars = s.char_indices().rev().scan(None, |state, (i, c)| {
+ let cls = break_property(c as u32);
+ let is_safe_pair = state
+ .replace(cls)
+ .map_or(false, |prev| is_safe_pair(cls, prev)); // Reversed since iterating backwards
+ Some((i, is_safe_pair))
+ });
+ chars.find(|&(_, is_safe_pair)| is_safe_pair);
+ // Include preceding char for `linebreaks` to pick up break before match (disallowed after sot)
+ s.split_at(chars.next().map_or(0, |(i, _)| i))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn it_works() {
+ assert_eq!(break_property(0xA), BreakClass::LineFeed);
+ assert_eq!(break_property(0xDB80), BreakClass::Surrogate);
+ assert_eq!(break_property(0xe01ef), BreakClass::CombiningMark);
+ assert_eq!(break_property(0x10ffff), BreakClass::Unknown);
+ }
+}
diff --git a/third_party/rust/unicode-linebreak/src/shared.rs b/third_party/rust/unicode-linebreak/src/shared.rs
new file mode 100644
index 0000000000..c73819f069
--- /dev/null
+++ b/third_party/rust/unicode-linebreak/src/shared.rs
@@ -0,0 +1,134 @@
+/// Unicode line breaking class.
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[repr(u8)]
+pub enum BreakClass {
+ // Non-tailorable
+ /// Cause a line break (after)
+ Mandatory,
+ /// Cause a line break (after), except between CR and LF
+ CarriageReturn,
+ /// Cause a line break (after)
+ LineFeed,
+ /// Prohibit a line break between the character and the preceding character
+ CombiningMark,
+ /// Cause a line break (after)
+ NextLine,
+ /// Do not occur in well-formed text
+ Surrogate,
+ /// Prohibit line breaks before and after
+ WordJoiner,
+ /// Provide a break opportunity
+ ZeroWidthSpace,
+ /// Prohibit line breaks before and after
+ NonBreakingGlue,
+ /// Enable indirect line breaks
+ Space,
+ /// Prohibit line breaks within joiner sequences
+ ZeroWidthJoiner,
+ // Break opportunities
+ /// Provide a line break opportunity before and after the character
+ BeforeAndAfter,
+ /// Generally provide a line break opportunity after the character
+ After,
+ /// Generally provide a line break opportunity before the character
+ Before,
+ /// Provide a line break opportunity after the character, except in numeric context
+ Hyphen,
+ /// Provide a line break opportunity contingent on additional information
+ Contingent,
+ // Characters prohibiting certain breaks
+ /// Prohibit line breaks before
+ ClosePunctuation,
+ /// Prohibit line breaks before
+ CloseParenthesis,
+ /// Prohibit line breaks before
+ Exclamation,
+ /// Allow only indirect line breaks between pairs
+ Inseparable,
+ /// Allow only indirect line breaks before
+ NonStarter,
+ /// Prohibit line breaks after
+ OpenPunctuation,
+ /// Act like they are both opening and closing
+ Quotation,
+ // Numeric context
+ /// Prevent breaks after any and before numeric
+ InfixSeparator,
+ /// Form numeric expressions for line breaking purposes
+ Numeric,
+ /// Do not break following a numeric expression
+ Postfix,
+ /// Do not break in front of a numeric expression
+ Prefix,
+ /// Prevent a break before, and allow a break after
+ Symbol,
+ // Other characters
+ /// Act like AL when the resolved EAW is N; otherwise, act as ID
+ Ambiguous,
+ /// Are alphabetic characters or symbols that are used with alphabetic characters
+ Alphabetic,
+ /// Treat as NS or ID for strict or normal breaking.
+ ConditionalJapaneseStarter,
+ /// Do not break from following Emoji Modifier
+ EmojiBase,
+ /// Do not break from preceding Emoji Base
+ EmojiModifier,
+ /// Form Korean syllable blocks
+ HangulLvSyllable,
+ /// Form Korean syllable blocks
+ HangulLvtSyllable,
+ /// Do not break around a following hyphen; otherwise act as Alphabetic
+ HebrewLetter,
+ /// Break before or after, except in some numeric context
+ Ideographic,
+ /// Form Korean syllable blocks
+ HangulLJamo,
+ /// Form Korean syllable blocks
+ HangulVJamo,
+ /// Form Korean syllable blocks
+ HangulTJamo,
+ /// Keep pairs together. For pairs, break before and after other classes
+ RegionalIndicator,
+ /// Provide a line break opportunity contingent on additional, language-specific context analysis
+ ComplexContext,
+ /// Have as yet unknown line breaking behavior or unassigned code positions
+ Unknown,
+}
+
+use BreakClass::{
+ After as BA, Alphabetic as AL, Ambiguous as AI, Before as BB, BeforeAndAfter as B2,
+ CarriageReturn as CR, CloseParenthesis as CP, ClosePunctuation as CL, CombiningMark as CM,
+ ComplexContext as SA, ConditionalJapaneseStarter as CJ, Contingent as CB, EmojiBase as EB,
+ EmojiModifier as EM, Exclamation as EX, HangulLJamo as JL, HangulLvSyllable as H2,
+ HangulLvtSyllable as H3, HangulTJamo as JT, HangulVJamo as JV, HebrewLetter as HL,
+ Hyphen as HY, Ideographic as ID, InfixSeparator as IS, Inseparable as IN, LineFeed as LF,
+ Mandatory as BK, NextLine as NL, NonBreakingGlue as GL, NonStarter as NS, Numeric as NU,
+ OpenPunctuation as OP, Postfix as PO, Prefix as PR, Quotation as QU, RegionalIndicator as RI,
+ Space as SP, Surrogate as SG, Symbol as SY, Unknown as XX, WordJoiner as WJ,
+ ZeroWidthJoiner as ZWJ, ZeroWidthSpace as ZW,
+};
+
+/// Ceiling for code points in the Basic Multilingual Place (BMP).
+const BMP_LIMIT: u32 = 0x10000;
+
+/// Shift size for getting index-3 table offset.
+const SHIFT_3: u32 = 4;
+/// Shift size for getting index-2 table offset.
+const SHIFT_2: u32 = 5 + SHIFT_3;
+/// Shift size for getting index-1 table offset.
+const SHIFT_1: u32 = 5 + SHIFT_2;
+/// Shift size for getting BMP block start.
+const BMP_SHIFT: u32 = 6;
+
+const INDEX_2_BLOCK_LENGTH: u32 = 1 << (SHIFT_1 - SHIFT_2);
+const INDEX_3_BLOCK_LENGTH: u32 = 1 << (SHIFT_2 - SHIFT_3);
+const SMALL_DATA_BLOCK_LENGTH: u32 = 1 << SHIFT_3;
+const BMP_DATA_BLOCK_LENGTH: u32 = 1 << BMP_SHIFT;
+
+const ALLOWED_BREAK_BIT: u8 = 0x80;
+const MANDATORY_BREAK_BIT: u8 = 0x40;
+
+#[allow(non_upper_case_globals)]
+const eot: u8 = 43;
+#[allow(non_upper_case_globals)]
+const sot: u8 = 44;
diff --git a/third_party/rust/unicode-linebreak/src/tables.rs b/third_party/rust/unicode-linebreak/src/tables.rs
new file mode 100644
index 0000000000..1a5d16b5ce
--- /dev/null
+++ b/third_party/rust/unicode-linebreak/src/tables.rs
@@ -0,0 +1,10 @@
+const BREAK_PROP_TRIE_HIGH_START: u32 = 918016;
+static BREAK_PROP_TRIE_INDEX: [u16; 2844] = [0, 64, 127, 191, 247, 247, 247, 247, 247, 247, 247, 304, 368, 417, 481, 247, 247, 247, 542, 247, 558, 607, 662, 726, 790, 843, 247, 892, 950, 1003, 1029, 1093, 1157, 1221, 1270, 1324, 1384, 1446, 1509, 1571, 1634, 1696, 1759, 1821, 1885, 1947, 2009, 2071, 2135, 2197, 2260, 2322, 2386, 2448, 2512, 2576, 2639, 2703, 2766, 2830, 2894, 2958, 3016, 3080, 3144, 3208, 3256, 3314, 3378, 3410, 3442, 3482, 247, 3546, 3601, 3663, 3710, 3747, 3782, 3814, 3878, 247, 247, 247, 247, 247, 247, 247, 247, 247, 3942, 3974, 4038, 4102, 3144, 4166, 4230, 4262, 4326, 4374, 4438, 4502, 4566, 4620, 4661, 4694, 4758, 4807, 4871, 4930, 4994, 5052, 5112, 5176, 5240, 5301, 247, 247, 247, 5365, 247, 247, 247, 247, 5429, 5487, 553, 5551, 5615, 5677, 5741, 5803, 5867, 5911, 5969, 6015, 6079, 6141, 6203, 6267, 6323, 247, 247, 6366, 6418, 6482, 6514, 6515, 6514, 6566, 6630, 6690, 6754, 6818, 6882, 6943, 7004, 7045, 7099, 7158, 247, 247, 247, 247, 247, 247, 7219, 7259, 247, 247, 247, 247, 247, 7321, 7375, 247, 247, 247, 247, 7398, 7462, 7510, 7574, 7606, 7670, 7734, 7798, 7825, 7889, 7889, 7889, 7931, 7995, 8059, 8120, 8181, 8245, 7889, 7809, 8294, 8262, 8358, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 247, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 8401, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 8452, 8509, 247, 247, 247, 247, 8573, 8637, 8699, 8731, 247, 247, 247, 8795, 8857, 8921, 8985, 9043, 9107, 9164, 9228, 9291, 9355, 9419, 3144, 9480, 9543, 9591, 247, 9639, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9723, 9703, 9711, 9719, 9727, 9707, 9715, 9779, 9836, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9900, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 10028, 10092, 247, 10153, 247, 247, 247, 247, 10172, 247, 10236, 10292, 10356, 10416, 247, 10470, 10534, 10596, 10645, 10708, 2656, 2686, 2715, 2746, 2778, 2778, 2778, 2779, 2778, 2778, 2778, 2779, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2811, 2843, 503, 247, 508, 10772, 616, 616, 9964, 9964, 247, 247, 247, 247, 247, 247, 247, 1253, 10788, 247, 247, 2531, 247, 247, 247, 247, 500, 2522, 1837, 9964, 9964, 247, 247, 10795, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 2522, 247, 247, 247, 1837, 551, 2055, 247, 247, 7593, 247, 1253, 247, 247, 10811, 247, 10827, 247, 247, 9631, 10842, 9964, 9964, 247, 247, 247, 247, 247, 247, 247, 247, 247, 616, 2235, 247, 247, 9631, 247, 2055, 247, 247, 1995, 247, 247, 247, 10844, 504, 504, 10859, 513, 10873, 9964, 9964, 9964, 9964, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 4421, 247, 4422, 1995, 9964, 509, 247, 247, 10889, 9964, 9964, 9964, 9964, 10905, 247, 247, 10915, 247, 10930, 247, 247, 247, 500, 783, 9964, 9964, 9964, 247, 10943, 247, 10954, 247, 1254, 9964, 9964, 9964, 9964, 247, 247, 247, 9627, 247, 630, 247, 247, 10970, 1769, 247, 10986, 4022, 11002, 247, 247, 247, 247, 9964, 9964, 247, 247, 11018, 11034, 247, 247, 247, 11050, 247, 624, 247, 1261, 247, 11066, 781, 9964, 9964, 9964, 9964, 9964, 247, 247, 247, 247, 4022, 9964, 9964, 9964, 247, 247, 247, 6454, 247, 247, 247, 4028, 247, 247, 4052, 2235, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 500, 247, 247, 11082, 6455, 9964, 9964, 9964, 4858, 247, 247, 1995, 247, 362, 11098, 9964, 247, 11114, 9964, 9964, 247, 2055, 9964, 247, 4421, 549, 247, 247, 360, 11130, 630, 2920, 11146, 549, 247, 247, 11161, 11175, 247, 4022, 2235, 549, 247, 361, 11191, 11207, 247, 247, 11223, 549, 247, 247, 365, 11239, 11255, 494, 6452, 247, 513, 356, 11271, 11286, 9964, 9964, 9964, 11302, 501, 11317, 247, 247, 353, 4811, 2235, 11333, 629, 506, 11348, 1947, 11364, 11378, 4817, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 247, 363, 11394, 11410, 6455, 9964, 247, 247, 247, 368, 11426, 2235, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 353, 11442, 11457, 11466, 9964, 9964, 247, 247, 247, 368, 11482, 2235, 11498, 9964, 247, 247, 357, 11514, 2235, 9964, 9964, 9964, 3144, 2817, 2686, 11530, 9476, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 356, 1067, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 247, 247, 940, 10032, 11546, 11558, 247, 11574, 11588, 2235, 9964, 9964, 9964, 9964, 622, 247, 247, 11604, 11619, 9964, 8688, 247, 247, 11635, 11651, 11667, 247, 247, 358, 11683, 11698, 247, 247, 247, 247, 4022, 11714, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 506, 247, 353, 3033, 11730, 940, 2522, 11746, 247, 3005, 3032, 4815, 9964, 9964, 9964, 9964, 1801, 247, 247, 11761, 11776, 2235, 11792, 247, 4674, 11808, 2235, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 11824, 11840, 493, 247, 11852, 11866, 2235, 9964, 9964, 9964, 9964, 9964, 1837, 247, 11882, 11897, 11911, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 3798, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 247, 247, 247, 247, 500, 11926, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 6453, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 247, 247, 247, 247, 6454, 247, 247, 247, 247, 247, 11942, 247, 247, 11956, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 11966, 247, 247, 247, 247, 247, 247, 247, 247, 11982, 11998, 4816, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 3955, 247, 247, 247, 247, 4421, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 247, 4022, 247, 500, 12014, 247, 247, 247, 247, 500, 2235, 247, 616, 12030, 247, 247, 247, 12046, 12058, 12074, 513, 1256, 247, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 247, 247, 247, 12085, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 247, 247, 2056, 367, 368, 368, 12101, 549, 9964, 9964, 9964, 9964, 12117, 4820, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7869, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 4422, 9964, 9964, 7868, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 1671, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7950, 12131, 9964, 12147, 12159, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7865, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 247, 247, 247, 247, 1253, 2522, 4022, 12175, 4818, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 368, 368, 1000, 368, 4815, 247, 247, 247, 247, 247, 247, 247, 6453, 9964, 9964, 9964, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 4422, 247, 247, 623, 247, 247, 247, 12191, 368, 12204, 247, 12216, 247, 247, 247, 1253, 9964, 247, 247, 247, 247, 12230, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 6453, 247, 6453, 247, 247, 247, 247, 247, 4421, 247, 4022, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 247, 247, 247, 510, 247, 247, 247, 502, 12244, 12258, 511, 247, 247, 247, 3549, 1670, 247, 3600, 12271, 493, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 624, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 12281, 12295, 12295, 12295, 368, 368, 368, 11672, 368, 368, 452, 12311, 12323, 4860, 678, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 500, 12335, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 3033, 12351, 12365, 247, 247, 247, 616, 9964, 4856, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 247, 2522, 12381, 9307, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 12397, 9964, 247, 247, 356, 12413, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 247, 356, 2235, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 12429, 500, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 247, 625, 4815, 9964, 9964, 247, 247, 247, 247, 12445, 12461, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 494, 247, 247, 10340, 12477, 9964, 9964, 9964, 9964, 494, 247, 247, 616, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 511, 247, 12492, 12505, 12519, 12535, 12549, 12557, 505, 2055, 12572, 2055, 9964, 9964, 9964, 6455, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 12588, 6514, 6564, 6514, 6514, 6514, 12604, 6514, 6514, 6514, 12588, 7889, 7889, 7889, 12617, 12623, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 12639, 12645, 7889, 12652, 12666, 7889, 7889, 12679, 7889, 7889, 7889, 7889, 12695, 12710, 12720, 12727, 12742, 12756, 12772, 12786, 7889, 7889, 7889, 7889, 6936, 11875, 12802, 6416, 6933, 7889, 7889, 12814, 7889, 12830, 7889, 7889, 7889, 12842, 7889, 12854, 7889, 7889, 7889, 7889, 12865, 247, 247, 12881, 7889, 7889, 12641, 12897, 12903, 7889, 7889, 7889, 247, 247, 247, 247, 247, 247, 247, 12919, 247, 247, 247, 247, 247, 12802, 7889, 7889, 6402, 247, 247, 247, 6935, 6933, 247, 247, 6935, 247, 6400, 7889, 7889, 7889, 7889, 7889, 12935, 12718, 12751, 12950, 7889, 7889, 7889, 12750, 7889, 7889, 7889, 12965, 12713, 12980, 7889, 7889, 247, 247, 247, 247, 247, 12919, 7889, 7889, 7889, 7889, 7889, 7889, 12898, 7889, 7889, 12702, 247, 247, 247, 247, 247, 247, 247, 247, 247, 512, 247, 247, 1253, 9964, 9964, 2235, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7889, 7863, 2093, 9964, 368, 368, 368, 368, 368, 368, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 9964, 368, 368, 368, 368, 368, 368, 368, 368, 368, 368, 368, 368, 368, 368, 368, 9964, 1077, 1109, 1141, 1173, 1205, 1237, 1269, 1295, 1327, 1359, 1391, 1423, 1455, 1487, 1519, 1546, 1578, 1585, 1617, 896, 896, 896, 896, 1638, 1578, 1670, 1699, 896, 896, 896, 896, 896, 1731, 1760, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 1578, 1792, 896, 1820, 202, 202, 202, 202, 202, 202, 202, 202, 1852, 202, 1884, 1903, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 1920, 1952, 1975, 896, 896, 896, 896, 2007, 896, 896, 896, 896, 896, 896, 896, 2023, 2055, 2087, 2119, 2141, 1578, 2173, 896, 2189, 2221, 2244, 2263, 2279, 2311, 896, 2336, 2368, 2400, 2432, 2464, 2496, 2528, 2560, 202, 2592, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 202, 2592, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 896, 2624];
+static BREAK_PROP_TRIE_DATA: [BreakClass; 12996] = [
+CM,CM,CM,CM,CM,CM,CM,CM,CM,BA,LF,BK,BK,CR,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,SP,EX,QU,AL,PR,PO,AL,QU,OP,CP,AL,PR,IS,HY,IS,SY,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,IS,IS,AL,AL,AL,EX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,OP,PR,CP,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,OP,BA,CL,AL,CM,CM,CM,CM,CM,NL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,GL,OP,PO,PR,PR,PR,AL,AI,AI,AL,AI,QU,AL,BA,AL,AL,PO,PR,AI,AI,BB,AL,AI,AI,AI,AI,AI,QU,AI,AI,AI,OP,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,BB,AI,AI,AI,BB,AI,AL,AL,AI,AL,AL,AL,AL,AL,AL,AL,AI,AI,AI,AI,AL,AI,AL,BB,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,GL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,GL,GL,GL,GL,GL,GL,GL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,IS,AL,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,IS,BA,XX,XX,AL,AL,PR,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,BA,CM,AL,CM,CM,AL,CM,CM,EX,CM,XX,XX,XX,XX,XX,XX,XX,XX,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,XX,XX,XX,XX,HL,HL,HL,HL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,PO,PO,PO,IS,IS,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,EX,CM,EX,EX,EX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,PO,NU,NU,AL,AL,AL,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,EX,AL,CM,CM,CM,CM,CM,CM,CM,AL,AL,CM,CM,CM,CM,CM,CM,AL,AL,CM,CM,AL,CM,CM,CM,CM,AL,AL,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,IS,EX,AL,XX,XX,CM,PR,PR,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,CM,CM,CM,AL,CM,CM,CM,CM,CM,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,XX,XX,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,XX,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,BA,BA,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,XX,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,XX,XX,XX,AL,AL,AL,AL,XX,XX,CM,AL,CM,CM,CM,CM,CM,XX,XX,CM,CM,XX,XX,CM,CM,CM,AL,XX,XX,XX,XX,XX,XX,XX,XX,CM,XX,XX,XX,XX,AL,AL,XX,AL,AL,AL,CM,CM,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,PO,PO,AL,AL,AL,AL,AL,PO,AL,PR,AL,AL,CM,XX,CM,CM,CM,XX,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,XX,AL,AL,XX,AL,AL,XX,XX,CM,XX,CM,CM,CM,XX,XX,XX,XX,CM,CM,XX,XX,CM,CM,CM,XX,XX,XX,CM,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,XX,AL,XX,XX,XX,XX,XX,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,CM,CM,AL,AL,AL,CM,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,CM,CM,CM,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,XX,AL,AL,AL,AL,AL,XX,XX,CM,AL,CM,CM,CM,CM,CM,CM,XX,CM,CM,CM,XX,CM,CM,CM,XX,XX,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,CM,CM,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,PR,XX,XX,XX,XX,XX,XX,XX,AL,CM,CM,CM,CM,CM,CM,XX,CM,CM,CM,XX,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,XX,AL,AL,AL,AL,AL,XX,XX,CM,AL,CM,CM,CM,CM,CM,XX,XX,CM,CM,XX,XX,CM,CM,CM,XX,XX,XX,XX,XX,XX,XX,CM,CM,CM,XX,XX,XX,XX,AL,AL,XX,AL,AL,AL,CM,CM,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,CM,AL,XX,AL,AL,AL,AL,AL,AL,XX,XX,XX,AL,AL,AL,XX,AL,AL,AL,AL,XX,XX,XX,AL,AL,XX,AL,XX,AL,AL,XX,XX,XX,AL,AL,XX,XX,XX,AL,AL,AL,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,CM,CM,CM,XX,XX,XX,CM,CM,CM,XX,CM,CM,CM,CM,XX,XX,AL,XX,XX,XX,XX,XX,XX,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,AL,AL,AL,AL,AL,AL,AL,PR,AL,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,CM,AL,CM,CM,CM,CM,CM,XX,CM,CM,CM,XX,CM,CM,CM,CM,XX,XX,XX,XX,XX,XX,XX,CM,CM,XX,AL,AL,AL,XX,XX,AL,XX,XX,AL,AL,CM,CM,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,XX,XX,XX,XX,XX,BB,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,BB,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,XX,XX,CM,AL,CM,CM,CM,CM,CM,XX,CM,CM,CM,XX,CM,CM,CM,CM,XX,XX,XX,XX,XX,XX,XX,CM,CM,XX,XX,XX,XX,XX,XX,AL,AL,XX,AL,AL,CM,CM,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,AL,AL,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,AL,CM,CM,CM,CM,CM,XX,CM,CM,CM,XX,CM,CM,CM,CM,AL,AL,XX,XX,XX,XX,AL,AL,AL,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,AL,AL,AL,AL,AL,AL,AL,PO,AL,AL,AL,AL,AL,AL,XX,CM,CM,CM,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,CM,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,XX,CM,XX,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,XX,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,CM,CM,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,XX,XX,XX,XX,PR,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,AL,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,BA,BA,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,SA,SA,XX,SA,XX,SA,SA,SA,SA,SA,XX,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,XX,SA,XX,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,XX,XX,SA,SA,SA,SA,SA,XX,SA,XX,SA,SA,SA,SA,SA,SA,SA,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,SA,SA,SA,SA,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,BB,BB,BB,BB,AL,BB,BB,GL,BB,BB,BA,GL,EX,EX,EX,EX,EX,GL,AL,EX,AL,AL,AL,CM,CM,AL,AL,AL,AL,AL,AL,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,BA,CM,AL,CM,AL,CM,OP,CL,OP,CL,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,BA,CM,CM,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,BA,BA,AL,AL,AL,AL,AL,AL,CM,AL,AL,AL,AL,AL,AL,XX,AL,AL,BB,BB,BA,BB,AL,AL,AL,AL,AL,GL,GL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,BA,BA,AL,AL,AL,AL,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,SA,SA,SA,SA,SA,SA,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,XX,XX,XX,XX,XX,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,XX,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,CM,CM,CM,AL,BA,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,XX,XX,BA,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,BA,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,OP,CL,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,BA,BA,BA,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,BA,BA,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,XX,CM,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,BA,BA,NS,SA,BA,AL,BA,PR,SA,SA,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,AL,AL,EX,EX,BA,BA,BB,AL,EX,EX,AL,CM,CM,CM,GL,CM,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,AL,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,XX,AL,XX,XX,XX,EX,EX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,XX,XX,SA,SA,SA,SA,SA,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,XX,XX,XX,XX,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,XX,XX,XX,XX,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,SA,XX,XX,XX,SA,SA,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,XX,XX,AL,AL,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,XX,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,XX,XX,CM,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,XX,XX,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,XX,XX,XX,XX,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,XX,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,BA,BA,AL,BA,BA,BA,BA,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,BA,BA,XX,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,BA,BA,BA,BA,BA,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,XX,AL,AL,AL,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,BA,BA,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,CM,CM,CM,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,CM,AL,AL,AL,AL,AL,AL,CM,AL,AL,CM,CM,CM,AL,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,GL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,GL,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,XX,AL,XX,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,BB,AL,XX,BA,BA,BA,BA,BA,BA,BA,GL,BA,BA,BA,ZW,CM,ZWJ,CM,CM,BA,GL,BA,BA,B2,AI,AI,AL,QU,QU,OP,QU,QU,QU,OP,QU,AI,AI,AL,AL,IN,IN,IN,BA,BK,BK,CM,CM,CM,CM,CM,GL,PO,PO,PO,PO,PO,PO,PO,PO,AL,QU,QU,AI,NS,NS,AL,AL,AL,AL,IS,OP,CL,NS,NS,NS,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,BA,PO,BA,BA,BA,BA,AL,BA,BA,BA,WJ,AL,AL,AL,AL,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,XX,XX,AI,AL,AL,AL,AL,AL,AL,AL,AL,OP,CL,AI,AL,AI,AI,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,OP,CL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,PR,PR,PR,PR,PR,PR,PR,PO,PR,PR,PR,PR,PR,PR,PR,PR,PR,PR,PR,PR,PR,PR,PO,PR,PR,PR,PR,PO,PR,PR,PO,PR,PR,PR,PR,PR,PR,PR,PR,PR,PR,PR,PR,PR,PR,PR,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,PO,AL,AI,AL,AL,AL,PO,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AL,PR,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AI,AL,AL,AL,AL,AL,AI,AL,AL,AI,AL,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AL,AL,AL,AL,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AL,XX,XX,XX,XX,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AI,AI,AL,AL,AL,AI,AI,AL,AL,AI,AL,AL,AL,AI,AL,AI,PR,PR,AL,AI,AL,AL,AL,AL,AI,AL,AL,AI,AI,AI,AI,AL,AL,AI,AL,AI,AL,AI,AI,AI,AI,AI,AI,AL,AI,AL,AL,AL,AL,AL,AI,AI,AI,AI,AL,AL,AL,AL,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AL,AL,AI,AL,AL,AL,AL,AL,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AI,AL,AL,AI,AI,AI,AI,AL,AL,AI,AI,AL,AL,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AI,AL,AL,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AL,AL,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,IN,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,OP,CL,OP,CL,AL,AL,AL,AL,AL,AL,AI,AL,AL,AL,AL,AL,AL,AL,ID,ID,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,OP,CL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,ID,ID,ID,ID,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AL,AL,AL,AL,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AL,AL,AI,AI,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AI,AL,AI,AI,AI,AI,AI,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AI,AI,AL,AL,AI,AI,AL,AL,AL,AL,AI,AI,AL,AL,AL,AL,AI,AI,AI,AL,AL,AI,AL,AL,AI,AI,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AI,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,ID,ID,ID,ID,AL,AI,AI,AL,AL,AI,AL,AL,AL,AL,AI,AI,AL,AL,AL,AL,ID,ID,AI,AI,ID,AL,ID,ID,ID,EB,ID,ID,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,ID,ID,ID,AL,AL,AL,AL,AI,AL,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AI,AL,AI,AI,AI,AL,AI,ID,AI,AI,AL,AI,AI,AL,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,ID,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,ID,ID,ID,ID,ID,ID,ID,ID,ID,AI,AI,AI,AI,ID,AL,ID,ID,ID,AI,ID,ID,AI,AI,AI,ID,ID,AI,AI,ID,AI,AI,ID,ID,ID,AL,AI,AL,AL,AL,AL,AI,AI,ID,AI,AI,AI,AI,AI,AI,ID,ID,ID,ID,ID,AI,ID,ID,EB,ID,AI,AI,ID,ID,ID,ID,ID,AL,AL,AL,ID,ID,EB,EB,EB,EB,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AL,AL,AL,QU,QU,QU,QU,QU,QU,AL,EX,EX,ID,AL,AL,AL,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,OP,CL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,OP,CL,OP,CL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,OP,CL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AI,AI,AI,AI,AI,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,AL,AL,XX,XX,XX,XX,XX,EX,BA,BA,BA,AL,EX,BA,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,XX,XX,XX,XX,XX,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,AL,BA,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,QU,QU,QU,QU,QU,QU,QU,QU,QU,QU,QU,QU,QU,QU,BA,BA,BA,BA,BA,BA,BA,BA,AL,BA,OP,BA,AL,AL,QU,QU,AL,AL,QU,QU,OP,CL,OP,CL,OP,CL,OP,CL,BA,BA,BA,BA,EX,AL,BA,BA,AL,BA,BA,AL,AL,AL,AL,AL,B2,B2,BA,BA,BA,AL,BA,BA,OP,BA,BA,BA,BA,BA,BA,BA,BA,AL,BA,AL,BA,BA,AL,AL,AL,EX,EX,OP,CL,OP,CL,OP,CL,OP,CL,BA,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,XX,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,XX,XX,XX,XX,BA,CL,CL,ID,ID,NS,ID,ID,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,ID,ID,OP,CL,OP,CL,OP,CL,OP,CL,NS,OP,CL,CL,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,CM,CM,CM,CM,CM,CM,ID,ID,ID,ID,ID,CM,ID,ID,ID,ID,ID,NS,NS,ID,ID,ID,XX,CJ,ID,CJ,ID,CJ,ID,CJ,ID,CJ,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,CJ,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,CJ,ID,CJ,ID,CJ,ID,ID,ID,ID,ID,ID,CJ,ID,ID,ID,ID,ID,ID,CJ,CJ,XX,XX,CM,CM,NS,NS,NS,NS,ID,NS,CJ,ID,CJ,ID,CJ,ID,CJ,ID,CJ,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,CJ,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,CJ,ID,CJ,ID,CJ,ID,ID,ID,ID,ID,ID,CJ,ID,ID,ID,ID,ID,ID,CJ,CJ,ID,ID,ID,ID,NS,CJ,NS,NS,ID,XX,XX,XX,XX,XX,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,XX,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CJ,CJ,CJ,CJ,CJ,CJ,CJ,CJ,CJ,CJ,CJ,CJ,CJ,CJ,CJ,CJ,ID,ID,ID,ID,ID,ID,ID,ID,AI,AI,AI,AI,AI,AI,AI,AI,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,NS,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,XX,XX,XX,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,BA,BA,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,BA,EX,BA,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,AL,BA,BA,BA,BA,BA,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,AL,AL,XX,AL,XX,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,AL,AL,AL,CM,AL,AL,AL,AL,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,AL,AL,AL,AL,CM,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,PO,AL,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,BB,BB,EX,EX,XX,XX,XX,XX,XX,XX,XX,XX,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,XX,XX,XX,XX,XX,BA,BA,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,BB,AL,AL,CM,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,BA,BA,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,JL,XX,XX,XX,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,BA,BA,BA,AL,AL,AL,AL,XX,AL,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,XX,XX,AL,AL,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,SA,SA,SA,SA,SA,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,CM,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,AL,BA,BA,BA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,SA,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,SA,SA,SA,SA,SA,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,BA,BA,AL,AL,AL,CM,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,BA,CM,CM,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,XX,XX,XX,XX,H2,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H2,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H2,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H2,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,H3,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,JV,XX,XX,XX,XX,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,JT,XX,XX,XX,XX,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,SG,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,HL,CM,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,AL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,XX,HL,HL,HL,HL,HL,XX,HL,XX,HL,HL,XX,HL,HL,XX,HL,HL,HL,HL,HL,HL,HL,HL,HL,HL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CL,OP,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,PO,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,IS,CL,CL,IS,IS,EX,EX,OP,CL,IN,XX,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,ID,ID,ID,ID,ID,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,OP,CL,ID,ID,OP,CL,ID,ID,ID,ID,ID,ID,ID,CL,ID,CL,XX,NS,NS,EX,EX,ID,OP,CL,OP,CL,OP,CL,ID,ID,ID,ID,ID,ID,ID,ID,XX,ID,PR,PO,ID,XX,XX,XX,XX,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,WJ,XX,EX,ID,ID,PR,PO,ID,ID,OP,CL,ID,ID,CL,ID,CL,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,NS,NS,ID,ID,ID,EX,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,OP,ID,CL,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,OP,ID,CL,ID,OP,CL,CL,OP,CL,CL,NS,ID,CJ,CJ,CJ,CJ,CJ,CJ,CJ,CJ,CJ,CJ,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,NS,NS,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,XX,XX,ID,ID,ID,ID,ID,ID,XX,XX,ID,ID,ID,ID,ID,ID,XX,XX,ID,ID,ID,ID,ID,ID,XX,XX,ID,ID,ID,XX,XX,XX,PO,PR,ID,ID,ID,PR,PR,XX,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CM,CM,CM,CB,AI,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,XX,AL,BA,BA,BA,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,XX,XX,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,BA,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,XX,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,XX,XX,XX,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,XX,XX,AL,XX,AL,AL,AL,AL,AL,AL,XX,AL,AL,XX,XX,XX,AL,XX,XX,AL,AL,AL,AL,AL,AL,XX,BA,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,BA,AL,CM,CM,CM,XX,CM,CM,XX,XX,XX,XX,XX,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,XX,XX,CM,CM,CM,XX,XX,XX,XX,CM,BA,BA,BA,BA,BA,BA,BA,BA,AL,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,CM,CM,XX,XX,XX,XX,AL,AL,AL,AL,AL,BA,BA,BA,BA,BA,BA,IN,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,XX,XX,XX,BA,BA,BA,BA,BA,BA,BA,AL,AL,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,CM,CM,BA,XX,XX,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,AL,AL,CM,CM,CM,CM,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,BA,BA,AL,AL,AL,AL,AL,XX,XX,CM,AL,AL,CM,CM,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,BA,BA,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,XX,XX,CM,CM,CM,CM,CM,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,BA,BA,BA,BA,AL,CM,CM,AL,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,CM,AL,BB,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,CM,AL,AL,AL,AL,BA,BA,AL,BA,CM,CM,CM,CM,AL,CM,CM,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,BB,AL,BA,BA,BA,CM,CM,CM,CM,CM,CM,CM,CM,BA,BA,AL,BA,BA,AL,CM,AL,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,XX,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,BA,XX,XX,XX,XX,XX,XX,CM,CM,CM,CM,XX,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,XX,AL,AL,XX,AL,AL,AL,AL,AL,XX,CM,CM,AL,CM,CM,AL,XX,XX,XX,XX,XX,XX,CM,XX,XX,XX,XX,XX,AL,AL,AL,CM,CM,XX,XX,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,BA,BA,BA,BA,AL,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,BA,BA,XX,AL,CM,AL,CM,CM,CM,CM,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,XX,XX,CM,CM,CM,CM,CM,CM,CM,CM,BB,BA,BA,EX,EX,AL,AL,AL,BA,BA,BA,BA,BA,BA,BA,BA,AL,AL,AL,AL,CM,CM,XX,XX,CM,BA,BA,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,BB,BB,BB,BB,BB,BB,BB,BB,BB,BB,BB,BB,BB,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,XX,XX,XX,XX,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,SA,SA,BA,BA,BA,SA,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,XX,XX,AL,AL,AL,AL,XX,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,XX,CM,CM,XX,XX,CM,CM,CM,CM,AL,CM,CM,BA,BA,BA,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,CM,CM,CM,CM,CM,CM,CM,XX,XX,CM,CM,CM,CM,CM,CM,AL,BB,AL,CM,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,AL,CM,CM,CM,CM,BB,AL,BA,BA,BA,BA,BB,AL,CM,XX,XX,XX,XX,XX,XX,XX,XX,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,CM,BA,BA,BA,AL,BB,BB,BA,BA,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,BB,BB,BB,BB,BB,BB,BB,BB,BB,BB,XX,XX,XX,XX,XX,XX,AL,BA,BA,BA,BA,BA,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,BB,EX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,XX,XX,XX,CM,XX,CM,CM,XX,CM,CM,CM,CM,CM,CM,AL,CM,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,XX,AL,AL,XX,AL,AL,AL,AL,AL,AL,CM,CM,XX,CM,CM,CM,CM,CM,AL,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,CM,CM,CM,CM,AL,AL,XX,XX,XX,XX,XX,XX,XX,CM,CM,AL,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,CM,CM,CM,BA,BA,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,PO,PO,PO,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,BA,BA,BA,BA,BA,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,OP,OP,OP,CL,CL,CL,AL,AL,CL,AL,AL,AL,OP,CL,OP,CL,AL,AL,AL,AL,AL,AL,AL,AL,AL,OP,CL,CL,AL,AL,AL,AL,GL,GL,GL,GL,GL,GL,GL,OP,CL,GL,GL,GL,OP,CL,OP,CL,CM,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,CM,CM,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,XX,XX,BA,BA,CM,CM,CM,CM,CM,BA,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,BA,BA,BA,AL,AL,AL,AL,AL,AL,BA,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,AL,AL,AL,AL,AL,AL,AL,BA,BA,AL,AL,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,XX,XX,XX,XX,XX,CM,NS,NS,NS,NS,GL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CJ,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CJ,CJ,CJ,XX,XX,CJ,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CJ,CJ,CJ,CJ,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,CM,CM,BA,AL,AL,AL,AL,AL,CM,CM,CM,CM,CM,AL,AL,AL,CM,CM,CM,AL,AL,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,CM,CM,CM,AL,AL,CM,CM,CM,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,XX,XX,AL,AL,XX,XX,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,AL,XX,AL,AL,AL,AL,AL,XX,AL,XX,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,AL,AL,AL,AL,AL,CM,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,AL,AL,BA,BA,BA,BA,AL,XX,XX,XX,XX,XX,AL,AL,AL,AL,AL,AL,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,CM,CM,XX,XX,CM,CM,CM,CM,CM,XX,CM,CM,XX,CM,CM,CM,CM,CM,XX,XX,XX,XX,XX,CM,CM,CM,CM,CM,CM,CM,AL,AL,AL,AL,AL,AL,AL,XX,XX,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,CM,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,XX,XX,XX,PR,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,XX,AL,AL,XX,AL,AL,AL,AL,CM,CM,CM,CM,CM,CM,CM,AL,XX,XX,XX,XX,NU,NU,NU,NU,NU,NU,NU,NU,NU,NU,XX,XX,XX,XX,OP,OP,PO,AL,AL,AL,AL,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,AL,AL,XX,AL,XX,XX,AL,XX,AL,AL,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,XX,AL,XX,AL,XX,XX,XX,XX,AL,XX,XX,XX,XX,AL,XX,AL,XX,AL,XX,AL,AL,AL,XX,AL,AL,XX,AL,XX,XX,AL,XX,AL,XX,AL,XX,AL,XX,AL,AL,XX,AL,XX,XX,AL,AL,AL,AL,XX,AL,AL,AL,AL,XX,AL,AL,AL,AL,XX,AL,XX,AL,AL,AL,XX,AL,AL,AL,AL,AL,XX,AL,AL,AL,AL,AL,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,ID,ID,ID,AI,AI,AI,AI,AI,AI,AI,AI,AI,AI,AL,AL,AL,ID,ID,ID,ID,ID,ID,RI,RI,RI,RI,RI,RI,RI,RI,RI,RI,RI,RI,RI,RI,RI,RI,ID,ID,ID,ID,ID,EB,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,AL,AL,ID,ID,ID,ID,ID,AL,ID,ID,ID,EB,EB,EB,ID,ID,EB,ID,ID,EB,EB,EB,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,EM,EM,EM,EM,EM,ID,ID,EB,EB,ID,ID,EB,EB,EB,EB,EB,EB,EB,EB,EB,EB,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,EB,EB,EB,EB,EB,EB,EB,EB,EB,EB,ID,ID,ID,EB,ID,ID,ID,EB,EB,EB,ID,EB,EB,EB,ID,ID,ID,ID,ID,ID,ID,EB,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,AL,ID,AL,ID,AL,ID,ID,ID,ID,ID,EB,ID,ID,ID,ID,AL,AL,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,AL,AL,AL,AL,AL,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,EB,EB,ID,ID,ID,ID,EB,ID,ID,ID,ID,ID,EB,ID,ID,ID,ID,EB,EB,ID,ID,ID,ID,ID,ID,ID,ID,ID,AL,AL,AL,AL,AL,AL,AL,AL,ID,ID,ID,ID,AL,AL,AL,AL,AL,AL,ID,ID,ID,ID,ID,ID,EB,EB,EB,ID,ID,ID,EB,EB,EB,EB,EB,AL,AL,AL,AL,AL,AL,QU,QU,QU,NS,NS,NS,AL,AL,AL,AL,ID,ID,ID,ID,EB,EB,EB,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,EB,ID,ID,ID,AL,AL,AL,AL,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,AL,EB,ID,ID,EB,EB,EB,EB,EB,EB,EB,EB,EB,EB,ID,ID,EB,EB,EB,ID,ID,ID,ID,ID,EB,EB,ID,EB,EB,ID,EB,ID,ID,ID,ID,EB,EB,EB,EB,EB,EB,EB,EB,EB,EB,EB,EB,EB,ID,ID,];
+
+static PAIR_TABLE: [[u8; 44]; 53] = [[192,193,194,221,196,221,198,199,200,201,221,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,221,221,212,223,224,225,226,227,228,229,230,231,232,221,221,235,],[192,193,2,221,196,221,198,199,200,201,221,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,221,221,212,223,224,225,226,227,228,229,230,231,232,221,221,235,],[192,193,194,221,196,221,198,199,200,201,221,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,221,221,212,223,224,225,226,227,228,229,230,231,232,221,221,235,],[0,1,2,3,4,29,6,7,8,9,3,139,12,141,14,143,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,159,160,161,162,35,164,165,166,167,168,29,29,235,],[192,193,194,221,196,221,198,199,200,201,221,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,221,221,212,223,224,225,226,227,228,229,230,231,232,221,221,235,],[0,1,2,29,4,29,6,7,8,9,29,139,12,141,14,143,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,159,160,161,162,35,164,165,166,167,168,29,29,235,],[0,1,2,6,4,29,6,7,8,9,6,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,31,32,33,34,35,36,37,38,39,40,29,29,235,],[0,1,2,157,4,157,134,7,136,45,157,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,157,157,148,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,8,4,29,6,7,8,9,8,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,31,32,33,34,35,36,37,38,39,40,29,29,235,],[0,1,2,157,4,157,6,7,136,9,157,139,140,141,142,143,16,17,18,147,148,149,150,23,152,153,154,27,157,157,148,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,10,4,29,6,7,8,9,10,139,12,141,14,143,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,159,160,161,162,35,164,165,166,167,168,29,29,235,],[0,1,2,11,4,157,6,7,8,50,11,11,12,141,14,143,16,17,18,19,20,149,22,23,152,153,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,12,4,157,6,7,136,9,12,139,12,141,14,143,16,17,18,19,20,149,22,23,152,153,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,13,4,29,6,7,8,9,13,11,12,13,14,143,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,31,32,33,34,35,36,37,38,39,40,29,29,235,],[0,1,2,14,4,157,6,7,136,9,14,139,12,141,14,143,16,17,18,19,20,149,22,23,24,153,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,15,4,157,6,7,8,9,15,139,140,141,142,143,16,17,18,147,148,149,22,23,152,153,154,27,157,157,148,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,16,4,157,6,7,8,48,16,139,12,141,14,143,16,17,18,19,20,149,22,23,152,25,26,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,17,4,29,6,7,8,49,17,139,12,141,14,143,16,17,18,19,20,149,22,23,24,25,26,27,29,29,20,159,160,161,162,35,164,165,166,167,168,29,29,235,],[0,1,2,18,4,157,6,7,8,9,18,139,12,141,14,143,16,17,18,19,20,149,22,23,152,153,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,19,4,157,6,7,8,9,19,139,12,141,14,143,16,17,18,19,20,149,22,23,152,153,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,20,4,157,6,7,8,9,20,139,12,141,14,143,16,17,18,19,20,149,22,23,152,153,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,21,4,29,6,7,8,46,21,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,31,32,33,34,35,36,37,38,39,40,29,29,235,],[0,1,2,22,4,29,6,7,8,47,22,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,31,32,33,34,35,36,37,38,39,40,29,29,235,],[0,1,2,23,4,29,6,7,8,9,23,139,12,141,14,143,16,17,18,19,20,149,22,23,24,153,154,27,29,29,20,159,160,161,162,35,164,165,166,167,168,29,29,235,],[0,1,2,24,4,29,6,7,8,9,24,139,12,141,14,143,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,159,160,161,162,35,164,165,166,167,168,29,29,235,],[0,1,2,25,4,29,6,7,8,9,25,139,12,141,14,143,16,17,18,19,20,21,22,23,24,153,154,27,29,29,20,159,160,161,162,35,164,165,166,167,168,29,29,235,],[0,1,2,26,4,29,6,7,8,9,26,139,12,141,14,143,16,17,18,19,20,21,22,23,24,153,154,27,29,29,20,31,32,33,34,35,36,37,38,39,168,29,29,235,],[0,1,2,27,4,157,6,7,8,9,27,139,12,141,14,143,16,17,18,19,20,149,22,23,24,153,154,27,157,157,20,159,160,161,162,35,164,165,166,167,168,157,157,235,],[0,1,2,29,4,29,6,7,8,9,29,139,12,141,14,143,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,159,160,161,162,35,164,165,166,167,168,29,29,235,],[0,1,2,29,4,29,6,7,8,9,29,139,12,141,14,143,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,159,160,161,162,35,164,165,166,167,168,29,29,235,],[0,1,2,20,4,157,6,7,8,9,20,139,12,141,14,143,16,17,18,19,20,149,22,23,152,153,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,31,4,157,6,7,8,9,31,139,12,141,14,143,16,17,18,19,20,149,22,23,152,25,154,27,157,157,20,159,32,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,32,4,157,6,7,8,9,32,139,12,141,14,143,16,17,18,19,20,149,22,23,152,25,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,33,4,157,6,7,8,9,33,139,12,141,14,143,16,17,18,19,20,149,22,23,152,25,154,27,157,157,20,159,160,161,162,163,164,165,38,39,168,157,157,235,],[0,1,2,34,4,157,6,7,8,9,34,139,12,141,14,143,16,17,18,19,20,149,22,23,152,25,154,27,157,157,20,159,160,161,162,163,164,165,166,39,168,157,157,235,],[0,1,2,35,4,29,6,7,8,9,35,139,51,141,51,143,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,159,160,161,162,35,164,165,166,167,168,29,29,235,],[0,1,2,36,4,157,6,7,8,9,36,139,12,141,14,143,16,17,18,19,20,149,22,23,152,25,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,37,4,157,6,7,8,9,37,139,12,141,14,143,16,17,18,19,20,149,22,23,152,25,154,27,157,157,20,159,160,33,34,163,164,37,38,167,168,157,157,235,],[0,1,2,38,4,157,6,7,8,9,38,139,12,141,14,143,16,17,18,19,20,149,22,23,152,25,154,27,157,157,20,159,160,161,162,163,164,165,38,39,168,157,157,235,],[0,1,2,39,4,157,6,7,8,9,39,139,12,141,14,143,16,17,18,19,20,149,22,23,152,25,154,27,157,157,20,159,160,161,162,163,164,165,166,39,168,157,157,235,],[0,1,2,40,4,157,6,7,8,9,40,139,12,141,14,143,16,17,18,19,20,149,22,23,152,153,154,27,157,157,20,159,160,161,162,163,164,165,166,167,52,157,157,235,],[0,1,2,29,4,29,6,7,8,9,29,139,12,141,14,143,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,159,160,161,162,35,164,165,166,167,168,29,29,235,],[0,1,2,29,4,29,6,7,8,9,29,139,12,141,14,143,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,159,160,161,162,35,164,165,166,167,168,29,29,235,],[0,1,2,157,4,157,6,7,136,9,157,139,12,141,14,143,16,17,18,19,20,149,22,23,152,153,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,29,4,29,6,7,8,9,29,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,31,32,33,34,35,36,37,38,39,40,29,29,43,],[0,1,2,157,4,157,134,7,136,45,157,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,157,157,148,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,29,4,29,6,7,8,46,29,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,31,32,33,34,35,36,37,38,39,40,29,29,235,],[0,1,2,157,4,157,6,7,136,47,157,139,140,141,142,143,16,17,18,147,148,21,150,23,152,153,154,27,157,157,148,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,157,4,157,6,7,136,48,157,139,140,141,142,143,16,17,18,147,20,149,150,23,152,153,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,157,4,157,6,7,136,49,157,139,140,141,142,143,16,17,18,147,20,149,150,23,152,153,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,157,4,157,6,7,136,50,157,11,140,141,142,143,16,17,18,147,148,149,150,23,152,153,154,27,157,157,148,159,160,161,162,163,164,165,166,167,168,157,157,235,],[0,1,2,51,4,29,6,7,8,9,51,11,12,13,14,143,16,17,18,19,20,21,22,23,24,25,26,27,29,29,20,31,32,33,34,35,36,37,38,39,40,29,29,235,],[0,1,2,52,4,157,6,7,8,9,52,139,12,141,14,143,16,17,18,19,20,149,22,23,152,153,154,27,157,157,20,159,160,161,162,163,164,165,166,167,168,157,157,235,],];
+
+ fn is_safe_pair(a: BreakClass, b: BreakClass) -> bool {
+ !matches!((a, b), (CM, CM)|(SP, CM)|(ZWJ, CM)|(BA, CM)|(HY, CM)|(RI, CM)|(CM, SG)|(SP, SG)|(ZWJ, SG)|(BA, SG)|(HY, SG)|(SP, WJ)|(CM, GL)|(SP, GL)|(ZWJ, GL)|(BA, GL)|(HY, GL)|(CM, SP)|(SP, SP)|(ZWJ, SP)|(CM, ZWJ)|(SP, ZWJ)|(ZWJ, ZWJ)|(BA, ZWJ)|(HY, ZWJ)|(RI, ZWJ)|(CM, B2)|(SP, B2)|(ZWJ, B2)|(BA, B2)|(HY, B2)|(CM, BA)|(SP, BA)|(ZWJ, BA)|(CM, BB)|(SP, BB)|(ZWJ, BB)|(BA, BB)|(HY, BB)|(CM, HY)|(SP, HY)|(ZWJ, HY)|(CM, CB)|(SP, CB)|(ZWJ, CB)|(SP, CL)|(SP, CP)|(SP, EX)|(CM, IN)|(SP, IN)|(ZWJ, IN)|(CM, NS)|(SP, NS)|(ZWJ, NS)|(CM, OP)|(SP, OP)|(ZWJ, OP)|(BA, OP)|(HY, OP)|(SP, QU)|(SP, IS)|(CM, NU)|(SP, NU)|(ZWJ, NU)|(BA, NU)|(CM, PO)|(SP, PO)|(ZWJ, PO)|(BA, PO)|(HY, PO)|(CM, PR)|(SP, PR)|(ZWJ, PR)|(BA, PR)|(HY, PR)|(SP, SY)|(CM, AI)|(SP, AI)|(ZWJ, AI)|(BA, AI)|(HY, AI)|(CM, AL)|(SP, AL)|(ZWJ, AL)|(BA, AL)|(HY, AL)|(CM, CJ)|(SP, CJ)|(ZWJ, CJ)|(CM, EB)|(SP, EB)|(ZWJ, EB)|(BA, EB)|(HY, EB)|(CM, EM)|(SP, EM)|(ZWJ, EM)|(BA, EM)|(HY, EM)|(CM, H2)|(SP, H2)|(ZWJ, H2)|(BA, H2)|(HY, H2)|(CM, H3)|(SP, H3)|(ZWJ, H3)|(BA, H3)|(HY, H3)|(CM, HL)|(SP, HL)|(ZWJ, HL)|(BA, HL)|(HY, HL)|(CM, ID)|(SP, ID)|(ZWJ, ID)|(BA, ID)|(HY, ID)|(CM, JL)|(SP, JL)|(ZWJ, JL)|(BA, JL)|(HY, JL)|(CM, JV)|(SP, JV)|(ZWJ, JV)|(BA, JV)|(HY, JV)|(CM, JT)|(SP, JT)|(ZWJ, JT)|(BA, JT)|(HY, JT)|(CM, RI)|(SP, RI)|(ZWJ, RI)|(BA, RI)|(HY, RI)|(RI, RI)|(CM, SA)|(SP, SA)|(ZWJ, SA)|(BA, SA)|(HY, SA)|(CM, XX)|(SP, XX)|(ZWJ, XX)|(BA, XX)|(HY, XX))
+ }
diff --git a/third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json b/third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json
index 24e445a094..33a5447821 100644
--- a/third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json
+++ b/third_party/rust/uniffi-example-arithmetic/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"056d32277bd81040868270ae29d54b3d59ba08833cc307e8aee14a61b914d658","build.rs":"7d98b49c1d9c868c4199f0417eaa017ab459cdd536e9a29851d5f707941f9ead","src/arithmetic.udl":"8554c6907ece627645f6b896f71430e5412bf19b0ac6becf63eb9a69868d0f7a","src/lib.rs":"c454193443e92d49f997c760f4131192fb66bf213bbac1710c1ebde19e765e5d","tests/bindings/test_arithmetic.kts":"e0e9347755db4e18f70b1b74c2d5a4aa328373015090ed959b46d65c2a205d92","tests/bindings/test_arithmetic.py":"3e41d69e21e96a6830197c760f3b7bddd754edc0c5515b7bd33b79cccb10f941","tests/bindings/test_arithmetic.rb":"ea0fdce0a4c7b557b427db77521da05240cd6e87d60a128ac2307fab3bbbc76d","tests/bindings/test_arithmetic.swift":"455b87d95fc690af9c35f9e43676e9c855dedddd2fc1c9e1cbaa6a02835c2d4c","tests/test_generated_bindings.rs":"26b92d6b3e648f6fadd4182cbdba4f412b73da48a789785fd98cd486b29abf05","uniffi.toml":"a2d4f46fa51dc1be1e8bcdf67ec13223637fc1b6c6437455cf53c2dae065fb45"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"359cffb76e0eac82aeec6a667f7670fa4b88346c2dd7c17febe71731fd6df58b","build.rs":"7d98b49c1d9c868c4199f0417eaa017ab459cdd536e9a29851d5f707941f9ead","src/arithmetic.udl":"8554c6907ece627645f6b896f71430e5412bf19b0ac6becf63eb9a69868d0f7a","src/lib.rs":"c454193443e92d49f997c760f4131192fb66bf213bbac1710c1ebde19e765e5d","tests/bindings/test_arithmetic.kts":"e0e9347755db4e18f70b1b74c2d5a4aa328373015090ed959b46d65c2a205d92","tests/bindings/test_arithmetic.py":"3e41d69e21e96a6830197c760f3b7bddd754edc0c5515b7bd33b79cccb10f941","tests/bindings/test_arithmetic.rb":"ea0fdce0a4c7b557b427db77521da05240cd6e87d60a128ac2307fab3bbbc76d","tests/bindings/test_arithmetic.swift":"455b87d95fc690af9c35f9e43676e9c855dedddd2fc1c9e1cbaa6a02835c2d4c","tests/test_generated_bindings.rs":"26b92d6b3e648f6fadd4182cbdba4f412b73da48a789785fd98cd486b29abf05","uniffi.toml":"a2d4f46fa51dc1be1e8bcdf67ec13223637fc1b6c6437455cf53c2dae065fb45"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/uniffi-example-arithmetic/Cargo.toml b/third_party/rust/uniffi-example-arithmetic/Cargo.toml
index 4efddceb0a..52f08bb9f6 100644
--- a/third_party/rust/uniffi-example-arithmetic/Cargo.toml
+++ b/third_party/rust/uniffi-example-arithmetic/Cargo.toml
@@ -28,15 +28,15 @@ crate-type = [
thiserror = "1.0"
[dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
[dev-dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
features = ["bindgen-tests"]
[build-dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
features = ["build"]
diff --git a/third_party/rust/uniffi-example-geometry/.cargo-checksum.json b/third_party/rust/uniffi-example-geometry/.cargo-checksum.json
index 43ff278460..52b8ef33db 100644
--- a/third_party/rust/uniffi-example-geometry/.cargo-checksum.json
+++ b/third_party/rust/uniffi-example-geometry/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"caa1fbdbfdf87670cf571dda2e693e8bc9ba38a0a8fbf803014664e84cde3213","build.rs":"fc5da645c8862e15f3b6879db179a1e5eec6161dc1cfbf95a4db9daf107a133f","src/geometry.udl":"7da7a6ec080c7117ec3c25206e23f9ed436e60b1a26fba34f991547680443550","src/lib.rs":"f9d004c97efb1a719368169f0aab181f27439eda3520c1afaca2420433226682","tests/bindings/test_geometry.kts":"e537185e3c699df1c0468525700e8a38f9a504b2a663c38442146b951e38e9a7","tests/bindings/test_geometry.py":"3ea483b8a4fbe13aefa6641177ae149f75f734bc32bf0da533b97c1abf3dc317","tests/bindings/test_geometry.rb":"17c2fe8a7b477419a6646983dd88f1b07a0304b58a568c03e9bfa640d5b2df5c","tests/bindings/test_geometry.swift":"a61fec6bfe16020809e20e4da372748c24366767138c5672a0bfff85c4b62d78","tests/test_generated_bindings.rs":"ff8fc093ccb6ee3ee2235c09276c7bb87234ad143667429cb721e46379577f3d"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"2d2c1ec66f7e2aaa4553b5338e68a7b64d68b84152547e1ccc4eeb019ca97103","build.rs":"fc5da645c8862e15f3b6879db179a1e5eec6161dc1cfbf95a4db9daf107a133f","src/geometry.udl":"7da7a6ec080c7117ec3c25206e23f9ed436e60b1a26fba34f991547680443550","src/lib.rs":"f9d004c97efb1a719368169f0aab181f27439eda3520c1afaca2420433226682","tests/bindings/test_geometry.kts":"e537185e3c699df1c0468525700e8a38f9a504b2a663c38442146b951e38e9a7","tests/bindings/test_geometry.py":"600e74ba0eba4e35d824c8f6d5bd5a2120a470017e8465c32d1e954a1939d323","tests/bindings/test_geometry.rb":"651de70af595f8b52ef030a03356939e8c1d0b40e44b4155b45d565d1d1dcbed","tests/bindings/test_geometry.swift":"a61fec6bfe16020809e20e4da372748c24366767138c5672a0bfff85c4b62d78","tests/test_generated_bindings.rs":"ff8fc093ccb6ee3ee2235c09276c7bb87234ad143667429cb721e46379577f3d"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/uniffi-example-geometry/Cargo.toml b/third_party/rust/uniffi-example-geometry/Cargo.toml
index b9fe377732..b9c8a0beb6 100644
--- a/third_party/rust/uniffi-example-geometry/Cargo.toml
+++ b/third_party/rust/uniffi-example-geometry/Cargo.toml
@@ -25,15 +25,15 @@ crate-type = [
]
[dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
[dev-dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
features = ["bindgen-tests"]
[build-dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
features = ["build"]
diff --git a/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py
index a40e2af6ec..fd6772be24 100644
--- a/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py
+++ b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.py
@@ -1,10 +1,10 @@
from geometry import *
-ln1 = Line(Point(0,0), Point(1,2))
-ln2 = Line(Point(1,1), Point(2,2))
+ln1 = Line(start=Point(coord_x=0, coord_y=0), end=Point(coord_x=1, coord_y=2))
+ln2 = Line(start=Point(coord_x=1, coord_y=1), end=Point(coord_x=2, coord_y=2))
assert gradient(ln1) == 2
assert gradient(ln2) == 1
-assert intersection(ln1, ln2) == Point(0, 0)
+assert intersection(ln1, ln2) == Point(coord_x=0, coord_y=0)
assert intersection(ln1, ln1) is None
diff --git a/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb
index 8b1280d823..90fdff684e 100644
--- a/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb
+++ b/third_party/rust/uniffi-example-geometry/tests/bindings/test_geometry.rb
@@ -6,11 +6,11 @@ require 'geometry'
include Test::Unit::Assertions
include Geometry
-ln1 = Line.new(Point.new(0.0, 0.0), Point.new(1.0, 2.0))
-ln2 = Line.new(Point.new(1.0, 1.0), Point.new(2.0, 2.0))
+ln1 = Line.new(start: Point.new(coord_x: 0.0, coord_y: 0.0), _end: Point.new(coord_x: 1.0, coord_y: 2.0))
+ln2 = Line.new(start: Point.new(coord_x: 1.0, coord_y: 1.0), _end: Point.new(coord_x: 2.0, coord_y: 2.0))
assert_equal Geometry.gradient(ln1), 2
assert_equal Geometry.gradient(ln2), 1
-assert_equal Geometry.intersection(ln1, ln2), Point.new(0, 0)
+assert_equal Geometry.intersection(ln1, ln2), Point.new(coord_x: 0, coord_y: 0)
assert Geometry.intersection(ln1, ln1).nil?
diff --git a/third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json b/third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json
index 87e0d96e40..2f1f261a13 100644
--- a/third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json
+++ b/third_party/rust/uniffi-example-rondpoint/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"e945bedc4897a588199714e71d6210a77d051925af39b345e605fc58fb782b50","build.rs":"ba88cce38ecd3321a7a93623755e3339e255360a7f946d3779ded804662c081a","src/lib.rs":"740d70ab5ca22eefcc291a56a9e4ed84e9669f4cfe3890e7d79bc56ae4b991a3","src/rondpoint.udl":"c903cb8c95b3ec1b103350857c3c3bc428bfd90c86a6c48089db9e0fc6e41eb5","tests/bindings/test_rondpoint.kts":"4aac8353278807f4add95c81f4c6c61187204b9767f882fd64872ed8ac1f6451","tests/bindings/test_rondpoint.py":"d618274170af767f8a5614a2565ea698b26ea3e1a222d5c110e7b2d00763e73b","tests/bindings/test_rondpoint.rb":"9cc49df311823d6caedbe7b05ff8c4da6329063c2ce16810192aaaa7edcdf5f5","tests/bindings/test_rondpoint.swift":"fa806e7e09c22ed44496658f6e0781765447bbdd250d7adf4b1152248ed70e69","tests/test_generated_bindings.rs":"5464f89e91c458f164b83a454c6df67a2953873e8a785a4720a2253d843f88e5"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"c0706abf631a178bfbc2e53b0b714d4a7b73059c46e1326efa4f52d235dc05f5","build.rs":"ba88cce38ecd3321a7a93623755e3339e255360a7f946d3779ded804662c081a","src/lib.rs":"740d70ab5ca22eefcc291a56a9e4ed84e9669f4cfe3890e7d79bc56ae4b991a3","src/rondpoint.udl":"c903cb8c95b3ec1b103350857c3c3bc428bfd90c86a6c48089db9e0fc6e41eb5","tests/bindings/test_rondpoint.kts":"4aac8353278807f4add95c81f4c6c61187204b9767f882fd64872ed8ac1f6451","tests/bindings/test_rondpoint.py":"af25a56c35da9a934fb9f098c25f57329c53d461be378e4c5089b12a45efa28b","tests/bindings/test_rondpoint.rb":"d4b4523084534266ea7ef3161021b9903cb8d7a75cf4624c59055af9f02d22f9","tests/bindings/test_rondpoint.swift":"fa806e7e09c22ed44496658f6e0781765447bbdd250d7adf4b1152248ed70e69","tests/test_generated_bindings.rs":"5464f89e91c458f164b83a454c6df67a2953873e8a785a4720a2253d843f88e5"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/uniffi-example-rondpoint/Cargo.toml b/third_party/rust/uniffi-example-rondpoint/Cargo.toml
index 64232cf2d2..44d9628df4 100644
--- a/third_party/rust/uniffi-example-rondpoint/Cargo.toml
+++ b/third_party/rust/uniffi-example-rondpoint/Cargo.toml
@@ -25,15 +25,15 @@ crate-type = [
]
[dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
[dev-dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
features = ["bindgen-tests"]
[build-dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
features = ["build"]
diff --git a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py
index ecfcc1e527..0b47c0fa5a 100644
--- a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py
+++ b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.py
@@ -2,7 +2,7 @@ import sys
import ctypes
from rondpoint import *
-dico = Dictionnaire(Enumeration.DEUX, True, 0, 123456789)
+dico = Dictionnaire(un=Enumeration.DEUX, deux=True, petit_nombre=0, gros_nombre=123456789)
copyDico = copie_dictionnaire(dico)
assert dico == copyDico
diff --git a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb
index 0121f6e0f9..faa4062019 100644
--- a/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb
+++ b/third_party/rust/uniffi-example-rondpoint/tests/bindings/test_rondpoint.rb
@@ -6,7 +6,12 @@ require 'rondpoint'
include Test::Unit::Assertions
include Rondpoint
-dico = Dictionnaire.new Enumeration::DEUX, true, 0, 123_456_789
+dico = Dictionnaire.new(
+ un: Enumeration::DEUX,
+ deux: true,
+ petit_nombre: 0,
+ gros_nombre: 123_456_789
+)
assert_equal dico, Rondpoint.copie_dictionnaire(dico)
diff --git a/third_party/rust/uniffi-example-sprites/.cargo-checksum.json b/third_party/rust/uniffi-example-sprites/.cargo-checksum.json
index 3542d7cfd7..8a890fb65c 100644
--- a/third_party/rust/uniffi-example-sprites/.cargo-checksum.json
+++ b/third_party/rust/uniffi-example-sprites/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"e2d06b68b0865f390496bffce512d19d5fc473b64ff23c705daabb4169b8301c","build.rs":"7e9d92d7c8fc17b359a29117b137ffc4d32f6c10b450d03e30a396339d8c9099","src/lib.rs":"d7984c0c10011b1bd939bce71dae7437ebb9090583b5d1b1cc133c2e5f60ab66","src/sprites.udl":"bfd35f04ba0549301189dfb8fc45b0f39bad00956c324f33be0e845fb7ff78aa","tests/bindings/test_sprites.kts":"06ed115325f37ce59ed6f33e2d651cd2aa352fddcc644580f62a6da6ca075844","tests/bindings/test_sprites.py":"2e6ce838cfb387586257703c3500062438e840dd7ae57d185cdc244dc0745b8f","tests/bindings/test_sprites.rb":"6289a1833c7c8f4583ee4f0488d680de2ee46cfb203095a9b66d7234e2f07d53","tests/bindings/test_sprites.swift":"b2c0a6f4d5edfd7de7c2ba77b838865ffda153a6f364f273456175192d3e6e00","tests/test_generated_bindings.rs":"9a22d693c97fc6d90031cc60f61ece1d9279165ad6a92c9fe937448e126e8de6"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"94385b28fdc7e33b7019fada486124c027d29b3d2b11b57e8d488658386f7974","build.rs":"7e9d92d7c8fc17b359a29117b137ffc4d32f6c10b450d03e30a396339d8c9099","src/lib.rs":"d7984c0c10011b1bd939bce71dae7437ebb9090583b5d1b1cc133c2e5f60ab66","src/sprites.udl":"bfd35f04ba0549301189dfb8fc45b0f39bad00956c324f33be0e845fb7ff78aa","tests/bindings/test_sprites.kts":"06ed115325f37ce59ed6f33e2d651cd2aa352fddcc644580f62a6da6ca075844","tests/bindings/test_sprites.py":"f976f2be7ab8b88e8b84def760a849c0d98f4c7b481f054f92b566325d78d52d","tests/bindings/test_sprites.rb":"009d545bb7167b7218211430cfaeeb143cc30617eedcf3e51baafe752ad43241","tests/bindings/test_sprites.swift":"b2c0a6f4d5edfd7de7c2ba77b838865ffda153a6f364f273456175192d3e6e00","tests/test_generated_bindings.rs":"9a22d693c97fc6d90031cc60f61ece1d9279165ad6a92c9fe937448e126e8de6"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/uniffi-example-sprites/Cargo.toml b/third_party/rust/uniffi-example-sprites/Cargo.toml
index 6caed17ab1..c6dc9d1f4b 100644
--- a/third_party/rust/uniffi-example-sprites/Cargo.toml
+++ b/third_party/rust/uniffi-example-sprites/Cargo.toml
@@ -25,15 +25,15 @@ crate-type = [
]
[dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
[dev-dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
features = ["bindgen-tests"]
[build-dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
features = ["build"]
diff --git a/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py
index 5142c2fc42..d04742e076 100644
--- a/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py
+++ b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.py
@@ -1,17 +1,17 @@
from sprites import *
sempty = Sprite(None)
-assert sempty.get_position() == Point(0, 0)
+assert sempty.get_position() == Point(x=0, y=0)
-s = Sprite(Point(0, 1))
-assert s.get_position() == Point(0, 1)
+s = Sprite(Point(x=0, y=1))
+assert s.get_position() == Point(x=0, y=1)
-s.move_to(Point(1, 2))
-assert s.get_position() == Point(1, 2)
+s.move_to(Point(x=1, y=2))
+assert s.get_position() == Point(x=1, y=2)
-s.move_by(Vector(-4, 2))
-assert s.get_position() == Point(-3, 4)
+s.move_by(Vector(dx=-4, dy=2))
+assert s.get_position() == Point(x=-3, y=4)
-srel = Sprite.new_relative_to(Point(0, 1), Vector(1, 1.5))
-assert srel.get_position() == Point(1, 2.5)
+srel = Sprite.new_relative_to(Point(x=0, y=1), Vector(dx=1, dy=1.5))
+assert srel.get_position() == Point(x=1, y=2.5)
diff --git a/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb
index 9d79b57026..fa73043979 100644
--- a/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb
+++ b/third_party/rust/uniffi-example-sprites/tests/bindings/test_sprites.rb
@@ -7,16 +7,16 @@ include Test::Unit::Assertions
include Sprites
sempty = Sprite.new(nil)
-assert_equal sempty.get_position, Point.new(0, 0)
+assert_equal sempty.get_position, Point.new(x: 0, y: 0)
-s = Sprite.new(Point.new(0, 1))
-assert_equal s.get_position, Point.new(0, 1)
+s = Sprite.new(Point.new(x: 0, y: 1))
+assert_equal s.get_position, Point.new(x: 0, y: 1)
-s.move_to(Point.new(1, 2))
-assert_equal s.get_position, Point.new(1, 2)
+s.move_to(Point.new(x: 1, y: 2))
+assert_equal s.get_position, Point.new(x: 1, y: 2)
-s.move_by(Vector.new(-4, 2))
-assert_equal s.get_position, Point.new(-3, 4)
+s.move_by(Vector.new(dx: -4, dy: 2))
+assert_equal s.get_position, Point.new(x: -3, y: 4)
-srel = Sprite.new_relative_to(Point.new(0, 1), Vector.new(1, 1.5))
-assert_equal srel.get_position, Point.new(1, 2.5)
+srel = Sprite.new_relative_to(Point.new(x: 0, y: 1), Vector.new(dx: 1, dy: 1.5))
+assert_equal srel.get_position, Point.new(x: 1, y: 2.5)
diff --git a/third_party/rust/uniffi-example-todolist/.cargo-checksum.json b/third_party/rust/uniffi-example-todolist/.cargo-checksum.json
index a0bf1d826c..6b3a53fc5e 100644
--- a/third_party/rust/uniffi-example-todolist/.cargo-checksum.json
+++ b/third_party/rust/uniffi-example-todolist/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"cb308f7bd5299ed45a3bd516390e21e28d0b02a1913e25917e30e9b8ebe084e3","build.rs":"709d073560a66876d2b975f5145a2dc36e1e3420d79328c8b62666526cae5b2d","src/lib.rs":"ccf6851beb2a3e481541dd8a3c3e3d2fbd513a410eef820221942098212a8184","src/todolist.udl":"1f8a24049c2340b9184e95facfc191ecdcb91541729ae7f20b4625d67685f13c","tests/bindings/test_todolist.kts":"f3d29b48e0193563fc4f131d91ea697f758174dcdb80ea554f233949e575bf55","tests/bindings/test_todolist.py":"f7430af9347df0daa954d38bc2203ce400affbb9a53fced4bb67a6796afa0664","tests/bindings/test_todolist.rb":"6524b5271a9cc0e2d78ca9f86ccb6973889926688a0843b4505a4f62d48f6dcb","tests/bindings/test_todolist.swift":"d1911b85fe0c8c0b42e5421b5af5d7359c9a65bba477d23560eb4b0f52e80662","tests/test_generated_bindings.rs":"46ef1fbedaac0c4867812ef2632a641eab36ab0ee12f5757567dd037aeddcbd3"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"9b182bf2d363240fe1f7b41c73da4bde96d87562812885e4f3175cb6b92ea56b","build.rs":"709d073560a66876d2b975f5145a2dc36e1e3420d79328c8b62666526cae5b2d","src/lib.rs":"ccf6851beb2a3e481541dd8a3c3e3d2fbd513a410eef820221942098212a8184","src/todolist.udl":"1f8a24049c2340b9184e95facfc191ecdcb91541729ae7f20b4625d67685f13c","tests/bindings/test_todolist.kts":"f3d29b48e0193563fc4f131d91ea697f758174dcdb80ea554f233949e575bf55","tests/bindings/test_todolist.py":"8ea1377fe336adaf4cade0fe073ae5825c3b38c63e46b7f4e92b87c0fe57c036","tests/bindings/test_todolist.rb":"e8840eb81477048718a56c5fa02c0fb2e28cf0a598a20c9393ba2262d6ffb89b","tests/bindings/test_todolist.swift":"d1911b85fe0c8c0b42e5421b5af5d7359c9a65bba477d23560eb4b0f52e80662","tests/test_generated_bindings.rs":"46ef1fbedaac0c4867812ef2632a641eab36ab0ee12f5757567dd037aeddcbd3"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/uniffi-example-todolist/Cargo.toml b/third_party/rust/uniffi-example-todolist/Cargo.toml
index 9421e726a0..cf4de491cd 100644
--- a/third_party/rust/uniffi-example-todolist/Cargo.toml
+++ b/third_party/rust/uniffi-example-todolist/Cargo.toml
@@ -29,15 +29,15 @@ once_cell = "1.12"
thiserror = "1.0"
[dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
[dev-dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
features = ["bindgen-tests"]
[build-dependencies.uniffi]
-version = "0.25"
+version = "0.27"
path = "../../uniffi"
features = ["build"]
diff --git a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py
index 017e999fb2..7b676c83de 100644
--- a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py
+++ b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.py
@@ -2,7 +2,7 @@ from todolist import *
todo = TodoList()
-entry = TodoEntry("Write bindings for strings in records")
+entry = TodoEntry(text="Write bindings for strings in records")
todo.add_item("Write python bindings")
@@ -20,7 +20,7 @@ assert(todo.get_last_entry().text == "Write bindings for strings in records")
todo.add_item("Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
assert(todo.get_last() == "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
-entry2 = TodoEntry("Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
+entry2 = TodoEntry(text="Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
todo.add_entry(entry2)
assert(todo.get_last_entry().text == "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
diff --git a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb
index d9e04f92e7..fc1a823f52 100644
--- a/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb
+++ b/third_party/rust/uniffi-example-todolist/tests/bindings/test_todolist.rb
@@ -7,7 +7,7 @@ include Test::Unit::Assertions
include Todolist
todo = TodoList.new
-entry = TodoEntry.new 'Write bindings for strings in records'
+entry = TodoEntry.new(text: 'Write bindings for strings in records')
todo.add_item('Write ruby bindings')
@@ -25,7 +25,7 @@ assert_equal todo.get_last_entry.text, 'Write bindings for strings in records'
todo.add_item("Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣")
assert_equal todo.get_last, "Test Ünicode hàndling without an entry can't believe I didn't test this at first 🤣"
-entry2 = TodoEntry.new("Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
+entry2 = TodoEntry.new(text: "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣")
todo.add_entry(entry2)
assert_equal todo.get_last_entry.text, "Test Ünicode hàndling in an entry can't believe I didn't test this at first 🤣"
@@ -44,4 +44,4 @@ todo.add_item "Test liveness after being demoted from default"
assert todo.get_last == "Test liveness after being demoted from default"
todo2.add_item "Test shared state through local vs default reference"
-assert Todolist.get_default_list.get_last == "Test shared state through local vs default reference" \ No newline at end of file
+assert Todolist.get_default_list.get_last == "Test shared state through local vs default reference"
diff --git a/third_party/rust/uniffi/.cargo-checksum.json b/third_party/rust/uniffi/.cargo-checksum.json
index 74d539b505..98c4aca54e 100644
--- a/third_party/rust/uniffi/.cargo-checksum.json
+++ b/third_party/rust/uniffi/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"5a5cf41b9eb4aac8e312fc9584c0d47585a5a20b13bc7cfb14c9b8813ea596e6","release.toml":"1aa1b131d4cc93b5eba8758a4401c70bc0d7fe5861e2ec147e9259fe7c0da472","src/cli.rs":"0b4791c263d6cf54e4e63dff9a8ead59838d5e7b45fbf5b7f77ab16f602bdb0d","src/lib.rs":"6bc2c11f466fbcd128827a57b5f93a77f716262200f4e5ad2ed8dd75845320fc","tests/ui/proc_macro_arc.rs":"fedc429603753e8ef953642a7295323ccb3f76fd3ae1ab181ad90c5eb88212bb","tests/ui/proc_macro_arc.stderr":"a24af227b907328c9cac6317ec9f43dbc45d7f7c77c603e5d72db7fa050e8b01","tests/ui/version_mismatch.rs":"16ea359e5853517ee0d0704c015ae8c825533109fbefd715130d0f4a51f15898","tests/ui/version_mismatch.stderr":"21dcb836253312ba8e3a0502cce6ff279818aaaadcea9628a41b196e0c8c94b6"},"package":"21345172d31092fd48c47fd56c53d4ae9e41c4b1f559fb8c38c1ab1685fd919f"} \ No newline at end of file
+{"files":{"Cargo.toml":"eb974d356d4da93a076434ff428c448f70e036a724bd9a0a7eae6b9ddff2346e","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","release.toml":"1aa1b131d4cc93b5eba8758a4401c70bc0d7fe5861e2ec147e9259fe7c0da472","src/cli.rs":"5c0b9bb93665f2f49f7e90335e65206887e26e96f2a533eb1203be27c9380c84","src/lib.rs":"422503d7cbac1360852287b1810c99663669625b9abf080a5fec22058bb73d8c","tests/ui/proc_macro_arc.rs":"fedc429603753e8ef953642a7295323ccb3f76fd3ae1ab181ad90c5eb88212bb","tests/ui/proc_macro_arc.stderr":"a24af227b907328c9cac6317ec9f43dbc45d7f7c77c603e5d72db7fa050e8b01","tests/ui/version_mismatch.rs":"16ea359e5853517ee0d0704c015ae8c825533109fbefd715130d0f4a51f15898","tests/ui/version_mismatch.stderr":"21dcb836253312ba8e3a0502cce6ff279818aaaadcea9628a41b196e0c8c94b6"},"package":"a5566fae48a5cb017005bf9cd622af5236b2a203a13fb548afde3506d3c68277"} \ No newline at end of file
diff --git a/third_party/rust/uniffi/Cargo.toml b/third_party/rust/uniffi/Cargo.toml
index 475c8ab9be..374e365502 100644
--- a/third_party/rust/uniffi/Cargo.toml
+++ b/third_party/rust/uniffi/Cargo.toml
@@ -12,11 +12,12 @@
[package]
edition = "2021"
name = "uniffi"
-version = "0.25.3"
+version = "0.27.1"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
description = "a multi-language bindings generator for rust"
homepage = "https://mozilla.github.io/uniffi-rs"
documentation = "https://mozilla.github.io/uniffi-rs"
+readme = "README.md"
keywords = [
"ffi",
"bindgen",
@@ -41,18 +42,18 @@ features = [
optional = true
[dependencies.uniffi_bindgen]
-version = "=0.25.3"
+version = "=0.27.1"
optional = true
[dependencies.uniffi_build]
-version = "=0.25.3"
+version = "=0.27.1"
optional = true
[dependencies.uniffi_core]
-version = "=0.25.3"
+version = "=0.27.1"
[dependencies.uniffi_macros]
-version = "=0.25.3"
+version = "=0.27.1"
[dev-dependencies.trybuild]
version = "1"
diff --git a/third_party/rust/uniffi/README.md b/third_party/rust/uniffi/README.md
new file mode 100644
index 0000000000..64ac3486a3
--- /dev/null
+++ b/third_party/rust/uniffi/README.md
@@ -0,0 +1,81 @@
+# UniFFI - a multi-language bindings generator for Rust
+
+UniFFI is a toolkit for building cross-platform software components in Rust.
+
+For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/)
+or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components).
+
+By writing your core business logic in Rust and describing its interface in an "object model",
+you can use UniFFI to help you:
+
+* Compile your Rust code into a shared library for use on different target platforms.
+* Generate bindings to load and use the library from different target languages.
+
+You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html).
+
+UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers;
+written once in Rust, auto-generated bindings allow that functionality to be called
+from both Kotlin (for Android apps) and Swift (for iOS apps).
+It also has a growing community of users shipping various cool things to many users.
+
+UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**.
+Additional foreign language bindings can be developed externally and we welcome contributions to list them here.
+See [Third-party foreign language bindings](#third-party-foreign-language-bindings).
+
+## User Guide
+
+You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/).
+
+We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on.
+We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time.
+
+### Etymology and Pronunciation
+
+ˈjuːnɪfaɪ. Pronounced to rhyme with "unify".
+
+A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages.
+
+uni - [Latin ūni-, from ūnus, one]
+FFI - [Abbreviation, Foreign Function Interface]
+
+## Alternative tools
+
+Other tools we know of which try and solve a similarly shaped problem are:
+
+* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of
+ the different approach taken by that tool](docs/diplomat-and-macros.md)
+* [Interoptopus](https://github.com/ralfbiedert/interoptopus/)
+
+(Please open a PR if you think other tools should be listed!)
+
+## Third-party foreign language bindings
+
+* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native.
+* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go)
+* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs)
+* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart)
+
+### External resources
+
+There are a few third-party resources that make it easier to work with UniFFI:
+
+* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/).
+* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts.
+* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful.
+* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure.
+
+(Please open a PR if you think other resources should be listed!)
+
+## Contributing
+
+If this tool sounds interesting to you, please help us develop it! You can:
+
+* View the [contributor guidelines](./docs/contributing.md).
+* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub.
+* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org)
+ room on Matrix.
+
+## Code of Conduct
+
+This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md).
diff --git a/third_party/rust/uniffi/src/cli.rs b/third_party/rust/uniffi/src/cli.rs
index b2d3adc2ae..77d7f219a9 100644
--- a/third_party/rust/uniffi/src/cli.rs
+++ b/third_party/rust/uniffi/src/cli.rs
@@ -5,6 +5,7 @@
use camino::Utf8PathBuf;
use clap::{Parser, Subcommand};
use uniffi_bindgen::bindings::TargetLanguage;
+use uniffi_bindgen::BindingGeneratorDefault;
// Structs to help our cmdline parsing. Note that docstrings below form part
// of the "help" output.
@@ -35,7 +36,7 @@ enum Commands {
#[clap(long, short)]
no_format: bool,
- /// Path to the optional uniffi config file. If not provided, uniffi-bindgen will try to guess it from the UDL's file location.
+ /// Path to optional uniffi config file. This config is merged with the `uniffi.toml` config present in each crate, with its values taking precedence.
#[clap(long, short)]
config: Option<Utf8PathBuf>,
@@ -95,21 +96,29 @@ pub fn run_main() -> anyhow::Result<()> {
if lib_file.is_some() {
panic!("--lib-file is not compatible with --library.")
}
- if config.is_some() {
- panic!("--config is not compatible with --library. The config file(s) will be found automatically.")
- }
let out_dir = out_dir.expect("--out-dir is required when using --library");
if language.is_empty() {
panic!("please specify at least one language with --language")
}
uniffi_bindgen::library_mode::generate_bindings(
- &source, crate_name, &language, &out_dir, !no_format,
+ &source,
+ crate_name,
+ &BindingGeneratorDefault {
+ target_languages: language,
+ try_format_code: !no_format,
+ },
+ config.as_deref(),
+ &out_dir,
+ !no_format,
)?;
} else {
uniffi_bindgen::generate_bindings(
&source,
config.as_deref(),
- language,
+ BindingGeneratorDefault {
+ target_languages: language,
+ try_format_code: !no_format,
+ },
out_dir.as_deref(),
lib_file.as_deref(),
crate_name.as_deref(),
diff --git a/third_party/rust/uniffi/src/lib.rs b/third_party/rust/uniffi/src/lib.rs
index 0625bd9c66..319b3c7836 100644
--- a/third_party/rust/uniffi/src/lib.rs
+++ b/third_party/rust/uniffi/src/lib.rs
@@ -17,8 +17,11 @@ pub use uniffi_bindgen::bindings::ruby::run_test as ruby_run_test;
pub use uniffi_bindgen::bindings::swift::run_test as swift_run_test;
#[cfg(feature = "bindgen")]
pub use uniffi_bindgen::{
- bindings::TargetLanguage, generate_bindings, generate_component_scaffolding,
- generate_component_scaffolding_for_crate, print_repr,
+ bindings::kotlin::gen_kotlin::KotlinBindingGenerator,
+ bindings::python::gen_python::PythonBindingGenerator,
+ bindings::ruby::gen_ruby::RubyBindingGenerator,
+ bindings::swift::gen_swift::SwiftBindingGenerator, bindings::TargetLanguage, generate_bindings,
+ generate_component_scaffolding, generate_component_scaffolding_for_crate, print_repr,
};
#[cfg(feature = "build")]
pub use uniffi_build::{generate_scaffolding, generate_scaffolding_for_crate};
diff --git a/third_party/rust/uniffi_bindgen/.cargo-checksum.json b/third_party/rust/uniffi_bindgen/.cargo-checksum.json
index 880d79a96f..40b0b6e3cc 100644
--- a/third_party/rust/uniffi_bindgen/.cargo-checksum.json
+++ b/third_party/rust/uniffi_bindgen/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"5285a403f48ecf7a073cbe1839c7afbd0e14998e8315b1ff8ecefb9e7171fc4e","askama.toml":"1a245b7803adca782837e125c49100147d2de0d5a1c949ff95e91af1701f6058","src/backend/config.rs":"4861dbf251dbb10beb1ed7e3eea7d79499a0de1cd9ce9ee8381a0e729c097dea","src/backend/filters.rs":"2da4eaa9af92e449f2fa20d06fc2ab2f758a9a10d3ad6cb8a94975184d40d2ff","src/backend/mod.rs":"2ee9d974cd259f7fb156028b4f4f7601691e94fb5692a6daf0d362df3ecf79a8","src/backend/types.rs":"598df3a861f5d53b2c710848943f6049dd43cb4f37aa81f2c08fd36fc5b2f5d5","src/bindings/kotlin/gen_kotlin/callback_interface.rs":"a6c7796ca66cbaabeef401b939d3c707bba17a77581da36a3a0b46f87630440e","src/bindings/kotlin/gen_kotlin/compounds.rs":"ebd2111a74032b336e0768facfb756a9422da2f9b413ee929b24c1c4315e6a06","src/bindings/kotlin/gen_kotlin/custom.rs":"7e619f7320796ecd8c4ced82904b4bd3c6a0043b55d5829274ab3040cdf9cd7f","src/bindings/kotlin/gen_kotlin/enum_.rs":"6559bb00d8e359126b016e549263c0c9bc1dfc5654ed662c0c2912b47931b1e4","src/bindings/kotlin/gen_kotlin/executor.rs":"58a192123fd2dd4b625f29d95ae6bf5161c2fef7bf50aa8790c3ae0e7a9430d9","src/bindings/kotlin/gen_kotlin/external.rs":"bcd2a44f2559a124aa287944ab59296239033372c6b4a7a3b625b1d41c441de2","src/bindings/kotlin/gen_kotlin/miscellany.rs":"6541987e627c4ff27a17ebe919b2b5cd97cb66ff41187ed636396b4e35ea2414","src/bindings/kotlin/gen_kotlin/mod.rs":"ccae80314058df0b4988d0965ab62b0dc872e8676592e7b55037cd54011e6854","src/bindings/kotlin/gen_kotlin/object.rs":"539ec05386c1e844bef09d4de8374760daa5ba99b009615c04be9c3927feb4c9","src/bindings/kotlin/gen_kotlin/primitives.rs":"2c3020416384a67855ca5086e485c4db6d7dcc3ce51343217b4a914b318ae350","src/bindings/kotlin/gen_kotlin/record.rs":"96fd1a180095a062b4a9b71d4f603b232f0133f46355a3e427c4064701d900f2","src/bindings/kotlin/gen_kotlin/variant.rs":"d111d6888745195fc2c24bdddc57359e771616102a8d182c5c8ad268b0a13460","src/bindings/kotlin/mod.rs":"ef88eb9b5b7d6f920c62a525ea4d4bf2a3b1a9154afaa012cdb2feea597fbf23","src/bindings/kotlin/templates/Async.kt":"064ce385ac0e68719de625e0172908257714b62da296ff14b2c0153b9966212a","src/bindings/kotlin/templates/BooleanHelper.kt":"28e8a5088c8d58c9bfdbc575af8d8725060521fdd7d092684a8044b24ae567c7","src/bindings/kotlin/templates/ByteArrayHelper.kt":"6fbb424556f631beb7f28c4168c568ad840445496a29d5c8f40a9a591b1661b1","src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt":"29fab10a8f6b699471e793e8d53f5bed74803a8c433ff80ccef5f56cf742c54a","src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt":"3b063ea03959c95327b6082c7edc0db0df83ee8b9e8d643b92ca45cf4fda458a","src/bindings/kotlin/templates/CustomTypeTemplate.kt":"be9bdc716731f3935a4d48728e33bfeb4acd514f3719ddbb273adcd6fb4ab31f","src/bindings/kotlin/templates/DurationHelper.kt":"414a98161538a26f3a9b357353270c1f245ad6ceed99496aca7162cf473a92fd","src/bindings/kotlin/templates/EnumTemplate.kt":"865fb1badd1a128390903ab8d9f42f9208c6db0eac5e53b88207282176cfd67f","src/bindings/kotlin/templates/ErrorTemplate.kt":"394c0093c0c86a0f2a14cd5fa60a70ba9970c65448867b6aca86fc25cfe08a4c","src/bindings/kotlin/templates/ExternalTypeTemplate.kt":"40df49116f9ea227c9a64a4f45bb7c2e99275c62e93f75290808e2c930911fba","src/bindings/kotlin/templates/FfiConverterTemplate.kt":"aa22962aaa9f641d48ccf44cb56d9f8a7736cbfaa01e1a1656662cfe5dd5c1d7","src/bindings/kotlin/templates/Float32Helper.kt":"662d95af3b629d143fb4d47cb7e9aa26ed28a5f3846de0341e28b0f9fb08bc25","src/bindings/kotlin/templates/Float64Helper.kt":"a77d099fa7d91e8702c1700e7949ffb6aaba9c6e8041ff48bab34b8e1fc9a0aa","src/bindings/kotlin/templates/ForeignExecutorTemplate.kt":"09c63a67adb8c6cb807108f02d7695d3425401ea0cc51b582cfd469a322fcce0","src/bindings/kotlin/templates/Helpers.kt":"90a11ec576e82265ba0f95fc330053779aada5976477f0d4a6b38619da1282cf","src/bindings/kotlin/templates/Int16Helper.kt":"7f83c4a48e1f3b2a59a3ca6a2662be8bc9baf3a5a748b31223cb3f51721ef249","src/bindings/kotlin/templates/Int32Helper.kt":"e02e4702175554b09fd2dd6ac3089dcd2c395f08ec60e762159566a9c9889450","src/bindings/kotlin/templates/Int64Helper.kt":"7a6fd6ca486852c89399c699935a9dfa1c32b9356d9a965cfde532581f05d9fa","src/bindings/kotlin/templates/Int8Helper.kt":"0554545494b6b9a76ce94f9c1723f8cf4230a13076feb75d620b1c9ca1ac4668","src/bindings/kotlin/templates/MapTemplate.kt":"07d20d8cf58a4bca950ac22dbec5e3471ac6c18c3cca562e45628de827b03ccf","src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt":"22226bb8dde52f12c77b40bbb8f7ceb7dcef313950b8d42b2f5fe44777158bfc","src/bindings/kotlin/templates/ObjectRuntime.kt":"7f38f54a0c889d7534d23afdace6b87b6ced5c024a36b5450078a06e071caad8","src/bindings/kotlin/templates/ObjectTemplate.kt":"3f3baea52b6923446827ea1ee3f5160edfe81e00c11f61ea1f72dbc6b796a6d8","src/bindings/kotlin/templates/OptionalTemplate.kt":"c916c4545d37087ee01a6b6aef966928691d26be539c388fce608e9e3ff4b0e7","src/bindings/kotlin/templates/RecordTemplate.kt":"8d573856de75b55b985594ac4e4a6f08da931dce6b52420654b5bb080eec414f","src/bindings/kotlin/templates/RustBufferTemplate.kt":"002878ce9ce9924231e55853b86768fc3dec2caef6a28098f01c3edd739ca076","src/bindings/kotlin/templates/SequenceTemplate.kt":"477a0f6714af151ca58cfc7c4f2cf0e878d391dd9db4efe964f19d5ad4f544f0","src/bindings/kotlin/templates/StringHelper.kt":"ec0441da90a394616d0ba3492eca50602161fe42062bc4f60e9a23191e71e009","src/bindings/kotlin/templates/TimestampHelper.kt":"353c2890f06ad6dda238e9aebb4bdff7bb838e17e46abf351ed3ff1fbc4e6580","src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt":"888ef82e2637ab104f0821803666c77212b5d5940414f71e899f8f8968ffe572","src/bindings/kotlin/templates/Types.kt":"4d87ef529f666db532fb5355a339fd50be3edd6225a703564455b81ef4d4a16d","src/bindings/kotlin/templates/UInt16Helper.kt":"e84a1f30a5a899ba2c5db614d3f3c74f25bccf6dd99bf68b8830829332d051e9","src/bindings/kotlin/templates/UInt32Helper.kt":"7cdf08cc580046935f27ba07b53685968608a102e0a6be305111037c63d7ddf8","src/bindings/kotlin/templates/UInt64Helper.kt":"fd7baacbf3ab6202ff83edcc66e5f7beb11a10053ba66d0b49547616cc7cbe1f","src/bindings/kotlin/templates/UInt8Helper.kt":"bbf5a6d66c995aea9fe2fa9840c6bfa78b03520a09469b984f0e1d43191e453a","src/bindings/kotlin/templates/macros.kt":"0f64366a9d7523b3d20d7e9d8b04eb064568772dec529a12f878acf5d2246a41","src/bindings/kotlin/templates/wrapper.kt":"d515ca22d12f13b1b99c5daa411ea35d9a288076e4b7208eaf88170e5da7477c","src/bindings/kotlin/test.rs":"0f752ab0afde20194afca07af94da9d1422300032696d5f845cd864fc63c5d51","src/bindings/mod.rs":"949f323d6eb5c018497103dbb9dcffb8f395eb5960694b551a24b4887e853afb","src/bindings/python/gen_python/callback_interface.rs":"5df3e091d3c88ef7645e570f693942161a9b9c6307419c15a2534fbc5da974af","src/bindings/python/gen_python/compounds.rs":"9b7187d35826e1b12dbc2b16a13aec783a51f0952e3e2d24adaefbd0ac005016","src/bindings/python/gen_python/custom.rs":"81501641648eb638f5a338c01a71db0d0e96601c3dda83acdb2d49072b387d42","src/bindings/python/gen_python/enum_.rs":"7c3f8f6a97c1491175c8b93b8f9ab13748e2f8084bb717836b6935d024805439","src/bindings/python/gen_python/error.rs":"161bd2e041e3a63a91899de173eec8450cc10e1e9552d064969aa72a02fdfd5e","src/bindings/python/gen_python/executor.rs":"dbbf2292c79f73dacb317a8645185e42f328a017951662975c0488399c562058","src/bindings/python/gen_python/external.rs":"0325e9a39645eb5454d716d4db76a4a31083ddfafa8e9ca063257292372a0637","src/bindings/python/gen_python/miscellany.rs":"d6f6305dd0af85b7ba87b70cbe6ecba00c83d5082c5bdcaf25962fff853973ea","src/bindings/python/gen_python/mod.rs":"4ac0dc0fd9aaabf2e1f9245d13e0090ee0e9c1235ef203bec9314cac5974e9f0","src/bindings/python/gen_python/object.rs":"a4d4c20a0a52687feff2b9a547a13aa9bda09b3af9ec26508646658a88eec8b3","src/bindings/python/gen_python/primitives.rs":"b830c68e20d8539b8ac5566f1ca0dd262c1b14712a747f79e70004cd8f409ba1","src/bindings/python/gen_python/record.rs":"f8e12ce43d7e0f37f05420a849e7867b7251f9790933609a4cb99050fd063089","src/bindings/python/mod.rs":"eac32ce383460d58d3ccf1d406173465fc8a1db8a24408df67620b7d14dcd0cd","src/bindings/python/templates/Async.py":"4a35a878883a548f3bbed929a9ec74c133e2e9cf08375989503e73ddc2f9c648","src/bindings/python/templates/BooleanHelper.py":"c19e38ae3daa29a831f2394a0a2e74c924711e55ddc85db8ac9b5b8b6da9cd92","src/bindings/python/templates/BytesHelper.py":"e8fb9919acc784fb056bba4ab8d5c04ca7b2275211f8397ef2a391833e3d5e8f","src/bindings/python/templates/CallbackInterfaceRuntime.py":"795d8826d5d2b397a91c531c6b1b76d9425728efdcb90514170c8c4f35053e40","src/bindings/python/templates/CallbackInterfaceTemplate.py":"3f38e7b290fce198d188a63dcfa74486810153816283e683d6e04896c2dbea9a","src/bindings/python/templates/CustomType.py":"12064dde5e1baf4d78e541837c414cdc7ee9e827a284c54a98ac92dfaf3478e8","src/bindings/python/templates/DurationHelper.py":"271c301bc480cd48d5df2aec15789dd360f4d3098a9f360d7f8f33fa0a7fcd0a","src/bindings/python/templates/EnumTemplate.py":"1cbc2206f045c3050be1912df581a5393d6f0a4a79c96d8b49661696d25830e0","src/bindings/python/templates/ErrorTemplate.py":"f9ab6c910024e88ff92a7575d5d00cdafe448b2e85d07a9edea57ae7b6dc5864","src/bindings/python/templates/ExternalTemplate.py":"ed4d65caf2de3fd2c2a3fd2658eb44cf91cd2f0878c017be63afa4394bf56be3","src/bindings/python/templates/Float32Helper.py":"80c0a0619d2c58c100ea8db37125878c8e8cf56c42f77195aa9b4b6b6d5716c8","src/bindings/python/templates/Float64Helper.py":"62c3ed0d646d3383c890d1f8fd7cb8639433971b9ba9261ac43c1391472eb141","src/bindings/python/templates/ForeignExecutorTemplate.py":"6a7903acc65b9dac17524767d94b142d38d25d5f5bb27133ee9fd7ed8fb5f5a9","src/bindings/python/templates/Helpers.py":"3265eeb5917e0090a7dbd50fdbd12e7d1b1832a58b347cd003ecf8a433c9ebcd","src/bindings/python/templates/Int16Helper.py":"ef7fd0035a80aef556bdbfbcf074751d4e25661f4e07f9bf41f48601d171e5aa","src/bindings/python/templates/Int32Helper.py":"af7e0176ed41260089426498946e47565a7d57e98dffaae4562dfe541c3019d1","src/bindings/python/templates/Int64Helper.py":"171908319be9edcfe3b178d1d74f0173df2aae6a4a92895a2079fa476caed7df","src/bindings/python/templates/Int8Helper.py":"d02a4a5452ec1096b1b1953e4d661d699f9f8f0ce5086a6f3577a0119f479666","src/bindings/python/templates/MapTemplate.py":"fd0bd7e396a6288a16ecb3dae087ab725be556789887a4dc0c00ab97d815f3fb","src/bindings/python/templates/NamespaceLibraryTemplate.py":"b78b7161d43a95f5a0b5d7da6cae0d8bf47017c6c77ad210e15be9531413baa9","src/bindings/python/templates/ObjectTemplate.py":"3af0c737d1b482169d62dee1b6c49f1a18ea3f485e9fc323b070f35cf00c242c","src/bindings/python/templates/OptionalTemplate.py":"59df962441fb1c50cb99be014c87ecf69450c1a3b60fa6763bd40e9e948124b1","src/bindings/python/templates/PointerManager.py":"22faf6a2801cf756f3b09415b597f0cd403a3872ac99a7e44e3b7b6217606cd7","src/bindings/python/templates/RecordTemplate.py":"04fcbd662bc9817597366046e09321d2516eb8240e796dd9b6f971c347475429","src/bindings/python/templates/RustBufferHelper.py":"8a8c20d195534e465a173dc778ae98957c50e209ec824af2d2b143f5ba6061f5","src/bindings/python/templates/RustBufferTemplate.py":"f5e247b0f8988f29ce94ab50b5fe7749d0423cda1d37b6145cc8a6c5a9818449","src/bindings/python/templates/SequenceTemplate.py":"047f19074fe08982b59001da2ba7318b331ce431d73503e111330579a3ba065f","src/bindings/python/templates/StringHelper.py":"a3f874ea9330413e854c2ebbeff5507e32a166de6967c5cea63ede1f021e267a","src/bindings/python/templates/TimestampHelper.py":"de099ce51ceaa86519c28bb38e21933ead36ff341f4907695029212bcdfed3ac","src/bindings/python/templates/TopLevelFunctionTemplate.py":"93b6101fae2cafdf1a9325bed07019609ac35bacef2dc31ba4be5c256d827473","src/bindings/python/templates/Types.py":"feb69d895e9279e52479146e1dfe2fec48c547378ef2cc0fb988f6acaf6bcc63","src/bindings/python/templates/UInt16Helper.py":"5fbb30ece1f9a2680b60baf680ec4e2936d64de2ff107018e751ef1c041443ef","src/bindings/python/templates/UInt32Helper.py":"84207c380e38a38bb919d58769384a0f4fa175ebbd04ac451b37ccfe01ff68a1","src/bindings/python/templates/UInt64Helper.py":"4606f381834740319a9f604a418ba149917a6dbd43d3d3d8da50c655893e2c8e","src/bindings/python/templates/UInt8Helper.py":"aecf7cf08b7dc75fe81e8dcac78dfce166b132d5c20b4301d845c479ab9f49ba","src/bindings/python/templates/macros.py":"7d0d08f418edf65ff365bf1fb37e3132aebb720dfecdcef3e3ce50529fe0eb6b","src/bindings/python/templates/wrapper.py":"91e8cbf18e5b7d0b2be31c0e09b230318d19406c316b453dab973341eb2c6add","src/bindings/python/test.rs":"48e93959ce3e34ff0191126416301b170239d3e2665711da786e0b8b7a90a2ab","src/bindings/ruby/gen_ruby/mod.rs":"7f3a94537c331a941e6e010e35563247f11f1fedaf971cb8538e17797cb17efa","src/bindings/ruby/gen_ruby/tests.rs":"7dcb86b08e643c43503f4cac6396833497f6988b004321c0067700ee29ffbf32","src/bindings/ruby/mod.rs":"0fdfab5306dc5c05fbcbfb273340d96ad70c5caf5074834ad6851af1a9a56734","src/bindings/ruby/templates/EnumTemplate.rb":"5480edb347f5829e478d19474691babd72f37616ed846d519b5a61cb1d6cf047","src/bindings/ruby/templates/ErrorTemplate.rb":"301c177e639f0a27f19d4935c5317e672aadecbee2f9bfa778df982320f5148d","src/bindings/ruby/templates/Helpers.rb":"ce7ed4be97dad507b991c69c28dc7bb6427e5e79a4b2fba9dad9dccabc3e090c","src/bindings/ruby/templates/NamespaceLibraryTemplate.rb":"9a24c427b9eba99d9e13181a5559a385b5d1d16beae2b72a402f2645b22a9048","src/bindings/ruby/templates/ObjectTemplate.rb":"0cfd9438e4821cf2164b23d748b3227a8cffbe2fab5b7eb70832228ccb628ee0","src/bindings/ruby/templates/RecordTemplate.rb":"4aeff886928ca972e5dc9b799581b30c66a6f6dce446af3285dd3ed6b422dea9","src/bindings/ruby/templates/RustBufferBuilder.rb":"8da4e425b36dde4f171b238cbe57e02fb55e91a45a82134c1dccc0fc360733c0","src/bindings/ruby/templates/RustBufferStream.rb":"43ad2defc772fd24b68df0736533c26597ba007e89b6a5ba0d31fbe356648151","src/bindings/ruby/templates/RustBufferTemplate.rb":"405a32592cab145175b64e21398f83e6e0f16354552c0395479270e732910e08","src/bindings/ruby/templates/TopLevelFunctionTemplate.rb":"88213e7e25bef664da939c04dd5621f438af735ffcb4d2d0c24a529538630069","src/bindings/ruby/templates/macros.rb":"79d7d0e9af749dadbf242f37c0f86af7c616ea5318da127747def40f6cdb20d1","src/bindings/ruby/templates/wrapper.rb":"f82b41543546f8e5804cd0e1785f4735d9dd95383566d0e5ba1cd4d9e8c0578d","src/bindings/ruby/test.rs":"d19837119725233bd9971ca2dfc3256156071c64e6dfaf07ad2307432c055bbb","src/bindings/swift/gen_swift/callback_interface.rs":"6b51276350f506f96fefd0ae8cb3afdcd514e8a529d9e982afc68cbf68d74578","src/bindings/swift/gen_swift/compounds.rs":"1aba37cf2f438423a4ce476eea6a36f71f1d5daddbb77c88556bc3abde287ca7","src/bindings/swift/gen_swift/custom.rs":"bddb601b4ea8810ecbad01271d5ec0b3958999b09bc9382c83637dfd43451734","src/bindings/swift/gen_swift/enum_.rs":"87be67ec3394616368d9ef8e99b7f234c053b3bee9a7f9e6f2dff37f147c8837","src/bindings/swift/gen_swift/executor.rs":"ab672e2d05acbc2c4a839af22034aa557d5e69f1d9c913158310ea1e93851557","src/bindings/swift/gen_swift/external.rs":"321974136d58e649e60b2a3f70a369dce2d49f474f79579f8e0d66eb63d2d634","src/bindings/swift/gen_swift/miscellany.rs":"7fc2444596d76545ad82ee6c4bed64a29dd4a0438d50bfaafe511f41f6a0e409","src/bindings/swift/gen_swift/mod.rs":"41e4bf2fbe622d0dba85363455e287e00dc48e4043910cef35c30ce170acf52b","src/bindings/swift/gen_swift/object.rs":"2269f65a6b58a24bd08fedb133a38b37663bcf11d0586c50a67028022706a156","src/bindings/swift/gen_swift/primitives.rs":"c8346601008ac6a6d07f08ec7395182c45a4d86c163dc1d6d9c326c49f2acda1","src/bindings/swift/gen_swift/record.rs":"5ad98ab04a5d8178daf0956db819c87d26aae7bf968184e88d512e34c02feb90","src/bindings/swift/mod.rs":"26ba270cb7913661f3cee703038d1ea4a70bff64c3b31351d6bc77e67cdee20d","src/bindings/swift/templates/Async.swift":"84b9be2b5eca2dcfad7eee0cb8d34fec613a4bfdc8a7170b8d11575e457f567b","src/bindings/swift/templates/BooleanHelper.swift":"f02e391bed44ca0e03c66c4e6a1545caaae490fc72c4cf5935e66220082a8c30","src/bindings/swift/templates/BridgingHeaderTemplate.h":"3f468869e77b9293836822b8f2ac348716ab5d487f7b8fef1a87ec30ddafa8d5","src/bindings/swift/templates/CallbackInterfaceRuntime.swift":"2c71ac715ad0bca6f73559748453ba37ca242c90de38f76876989be05d21c49b","src/bindings/swift/templates/CallbackInterfaceTemplate.swift":"790f50b49d5b07dd44e8b215ed1fff02991c1f65aba9a9a9925275a544348813","src/bindings/swift/templates/CustomType.swift":"71520eb38a4be9035dca9e3e0402f386e7eaa79b28568bbc2f20d3fd53b4544d","src/bindings/swift/templates/DataHelper.swift":"df11547a2df57dcca0ff9cddc691bb5fa07d5ffd3d328d1c3b4443078008b111","src/bindings/swift/templates/DurationHelper.swift":"cbc41aaa58bda6c2313ede36a9f656a01a28f9c72aa1624e0e1c1da7b841ffb6","src/bindings/swift/templates/EnumTemplate.swift":"fe205dd28defea8ed6126a45b2a95240a920dfebda8927134a50c3b6d0d7e9d7","src/bindings/swift/templates/ErrorTemplate.swift":"3dddb278763b75b38294c1165522fa91078a951ce05c91fbdfde43b5a097f34f","src/bindings/swift/templates/Float32Helper.swift":"ea32538058c4b3c72b1cd2530ac00d0349fadab5e1bc617d33aae4c87692fc98","src/bindings/swift/templates/Float64Helper.swift":"e27e9424dc6e97b8cacc6ca4c796dd2d16dcfcb877e2f19c45eca03381a41e78","src/bindings/swift/templates/ForeignExecutorTemplate.swift":"205933825e691fec525286d263ea34d592cc462257764ee76325bf98cb3cd240","src/bindings/swift/templates/Helpers.swift":"a88fd909787b855998671e551cdb3284109e2fd2b2e7492b1c93c82aad0e9d35","src/bindings/swift/templates/Int16Helper.swift":"204906911813a3931436057c23032f4c4e39e023df90d641d6c6086aefe2f820","src/bindings/swift/templates/Int32Helper.swift":"0997f059c9c4edd3c41aee0bbad4aa2bda6d791a0d623ad8014d5aa6bdae718d","src/bindings/swift/templates/Int64Helper.swift":"bcf8c2deebb3ee9bce87735adc4bd100981989943b69f6a7fb499a9aec4c25d9","src/bindings/swift/templates/Int8Helper.swift":"ad1ec0fa213724933fa4dc4e2e304e13ac4722b774bfffac44793986b997dd33","src/bindings/swift/templates/MapTemplate.swift":"53971ec388417b02519f8deb8d66361ab4693eae77d116f6051cbea4738054ec","src/bindings/swift/templates/ModuleMapTemplate.modulemap":"99ad1e9bf550a21497296f9248ecd4385dd6d0b5892951d24cf990cdbf3eec2c","src/bindings/swift/templates/ObjectTemplate.swift":"4633572ac6d27a0f82f3b125c2136ad4fa126391c0b64b5db1bde4b04a77f807","src/bindings/swift/templates/OptionalTemplate.swift":"2376487ceadca3975f0e82ddf6ce61af8bbbf5b0592fa9cd977460f148d8c99d","src/bindings/swift/templates/RecordTemplate.swift":"16e0b98354b624a8922d7d384a005fa660a6a388d70381872ebbcd0de9fb78a4","src/bindings/swift/templates/RustBufferTemplate.swift":"f4422fdf0cb5b4db267d461f063dedc319ea1a5a13bae1b82c3f108ba8c658bb","src/bindings/swift/templates/SequenceTemplate.swift":"8425b279197582a94b4cf363ab0463907a68a624e24045720ef7df2bcacf3383","src/bindings/swift/templates/StringHelper.swift":"968b9b9b7fbe06a2ac2143016edaff3552e201652accb8d613b03645f0d24a90","src/bindings/swift/templates/TimestampHelper.swift":"82eece13aa186c8e3745c8ad2f1290639ca9689573018a2bdc5c75afbae58c26","src/bindings/swift/templates/TopLevelFunctionTemplate.swift":"ffe0287861e67dcad2be77f30c00c0a326ab59ffbd37409de3bafc969d49df26","src/bindings/swift/templates/Types.swift":"98c654bfc5d2d4ece965cfe15b00e7151b815e26bfb55abf17e799efffccc2c0","src/bindings/swift/templates/UInt16Helper.swift":"d6fba577324fc0e9e50329c968df99341de418011be126bd29702f8a94d87c02","src/bindings/swift/templates/UInt32Helper.swift":"5e0cf151a0c40098b3d96815ba3de23e15fe52f3c517577e1b0c7e7a4c12428f","src/bindings/swift/templates/UInt64Helper.swift":"17237b38d09ced8d2a8ff1ad9ca86873a19e417280e0e60f33d7063397ea4b7b","src/bindings/swift/templates/UInt8Helper.swift":"c4cb2ee4a78b54ca8d0013383c6b43e9ecd42776e3dc7d6e40086325d41714e5","src/bindings/swift/templates/macros.swift":"438091831b355b0ba6726dab7c17c0687ca58854c7799e946a8012e95a42f4ba","src/bindings/swift/templates/wrapper.swift":"d83a1b8ac3ffc761d4e560adae57d5ad275e0fd1bf3fdc35f3b3b3699317c6e6","src/bindings/swift/test.rs":"c15d19e7f324613e2dbd7995dffba875ed430919a0882f05e8e1f6cc8aea613c","src/interface/callbacks.rs":"34384f1a4e89cd30e2c35beab2bbd8cc0a9e3dd21ed0b390859610fbe209b16a","src/interface/enum_.rs":"3a4f9e77d80128444a8a226ef1e5ad7cd339f8d815aa7012554cf94c2e4edd98","src/interface/ffi.rs":"fa23a2e6fcd89d956523bd0aa5c946138ac629752f0be9085e09f78aade75fac","src/interface/function.rs":"835ee542cb19c67fb95502a790771e54deb712272baed8bdc3f6bfabd9c69d6f","src/interface/mod.rs":"5bd3b5a2dd91382089323a21d4d0419dd5621799add4786a4710df0afebcca69","src/interface/object.rs":"2364c4e7f887d6e4256d065f9ad049ffad19f92fe1c1d76789c33a22376bdd7f","src/interface/record.rs":"931dbaa8cb440debfcf14d51be3e6c517ba2c28655033ad2486e384a9bea25c4","src/interface/universe.rs":"66deaa33394e401e3005670731a0685068302fc01640655335a7cbe9fb4cf48e","src/lib.rs":"64a6239dbaa8196089600e7b0c577b43eefc4a9935893292298bf400fd77a03d","src/library_mode.rs":"89c9fc47db09ccb779fd2842d3c7e07b7863164bb5585eeaf959490ca5555438","src/macro_metadata/ci.rs":"07482b87bf5912277d114b24ad3e560cac94ca0087bd6ee9bee2c328d992bb18","src/macro_metadata/extract.rs":"a106a5b6ad8e5deba474646162b83ec4065796c58c60fc13dfbf1a72ed713833","src/macro_metadata/mod.rs":"bcb5e9a015510e9d74c288da928a4bfb8d80926a8ff85227c0eae8cdb2605519","src/scaffolding/mod.rs":"65749e72181c63edae55d49493aa49a1efece93c0a27deda230f5de4b9ba7d60","src/scaffolding/templates/CallbackInterfaceTemplate.rs":"0581f257c0eb7f0be922ef3ebf7892aafad15e9dc7865e53fc9396375f3188fb","src/scaffolding/templates/Checksums.rs":"ee926e840875c2e48e1d0cc5185c11f7a1ed3bde5264b07540812cb13c1d7481","src/scaffolding/templates/EnumTemplate.rs":"350cdcb4f23be6e6e2b442e0d4ea65549bb35a407bef1ac745a37a2e2b527fae","src/scaffolding/templates/ErrorTemplate.rs":"b93e8bc08d818fbf8976b766635ae192042b78c85f8086d4ee41ae03ab7f3b6f","src/scaffolding/templates/ExternalTypesTemplate.rs":"6cf8a89d9e6f1b6f5af4bc86e49454323fa4981846ec76d6661fcff41c348a8e","src/scaffolding/templates/ObjectTemplate.rs":"df614156bee529fd28905a6ad2270685e51736eade6af856edba5de6e9484141","src/scaffolding/templates/RecordTemplate.rs":"5b0f351739d5770f874fb7da56700c557df1d73ac219f3b18c4212d26fc422e0","src/scaffolding/templates/ReexportUniFFIScaffolding.rs":"aa8a1ffa98b6033707d965f90b5709474ed6bc79486fb47dacae8417fc056cf8","src/scaffolding/templates/TopLevelFunctionTemplate.rs":"96b99b38d074492673797737ddac0683c803a3908e03cc9b999d16f7a76ed178","src/scaffolding/templates/UdlMetadata.rs":"d7c50af1de92ef85630b385a910c7b29875502d622eb90da5541a7012b93d9e2","src/scaffolding/templates/macros.rs":"ea6bacd8dd9116ad739bdafe893d70407050f35e4a7ac8dd2c78b8ef34263e8e","src/scaffolding/templates/scaffolding_template.rs":"c8e18306a73ec5b764f665660fc5c91d498b63b6c3f489e524b2bae50f81f231"},"package":"fd992f2929a053829d5875af1eff2ee3d7a7001cb3b9a46cc7895f2caede6940"} \ No newline at end of file
+{"files":{"Cargo.toml":"037dacd80bb367cfc530c5ca19fbfac091f385cf88ad5bd33c2009fde6d06e3e","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","askama.toml":"1a245b7803adca782837e125c49100147d2de0d5a1c949ff95e91af1701f6058","src/backend/config.rs":"4861dbf251dbb10beb1ed7e3eea7d79499a0de1cd9ce9ee8381a0e729c097dea","src/backend/filters.rs":"8a818952896f9c5f438d6705bb28f56e77fbfce6d9757a2d74e1b3a925ed36e1","src/backend/mod.rs":"2ee9d974cd259f7fb156028b4f4f7601691e94fb5692a6daf0d362df3ecf79a8","src/backend/types.rs":"598df3a861f5d53b2c710848943f6049dd43cb4f37aa81f2c08fd36fc5b2f5d5","src/bindings/kotlin/gen_kotlin/callback_interface.rs":"741100c2b4b484583d34408b276394078a24918c47101fbaa6233df9c4da32f2","src/bindings/kotlin/gen_kotlin/compounds.rs":"b40d1ab8c70d7da458ff45d2ce58efb6cc3b24bf560c093cbec7d0854d461dc4","src/bindings/kotlin/gen_kotlin/custom.rs":"7e619f7320796ecd8c4ced82904b4bd3c6a0043b55d5829274ab3040cdf9cd7f","src/bindings/kotlin/gen_kotlin/enum_.rs":"6559bb00d8e359126b016e549263c0c9bc1dfc5654ed662c0c2912b47931b1e4","src/bindings/kotlin/gen_kotlin/external.rs":"38f42be67105b9a2ca5ecefec959e6659af728bedb9d107a51c595fe6ff5d332","src/bindings/kotlin/gen_kotlin/miscellany.rs":"6541987e627c4ff27a17ebe919b2b5cd97cb66ff41187ed636396b4e35ea2414","src/bindings/kotlin/gen_kotlin/mod.rs":"34328d11c59a67159620a21bc660fae149bbf452a817d6c9d3f7657cc5b79134","src/bindings/kotlin/gen_kotlin/object.rs":"1cb8d1f5eaf06ceaadb6d2cedca482fdd1502c24500ba270d8fcac48ef2f1231","src/bindings/kotlin/gen_kotlin/primitives.rs":"249896ec7d18f0f8d1d5dc8dc66ea6f3d0cc7b13344ab6892fb985f339d99b9f","src/bindings/kotlin/gen_kotlin/record.rs":"96fd1a180095a062b4a9b71d4f603b232f0133f46355a3e427c4064701d900f2","src/bindings/kotlin/gen_kotlin/variant.rs":"d111d6888745195fc2c24bdddc57359e771616102a8d182c5c8ad268b0a13460","src/bindings/kotlin/mod.rs":"ef88eb9b5b7d6f920c62a525ea4d4bf2a3b1a9154afaa012cdb2feea597fbf23","src/bindings/kotlin/templates/Async.kt":"2130ef176ffabe85cc737059203f2bda38df1f22f2398aa338c9d3099e6d46e2","src/bindings/kotlin/templates/BooleanHelper.kt":"50d8a5109e2d2676f25a02772079efbaac61776a76e3e84eebd1fb13294842de","src/bindings/kotlin/templates/ByteArrayHelper.kt":"dc4aafffacb1fa8f3b4e15f714c13b8d715eec178c63bdba6260baf612dd80d8","src/bindings/kotlin/templates/CallbackInterfaceImpl.kt":"be4d5a5d3ed4f7b1d4c9822905c5732bdb8593c3dbf8d4aabab62291d7ccec99","src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt":"76689c1bfa8aa7dc6e2c9e77c42212b9f317763fb35cd7704ca470675dd2648d","src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt":"b497250899bfd0c79bd01d77f23454b12b108fa269055d6f3699be74fb93d015","src/bindings/kotlin/templates/CustomTypeTemplate.kt":"d42eba4334c39749037d14ef9e2219a2e515479c18905df3f49534424317c848","src/bindings/kotlin/templates/DurationHelper.kt":"dfb45fe1b47bc04dd8c70cb98531c40606eca554791132ee6bed2846f8ee099c","src/bindings/kotlin/templates/EnumTemplate.kt":"7cefbb1e29d4e89420f6a95275bbab891984a56978cf4852a1e52bcc82afd9e8","src/bindings/kotlin/templates/ErrorTemplate.kt":"8f41de90753a42cfe33ba837997baa2954208b987e70cec13ecb4124faf25aa6","src/bindings/kotlin/templates/ExternalTypeTemplate.kt":"b1df8566d000431bfc3820a2e455426e810cba6d8683e77ab78ab0bb7d003720","src/bindings/kotlin/templates/FfiConverterTemplate.kt":"bc0bdbc99ee2459f50c84abf6c1bb236a6179eaf519f1c5f5b3f72d8184dc662","src/bindings/kotlin/templates/Float32Helper.kt":"789246343d34594fc39072c1a5393b848cecadb353659fc6e9080fc7e760fc21","src/bindings/kotlin/templates/Float64Helper.kt":"b87eac72da313b1d559b1738bba1c771f43bb7566fdbb3a34546dd56beeb5832","src/bindings/kotlin/templates/HandleMap.kt":"feb456ea4dfb2ad07331d49d606faf396737817c6f6712a2d9a9d843daebdc1e","src/bindings/kotlin/templates/Helpers.kt":"e7657732f44e8092d492ef291e3ce11aa803531d82be99645b8513c00dca99ac","src/bindings/kotlin/templates/Int16Helper.kt":"54bb1aefbcae1c3c10e0cdc6a9d45e070e3ca57c9ffa53b2d65e5c59808c9743","src/bindings/kotlin/templates/Int32Helper.kt":"49b3e5274d5c7853d227ec986d0a0c71621a448391b2c9aaf4351b86f310dadf","src/bindings/kotlin/templates/Int64Helper.kt":"264fd99a4109f0b2c40b806fc1a0181f27331346a02d94e398f1e34110e3414b","src/bindings/kotlin/templates/Int8Helper.kt":"a50c315d8474a212914f10f43ca7e75061bb73067e0de686b182891c6c7f1bc3","src/bindings/kotlin/templates/Interface.kt":"c66912069c3f61848bbee3d1886abbfa74895f759853f6b4a3c62cef5976766c","src/bindings/kotlin/templates/MapTemplate.kt":"f7e0360d3be74e543573bd56925bf25c6c22e6203aecc1cf519464704eeeb0ee","src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt":"db4c30cfcb709c5413892cf3cf69391ba36c6160543274e8d1f2bedf9001d058","src/bindings/kotlin/templates/ObjectCleanerHelper.kt":"9ebcfcb3fe7788e93cf8cba30fd7470b363719e9ed25cdde43c95aebe1b90c2a","src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt":"049e1e32a23b7923393e3dcabce49532737d44e9dbb331f62984ada67bde3125","src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt":"b6287f72afdb0ab9af5e56136c28e6a4f5e18a50305bce8923ee061b9406cfc3","src/bindings/kotlin/templates/ObjectTemplate.kt":"6a776feb36b0379c43e0013a26ba85cdef385aa1e59b4c2efa7a794140aa99bb","src/bindings/kotlin/templates/OptionalTemplate.kt":"918f2029e60710f4b048a77830b12b388c917af1a488c9f05f38323c58ee0f9e","src/bindings/kotlin/templates/README.md":"83587ff54a31fa47d2c0849cb5db52d6f079551e1cfb73c76c6dd02a7b164ad9","src/bindings/kotlin/templates/RecordTemplate.kt":"677bb63ae4fae9117e9c77928370a8911ab959c6b884c6af2ba4efc686c52721","src/bindings/kotlin/templates/RustBufferTemplate.kt":"b3b78b2c41cbfff6262d758f9ebe064e76d20841ec4db7705142449f7ffc75a9","src/bindings/kotlin/templates/SequenceTemplate.kt":"c1aa28ca87528c97c62656f850205023c2eb8d218264ef7b1e70207ab4f1b9b6","src/bindings/kotlin/templates/StringHelper.kt":"4e942e36af05dc823d5f28ab336c55ff86bf0f95bbb748399bcaa8ad291c7032","src/bindings/kotlin/templates/TimestampHelper.kt":"70137e78de18796996889e8d648f1e90830aa652a93a0b4639ff9f7ccb967a25","src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt":"68c714cc8c7fa244166c5902a59c90317cdfc402193624cade405e3454f9bf67","src/bindings/kotlin/templates/Types.kt":"c725f7e57eda5b2d52c3c92b24f93d9321591e72c89d16973163b3b8d713b85b","src/bindings/kotlin/templates/UInt16Helper.kt":"ee96270f426933cfcd914894d4c7895544f7e3d4a7c24be78afc2896b46cbcbe","src/bindings/kotlin/templates/UInt32Helper.kt":"b2d7543098277e7b92502a0a6693dc25dd42e360f776b19987a48dd7fc6db7fd","src/bindings/kotlin/templates/UInt64Helper.kt":"fc855eb78a4b50d76fc53509dae8218c48a221db5bf73cf5368d755fb9aae478","src/bindings/kotlin/templates/UInt8Helper.kt":"af22d9e6f99fe9d8d7d5175cb03f7a9f62628c9dd939dbbfb5a4085359e52e0e","src/bindings/kotlin/templates/macros.kt":"0a221962503f6977b129eb3c1e3772e3e9d51cbab6d813c55b0387c24d784184","src/bindings/kotlin/templates/wrapper.kt":"a02028a86c620679602f26714c7feb4a306867cda1cba8240ca6e83d99cebd91","src/bindings/kotlin/test.rs":"28bf88a9e9aa9510adbe78005a2027a62818433f49426172046dc83a3ad41911","src/bindings/mod.rs":"949f323d6eb5c018497103dbb9dcffb8f395eb5960694b551a24b4887e853afb","src/bindings/python/gen_python/callback_interface.rs":"5df3e091d3c88ef7645e570f693942161a9b9c6307419c15a2534fbc5da974af","src/bindings/python/gen_python/compounds.rs":"4a83b02e11ae969ab360ba61df44d91dc790f372b5960b350b0b019a57d19de4","src/bindings/python/gen_python/custom.rs":"81501641648eb638f5a338c01a71db0d0e96601c3dda83acdb2d49072b387d42","src/bindings/python/gen_python/enum_.rs":"7c3f8f6a97c1491175c8b93b8f9ab13748e2f8084bb717836b6935d024805439","src/bindings/python/gen_python/error.rs":"161bd2e041e3a63a91899de173eec8450cc10e1e9552d064969aa72a02fdfd5e","src/bindings/python/gen_python/external.rs":"d7101124c22dd7837e227a7f1b683c57b92229a2cd5b25b06740f2fe3d76bed5","src/bindings/python/gen_python/miscellany.rs":"d6f6305dd0af85b7ba87b70cbe6ecba00c83d5082c5bdcaf25962fff853973ea","src/bindings/python/gen_python/mod.rs":"b8aac9a146551cd660f1cd310f8ef02e3bd4a11540a087551bbb7c7706b99e16","src/bindings/python/gen_python/object.rs":"a4d4c20a0a52687feff2b9a547a13aa9bda09b3af9ec26508646658a88eec8b3","src/bindings/python/gen_python/primitives.rs":"b830c68e20d8539b8ac5566f1ca0dd262c1b14712a747f79e70004cd8f409ba1","src/bindings/python/gen_python/record.rs":"f8e12ce43d7e0f37f05420a849e7867b7251f9790933609a4cb99050fd063089","src/bindings/python/mod.rs":"eac32ce383460d58d3ccf1d406173465fc8a1db8a24408df67620b7d14dcd0cd","src/bindings/python/templates/Async.py":"f1cf32d8e28b5e2fcbad6ccd00d03fd49f4b54eac47adcfe23cbf786d523eee7","src/bindings/python/templates/BooleanHelper.py":"cf7bcd414197258b0cfa54c6ad2aeb81a1a6a4a45af5b6aaf6f8e484bc5af59d","src/bindings/python/templates/BytesHelper.py":"8c39cf1760678316cf2b3903632f2bacae4f8aaa961b37eeb03e06e9f07241d1","src/bindings/python/templates/CallbackInterfaceImpl.py":"7dbb049ffebc3565ffb4605d53843c69f782f3e86472e060e3194be4986d328b","src/bindings/python/templates/CallbackInterfaceRuntime.py":"54dbda8a6ffe284ef2045da290a69d37974fed672eb57309c9fc7ac665969397","src/bindings/python/templates/CallbackInterfaceTemplate.py":"ef235bd7927592eb19a2db422352a435b7466595ef31e4822a16c3caa24cdda6","src/bindings/python/templates/CustomType.py":"4647a60dbe63ead2b23d07cf3a3a4a190a219d81357532364fd4afdf990d6e1b","src/bindings/python/templates/DurationHelper.py":"eb9278b546f79b71525ae61a5b30bfe4a1260fd2268c87c600d157bf9b0e2a44","src/bindings/python/templates/EnumTemplate.py":"49903d969b8b160d8f1a0747c803d5f54a6f000a6781493eacad1f6ca7811d7a","src/bindings/python/templates/ErrorTemplate.py":"d7af297596e5ef894e3fdaeb92bd6446843c987f8283c77973bd10fce537c9c1","src/bindings/python/templates/ExternalTemplate.py":"0cd36fc89f0a587dadfe0cb89c4d45a641822ba07cb9410299bdcd73ad3edb79","src/bindings/python/templates/Float32Helper.py":"4aa522163f121fcb84d2f024774d8dd9321c31f09b9a95da3a3131b6d2756971","src/bindings/python/templates/Float64Helper.py":"e7fa247fd9c3907b818f0d1ba28c2cee897e75fdd07fdacad1b8a2b5c26ba418","src/bindings/python/templates/HandleMap.py":"9dbfdcb4ddde5927fd9b9fb26b5194bc16b1d2280c2259895fd0ea443af4afd6","src/bindings/python/templates/Helpers.py":"09ddd46d6fcc6ee7e9b1c123b0830426c967f94e22ab18b3ee248b873f7d5ebc","src/bindings/python/templates/Int16Helper.py":"613345b35e63e7284caf97de9630747ec9cdadc8dd3f8451d2e878cb762958f5","src/bindings/python/templates/Int32Helper.py":"758b093b66dc0a8d3f0b13b9388d21f47de31b5e948689041c4d43ef98cf2c4f","src/bindings/python/templates/Int64Helper.py":"c7e76441ee14e78e856f8819f73243bc04b33ec16083ae7390e0ec27141855f2","src/bindings/python/templates/Int8Helper.py":"d963a76b218a32ea2b3bb26f265dbbc47e859b7d1bc939b43fd9b93c51a62292","src/bindings/python/templates/MapTemplate.py":"9dc81ebced353d0137ef6fe3187e170e3e72d32a3b5520dbbcc1f95354ebf62d","src/bindings/python/templates/NamespaceLibraryTemplate.py":"e480a80a27ed5e54a3ff9c72d3d6ab13343764da6c413d813c4bd72429139193","src/bindings/python/templates/ObjectTemplate.py":"976aa726baf36b53d1c319b262c34b8b2de2e414cb8d3c645ed3bf006833b9e8","src/bindings/python/templates/OptionalTemplate.py":"2629f3b46ff394df620bbff1699935e6844d9aa017e74ac43c0b38acd05f8d42","src/bindings/python/templates/Protocol.py":"8446fe51d7c9d16d7086694cee8016c6f571dc5c930fd18848fadcf109aa0566","src/bindings/python/templates/RecordTemplate.py":"c99d10cc061af339349bb0c7e8b67223fdcd9064362badc137a2ad0df17c57c0","src/bindings/python/templates/RustBufferHelper.py":"a48e5ed1dcde19993ae50bec9b881afa3bc6dd5f7d8257fd60214f2100224929","src/bindings/python/templates/RustBufferTemplate.py":"017f31fd5075306f5c8c2bd0e3a21ea965c694c0daf2523187ab076fc786e9ca","src/bindings/python/templates/SequenceTemplate.py":"1b262e5f546a1923de6968e0233cc621a5fae16062e9e6ac874c9b62d8f145df","src/bindings/python/templates/StringHelper.py":"b303b7fcbbc0981a28c6a7d0cc5bd90f8e9c8b8d572792e217a324b2bdb95dbd","src/bindings/python/templates/TimestampHelper.py":"b3da14de54822f44ada4459355c842550b944b3cd2a85a4eac0f59e82d646877","src/bindings/python/templates/TopLevelFunctionTemplate.py":"1d9da1b6ca2175b30f3277a46a1749590490e82bee6b990ff35efb04e5f102ef","src/bindings/python/templates/Types.py":"3653e2cf773493c6ddfd13ef298b0c7cb33fabc1dba495fca64b9287aae03042","src/bindings/python/templates/UInt16Helper.py":"8ffe4b69a5d4a2b3c5677ff1d8954efc67ab67713ffe297380e930e0379d493d","src/bindings/python/templates/UInt32Helper.py":"83f9603aceae05f2134c7183313ab0a1a8f64cabd8070ae19557494fe41dd6d2","src/bindings/python/templates/UInt64Helper.py":"97269025377a256e821e57991b07e17af05f4d1c4228e01fe5f243d784cb509d","src/bindings/python/templates/UInt8Helper.py":"4896723ed0ab8f5aef4a58d599e0a0dbd63d373f5740821c21b4b429b6a7afda","src/bindings/python/templates/macros.py":"d766feb4dedd2d0e4cd2052da7a69c0b074b97f880b857ee457faa43975230ed","src/bindings/python/templates/wrapper.py":"ab05168e3d01d1a26e9589cd9855d7776c46c59d699f1402a29dfae6eb9ebfbc","src/bindings/python/test.rs":"69d3ee230820f38d743438c8212e1bfc4e92f948d9e73548a38c093e164b2759","src/bindings/ruby/gen_ruby/mod.rs":"861be105f9001d4ad8f7b8ac4a303a95459ec7de7a0c2fdac14a083c43d5a07c","src/bindings/ruby/gen_ruby/tests.rs":"7dcb86b08e643c43503f4cac6396833497f6988b004321c0067700ee29ffbf32","src/bindings/ruby/mod.rs":"0fdfab5306dc5c05fbcbfb273340d96ad70c5caf5074834ad6851af1a9a56734","src/bindings/ruby/templates/EnumTemplate.rb":"5480edb347f5829e478d19474691babd72f37616ed846d519b5a61cb1d6cf047","src/bindings/ruby/templates/ErrorTemplate.rb":"301c177e639f0a27f19d4935c5317e672aadecbee2f9bfa778df982320f5148d","src/bindings/ruby/templates/Helpers.rb":"ce7ed4be97dad507b991c69c28dc7bb6427e5e79a4b2fba9dad9dccabc3e090c","src/bindings/ruby/templates/NamespaceLibraryTemplate.rb":"9a24c427b9eba99d9e13181a5559a385b5d1d16beae2b72a402f2645b22a9048","src/bindings/ruby/templates/ObjectTemplate.rb":"a1c0cc38865195d61df3540284f4756f1b6406b205d74e3855e7089d763b2791","src/bindings/ruby/templates/RecordTemplate.rb":"343a4b159cf298045747fb48f17552e3bf2c9775fa5b4fa40b424976dc67e33a","src/bindings/ruby/templates/RustBufferBuilder.rb":"a36d9183f3e66cbbb1c3e584b78ab86e01bd6b89a4a5ef9614c5df24dc383acc","src/bindings/ruby/templates/RustBufferStream.rb":"ab4fc736906e320fca56dca280daf40138ba443d957c42fbf5cfbf1c6acf463a","src/bindings/ruby/templates/RustBufferTemplate.rb":"de577fbae811f72e260270656f2c12ad7a4d157c78f97898d0cd4e309d92ad6a","src/bindings/ruby/templates/TopLevelFunctionTemplate.rb":"26c9c2d53853792270795bd822e41968e995375478d246f808f9935af77a7d6a","src/bindings/ruby/templates/macros.rb":"dc60ed79844b04fe828a24aef3550a6b6c30f7c0b66f03608d7c56725287ceed","src/bindings/ruby/templates/wrapper.rb":"f82b41543546f8e5804cd0e1785f4735d9dd95383566d0e5ba1cd4d9e8c0578d","src/bindings/ruby/test.rs":"027d62085498b20977f025117e1fb7c30923a189961d679823f16ca62a575d0d","src/bindings/swift/gen_swift/callback_interface.rs":"1a2b56d16db841574be0762d66b57fbaef0519273d45c47ca687bf656546f201","src/bindings/swift/gen_swift/compounds.rs":"d62206bdab8a2a65b19342933efad54c171f0f8c217b82ee8b41617043662fe5","src/bindings/swift/gen_swift/custom.rs":"bddb601b4ea8810ecbad01271d5ec0b3958999b09bc9382c83637dfd43451734","src/bindings/swift/gen_swift/enum_.rs":"87be67ec3394616368d9ef8e99b7f234c053b3bee9a7f9e6f2dff37f147c8837","src/bindings/swift/gen_swift/external.rs":"a1d34b688679a74b0ddcfcb1147a7064b53883d9df9c0670f950078516492ee7","src/bindings/swift/gen_swift/miscellany.rs":"7fc2444596d76545ad82ee6c4bed64a29dd4a0438d50bfaafe511f41f6a0e409","src/bindings/swift/gen_swift/mod.rs":"e6c12506217d0a5479e946998a24ee984e4ea4c4f19334cbd014f53504300181","src/bindings/swift/gen_swift/object.rs":"45a6d6bb053f3ef397ab8c6feba8d0e126a8d14cd87597d25015f97c6ffc3417","src/bindings/swift/gen_swift/primitives.rs":"26a29ea764988d9e021bbac6505ef45e49ae42426522d6e3822e949b6f0b589c","src/bindings/swift/gen_swift/record.rs":"5ad98ab04a5d8178daf0956db819c87d26aae7bf968184e88d512e34c02feb90","src/bindings/swift/mod.rs":"26ba270cb7913661f3cee703038d1ea4a70bff64c3b31351d6bc77e67cdee20d","src/bindings/swift/templates/Async.swift":"1645ac8dbea8575dec05acf0aeb18e210f76231c36ea0178b183e02a3ff6e18f","src/bindings/swift/templates/BooleanHelper.swift":"f02e391bed44ca0e03c66c4e6a1545caaae490fc72c4cf5935e66220082a8c30","src/bindings/swift/templates/BridgingHeaderTemplate.h":"4e1e91859c4fc6f40db32648645f046fb7e71841f44ae84737ea85bdecff7fa3","src/bindings/swift/templates/CallbackInterfaceImpl.swift":"514a0932c445e4040460da2969e4f21595e17b9b960eb23c6d1526e47dd56c51","src/bindings/swift/templates/CallbackInterfaceRuntime.swift":"a5def6b3b41698a42e6ccf5c85d365fe0abc7eff629d9f49d9d396ee90aad3a0","src/bindings/swift/templates/CallbackInterfaceTemplate.swift":"4dcab3e590f897499782aef3c657b9b838b312d8b49a018bf0f1ebde15ada786","src/bindings/swift/templates/CustomType.swift":"71520eb38a4be9035dca9e3e0402f386e7eaa79b28568bbc2f20d3fd53b4544d","src/bindings/swift/templates/DataHelper.swift":"df11547a2df57dcca0ff9cddc691bb5fa07d5ffd3d328d1c3b4443078008b111","src/bindings/swift/templates/DurationHelper.swift":"cbc41aaa58bda6c2313ede36a9f656a01a28f9c72aa1624e0e1c1da7b841ffb6","src/bindings/swift/templates/EnumTemplate.swift":"4b980f8bfe65266d27d561e88c7d79d87f426b35b4b842ef80c5d56841e2f672","src/bindings/swift/templates/ErrorTemplate.swift":"1233d119320a44dbf6099681595dda9bf5dd2a1474af4380b704bff0563c38ef","src/bindings/swift/templates/Float32Helper.swift":"ea32538058c4b3c72b1cd2530ac00d0349fadab5e1bc617d33aae4c87692fc98","src/bindings/swift/templates/Float64Helper.swift":"e27e9424dc6e97b8cacc6ca4c796dd2d16dcfcb877e2f19c45eca03381a41e78","src/bindings/swift/templates/HandleMap.swift":"acd2b06d678e64a573f7b842c7d08b87140ddb5d7146c0bf3401d99999399ec2","src/bindings/swift/templates/Helpers.swift":"491553eb82cdc5c944451a541d4e4655537cccb961f220783459b57b2311ca84","src/bindings/swift/templates/Int16Helper.swift":"204906911813a3931436057c23032f4c4e39e023df90d641d6c6086aefe2f820","src/bindings/swift/templates/Int32Helper.swift":"0997f059c9c4edd3c41aee0bbad4aa2bda6d791a0d623ad8014d5aa6bdae718d","src/bindings/swift/templates/Int64Helper.swift":"bcf8c2deebb3ee9bce87735adc4bd100981989943b69f6a7fb499a9aec4c25d9","src/bindings/swift/templates/Int8Helper.swift":"ad1ec0fa213724933fa4dc4e2e304e13ac4722b774bfffac44793986b997dd33","src/bindings/swift/templates/MapTemplate.swift":"53971ec388417b02519f8deb8d66361ab4693eae77d116f6051cbea4738054ec","src/bindings/swift/templates/ModuleMapTemplate.modulemap":"99ad1e9bf550a21497296f9248ecd4385dd6d0b5892951d24cf990cdbf3eec2c","src/bindings/swift/templates/ObjectTemplate.swift":"37e57815e60900ae48b953fe01e01535d4ab8076f6160fc93c37dd08fdee47a4","src/bindings/swift/templates/OptionalTemplate.swift":"2376487ceadca3975f0e82ddf6ce61af8bbbf5b0592fa9cd977460f148d8c99d","src/bindings/swift/templates/Protocol.swift":"2614b1378cadf14e7617fedd7367c227ac2a774d528acd3a42e44fd0c4f58528","src/bindings/swift/templates/RecordTemplate.swift":"f9f576b72fda9d1e1db34d1765ec6ec8206103a297329720c1c9a1f58ad085b5","src/bindings/swift/templates/RustBufferTemplate.swift":"89ed33846c0cfb220e823a1002238b16f006f3170d8de0dbbf7775d4f8143c31","src/bindings/swift/templates/SequenceTemplate.swift":"8425b279197582a94b4cf363ab0463907a68a624e24045720ef7df2bcacf3383","src/bindings/swift/templates/StringHelper.swift":"968b9b9b7fbe06a2ac2143016edaff3552e201652accb8d613b03645f0d24a90","src/bindings/swift/templates/TimestampHelper.swift":"82eece13aa186c8e3745c8ad2f1290639ca9689573018a2bdc5c75afbae58c26","src/bindings/swift/templates/TopLevelFunctionTemplate.swift":"7aa473a5b12ad7623f61d6c31f6879f269f51d2c4134dd899ce24c7b31ef35f1","src/bindings/swift/templates/Types.swift":"15e255e35e267f2aca49ed5a4fe16ef79520f4261433fd30c5e6c7f637a4d3f6","src/bindings/swift/templates/UInt16Helper.swift":"d6fba577324fc0e9e50329c968df99341de418011be126bd29702f8a94d87c02","src/bindings/swift/templates/UInt32Helper.swift":"5e0cf151a0c40098b3d96815ba3de23e15fe52f3c517577e1b0c7e7a4c12428f","src/bindings/swift/templates/UInt64Helper.swift":"17237b38d09ced8d2a8ff1ad9ca86873a19e417280e0e60f33d7063397ea4b7b","src/bindings/swift/templates/UInt8Helper.swift":"c4cb2ee4a78b54ca8d0013383c6b43e9ecd42776e3dc7d6e40086325d41714e5","src/bindings/swift/templates/macros.swift":"b30ffd93fe2213e13c3b9910bf2404403b4b231d4cd32c81e0f76c3bb4d151b5","src/bindings/swift/templates/wrapper.swift":"e553af470320391d150e6489eac549064689a37e5db6947914ce5609d0128031","src/bindings/swift/test.rs":"f55ba6c05c250093b26ae91404fd9200951462c1cd99e6b2718f7fb4ebcb7fbb","src/interface/callbacks.rs":"4a019376ec8fbaec495a9e3a1d5cb079af65767b6d85bc9f508f92a1e7f5344f","src/interface/enum_.rs":"7baee60e02cc7f751d7a941e877c10a6afaffea626e79897a0e8b17702f13c15","src/interface/ffi.rs":"11b48aaf22fd9cd9eeded30afe950b26cc1c6d8ec6f9385c9e4cd3bdb2881f43","src/interface/function.rs":"be0f9f268e1947381fa235c5a0cf3c1965fd73121172d31f9c130acf539f2ac0","src/interface/mod.rs":"b97b11295b91691e7e6b7b023bba019729ad02f2204bba460c48acf62c5ee363","src/interface/object.rs":"d37d55edc62f52cf7fac4e3b8be1e46557dcbcfa8eb2e5998a91be2c6c062d92","src/interface/record.rs":"d8ddf873c35beaff45ab522bc4cb809c459a7937fd4061dae8c2db0db4c4edb4","src/interface/universe.rs":"76f368ff2b5326c517025a460405d343618bcc9fc9cfb28346313c8f7a335050","src/lib.rs":"2e3adccd5f0a3dacce6e533edfb5640ddee05e4f87ce8144cba859a14af219f6","src/library_mode.rs":"43ee55e4bb8d27dcec8a164961f22de941603d79d4e10c270ba9e7a751d92a1b","src/macro_metadata/ci.rs":"fa87ef42065c821aa89d3fb7938888c035ce6fc03bb3fce575a878c8e49012fb","src/macro_metadata/extract.rs":"7554c7b19b50d40e90bb503320311c2caa3b1e45e4376a9266ce32c0d48afffb","src/macro_metadata/mod.rs":"bcb5e9a015510e9d74c288da928a4bfb8d80926a8ff85227c0eae8cdb2605519","src/scaffolding/mod.rs":"66c3f2d9e81ded234fdd5b34cbb1da334cc271fa6ff3daa41b9acf9ac02f2194","src/scaffolding/templates/CallbackInterfaceTemplate.rs":"11acee064df46f7b5132401ae49c03c77f296bc04065085d6fd5c4ad6b628718","src/scaffolding/templates/Checksums.rs":"ee926e840875c2e48e1d0cc5185c11f7a1ed3bde5264b07540812cb13c1d7481","src/scaffolding/templates/EnumTemplate.rs":"305b8f0e6ec38300f0ae576a1bf1c576d0088d0df8d0b45818ad25f0216a7ac0","src/scaffolding/templates/ErrorTemplate.rs":"e6ec4e1d4c594d9f14a8dfe0a24103a66c0cc91d2129f0e1644775740f85bea1","src/scaffolding/templates/ExternalTypesTemplate.rs":"4c45cefca1774de3f3b650ce3b9a1b1b8fc10c62e0e48e54ac300748c32959f6","src/scaffolding/templates/ObjectTemplate.rs":"80689b74cbb426e6ce8bce77359b122d747b34b48d9a30aef44f93c9aa726fa7","src/scaffolding/templates/RecordTemplate.rs":"644177d86b52bf39c277b4e60a66f594b3fb0454f6b62837f9041297135c09c9","src/scaffolding/templates/ReexportUniFFIScaffolding.rs":"aa8a1ffa98b6033707d965f90b5709474ed6bc79486fb47dacae8417fc056cf8","src/scaffolding/templates/TopLevelFunctionTemplate.rs":"c11a688cafc2e21c3be105533b34c1f73eab55202936f7c8a97191d7e27f26e0","src/scaffolding/templates/UdlMetadata.rs":"d7c50af1de92ef85630b385a910c7b29875502d622eb90da5541a7012b93d9e2","src/scaffolding/templates/macros.rs":"ea6bacd8dd9116ad739bdafe893d70407050f35e4a7ac8dd2c78b8ef34263e8e","src/scaffolding/templates/scaffolding_template.rs":"c8e18306a73ec5b764f665660fc5c91d498b63b6c3f489e524b2bae50f81f231"},"package":"4a77bb514bcd4bf27c9bd404d7c3f2a6a8131b957eba9c22cfeb7751c4278e09"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_bindgen/Cargo.toml b/third_party/rust/uniffi_bindgen/Cargo.toml
index 9469a9cf25..f0c7af8665 100644
--- a/third_party/rust/uniffi_bindgen/Cargo.toml
+++ b/third_party/rust/uniffi_bindgen/Cargo.toml
@@ -12,11 +12,12 @@
[package]
edition = "2021"
name = "uniffi_bindgen"
-version = "0.25.3"
+version = "0.27.1"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
description = "a multi-language bindings generator for rust (codegen and cli tooling)"
homepage = "https://mozilla.github.io/uniffi-rs"
documentation = "https://mozilla.github.io/uniffi-rs"
+readme = "README.md"
keywords = [
"ffi",
"bindgen",
@@ -54,7 +55,7 @@ version = "2.7.0"
version = "0.3"
[dependencies.goblin]
-version = "0.6"
+version = "0.8"
[dependencies.heck]
version = "0.4"
@@ -67,15 +68,19 @@ version = "1.0"
[dependencies.serde]
version = "1"
+features = ["derive"]
+
+[dependencies.textwrap]
+version = "0.16"
[dependencies.toml]
version = "0.5"
[dependencies.uniffi_meta]
-version = "=0.25.3"
+version = "=0.27.1"
[dependencies.uniffi_testing]
-version = "=0.25.3"
+version = "=0.27.1"
[dependencies.uniffi_udl]
-version = "=0.25.3"
+version = "=0.27.1"
diff --git a/third_party/rust/uniffi_bindgen/README.md b/third_party/rust/uniffi_bindgen/README.md
new file mode 100644
index 0000000000..64ac3486a3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/README.md
@@ -0,0 +1,81 @@
+# UniFFI - a multi-language bindings generator for Rust
+
+UniFFI is a toolkit for building cross-platform software components in Rust.
+
+For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/)
+or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components).
+
+By writing your core business logic in Rust and describing its interface in an "object model",
+you can use UniFFI to help you:
+
+* Compile your Rust code into a shared library for use on different target platforms.
+* Generate bindings to load and use the library from different target languages.
+
+You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html).
+
+UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers;
+written once in Rust, auto-generated bindings allow that functionality to be called
+from both Kotlin (for Android apps) and Swift (for iOS apps).
+It also has a growing community of users shipping various cool things to many users.
+
+UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**.
+Additional foreign language bindings can be developed externally and we welcome contributions to list them here.
+See [Third-party foreign language bindings](#third-party-foreign-language-bindings).
+
+## User Guide
+
+You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/).
+
+We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on.
+We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time.
+
+### Etymology and Pronunciation
+
+ˈjuːnɪfaɪ. Pronounced to rhyme with "unify".
+
+A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages.
+
+uni - [Latin ūni-, from ūnus, one]
+FFI - [Abbreviation, Foreign Function Interface]
+
+## Alternative tools
+
+Other tools we know of which try and solve a similarly shaped problem are:
+
+* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of
+ the different approach taken by that tool](docs/diplomat-and-macros.md)
+* [Interoptopus](https://github.com/ralfbiedert/interoptopus/)
+
+(Please open a PR if you think other tools should be listed!)
+
+## Third-party foreign language bindings
+
+* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native.
+* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go)
+* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs)
+* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart)
+
+### External resources
+
+There are a few third-party resources that make it easier to work with UniFFI:
+
+* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/).
+* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts.
+* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful.
+* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure.
+
+(Please open a PR if you think other resources should be listed!)
+
+## Contributing
+
+If this tool sounds interesting to you, please help us develop it! You can:
+
+* View the [contributor guidelines](./docs/contributing.md).
+* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub.
+* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org)
+ room on Matrix.
+
+## Code of Conduct
+
+This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md).
diff --git a/third_party/rust/uniffi_bindgen/src/backend/filters.rs b/third_party/rust/uniffi_bindgen/src/backend/filters.rs
index 0d2da8cab2..f4dde0e420 100644
--- a/third_party/rust/uniffi_bindgen/src/backend/filters.rs
+++ b/third_party/rust/uniffi_bindgen/src/backend/filters.rs
@@ -13,12 +13,12 @@ use std::fmt;
// Need to define an error that implements std::error::Error, which neither String nor
// anyhow::Error do.
#[derive(Debug)]
-struct UniFFIError {
+pub struct UniFFIError {
message: String,
}
impl UniFFIError {
- fn new(message: String) -> Self {
+ pub fn new(message: String) -> Self {
Self { message }
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs
index e20020e87c..ae4bffc973 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/callback_interface.rs
@@ -26,6 +26,6 @@ impl CodeType for CallbackInterfaceCodeType {
}
fn initialization_fn(&self) -> Option<String> {
- Some(format!("{}.register", self.ffi_converter_name()))
+ Some(format!("uniffiCallbackInterface{}.register", self.id))
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs
index 4329f32f4c..8d075bbedb 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/compounds.rs
@@ -5,55 +5,81 @@
use super::{AsCodeType, CodeType};
use crate::backend::{Literal, Type};
use crate::ComponentInterface;
-use paste::paste;
-fn render_literal(literal: &Literal, inner: &Type, ci: &ComponentInterface) -> String {
- match literal {
- Literal::Null => "null".into(),
- Literal::EmptySequence => "listOf()".into(),
- Literal::EmptyMap => "mapOf()".into(),
+#[derive(Debug)]
+pub struct OptionalCodeType {
+ inner: Type,
+}
- // For optionals
- _ => super::KotlinCodeOracle.find(inner).literal(literal, ci),
+impl OptionalCodeType {
+ pub fn new(inner: Type) -> Self {
+ Self { inner }
+ }
+ fn inner(&self) -> &Type {
+ &self.inner
}
}
-macro_rules! impl_code_type_for_compound {
- ($T:ty, $type_label_pattern:literal, $canonical_name_pattern: literal) => {
- paste! {
- #[derive(Debug)]
- pub struct $T {
- inner: Type,
- }
-
- impl $T {
- pub fn new(inner: Type) -> Self {
- Self { inner }
- }
- fn inner(&self) -> &Type {
- &self.inner
- }
- }
-
- impl CodeType for $T {
- fn type_label(&self, ci: &ComponentInterface) -> String {
- format!($type_label_pattern, super::KotlinCodeOracle.find(self.inner()).type_label(ci))
- }
-
- fn canonical_name(&self) -> String {
- format!($canonical_name_pattern, super::KotlinCodeOracle.find(self.inner()).canonical_name())
- }
-
- fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
- render_literal(literal, self.inner(), ci)
- }
- }
+impl CodeType for OptionalCodeType {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ format!(
+ "{}?",
+ super::KotlinCodeOracle.find(self.inner()).type_label(ci)
+ )
+ }
+
+ fn canonical_name(&self) -> String {
+ format!(
+ "Optional{}",
+ super::KotlinCodeOracle.find(self.inner()).canonical_name()
+ )
+ }
+
+ fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
+ match literal {
+ Literal::None => "null".into(),
+ Literal::Some { inner } => super::KotlinCodeOracle.find(&self.inner).literal(inner, ci),
+ _ => panic!("Invalid literal for Optional type: {literal:?}"),
}
}
- }
+}
-impl_code_type_for_compound!(OptionalCodeType, "{}?", "Optional{}");
-impl_code_type_for_compound!(SequenceCodeType, "List<{}>", "Sequence{}");
+#[derive(Debug)]
+pub struct SequenceCodeType {
+ inner: Type,
+}
+
+impl SequenceCodeType {
+ pub fn new(inner: Type) -> Self {
+ Self { inner }
+ }
+ fn inner(&self) -> &Type {
+ &self.inner
+ }
+}
+
+impl CodeType for SequenceCodeType {
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ format!(
+ "List<{}>",
+ super::KotlinCodeOracle.find(self.inner()).type_label(ci)
+ )
+ }
+
+ fn canonical_name(&self) -> String {
+ format!(
+ "Sequence{}",
+ super::KotlinCodeOracle.find(self.inner()).canonical_name()
+ )
+ }
+
+ fn literal(&self, literal: &Literal, _ci: &ComponentInterface) -> String {
+ match literal {
+ Literal::EmptySequence => "listOf()".into(),
+ _ => panic!("Invalid literal for List type: {literal:?}"),
+ }
+ }
+}
#[derive(Debug)]
pub struct MapCodeType {
@@ -92,7 +118,10 @@ impl CodeType for MapCodeType {
)
}
- fn literal(&self, literal: &Literal, ci: &ComponentInterface) -> String {
- render_literal(literal, &self.value, ci)
+ fn literal(&self, literal: &Literal, _ci: &ComponentInterface) -> String {
+ match literal {
+ Literal::EmptyMap => "mapOf()".into(),
+ _ => panic!("Invalid literal for Map type: {literal:?}"),
+ }
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs
deleted file mode 100644
index 154e12a381..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/executor.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-/* 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/. */
-
-use super::CodeType;
-use crate::ComponentInterface;
-
-#[derive(Debug)]
-pub struct ForeignExecutorCodeType;
-
-impl CodeType for ForeignExecutorCodeType {
- fn type_label(&self, _ci: &ComponentInterface) -> String {
- // Kotlin uses a CoroutineScope for ForeignExecutor
- "CoroutineScope".into()
- }
-
- fn canonical_name(&self) -> String {
- "ForeignExecutor".into()
- }
-
- fn initialization_fn(&self) -> Option<String> {
- Some("FfiConverterForeignExecutor.register".into())
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
index 3ecf09d47f..d55c78f760 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/external.rs
@@ -17,8 +17,8 @@ impl ExternalCodeType {
}
impl CodeType for ExternalCodeType {
- fn type_label(&self, _ci: &ComponentInterface) -> String {
- self.name.clone()
+ fn type_label(&self, ci: &ComponentInterface) -> String {
+ super::KotlinCodeOracle.class_name(ci, &self.name)
}
fn canonical_name(&self) -> String {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
index 1ed0575a9a..c4fc8e0ed6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
@@ -7,20 +7,21 @@ use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::fmt::Debug;
-use anyhow::{Context, Result};
+use anyhow::{bail, Context, Result};
use askama::Template;
+use camino::Utf8Path;
use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase};
use serde::{Deserialize, Serialize};
use crate::backend::TemplateExpression;
+use crate::bindings::kotlin;
use crate::interface::*;
-use crate::BindingsConfig;
+use crate::{BindingGenerator, BindingsConfig};
mod callback_interface;
mod compounds;
mod custom;
mod enum_;
-mod executor;
mod external;
mod miscellany;
mod object;
@@ -28,6 +29,28 @@ mod primitives;
mod record;
mod variant;
+pub struct KotlinBindingGenerator;
+impl BindingGenerator for KotlinBindingGenerator {
+ type Config = Config;
+
+ fn write_bindings(
+ &self,
+ ci: &ComponentInterface,
+ config: &Config,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+ ) -> Result<()> {
+ kotlin::write_bindings(config, ci, out_dir, try_format_code)
+ }
+
+ fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> {
+ if cdylib_name.is_none() {
+ bail!("Generate bindings for Kotlin requires a cdylib, but {library_path} was given");
+ }
+ Ok(())
+ }
+}
+
trait CodeType: Debug {
/// The language specific label used to reference this type. This will be used in
/// method signatures and property declarations.
@@ -73,10 +96,21 @@ trait CodeType: Debug {
pub struct Config {
package_name: Option<String>,
cdylib_name: Option<String>,
+ generate_immutable_records: Option<bool>,
#[serde(default)]
custom_types: HashMap<String, CustomTypeConfig>,
#[serde(default)]
external_packages: HashMap<String, String>,
+ #[serde(default)]
+ android: bool,
+ #[serde(default)]
+ android_cleaner: Option<bool>,
+}
+
+impl Config {
+ pub(crate) fn android_cleaner(&self) -> bool {
+ self.android_cleaner.unwrap_or(self.android)
+ }
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
@@ -103,6 +137,11 @@ impl Config {
"uniffi".into()
}
}
+
+ /// Whether to generate immutable records (`val` instead of `var`)
+ pub fn generate_immutable_records(&self) -> bool {
+ self.generate_immutable_records.unwrap_or(false)
+ }
}
impl BindingsConfig for Config {
@@ -236,7 +275,6 @@ pub struct KotlinWrapper<'a> {
ci: &'a ComponentInterface,
type_helper_code: String,
type_imports: BTreeSet<ImportRequirement>,
- has_async_fns: bool,
}
impl<'a> KotlinWrapper<'a> {
@@ -249,7 +287,6 @@ impl<'a> KotlinWrapper<'a> {
ci,
type_helper_code,
type_imports,
- has_async_fns: ci.has_async_fns(),
}
}
@@ -258,10 +295,6 @@ impl<'a> KotlinWrapper<'a> {
.iter_types()
.map(|t| KotlinCodeOracle.find(t))
.filter_map(|ct| ct.initialization_fn())
- .chain(
- self.has_async_fns
- .then(|| "uniffiRustFutureContinuationCallback.register".into()),
- )
.collect()
}
@@ -301,7 +334,12 @@ impl KotlinCodeOracle {
/// Get the idiomatic Kotlin rendering of a variable name.
fn var_name(&self, nm: &str) -> String {
- format!("`{}`", nm.to_string().to_lower_camel_case())
+ format!("`{}`", self.var_name_raw(nm))
+ }
+
+ /// `var_name` without the backticks. Useful for using in `@Structure.FieldOrder`.
+ pub fn var_name_raw(&self, nm: &str) -> String {
+ nm.to_string().to_lower_camel_case()
}
/// Get the idiomatic Kotlin rendering of an individual enum variant.
@@ -309,14 +347,78 @@ impl KotlinCodeOracle {
nm.to_string().to_shouty_snake_case()
}
- fn ffi_type_label_by_value(ffi_type: &FfiType) -> String {
+ /// Get the idiomatic Kotlin rendering of an FFI callback function name
+ fn ffi_callback_name(&self, nm: &str) -> String {
+ format!("Uniffi{}", nm.to_upper_camel_case())
+ }
+
+ /// Get the idiomatic Kotlin rendering of an FFI struct name
+ fn ffi_struct_name(&self, nm: &str) -> String {
+ format!("Uniffi{}", nm.to_upper_camel_case())
+ }
+
+ fn ffi_type_label_by_value(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ FfiType::RustBuffer(_) => format!("{}.ByValue", self.ffi_type_label(ffi_type)),
+ FfiType::Struct(name) => format!("{}.UniffiByValue", self.ffi_struct_name(name)),
+ _ => self.ffi_type_label(ffi_type),
+ }
+ }
+
+ /// FFI type name to use inside structs
+ ///
+ /// The main requirement here is that all types must have default values or else the struct
+ /// won't work in some JNA contexts.
+ fn ffi_type_label_for_ffi_struct(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ // Make callbacks function pointers nullable. This matches the semantics of a C
+ // function pointer better and allows for `null` as a default value.
+ FfiType::Callback(name) => format!("{}?", self.ffi_callback_name(name)),
+ _ => self.ffi_type_label_by_value(ffi_type),
+ }
+ }
+
+ /// Default values for FFI
+ ///
+ /// This is used to:
+ /// - Set a default return value for error results
+ /// - Set a default for structs, which JNA sometimes requires
+ fn ffi_default_value(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ FfiType::UInt8 | FfiType::Int8 => "0.toByte()".to_owned(),
+ FfiType::UInt16 | FfiType::Int16 => "0.toShort()".to_owned(),
+ FfiType::UInt32 | FfiType::Int32 => "0".to_owned(),
+ FfiType::UInt64 | FfiType::Int64 => "0.toLong()".to_owned(),
+ FfiType::Float32 => "0.0f".to_owned(),
+ FfiType::Float64 => "0.0".to_owned(),
+ FfiType::RustArcPtr(_) => "Pointer.NULL".to_owned(),
+ FfiType::RustBuffer(_) => "RustBuffer.ByValue()".to_owned(),
+ FfiType::Callback(_) => "null".to_owned(),
+ FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue()".to_owned(),
+ _ => unimplemented!("ffi_default_value: {ffi_type:?}"),
+ }
+ }
+
+ fn ffi_type_label_by_reference(&self, ffi_type: &FfiType) -> String {
match ffi_type {
- FfiType::RustBuffer(_) => format!("{}.ByValue", Self::ffi_type_label(ffi_type)),
- _ => Self::ffi_type_label(ffi_type),
+ FfiType::Int8
+ | FfiType::UInt8
+ | FfiType::Int16
+ | FfiType::UInt16
+ | FfiType::Int32
+ | FfiType::UInt32
+ | FfiType::Int64
+ | FfiType::UInt64
+ | FfiType::Float32
+ | FfiType::Float64 => format!("{}ByReference", self.ffi_type_label(ffi_type)),
+ FfiType::RustArcPtr(_) => "PointerByReference".to_owned(),
+ // JNA structs default to ByReference
+ FfiType::RustBuffer(_) | FfiType::Struct(_) => self.ffi_type_label(ffi_type),
+ _ => panic!("{ffi_type:?} by reference is not implemented"),
}
}
- fn ffi_type_label(ffi_type: &FfiType) -> String {
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
match ffi_type {
// Note that unsigned integers in Kotlin are currently experimental, but java.nio.ByteBuffer does not
// support them yet. Thus, we use the signed variants to represent both signed and unsigned
@@ -327,19 +429,35 @@ impl KotlinCodeOracle {
FfiType::Int64 | FfiType::UInt64 => "Long".to_string(),
FfiType::Float32 => "Float".to_string(),
FfiType::Float64 => "Double".to_string(),
+ FfiType::Handle => "Long".to_string(),
FfiType::RustArcPtr(_) => "Pointer".to_string(),
FfiType::RustBuffer(maybe_suffix) => {
format!("RustBuffer{}", maybe_suffix.as_deref().unwrap_or_default())
}
+ FfiType::RustCallStatus => "UniffiRustCallStatus.ByValue".to_string(),
FfiType::ForeignBytes => "ForeignBytes.ByValue".to_string(),
- FfiType::ForeignCallback => "ForeignCallback".to_string(),
- FfiType::ForeignExecutorHandle => "USize".to_string(),
- FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback".to_string(),
- FfiType::RustFutureHandle => "Pointer".to_string(),
- FfiType::RustFutureContinuationCallback => {
- "UniFffiRustFutureContinuationCallbackType".to_string()
- }
- FfiType::RustFutureContinuationData => "USize".to_string(),
+ FfiType::Callback(name) => self.ffi_callback_name(name),
+ FfiType::Struct(name) => self.ffi_struct_name(name),
+ FfiType::Reference(inner) => self.ffi_type_label_by_reference(inner),
+ FfiType::VoidPointer => "Pointer".to_string(),
+ }
+ }
+
+ /// Get the name of the interface and class name for an object.
+ ///
+ /// If we support callback interfaces, the interface name is the object name, and the class name is derived from that.
+ /// Otherwise, the class name is the object name and the interface name is derived from that.
+ ///
+ /// This split determines what types `FfiConverter.lower()` inputs. If we support callback
+ /// interfaces, `lower` must lower anything that implements the interface. If not, then lower
+ /// only lowers the concrete class.
+ fn object_names(&self, ci: &ComponentInterface, obj: &Object) -> (String, String) {
+ let class_name = self.class_name(ci, obj.name());
+ if obj.has_callback_interface() {
+ let impl_name = format!("{class_name}Impl");
+ (class_name, impl_name)
+ } else {
+ (format!("{class_name}Interface"), class_name)
}
}
}
@@ -376,12 +494,11 @@ impl<T: AsType> AsCodeType for T {
Type::Duration => Box::new(miscellany::DurationCodeType),
Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)),
- Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)),
+ Type::Object { name, imp, .. } => Box::new(object::ObjectCodeType::new(name, imp)),
Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)),
Type::CallbackInterface { name, .. } => {
Box::new(callback_interface::CallbackInterfaceCodeType::new(name))
}
- Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
Type::Optional { inner_type } => {
Box::new(compounds::OptionalCodeType::new(*inner_type))
}
@@ -401,6 +518,7 @@ impl<T: AsType> AsCodeType for T {
mod filters {
use super::*;
pub use crate::backend::filters::*;
+ use uniffi_meta::LiteralMetadata;
pub(super) fn type_name(
as_ct: &impl AsCodeType,
@@ -454,8 +572,52 @@ mod filters {
Ok(as_ct.as_codetype().literal(literal, ci))
}
+ // Get the idiomatic Kotlin rendering of an integer.
+ fn int_literal(t: &Option<Type>, base10: String) -> Result<String, askama::Error> {
+ if let Some(t) = t {
+ match t {
+ Type::Int8 | Type::Int16 | Type::Int32 | Type::Int64 => Ok(base10),
+ Type::UInt8 | Type::UInt16 | Type::UInt32 | Type::UInt64 => Ok(base10 + "u"),
+ _ => Err(askama::Error::Custom(Box::new(UniFFIError::new(
+ "Only ints are supported.".to_string(),
+ )))),
+ }
+ } else {
+ Err(askama::Error::Custom(Box::new(UniFFIError::new(
+ "Enum hasn't defined a repr".to_string(),
+ ))))
+ }
+ }
+
+ // Get the idiomatic Kotlin rendering of an individual enum variant's discriminant
+ pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> {
+ let literal = e.variant_discr(*index).expect("invalid index");
+ match literal {
+ // Kotlin doesn't convert between signed and unsigned by default
+ // so we'll need to make sure we define the type as appropriately
+ LiteralMetadata::UInt(v, _, _) => int_literal(e.variant_discr_type(), v.to_string()),
+ LiteralMetadata::Int(v, _, _) => int_literal(e.variant_discr_type(), v.to_string()),
+ _ => Err(askama::Error::Custom(Box::new(UniFFIError::new(
+ "Only ints are supported.".to_string(),
+ )))),
+ }
+ }
+
pub fn ffi_type_name_by_value(type_: &FfiType) -> Result<String, askama::Error> {
- Ok(KotlinCodeOracle::ffi_type_label_by_value(type_))
+ Ok(KotlinCodeOracle.ffi_type_label_by_value(type_))
+ }
+
+ pub fn ffi_type_name_for_ffi_struct(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.ffi_type_label_for_ffi_struct(type_))
+ }
+
+ pub fn ffi_default_value(type_: FfiType) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.ffi_default_value(&type_))
+ }
+
+ /// Get the idiomatic Kotlin rendering of a function name.
+ pub fn class_name(nm: &str, ci: &ComponentInterface) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.class_name(ci, nm))
}
/// Get the idiomatic Kotlin rendering of a function name.
@@ -468,6 +630,11 @@ mod filters {
Ok(KotlinCodeOracle.var_name(nm))
}
+ /// Get the idiomatic Kotlin rendering of a variable name.
+ pub fn var_name_raw(nm: &str) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.var_name_raw(nm))
+ }
+
/// Get a String representing the name used for an individual enum variant.
pub fn variant_name(v: &Variant) -> Result<String, askama::Error> {
Ok(KotlinCodeOracle.enum_variant_name(v.name()))
@@ -478,13 +645,30 @@ mod filters {
Ok(KotlinCodeOracle.convert_error_suffix(&name))
}
+ /// Get the idiomatic Kotlin rendering of an FFI callback function name
+ pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.ffi_callback_name(nm))
+ }
+
+ /// Get the idiomatic Kotlin rendering of an FFI struct name
+ pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(KotlinCodeOracle.ffi_struct_name(nm))
+ }
+
+ pub fn object_names(
+ obj: &Object,
+ ci: &ComponentInterface,
+ ) -> Result<(String, String), askama::Error> {
+ Ok(KotlinCodeOracle.object_names(ci, obj))
+ }
+
pub fn async_poll(
callable: impl Callable,
ci: &ComponentInterface,
) -> Result<String, askama::Error> {
let ffi_func = callable.ffi_rust_future_poll(ci);
Ok(format!(
- "{{ future, continuation -> _UniFFILib.INSTANCE.{ffi_func}(future, continuation) }}"
+ "{{ future, callback, continuation -> UniffiLib.INSTANCE.{ffi_func}(future, callback, continuation) }}"
))
}
@@ -493,7 +677,7 @@ mod filters {
ci: &ComponentInterface,
) -> Result<String, askama::Error> {
let ffi_func = callable.ffi_rust_future_complete(ci);
- let call = format!("_UniFFILib.INSTANCE.{ffi_func}(future, continuation)");
+ let call = format!("UniffiLib.INSTANCE.{ffi_func}(future, continuation)");
let call = match callable.return_type() {
Some(Type::External {
kind: ExternalKind::DataClass,
@@ -502,7 +686,7 @@ mod filters {
}) => {
// Need to convert the RustBuffer from our package to the RustBuffer of the external package
let suffix = KotlinCodeOracle.class_name(ci, &name);
- format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity, it.len, it.data) }}")
+ format!("{call}.let {{ RustBuffer{suffix}.create(it.capacity.toULong(), it.len.toULong(), it.data) }}")
}
_ => call,
};
@@ -515,7 +699,7 @@ mod filters {
) -> Result<String, askama::Error> {
let ffi_func = callable.ffi_rust_future_free(ci);
Ok(format!(
- "{{ future -> _UniFFILib.INSTANCE.{ffi_func}(future) }}"
+ "{{ future -> UniffiLib.INSTANCE.{ffi_func}(future) }}"
))
}
@@ -527,4 +711,13 @@ mod filters {
pub fn unquote(nm: &str) -> Result<String, askama::Error> {
Ok(nm.trim_matches('`').to_string())
}
+
+ /// Get the idiomatic Kotlin rendering of docstring
+ pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> {
+ let middle = textwrap::indent(&textwrap::dedent(docstring), " * ");
+ let wrapped = format!("/**\n{middle}\n */");
+
+ let spaces = usize::try_from(*spaces).unwrap_or_default();
+ Ok(textwrap::indent(&wrapped, &" ".repeat(spaces)))
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
index c39ae59cce..5a4305d14a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/object.rs
@@ -3,25 +3,32 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use super::CodeType;
-use crate::ComponentInterface;
+use crate::{interface::ObjectImpl, ComponentInterface};
#[derive(Debug)]
pub struct ObjectCodeType {
- id: String,
+ name: String,
+ imp: ObjectImpl,
}
impl ObjectCodeType {
- pub fn new(id: String) -> Self {
- Self { id }
+ pub fn new(name: String, imp: ObjectImpl) -> Self {
+ Self { name, imp }
}
}
impl CodeType for ObjectCodeType {
fn type_label(&self, ci: &ComponentInterface) -> String {
- super::KotlinCodeOracle.class_name(ci, &self.id)
+ super::KotlinCodeOracle.class_name(ci, &self.name)
}
fn canonical_name(&self) -> String {
- format!("Type{}", self.id)
+ format!("Type{}", self.name)
+ }
+
+ fn initialization_fn(&self) -> Option<String> {
+ self.imp
+ .has_callback_interface()
+ .then(|| format!("uniffiCallbackInterface{}.register", self.name))
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
index 22495fa209..0bc5a5d99e 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/gen_kotlin/primitives.rs
@@ -9,7 +9,11 @@ use paste::paste;
fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String {
fn typed_number(type_: &Type, num_str: String) -> String {
- match type_ {
+ let unwrapped_type = match type_ {
+ Type::Optional { inner_type } => inner_type,
+ t => t,
+ };
+ match unwrapped_type {
// Bytes, Shorts and Ints can all be inferred from the type.
Type::Int8 | Type::Int16 | Type::Int32 => num_str,
Type::Int64 => format!("{num_str}L"),
@@ -19,7 +23,7 @@ fn render_literal(literal: &Literal, _ci: &ComponentInterface) -> String {
Type::Float32 => format!("{num_str}f"),
Type::Float64 => num_str,
- _ => panic!("Unexpected literal: {num_str} is not a number"),
+ _ => panic!("Unexpected literal: {num_str} for type: {type_:?}"),
}
}
@@ -56,7 +60,7 @@ macro_rules! impl_code_type_for_primitive {
impl CodeType for $T {
fn type_label(&self, _ci: &ComponentInterface) -> String {
- $class_name.into()
+ format!("kotlin.{}", $class_name)
}
fn canonical_name(&self) -> String {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
index c6a32655f2..b28fbd2c80 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Async.kt
@@ -1,44 +1,117 @@
// Async return type handlers
-internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toShort()
-internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toShort()
+internal const val UNIFFI_RUST_FUTURE_POLL_READY = 0.toByte()
+internal const val UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1.toByte()
-internal val uniffiContinuationHandleMap = UniFfiHandleMap<CancellableContinuation<Short>>()
+internal val uniffiContinuationHandleMap = UniffiHandleMap<CancellableContinuation<Byte>>()
// FFI type for Rust future continuations
-internal object uniffiRustFutureContinuationCallback: UniFffiRustFutureContinuationCallbackType {
- override fun callback(continuationHandle: USize, pollResult: Short) {
- uniffiContinuationHandleMap.remove(continuationHandle)?.resume(pollResult)
- }
-
- internal fun register(lib: _UniFFILib) {
- lib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(this)
+internal object uniffiRustFutureContinuationCallbackImpl: UniffiRustFutureContinuationCallback {
+ override fun callback(data: Long, pollResult: Byte) {
+ uniffiContinuationHandleMap.remove(data).resume(pollResult)
}
}
internal suspend fun<T, F, E: Exception> uniffiRustCallAsync(
- rustFuture: Pointer,
- pollFunc: (Pointer, USize) -> Unit,
- completeFunc: (Pointer, RustCallStatus) -> F,
- freeFunc: (Pointer) -> Unit,
+ rustFuture: Long,
+ pollFunc: (Long, UniffiRustFutureContinuationCallback, Long) -> Unit,
+ completeFunc: (Long, UniffiRustCallStatus) -> F,
+ freeFunc: (Long) -> Unit,
liftFunc: (F) -> T,
- errorHandler: CallStatusErrorHandler<E>
+ errorHandler: UniffiRustCallStatusErrorHandler<E>
): T {
try {
do {
- val pollResult = suspendCancellableCoroutine<Short> { continuation ->
+ val pollResult = suspendCancellableCoroutine<Byte> { continuation ->
pollFunc(
rustFuture,
+ uniffiRustFutureContinuationCallbackImpl,
uniffiContinuationHandleMap.insert(continuation)
)
}
} while (pollResult != UNIFFI_RUST_FUTURE_POLL_READY);
return liftFunc(
- rustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) })
+ uniffiRustCallWithError(errorHandler, { status -> completeFunc(rustFuture, status) })
)
} finally {
freeFunc(rustFuture)
}
}
+{%- if ci.has_async_callback_interface_definition() %}
+internal inline fun<T> uniffiTraitInterfaceCallAsync(
+ crossinline makeCall: suspend () -> T,
+ crossinline handleSuccess: (T) -> Unit,
+ crossinline handleError: (UniffiRustCallStatus.ByValue) -> Unit,
+): UniffiForeignFuture {
+ // Using `GlobalScope` is labeled as a "delicate API" and generally discouraged in Kotlin programs, since it breaks structured concurrency.
+ // However, our parent task is a Rust future, so we're going to need to break structure concurrency in any case.
+ //
+ // Uniffi does its best to support structured concurrency across the FFI.
+ // If the Rust future is dropped, `uniffiForeignFutureFreeImpl` is called, which will cancel the Kotlin coroutine if it's still running.
+ @OptIn(DelicateCoroutinesApi::class)
+ val job = GlobalScope.launch {
+ try {
+ handleSuccess(makeCall())
+ } catch(e: Exception) {
+ handleError(
+ UniffiRustCallStatus.create(
+ UNIFFI_CALL_UNEXPECTED_ERROR,
+ {{ Type::String.borrow()|lower_fn }}(e.toString()),
+ )
+ )
+ }
+ }
+ val handle = uniffiForeignFutureHandleMap.insert(job)
+ return UniffiForeignFuture(handle, uniffiForeignFutureFreeImpl)
+}
+
+internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallAsyncWithError(
+ crossinline makeCall: suspend () -> T,
+ crossinline handleSuccess: (T) -> Unit,
+ crossinline handleError: (UniffiRustCallStatus.ByValue) -> Unit,
+ crossinline lowerError: (E) -> RustBuffer.ByValue,
+): UniffiForeignFuture {
+ // See uniffiTraitInterfaceCallAsync for details on `DelicateCoroutinesApi`
+ @OptIn(DelicateCoroutinesApi::class)
+ val job = GlobalScope.launch {
+ try {
+ handleSuccess(makeCall())
+ } catch(e: Exception) {
+ if (e is E) {
+ handleError(
+ UniffiRustCallStatus.create(
+ UNIFFI_CALL_ERROR,
+ lowerError(e),
+ )
+ )
+ } else {
+ handleError(
+ UniffiRustCallStatus.create(
+ UNIFFI_CALL_UNEXPECTED_ERROR,
+ {{ Type::String.borrow()|lower_fn }}(e.toString()),
+ )
+ )
+ }
+ }
+ }
+ val handle = uniffiForeignFutureHandleMap.insert(job)
+ return UniffiForeignFuture(handle, uniffiForeignFutureFreeImpl)
+}
+
+internal val uniffiForeignFutureHandleMap = UniffiHandleMap<Job>()
+
+internal object uniffiForeignFutureFreeImpl: UniffiForeignFutureFree {
+ override fun callback(handle: Long) {
+ val job = uniffiForeignFutureHandleMap.remove(handle)
+ if (!job.isCompleted) {
+ job.cancel()
+ }
+ }
+}
+
+// For testing
+public fun uniffiForeignFutureHandleCount() = uniffiForeignFutureHandleMap.size
+
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
index 8cfa2ce000..c6b266066d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/BooleanHelper.kt
@@ -11,7 +11,7 @@ public object FfiConverterBoolean: FfiConverter<Boolean, Byte> {
return if (value) 1.toByte() else 0.toByte()
}
- override fun allocationSize(value: Boolean) = 1
+ override fun allocationSize(value: Boolean) = 1UL
override fun write(value: Boolean, buf: ByteBuffer) {
buf.put(lower(value))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
index 4840a199b4..c9449069e2 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ByteArrayHelper.kt
@@ -5,8 +5,8 @@ public object FfiConverterByteArray: FfiConverterRustBuffer<ByteArray> {
buf.get(byteArr)
return byteArr
}
- override fun allocationSize(value: ByteArray): Int {
- return 4 + value.size
+ override fun allocationSize(value: ByteArray): ULong {
+ return 4UL + value.size.toULong()
}
override fun write(value: ByteArray, buf: ByteBuffer) {
buf.putInt(value.size)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt
new file mode 100644
index 0000000000..30a39d9afb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceImpl.kt
@@ -0,0 +1,117 @@
+{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %}
+
+{%- let trait_impl=format!("uniffiCallbackInterface{}", name) %}
+
+// Put the implementation in an object so we don't pollute the top-level namespace
+internal object {{ trait_impl }} {
+ {%- for (ffi_callback, meth) in vtable_methods.iter() %}
+ internal object {{ meth.name()|var_name }}: {{ ffi_callback.name()|ffi_callback_name }} {
+ override fun callback(
+ {%- for arg in ffi_callback.arguments() -%}
+ {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }},
+ {%- endfor -%}
+ {%- if ffi_callback.has_rust_call_status_arg() -%}
+ uniffiCallStatus: UniffiRustCallStatus,
+ {%- endif -%}
+ )
+ {%- match ffi_callback.return_type() %}
+ {%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }},
+ {%- when None %}
+ {%- endmatch %} {
+ val uniffiObj = {{ ffi_converter_name }}.handleMap.get(uniffiHandle)
+ val makeCall = {% if meth.is_async() %}suspend {% endif %}{ ->
+ uniffiObj.{{ meth.name()|fn_name() }}(
+ {%- for arg in meth.arguments() %}
+ {{ arg|lift_fn }}({{ arg.name()|var_name }}),
+ {%- endfor %}
+ )
+ }
+ {%- if !meth.is_async() %}
+
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ val writeReturn = { value: {{ return_type|type_name(ci) }} -> uniffiOutReturn.setValue({{ return_type|lower_fn }}(value)) }
+ {%- when None %}
+ val writeReturn = { _: Unit -> Unit }
+ {%- endmatch %}
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ uniffiTraitInterfaceCall(uniffiCallStatus, makeCall, writeReturn)
+ {%- when Some(error_type) %}
+ uniffiTraitInterfaceCallWithError(
+ uniffiCallStatus,
+ makeCall,
+ writeReturn,
+ { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) }
+ )
+ {%- endmatch %}
+
+ {%- else %}
+ val uniffiHandleSuccess = { {% if meth.return_type().is_some() %}returnValue{% else %}_{% endif %}: {% match meth.return_type() %}{%- when Some(return_type) %}{{ return_type|type_name(ci) }}{%- when None %}Unit{% endmatch %} ->
+ val uniffiResult = {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}.UniffiByValue(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ {{ return_type|lower_fn }}(returnValue),
+ {%- when None %}
+ {%- endmatch %}
+ UniffiRustCallStatus.ByValue()
+ )
+ uniffiResult.write()
+ uniffiFutureCallback.callback(uniffiCallbackData, uniffiResult)
+ }
+ val uniffiHandleError = { callStatus: UniffiRustCallStatus.ByValue ->
+ uniffiFutureCallback.callback(
+ uniffiCallbackData,
+ {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}.UniffiByValue(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ {{ return_type.into()|ffi_default_value }},
+ {%- when None %}
+ {%- endmatch %}
+ callStatus,
+ ),
+ )
+ }
+
+ uniffiOutReturn.uniffiSetValue(
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ uniffiTraitInterfaceCallAsync(
+ makeCall,
+ uniffiHandleSuccess,
+ uniffiHandleError
+ )
+ {%- when Some(error_type) %}
+ uniffiTraitInterfaceCallAsyncWithError(
+ makeCall,
+ uniffiHandleSuccess,
+ uniffiHandleError,
+ { e: {{error_type|type_name(ci) }} -> {{ error_type|lower_fn }}(e) }
+ )
+ {%- endmatch %}
+ )
+ {%- endif %}
+ }
+ }
+ {%- endfor %}
+
+ internal object uniffiFree: {{ "CallbackInterfaceFree"|ffi_callback_name }} {
+ override fun callback(handle: Long) {
+ {{ ffi_converter_name }}.handleMap.remove(handle)
+ }
+ }
+
+ internal var vtable = {{ vtable|ffi_type_name_by_value }}(
+ {%- for (ffi_callback, meth) in vtable_methods.iter() %}
+ {{ meth.name()|var_name() }},
+ {%- endfor %}
+ uniffiFree,
+ )
+
+ // Registers the foreign callback with the Rust side.
+ // This method is generated for each callback interface.
+ internal fun register(lib: UniffiLib) {
+ lib.{{ ffi_init_callback.name() }}(vtable)
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
index 62a71e02f1..d58a651e24 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceRuntime.kt
@@ -1,43 +1,3 @@
-internal typealias Handle = Long
-internal class ConcurrentHandleMap<T>(
- private val leftMap: MutableMap<Handle, T> = mutableMapOf(),
- private val rightMap: MutableMap<T, Handle> = mutableMapOf()
-) {
- private val lock = java.util.concurrent.locks.ReentrantLock()
- private val currentHandle = AtomicLong(0L)
- private val stride = 1L
-
- fun insert(obj: T): Handle =
- lock.withLock {
- rightMap[obj] ?:
- currentHandle.getAndAdd(stride)
- .also { handle ->
- leftMap[handle] = obj
- rightMap[obj] = handle
- }
- }
-
- fun get(handle: Handle) = lock.withLock {
- leftMap[handle]
- }
-
- fun delete(handle: Handle) {
- this.remove(handle)
- }
-
- fun remove(handle: Handle): T? =
- lock.withLock {
- leftMap.remove(handle)?.let { obj ->
- rightMap.remove(obj)
- obj
- }
- }
-}
-
-interface ForeignCallback : com.sun.jna.Callback {
- public fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int
-}
-
// Magic number for the Rust proxy to call using the same mechanism as every other method,
// to free the callback once it's dropped by Rust.
internal const val IDX_CALLBACK_FREE = 0
@@ -46,31 +6,22 @@ internal const val UNIFFI_CALLBACK_SUCCESS = 0
internal const val UNIFFI_CALLBACK_ERROR = 1
internal const val UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2
-public abstract class FfiConverterCallbackInterface<CallbackInterface>(
- protected val foreignCallback: ForeignCallback
-): FfiConverter<CallbackInterface, Handle> {
- private val handleMap = ConcurrentHandleMap<CallbackInterface>()
-
- // Registers the foreign callback with the Rust side.
- // This method is generated for each callback interface.
- internal abstract fun register(lib: _UniFFILib)
+public abstract class FfiConverterCallbackInterface<CallbackInterface: Any>: FfiConverter<CallbackInterface, Long> {
+ internal val handleMap = UniffiHandleMap<CallbackInterface>()
- fun drop(handle: Handle): RustBuffer.ByValue {
- return handleMap.remove(handle).let { RustBuffer.ByValue() }
+ internal fun drop(handle: Long) {
+ handleMap.remove(handle)
}
- override fun lift(value: Handle): CallbackInterface {
- return handleMap.get(value) ?: throw InternalException("No callback in handlemap; this is a Uniffi bug")
+ override fun lift(value: Long): CallbackInterface {
+ return handleMap.get(value)
}
override fun read(buf: ByteBuffer) = lift(buf.getLong())
- override fun lower(value: CallbackInterface) =
- handleMap.insert(value).also {
- assert(handleMap.get(it) === value) { "Handle map is not returning the object we just placed there. This is a bug in the HandleMap." }
- }
+ override fun lower(value: CallbackInterface) = handleMap.insert(value)
- override fun allocationSize(value: CallbackInterface) = 8
+ override fun allocationSize(value: CallbackInterface) = 8UL
override fun write(value: CallbackInterface, buf: ByteBuffer) {
buf.putLong(lower(value))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
index 5a29f0acc3..d2cdee4f33 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CallbackInterfaceTemplate.kt
@@ -1,129 +1,13 @@
{%- let cbi = ci|get_callback_interface_definition(name) %}
-{%- let type_name = cbi|type_name(ci) %}
-{%- let foreign_callback = format!("ForeignCallback{}", canonical_type_name) %}
-
-{% if self.include_once_check("CallbackInterfaceRuntime.kt") %}{% include "CallbackInterfaceRuntime.kt" %}{% endif %}
-{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
-{{- self.add_import("java.util.concurrent.locks.ReentrantLock") }}
-{{- self.add_import("kotlin.concurrent.withLock") }}
-
-// Declaration and FfiConverters for {{ type_name }} Callback Interface
-
-public interface {{ type_name }} {
- {% for meth in cbi.methods() -%}
- fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
- {%- else -%}
- {%- endmatch %}
- {% endfor %}
- companion object
-}
-
-// The ForeignCallback that is passed to Rust.
-internal class {{ foreign_callback }} : ForeignCallback {
- @Suppress("TooGenericExceptionCaught")
- override fun callback(handle: Handle, method: Int, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int {
- val cb = {{ ffi_converter_name }}.lift(handle)
- return when (method) {
- IDX_CALLBACK_FREE -> {
- {{ ffi_converter_name }}.drop(handle)
- // Successful return
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- UNIFFI_CALLBACK_SUCCESS
- }
- {% for meth in cbi.methods() -%}
- {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
- {{ loop.index }} -> {
- // Call the method, write to outBuf and return a status code
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for info
- try {
- this.{{ method_name }}(cb, argsData, argsLen, outBuf)
- } catch (e: Throwable) {
- // Unexpected error
- try {
- // Try to serialize the error into a string
- outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower(e.toString()))
- } catch (e: Throwable) {
- // If that fails, then it's time to give up and just return
- }
- UNIFFI_CALLBACK_UNEXPECTED_ERROR
- }
- }
- {% endfor %}
- else -> {
- // An unexpected error happened.
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- try {
- // Try to serialize the error into a string
- outBuf.setValue({{ Type::String.borrow()|ffi_converter_name }}.lower("Invalid Callback index"))
- } catch (e: Throwable) {
- // If that fails, then it's time to give up and just return
- }
- UNIFFI_CALLBACK_UNEXPECTED_ERROR
- }
- }
- }
-
- {% for meth in cbi.methods() -%}
- {% let method_name = format!("invoke_{}", meth.name())|fn_name %}
- @Suppress("UNUSED_PARAMETER")
- private fun {{ method_name }}(kotlinCallbackInterface: {{ type_name }}, argsData: Pointer, argsLen: Int, outBuf: RustBufferByReference): Int {
- {%- if meth.arguments().len() > 0 %}
- val argsBuf = argsData.getByteBuffer(0, argsLen.toLong()).also {
- it.order(ByteOrder.BIG_ENDIAN)
- }
- {%- endif %}
-
- {%- match meth.return_type() %}
- {%- when Some with (return_type) %}
- fun makeCall() : Int {
- val returnValue = kotlinCallbackInterface.{{ meth.name()|fn_name }}(
- {%- for arg in meth.arguments() %}
- {{ arg|read_fn }}(argsBuf)
- {% if !loop.last %}, {% endif %}
- {%- endfor %}
- )
- outBuf.setValue({{ return_type|ffi_converter_name }}.lowerIntoRustBuffer(returnValue))
- return UNIFFI_CALLBACK_SUCCESS
- }
- {%- when None %}
- fun makeCall() : Int {
- kotlinCallbackInterface.{{ meth.name()|fn_name }}(
- {%- for arg in meth.arguments() %}
- {{ arg|read_fn }}(argsBuf)
- {%- if !loop.last %}, {% endif %}
- {%- endfor %}
- )
- return UNIFFI_CALLBACK_SUCCESS
- }
- {%- endmatch %}
-
- {%- match meth.throws_type() %}
- {%- when None %}
- fun makeCallAndHandleError() : Int = makeCall()
- {%- when Some(error_type) %}
- fun makeCallAndHandleError() : Int = try {
- makeCall()
- } catch (e: {{ error_type|type_name(ci) }}) {
- // Expected error, serialize it into outBuf
- outBuf.setValue({{ error_type|ffi_converter_name }}.lowerIntoRustBuffer(e))
- UNIFFI_CALLBACK_ERROR
- }
- {%- endmatch %}
-
- return makeCallAndHandleError()
- }
- {% endfor %}
-}
-
-// The ffiConverter which transforms the Callbacks in to Handles to pass to Rust.
-public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ type_name }}>(
- foreignCallback = {{ foreign_callback }}()
-) {
- override fun register(lib: _UniFFILib) {
- rustCall() { status ->
- lib.{{ cbi.ffi_init_callback().name() }}(this.foreignCallback, status)
- }
- }
-}
+{%- let ffi_init_callback = cbi.ffi_init_callback() %}
+{%- let interface_name = cbi|type_name(ci) %}
+{%- let interface_docstring = cbi.docstring() %}
+{%- let methods = cbi.methods() %}
+{%- let vtable = cbi.vtable() %}
+{%- let vtable_methods = cbi.vtable_methods() %}
+
+{% include "Interface.kt" %}
+{% include "CallbackInterfaceImpl.kt" %}
+
+// The ffiConverter which transforms the Callbacks in to handles to pass to Rust.
+public object {{ ffi_converter_name }}: FfiConverterCallbackInterface<{{ interface_name }}>()
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
index 04150c5d78..aeb5f58002 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/CustomTypeTemplate.kt
@@ -49,7 +49,7 @@ public object {{ ffi_converter_name }}: FfiConverter<{{ name }}, {{ ffi_type_nam
return {{ config.into_custom.render("builtinValue") }}
}
- override fun allocationSize(value: {{ name }}): Int {
+ override fun allocationSize(value: {{ name }}): ULong {
val builtinValue = {{ config.from_custom.render("value") }}
return {{ builtin|allocation_size_fn }}(builtinValue)
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
index 4237c6f9a8..62e02607f3 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/DurationHelper.kt
@@ -14,7 +14,7 @@ public object FfiConverterDuration: FfiConverterRustBuffer<java.time.Duration> {
}
// 8 bytes for seconds, 4 bytes for nanoseconds
- override fun allocationSize(value: java.time.Duration) = 12
+ override fun allocationSize(value: java.time.Duration) = 12UL
override fun write(value: java.time.Duration, buf: ByteBuffer) {
if (value.seconds < 0) {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
index d4c4a1684a..8d1c2235ec 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/EnumTemplate.kt
@@ -7,12 +7,25 @@
{%- if e.is_flat() %}
+{%- call kt::docstring(e, 0) %}
+{% match e.variant_discr_type() %}
+{% when None %}
enum class {{ type_name }} {
{% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
{{ variant|variant_name }}{% if loop.last %};{% else %},{% endif %}
{%- endfor %}
companion object
}
+{% when Some with (variant_discr_type) %}
+enum class {{ type_name }}(val value: {{ variant_discr_type|type_name(ci) }}) {
+ {% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
+ {{ variant|variant_name }}({{ e|variant_discr_literal(loop.index0) }}){% if loop.last %};{% else %},{% endif %}
+ {%- endfor %}
+ companion object
+}
+{% endmatch %}
public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
override fun read(buf: ByteBuffer) = try {
@@ -21,7 +34,7 @@ public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}
throw RuntimeException("invalid enum value, something is very wrong!!", e)
}
- override fun allocationSize(value: {{ type_name }}) = 4
+ override fun allocationSize(value: {{ type_name }}) = 4UL
override fun write(value: {{ type_name }}, buf: ByteBuffer) {
buf.putInt(value.ordinal + 1)
@@ -30,15 +43,18 @@ public object {{ e|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}
{% else %}
+{%- call kt::docstring(e, 0) %}
sealed class {{ type_name }}{% if contains_object_references %}: Disposable {% endif %} {
{% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
{% if !variant.has_fields() -%}
object {{ variant|type_name(ci) }} : {{ type_name }}()
{% else -%}
data class {{ variant|type_name(ci) }}(
- {% for field in variant.fields() -%}
- val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
- {% endfor -%}
+ {%- for field in variant.fields() -%}
+ {%- call kt::docstring(field, 8) %}
+ val {% call kt::field_name(field, loop.index) %}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
+ {%- endfor -%}
) : {{ type_name }}() {
companion object
}
@@ -83,9 +99,9 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }
is {{ type_name }}.{{ variant|type_name(ci) }} -> {
// Add the size for the Int that specifies the variant plus the size needed for all fields
(
- 4
+ 4UL
{%- for field in variant.fields() %}
- + {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
+ + {{ field|allocation_size_fn }}(value.{%- call kt::field_name(field, loop.index) -%})
{%- endfor %}
)
}
@@ -98,7 +114,7 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }
is {{ type_name }}.{{ variant|type_name(ci) }} -> {
buf.putInt({{ loop.index }})
{%- for field in variant.fields() %}
- {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {{ field|write_fn }}(value.{%- call kt::field_name(field, loop.index) -%}, buf)
{%- endfor %}
Unit
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
index 986db5424d..4760c03fd6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
@@ -3,24 +3,26 @@
{%- let canonical_type_name = type_|canonical_name %}
{% if e.is_flat() %}
+{%- call kt::docstring(e, 0) %}
sealed class {{ type_name }}(message: String): Exception(message){% if contains_object_references %}, Disposable {% endif %} {
- // Each variant is a nested class
- // Flat enums carries a string error message, so no special implementation is necessary.
{% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
class {{ variant|error_variant_name }}(message: String) : {{ type_name }}(message)
{% endfor %}
- companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ type_name }}> {
override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf)
}
}
{%- else %}
+{%- call kt::docstring(e, 0) %}
sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} {
- // Each variant is a nested class
{% for variant in e.variants() -%}
+ {%- call kt::docstring(variant, 4) %}
{%- let variant_name = variant|error_variant_name %}
class {{ variant_name }}(
{% for field in variant.fields() -%}
+ {%- call kt::docstring(field, 8) %}
val {{ field.name()|var_name }}: {{ field|type_name(ci) }}{% if loop.last %}{% else %}, {% endif %}
{% endfor -%}
) : {{ type_name }}() {
@@ -29,7 +31,7 @@ sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Di
}
{% endfor %}
- companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
+ companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ type_name }}> {
override fun lift(error_buf: RustBuffer.ByValue): {{ type_name }} = {{ ffi_converter_name }}.lift(error_buf)
}
@@ -76,15 +78,15 @@ public object {{ e|ffi_converter_name }} : FfiConverterRustBuffer<{{ type_name }
{%- endif %}
}
- override fun allocationSize(value: {{ type_name }}): Int {
+ override fun allocationSize(value: {{ type_name }}): ULong {
{%- if e.is_flat() %}
- return 4
+ return 4UL
{%- else %}
return when(value) {
{%- for variant in e.variants() %}
is {{ type_name }}.{{ variant|error_variant_name }} -> (
// Add the size for the Int that specifies the variant plus the size needed for all fields
- 4
+ 4UL
{%- for field in variant.fields() %}
+ {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }})
{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
index 0fade7a0bc..b7e77f0b2d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ExternalTypeTemplate.kt
@@ -1,5 +1,5 @@
{%- let package_name=self.external_type_package_name(module_path, namespace) %}
-{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name) %}
+{%- let fully_qualified_type_name = "{}.{}"|format(package_name, name|class_name(ci)) %}
{%- let fully_qualified_ffi_converter_name = "{}.FfiConverterType{}"|format(package_name, name) %}
{%- let fully_qualified_rustbuffer_name = "{}.RustBuffer"|format(package_name) %}
{%- let local_rustbuffer_name = "RustBuffer{}"|format(name) %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
index 3b2c9d225a..0de90b9c4b 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/FfiConverterTemplate.kt
@@ -20,7 +20,7 @@ public interface FfiConverter<KotlinType, FfiType> {
// encoding, so we pessimistically allocate the largest size possible (3
// bytes per codepoint). Allocating extra bytes is not really a big deal
// because the `RustBuffer` is short-lived.
- fun allocationSize(value: KotlinType): Int
+ fun allocationSize(value: KotlinType): ULong
// Write a Kotlin type to a `ByteBuffer`
fun write(value: KotlinType, buf: ByteBuffer)
@@ -34,11 +34,11 @@ public interface FfiConverter<KotlinType, FfiType> {
fun lowerIntoRustBuffer(value: KotlinType): RustBuffer.ByValue {
val rbuf = RustBuffer.alloc(allocationSize(value))
try {
- val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity.toLong()).also {
+ val bbuf = rbuf.data!!.getByteBuffer(0, rbuf.capacity).also {
it.order(ByteOrder.BIG_ENDIAN)
}
write(value, bbuf)
- rbuf.writeField("len", bbuf.position())
+ rbuf.writeField("len", bbuf.position().toLong())
return rbuf
} catch (e: Throwable) {
RustBuffer.free(rbuf)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
index eafec5d122..be91ac8fcb 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float32Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterFloat: FfiConverter<Float, Float> {
return value
}
- override fun allocationSize(value: Float) = 4
+ override fun allocationSize(value: Float) = 4UL
override fun write(value: Float, buf: ByteBuffer) {
buf.putFloat(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
index 9fc2892c95..5eb465f0df 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Float64Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterDouble: FfiConverter<Double, Double> {
return value
}
- override fun allocationSize(value: Double) = 8
+ override fun allocationSize(value: Double) = 8UL
override fun write(value: Double, buf: ByteBuffer) {
buf.putDouble(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt
deleted file mode 100644
index 3544b2f9e6..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ForeignExecutorTemplate.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-{{ self.add_import("kotlinx.coroutines.CoroutineScope") }}
-{{ self.add_import("kotlinx.coroutines.delay") }}
-{{ self.add_import("kotlinx.coroutines.isActive") }}
-{{ self.add_import("kotlinx.coroutines.launch") }}
-
-internal const val UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0.toByte()
-internal const val UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1.toByte()
-internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0.toByte()
-internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED = 1.toByte()
-internal const val UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2.toByte()
-
-// Callback function to execute a Rust task. The Kotlin code schedules these in a coroutine then
-// invokes them.
-internal interface UniFfiRustTaskCallback : com.sun.jna.Callback {
- fun callback(rustTaskData: Pointer?, statusCode: Byte)
-}
-
-internal object UniFfiForeignExecutorCallback : com.sun.jna.Callback {
- fun callback(handle: USize, delayMs: Int, rustTask: UniFfiRustTaskCallback?, rustTaskData: Pointer?) : Byte {
- if (rustTask == null) {
- FfiConverterForeignExecutor.drop(handle)
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
- } else {
- val coroutineScope = FfiConverterForeignExecutor.lift(handle)
- if (coroutineScope.isActive) {
- val job = coroutineScope.launch {
- if (delayMs > 0) {
- delay(delayMs.toLong())
- }
- rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS)
- }
- job.invokeOnCompletion { cause ->
- if (cause != null) {
- rustTask.callback(rustTaskData, UNIFFI_RUST_TASK_CALLBACK_CANCELLED)
- }
- }
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
- } else {
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELLED
- }
- }
- }
-}
-
-public object FfiConverterForeignExecutor: FfiConverter<CoroutineScope, USize> {
- internal val handleMap = UniFfiHandleMap<CoroutineScope>()
-
- internal fun drop(handle: USize) {
- handleMap.remove(handle)
- }
-
- internal fun register(lib: _UniFFILib) {
- {%- match ci.ffi_foreign_executor_callback_set() %}
- {%- when Some with (fn) %}
- lib.{{ fn.name() }}(UniFfiForeignExecutorCallback)
- {%- when None %}
- {#- No foreign executor, we don't set anything #}
- {% endmatch %}
- }
-
- // Number of live handles, exposed so we can test the memory management
- public fun handleCount() : Int {
- return handleMap.size
- }
-
- override fun allocationSize(value: CoroutineScope) = USize.size
-
- override fun lift(value: USize): CoroutineScope {
- return handleMap.get(value) ?: throw RuntimeException("unknown handle in FfiConverterForeignExecutor.lift")
- }
-
- override fun read(buf: ByteBuffer): CoroutineScope {
- return lift(USize.readFromBuffer(buf))
- }
-
- override fun lower(value: CoroutineScope): USize {
- return handleMap.insert(value)
- }
-
- override fun write(value: CoroutineScope, buf: ByteBuffer) {
- lower(value).writeToBuffer(buf)
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt
new file mode 100644
index 0000000000..3a56648190
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/HandleMap.kt
@@ -0,0 +1,27 @@
+// Map handles to objects
+//
+// This is used pass an opaque 64-bit handle representing a foreign object to the Rust code.
+internal class UniffiHandleMap<T: Any> {
+ private val map = ConcurrentHashMap<Long, T>()
+ private val counter = java.util.concurrent.atomic.AtomicLong(0)
+
+ val size: Int
+ get() = map.size
+
+ // Insert a new object into the handle map and get a handle for it
+ fun insert(obj: T): Long {
+ val handle = counter.getAndAdd(1)
+ map.put(handle, obj)
+ return handle
+ }
+
+ // Get an object from the handle map
+ fun get(handle: Long): T {
+ return map.get(handle) ?: throw InternalException("UniffiHandleMap.get: Invalid handle")
+ }
+
+ // Remove an entry from the handlemap and get the Kotlin object back
+ fun remove(handle: Long): T {
+ return map.remove(handle) ?: throw InternalException("UniffiHandleMap: Invalid handle")
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
index 382a5f7413..1fdbd3ffc0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Helpers.kt
@@ -1,30 +1,43 @@
// A handful of classes and functions to support the generated data structures.
// This would be a good candidate for isolating in its own ffi-support lib.
-// Error runtime.
+
+internal const val UNIFFI_CALL_SUCCESS = 0.toByte()
+internal const val UNIFFI_CALL_ERROR = 1.toByte()
+internal const val UNIFFI_CALL_UNEXPECTED_ERROR = 2.toByte()
+
@Structure.FieldOrder("code", "error_buf")
-internal open class RustCallStatus : Structure() {
+internal open class UniffiRustCallStatus : Structure() {
@JvmField var code: Byte = 0
@JvmField var error_buf: RustBuffer.ByValue = RustBuffer.ByValue()
- class ByValue: RustCallStatus(), Structure.ByValue
+ class ByValue: UniffiRustCallStatus(), Structure.ByValue
fun isSuccess(): Boolean {
- return code == 0.toByte()
+ return code == UNIFFI_CALL_SUCCESS
}
fun isError(): Boolean {
- return code == 1.toByte()
+ return code == UNIFFI_CALL_ERROR
}
fun isPanic(): Boolean {
- return code == 2.toByte()
+ return code == UNIFFI_CALL_UNEXPECTED_ERROR
+ }
+
+ companion object {
+ fun create(code: Byte, errorBuf: RustBuffer.ByValue): UniffiRustCallStatus.ByValue {
+ val callStatus = UniffiRustCallStatus.ByValue()
+ callStatus.code = code
+ callStatus.error_buf = errorBuf
+ return callStatus
+ }
}
}
class InternalException(message: String) : Exception(message)
// Each top-level error class has a companion object that can lift the error from the call status's rust buffer
-interface CallStatusErrorHandler<E> {
+interface UniffiRustCallStatusErrorHandler<E> {
fun lift(error_buf: RustBuffer.ByValue): E;
}
@@ -33,15 +46,15 @@ interface CallStatusErrorHandler<E> {
// synchronize itself
// Call a rust function that returns a Result<>. Pass in the Error class companion that corresponds to the Err
-private inline fun <U, E: Exception> rustCallWithError(errorHandler: CallStatusErrorHandler<E>, callback: (RustCallStatus) -> U): U {
- var status = RustCallStatus();
+private inline fun <U, E: Exception> uniffiRustCallWithError(errorHandler: UniffiRustCallStatusErrorHandler<E>, callback: (UniffiRustCallStatus) -> U): U {
+ var status = UniffiRustCallStatus();
val return_value = callback(status)
- checkCallStatus(errorHandler, status)
+ uniffiCheckCallStatus(errorHandler, status)
return return_value
}
-// Check RustCallStatus and throw an error if the call wasn't successful
-private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E>, status: RustCallStatus) {
+// Check UniffiRustCallStatus and throw an error if the call wasn't successful
+private fun<E: Exception> uniffiCheckCallStatus(errorHandler: UniffiRustCallStatusErrorHandler<E>, status: UniffiRustCallStatus) {
if (status.isSuccess()) {
return
} else if (status.isError()) {
@@ -60,8 +73,8 @@ private fun<E: Exception> checkCallStatus(errorHandler: CallStatusErrorHandler<E
}
}
-// CallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
-object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
+// UniffiRustCallStatusErrorHandler implementation for times when we don't expect a CALL_ERROR
+object UniffiNullRustCallStatusErrorHandler: UniffiRustCallStatusErrorHandler<InternalException> {
override fun lift(error_buf: RustBuffer.ByValue): InternalException {
RustBuffer.free(error_buf)
return InternalException("Unexpected CALL_ERROR")
@@ -69,93 +82,38 @@ object NullCallStatusErrorHandler: CallStatusErrorHandler<InternalException> {
}
// Call a rust function that returns a plain value
-private inline fun <U> rustCall(callback: (RustCallStatus) -> U): U {
- return rustCallWithError(NullCallStatusErrorHandler, callback);
+private inline fun <U> uniffiRustCall(callback: (UniffiRustCallStatus) -> U): U {
+ return uniffiRustCallWithError(UniffiNullRustCallStatusErrorHandler, callback);
}
-// IntegerType that matches Rust's `usize` / C's `size_t`
-public class USize(value: Long = 0) : IntegerType(Native.SIZE_T_SIZE, value, true) {
- // This is needed to fill in the gaps of IntegerType's implementation of Number for Kotlin.
- override fun toByte() = toInt().toByte()
- // Needed until https://youtrack.jetbrains.com/issue/KT-47902 is fixed.
- @Deprecated("`toInt().toChar()` is deprecated")
- override fun toChar() = toInt().toChar()
- override fun toShort() = toInt().toShort()
-
- fun writeToBuffer(buf: ByteBuffer) {
- // Make sure we always write usize integers using native byte-order, since they may be
- // casted to pointer values
- buf.order(ByteOrder.nativeOrder())
- try {
- when (Native.SIZE_T_SIZE) {
- 4 -> buf.putInt(toInt())
- 8 -> buf.putLong(toLong())
- else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}")
- }
- } finally {
- buf.order(ByteOrder.BIG_ENDIAN)
- }
- }
-
- companion object {
- val size: Int
- get() = Native.SIZE_T_SIZE
-
- fun readFromBuffer(buf: ByteBuffer) : USize {
- // Make sure we always read usize integers using native byte-order, since they may be
- // casted from pointer values
- buf.order(ByteOrder.nativeOrder())
- try {
- return when (Native.SIZE_T_SIZE) {
- 4 -> USize(buf.getInt().toLong())
- 8 -> USize(buf.getLong())
- else -> throw RuntimeException("Invalid SIZE_T_SIZE: ${Native.SIZE_T_SIZE}")
- }
- } finally {
- buf.order(ByteOrder.BIG_ENDIAN)
- }
- }
+internal inline fun<T> uniffiTraitInterfaceCall(
+ callStatus: UniffiRustCallStatus,
+ makeCall: () -> T,
+ writeReturn: (T) -> Unit,
+) {
+ try {
+ writeReturn(makeCall())
+ } catch(e: Exception) {
+ callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR
+ callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString())
}
}
-
-// Map handles to objects
-//
-// This is used when the Rust code expects an opaque pointer to represent some foreign object.
-// Normally we would pass a pointer to the object, but JNA doesn't support getting a pointer from an
-// object reference , nor does it support leaking a reference to Rust.
-//
-// Instead, this class maps USize values to objects so that we can pass a pointer-sized type to
-// Rust when it needs an opaque pointer.
-//
-// TODO: refactor callbacks to use this class
-internal class UniFfiHandleMap<T: Any> {
- private val map = ConcurrentHashMap<USize, T>()
- // Use AtomicInteger for our counter, since we may be on a 32-bit system. 4 billion possible
- // values seems like enough. If somehow we generate 4 billion handles, then this will wrap
- // around back to zero and we can assume the first handle generated will have been dropped by
- // then.
- private val counter = java.util.concurrent.atomic.AtomicInteger(0)
-
- val size: Int
- get() = map.size
-
- fun insert(obj: T): USize {
- val handle = USize(counter.getAndAdd(1).toLong())
- map.put(handle, obj)
- return handle
- }
-
- fun get(handle: USize): T? {
- return map.get(handle)
- }
-
- fun remove(handle: USize): T? {
- return map.remove(handle)
+internal inline fun<T, reified E: Throwable> uniffiTraitInterfaceCallWithError(
+ callStatus: UniffiRustCallStatus,
+ makeCall: () -> T,
+ writeReturn: (T) -> Unit,
+ lowerError: (E) -> RustBuffer.ByValue
+) {
+ try {
+ writeReturn(makeCall())
+ } catch(e: Exception) {
+ if (e is E) {
+ callStatus.code = UNIFFI_CALL_ERROR
+ callStatus.error_buf = lowerError(e)
+ } else {
+ callStatus.code = UNIFFI_CALL_UNEXPECTED_ERROR
+ callStatus.error_buf = {{ Type::String.borrow()|lower_fn }}(e.toString())
+ }
}
}
-
-// FFI type for Rust future continuations
-internal interface UniFffiRustFutureContinuationCallbackType : com.sun.jna.Callback {
- fun callback(continuationHandle: USize, pollResult: Short);
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
index 75564276be..de8296fff6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int16Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterShort: FfiConverter<Short, Short> {
return value
}
- override fun allocationSize(value: Short) = 2
+ override fun allocationSize(value: Short) = 2UL
override fun write(value: Short, buf: ByteBuffer) {
buf.putShort(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
index b7a8131c8b..171809a9c4 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int32Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterInt: FfiConverter<Int, Int> {
return value
}
- override fun allocationSize(value: Int) = 4
+ override fun allocationSize(value: Int) = 4UL
override fun write(value: Int, buf: ByteBuffer) {
buf.putInt(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
index 601cfc7c2c..35cf8f3169 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int64Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterLong: FfiConverter<Long, Long> {
return value
}
- override fun allocationSize(value: Long) = 8
+ override fun allocationSize(value: Long) = 8UL
override fun write(value: Long, buf: ByteBuffer) {
buf.putLong(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
index 9237768dbf..27c98a6659 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Int8Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterByte: FfiConverter<Byte, Byte> {
return value
}
- override fun allocationSize(value: Byte) = 1
+ override fun allocationSize(value: Byte) = 1UL
override fun write(value: Byte, buf: ByteBuffer) {
buf.put(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt
new file mode 100644
index 0000000000..0b4249fb11
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Interface.kt
@@ -0,0 +1,14 @@
+{%- call kt::docstring_value(interface_docstring, 0) %}
+public interface {{ interface_name }} {
+ {% for meth in methods.iter() -%}
+ {%- call kt::docstring(meth, 4) %}
+ {% if meth.is_async() -%}suspend {% endif -%}
+ fun {{ meth.name()|fn_name }}({% call kt::arg_list(meth, true) %})
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+ companion object
+}
+
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
index 776c402727..a80418eb00 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/MapTemplate.kt
@@ -12,8 +12,8 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<Map<{{ key_type_n
}
}
- override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): Int {
- val spaceForMapSize = 4
+ override fun allocationSize(value: Map<{{ key_type_name }}, {{ value_type_name }}>): ULong {
+ val spaceForMapSize = 4UL
val spaceForChildren = value.map { (k, v) ->
{{ key_type|allocation_size_fn }}(k) +
{{ value_type|allocation_size_fn }}(v)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
index 6a3aeada35..1bac8a435c 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt
@@ -13,14 +13,57 @@ private inline fun <reified Lib : Library> loadIndirect(
return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
}
+// Define FFI callback types
+{%- for def in ci.ffi_definitions() %}
+{%- match def %}
+{%- when FfiDefinition::CallbackFunction(callback) %}
+internal interface {{ callback.name()|ffi_callback_name }} : com.sun.jna.Callback {
+ fun callback(
+ {%- for arg in callback.arguments() -%}
+ {{ arg.name().borrow()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value }},
+ {%- endfor -%}
+ {%- if callback.has_rust_call_status_arg() -%}
+ uniffiCallStatus: UniffiRustCallStatus,
+ {%- endif -%}
+ )
+ {%- match callback.return_type() %}
+ {%- when Some(return_type) %}: {{ return_type|ffi_type_name_by_value }}
+ {%- when None %}
+ {%- endmatch %}
+}
+{%- when FfiDefinition::Struct(ffi_struct) %}
+@Structure.FieldOrder({% for field in ffi_struct.fields() %}"{{ field.name()|var_name_raw }}"{% if !loop.last %}, {% endif %}{% endfor %})
+internal open class {{ ffi_struct.name()|ffi_struct_name }}(
+ {%- for field in ffi_struct.fields() %}
+ @JvmField internal var {{ field.name()|var_name }}: {{ field.type_().borrow()|ffi_type_name_for_ffi_struct }} = {{ field.type_()|ffi_default_value }},
+ {%- endfor %}
+) : Structure() {
+ class UniffiByValue(
+ {%- for field in ffi_struct.fields() %}
+ {{ field.name()|var_name }}: {{ field.type_().borrow()|ffi_type_name_for_ffi_struct }} = {{ field.type_()|ffi_default_value }},
+ {%- endfor %}
+ ): {{ ffi_struct.name()|ffi_struct_name }}({%- for field in ffi_struct.fields() %}{{ field.name()|var_name }}, {%- endfor %}), Structure.ByValue
+
+ internal fun uniffiSetValue(other: {{ ffi_struct.name()|ffi_struct_name }}) {
+ {%- for field in ffi_struct.fields() %}
+ {{ field.name()|var_name }} = other.{{ field.name()|var_name }}
+ {%- endfor %}
+ }
+
+}
+{%- when FfiDefinition::Function(_) %}
+{# functions are handled below #}
+{%- endmatch %}
+{%- endfor %}
+
// A JNA Library to expose the extern-C FFI definitions.
// This is an implementation detail which will be called internally by the public API.
-internal interface _UniFFILib : Library {
+internal interface UniffiLib : Library {
companion object {
- internal val INSTANCE: _UniFFILib by lazy {
- loadIndirect<_UniFFILib>(componentName = "{{ ci.namespace() }}")
- .also { lib: _UniFFILib ->
+ internal val INSTANCE: UniffiLib by lazy {
+ loadIndirect<UniffiLib>(componentName = "{{ ci.namespace() }}")
+ .also { lib: UniffiLib ->
uniffiCheckContractApiVersion(lib)
uniffiCheckApiChecksums(lib)
{% for fn in self.initialization_fns() -%}
@@ -28,6 +71,12 @@ internal interface _UniFFILib : Library {
{% endfor -%}
}
}
+ {% if ci.contains_object_types() %}
+ // The Cleaner for the whole library
+ internal val CLEANER: UniffiCleaner by lazy {
+ UniffiCleaner.create()
+ }
+ {%- endif %}
}
{% for func in ci.iter_ffi_function_definitions() -%}
@@ -37,7 +86,7 @@ internal interface _UniFFILib : Library {
{% endfor %}
}
-private fun uniffiCheckContractApiVersion(lib: _UniFFILib) {
+private fun uniffiCheckContractApiVersion(lib: UniffiLib) {
// Get the bindings contract version from our ComponentInterface
val bindings_contract_version = {{ ci.uniffi_contract_version() }}
// Get the scaffolding contract version by calling the into the dylib
@@ -48,7 +97,7 @@ private fun uniffiCheckContractApiVersion(lib: _UniFFILib) {
}
@Suppress("UNUSED_PARAMETER")
-private fun uniffiCheckApiChecksums(lib: _UniFFILib) {
+private fun uniffiCheckApiChecksums(lib: UniffiLib) {
{%- for (name, expected_checksum) in ci.iter_checksums() %}
if (lib.{{ name }}() != {{ expected_checksum }}.toShort()) {
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt
new file mode 100644
index 0000000000..e3e85544d7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelper.kt
@@ -0,0 +1,40 @@
+
+// The cleaner interface for Object finalization code to run.
+// This is the entry point to any implementation that we're using.
+//
+// The cleaner registers objects and returns cleanables, so now we are
+// defining a `UniffiCleaner` with a `UniffiClenaer.Cleanable` to abstract the
+// different implmentations available at compile time.
+interface UniffiCleaner {
+ interface Cleanable {
+ fun clean()
+ }
+
+ fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable
+
+ companion object
+}
+
+// The fallback Jna cleaner, which is available for both Android, and the JVM.
+private class UniffiJnaCleaner : UniffiCleaner {
+ private val cleaner = com.sun.jna.internal.Cleaner.getCleaner()
+
+ override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
+ UniffiJnaCleanable(cleaner.register(value, cleanUpTask))
+}
+
+private class UniffiJnaCleanable(
+ private val cleanable: com.sun.jna.internal.Cleaner.Cleanable,
+) : UniffiCleaner.Cleanable {
+ override fun clean() = cleanable.clean()
+}
+
+// We decide at uniffi binding generation time whether we were
+// using Android or not.
+// There are further runtime checks to chose the correct implementation
+// of the cleaner.
+{% if config.android_cleaner() %}
+{%- include "ObjectCleanerHelperAndroid.kt" %}
+{%- else %}
+{%- include "ObjectCleanerHelperJvm.kt" %}
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt
new file mode 100644
index 0000000000..d025879848
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperAndroid.kt
@@ -0,0 +1,26 @@
+{{- self.add_import("android.os.Build") }}
+{{- self.add_import("androidx.annotation.RequiresApi") }}
+
+private fun UniffiCleaner.Companion.create(): UniffiCleaner =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ AndroidSystemCleaner()
+ } else {
+ UniffiJnaCleaner()
+ }
+
+// The SystemCleaner, available from API Level 33.
+// Some API Level 33 OSes do not support using it, so we require API Level 34.
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+private class AndroidSystemCleaner : UniffiCleaner {
+ val cleaner = android.system.SystemCleaner.cleaner()
+
+ override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
+ AndroidSystemCleanable(cleaner.register(value, cleanUpTask))
+}
+
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+private class AndroidSystemCleanable(
+ private val cleanable: java.lang.ref.Cleaner.Cleanable,
+) : UniffiCleaner.Cleanable {
+ override fun clean() = cleanable.clean()
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt
new file mode 100644
index 0000000000..c43bc167fc
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectCleanerHelperJvm.kt
@@ -0,0 +1,25 @@
+private fun UniffiCleaner.Companion.create(): UniffiCleaner =
+ try {
+ // For safety's sake: if the library hasn't been run in android_cleaner = true
+ // mode, but is being run on Android, then we still need to think about
+ // Android API versions.
+ // So we check if java.lang.ref.Cleaner is there, and use that…
+ java.lang.Class.forName("java.lang.ref.Cleaner")
+ JavaLangRefCleaner()
+ } catch (e: ClassNotFoundException) {
+ // … otherwise, fallback to the JNA cleaner.
+ UniffiJnaCleaner()
+ }
+
+private class JavaLangRefCleaner : UniffiCleaner {
+ val cleaner = java.lang.ref.Cleaner.create()
+
+ override fun register(value: Any, cleanUpTask: Runnable): UniffiCleaner.Cleanable =
+ JavaLangRefCleanable(cleaner.register(value, cleanUpTask))
+}
+
+private class JavaLangRefCleanable(
+ val cleanable: java.lang.ref.Cleaner.Cleanable
+) : UniffiCleaner.Cleanable {
+ override fun clean() = cleanable.clean()
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt
deleted file mode 100644
index b9352c690f..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectRuntime.kt
+++ /dev/null
@@ -1,161 +0,0 @@
-// Interface implemented by anything that can contain an object reference.
-//
-// Such types expose a `destroy()` method that must be called to cleanly
-// dispose of the contained objects. Failure to call this method may result
-// in memory leaks.
-//
-// The easiest way to ensure this method is called is to use the `.use`
-// helper method to execute a block and destroy the object at the end.
-interface Disposable {
- fun destroy()
- companion object {
- fun destroy(vararg args: Any?) {
- args.filterIsInstance<Disposable>()
- .forEach(Disposable::destroy)
- }
- }
-}
-
-inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
- try {
- block(this)
- } finally {
- try {
- // N.B. our implementation is on the nullable type `Disposable?`.
- this?.destroy()
- } catch (e: Throwable) {
- // swallow
- }
- }
-
-// The base class for all UniFFI Object types.
-//
-// This class provides core operations for working with the Rust `Arc<T>` pointer to
-// the live Rust struct on the other side of the FFI.
-//
-// There's some subtlety here, because we have to be careful not to operate on a Rust
-// struct after it has been dropped, and because we must expose a public API for freeing
-// the Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
-//
-// * Each `FFIObject` instance holds an opaque pointer to the underlying Rust struct.
-// Method calls need to read this pointer from the object's state and pass it in to
-// the Rust FFI.
-//
-// * When an `FFIObject` is no longer needed, its pointer should be passed to a
-// special destructor function provided by the Rust FFI, which will drop the
-// underlying Rust struct.
-//
-// * Given an `FFIObject` instance, calling code is expected to call the special
-// `destroy` method in order to free it after use, either by calling it explicitly
-// or by using a higher-level helper like the `use` method. Failing to do so will
-// leak the underlying Rust struct.
-//
-// * We can't assume that calling code will do the right thing, and must be prepared
-// to handle Kotlin method calls executing concurrently with or even after a call to
-// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
-//
-// * We must never allow Rust code to operate on the underlying Rust struct after
-// the destructor has been called, and must never call the destructor more than once.
-// Doing so may trigger memory unsafety.
-//
-// If we try to implement this with mutual exclusion on access to the pointer, there is the
-// possibility of a race between a method call and a concurrent call to `destroy`:
-//
-// * Thread A starts a method call, reads the value of the pointer, but is interrupted
-// before it can pass the pointer over the FFI to Rust.
-// * Thread B calls `destroy` and frees the underlying Rust struct.
-// * Thread A resumes, passing the already-read pointer value to Rust and triggering
-// a use-after-free.
-//
-// One possible solution would be to use a `ReadWriteLock`, with each method call taking
-// a read lock (and thus allowed to run concurrently) and the special `destroy` method
-// taking a write lock (and thus blocking on live method calls). However, we aim not to
-// generate methods with any hidden blocking semantics, and a `destroy` method that might
-// block if called incorrectly seems to meet that bar.
-//
-// So, we achieve our goals by giving each `FFIObject` an associated `AtomicLong` counter to track
-// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
-// has been called. These are updated according to the following rules:
-//
-// * The initial value of the counter is 1, indicating a live object with no in-flight calls.
-// The initial value for the flag is false.
-//
-// * At the start of each method call, we atomically check the counter.
-// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
-// If it is nonzero them we atomically increment it by 1 and proceed with the method call.
-//
-// * At the end of each method call, we atomically decrement and check the counter.
-// If it has reached zero then we destroy the underlying Rust struct.
-//
-// * When `destroy` is called, we atomically flip the flag from false to true.
-// If the flag was already true we silently fail.
-// Otherwise we atomically decrement and check the counter.
-// If it has reached zero then we destroy the underlying Rust struct.
-//
-// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
-// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
-//
-// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
-// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
-// of the underlying Rust code.
-//
-// In the future we may be able to replace some of this with automatic finalization logic, such as using
-// the new "Cleaner" functionaility in Java 9. The above scheme has been designed to work even if `destroy` is
-// invoked by garbage-collection machinery rather than by calling code (which by the way, it's apparently also
-// possible for the JVM to finalize an object while there is an in-flight call to one of its methods [1],
-// so there would still be some complexity here).
-//
-// Sigh...all of this for want of a robust finalization mechanism.
-//
-// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
-//
-abstract class FFIObject(
- protected val pointer: Pointer
-): Disposable, AutoCloseable {
-
- private val wasDestroyed = AtomicBoolean(false)
- private val callCounter = AtomicLong(1)
-
- open protected fun freeRustArcPtr() {
- // To be overridden in subclasses.
- }
-
- override fun destroy() {
- // Only allow a single call to this method.
- // TODO: maybe we should log a warning if called more than once?
- if (this.wasDestroyed.compareAndSet(false, true)) {
- // This decrement always matches the initial count of 1 given at creation time.
- if (this.callCounter.decrementAndGet() == 0L) {
- this.freeRustArcPtr()
- }
- }
- }
-
- @Synchronized
- override fun close() {
- this.destroy()
- }
-
- internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
- // Check and increment the call counter, to keep the object alive.
- // This needs a compare-and-set retry loop in case of concurrent updates.
- do {
- val c = this.callCounter.get()
- if (c == 0L) {
- throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
- }
- if (c == Long.MAX_VALUE) {
- throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
- }
- } while (! this.callCounter.compareAndSet(c, c + 1L))
- // Now we can safely do the method call without the pointer being freed concurrently.
- try {
- return block(this.pointer)
- } finally {
- // This decrement always matches the increment we performed above.
- if (this.callCounter.decrementAndGet() == 0L) {
- this.freeRustArcPtr()
- }
- }
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
index 8ce27a5d04..62cac7a4d0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt
@@ -1,125 +1,282 @@
+// This template implements a class for working with a Rust struct via a Pointer/Arc<T>
+// to the live Rust struct on the other side of the FFI.
+//
+// Each instance implements core operations for working with the Rust `Arc<T>` and the
+// Kotlin Pointer to work with the live Rust struct on the other side of the FFI.
+//
+// There's some subtlety here, because we have to be careful not to operate on a Rust
+// struct after it has been dropped, and because we must expose a public API for freeing
+// theq Kotlin wrapper object in lieu of reliable finalizers. The core requirements are:
+//
+// * Each instance holds an opaque pointer to the underlying Rust struct.
+// Method calls need to read this pointer from the object's state and pass it in to
+// the Rust FFI.
+//
+// * When an instance is no longer needed, its pointer should be passed to a
+// special destructor function provided by the Rust FFI, which will drop the
+// underlying Rust struct.
+//
+// * Given an instance, calling code is expected to call the special
+// `destroy` method in order to free it after use, either by calling it explicitly
+// or by using a higher-level helper like the `use` method. Failing to do so risks
+// leaking the underlying Rust struct.
+//
+// * We can't assume that calling code will do the right thing, and must be prepared
+// to handle Kotlin method calls executing concurrently with or even after a call to
+// `destroy`, and to handle multiple (possibly concurrent!) calls to `destroy`.
+//
+// * We must never allow Rust code to operate on the underlying Rust struct after
+// the destructor has been called, and must never call the destructor more than once.
+// Doing so may trigger memory unsafety.
+//
+// * To mitigate many of the risks of leaking memory and use-after-free unsafety, a `Cleaner`
+// is implemented to call the destructor when the Kotlin object becomes unreachable.
+// This is done in a background thread. This is not a panacea, and client code should be aware that
+// 1. the thread may starve if some there are objects that have poorly performing
+// `drop` methods or do significant work in their `drop` methods.
+// 2. the thread is shared across the whole library. This can be tuned by using `android_cleaner = true`,
+// or `android = true` in the [`kotlin` section of the `uniffi.toml` file](https://mozilla.github.io/uniffi-rs/kotlin/configuration.html).
+//
+// If we try to implement this with mutual exclusion on access to the pointer, there is the
+// possibility of a race between a method call and a concurrent call to `destroy`:
+//
+// * Thread A starts a method call, reads the value of the pointer, but is interrupted
+// before it can pass the pointer over the FFI to Rust.
+// * Thread B calls `destroy` and frees the underlying Rust struct.
+// * Thread A resumes, passing the already-read pointer value to Rust and triggering
+// a use-after-free.
+//
+// One possible solution would be to use a `ReadWriteLock`, with each method call taking
+// a read lock (and thus allowed to run concurrently) and the special `destroy` method
+// taking a write lock (and thus blocking on live method calls). However, we aim not to
+// generate methods with any hidden blocking semantics, and a `destroy` method that might
+// block if called incorrectly seems to meet that bar.
+//
+// So, we achieve our goals by giving each instance an associated `AtomicLong` counter to track
+// the number of in-flight method calls, and an `AtomicBoolean` flag to indicate whether `destroy`
+// has been called. These are updated according to the following rules:
+//
+// * The initial value of the counter is 1, indicating a live object with no in-flight calls.
+// The initial value for the flag is false.
+//
+// * At the start of each method call, we atomically check the counter.
+// If it is 0 then the underlying Rust struct has already been destroyed and the call is aborted.
+// If it is nonzero them we atomically increment it by 1 and proceed with the method call.
+//
+// * At the end of each method call, we atomically decrement and check the counter.
+// If it has reached zero then we destroy the underlying Rust struct.
+//
+// * When `destroy` is called, we atomically flip the flag from false to true.
+// If the flag was already true we silently fail.
+// Otherwise we atomically decrement and check the counter.
+// If it has reached zero then we destroy the underlying Rust struct.
+//
+// Astute readers may observe that this all sounds very similar to the way that Rust's `Arc<T>` works,
+// and indeed it is, with the addition of a flag to guard against multiple calls to `destroy`.
+//
+// The overall effect is that the underlying Rust struct is destroyed only when `destroy` has been
+// called *and* all in-flight method calls have completed, avoiding violating any of the expectations
+// of the underlying Rust code.
+//
+// This makes a cleaner a better alternative to _not_ calling `destroy()` as
+// and when the object is finished with, but the abstraction is not perfect: if the Rust object's `drop`
+// method is slow, and/or there are many objects to cleanup, and it's on a low end Android device, then the cleaner
+// thread may be starved, and the app will leak memory.
+//
+// In this case, `destroy`ing manually may be a better solution.
+//
+// The cleaner can live side by side with the manual calling of `destroy`. In the order of responsiveness, uniffi objects
+// with Rust peers are reclaimed:
+//
+// 1. By calling the `destroy` method of the object, which calls `rustObject.free()`. If that doesn't happen:
+// 2. When the object becomes unreachable, AND the Cleaner thread gets to call `rustObject.free()`. If the thread is starved then:
+// 3. The memory is reclaimed when the process terminates.
+//
+// [1] https://stackoverflow.com/questions/24376768/can-java-finalize-an-object-when-it-is-still-in-scope/24380219
+//
+
+{{ self.add_import("java.util.concurrent.atomic.AtomicBoolean") }}
+{%- if self.include_once_check("interface-support") %}
+ {%- include "ObjectCleanerHelper.kt" %}
+{%- endif %}
+
{%- let obj = ci|get_object_definition(name) %}
-{%- if self.include_once_check("ObjectRuntime.kt") %}{% include "ObjectRuntime.kt" %}{% endif %}
-{{- self.add_import("java.util.concurrent.atomic.AtomicLong") }}
-{{- self.add_import("java.util.concurrent.atomic.AtomicBoolean") }}
+{%- let (interface_name, impl_class_name) = obj|object_names(ci) %}
+{%- let methods = obj.methods() %}
+{%- let interface_docstring = obj.docstring() %}
+{%- let is_error = ci.is_name_used_as_error(name) %}
+{%- let ffi_converter_name = obj|ffi_converter_name %}
-public interface {{ type_name }}Interface {
- {% for meth in obj.methods() -%}
- {%- match meth.throws_type() -%}
- {%- when Some with (throwable) -%}
- @Throws({{ throwable|type_name(ci) }}::class)
- {%- when None -%}
- {%- endmatch %}
- {% if meth.is_async() -%}
- suspend fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
- {%- else -%}
- fun {{ meth.name()|fn_name }}({% call kt::arg_list_decl(meth) %})
- {%- endif %}
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) %}: {{ return_type|type_name(ci) -}}
- {%- when None -%}
- {%- endmatch -%}
+{%- include "Interface.kt" %}
- {% endfor %}
- companion object
-}
+{%- call kt::docstring(obj, 0) %}
+{% if (is_error) %}
+open class {{ impl_class_name }} : Exception, Disposable, AutoCloseable, {{ interface_name }} {
+{% else -%}
+open class {{ impl_class_name }}: Disposable, AutoCloseable, {{ interface_name }} {
+{%- endif %}
-class {{ type_name }}(
- pointer: Pointer
-) : FFIObject(pointer), {{ type_name }}Interface {
+ constructor(pointer: Pointer) {
+ this.pointer = pointer
+ this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer))
+ }
+
+ /**
+ * This constructor can be used to instantiate a fake object. Only used for tests. Any
+ * attempt to actually use an object constructed this way will fail as there is no
+ * connected Rust object.
+ */
+ @Suppress("UNUSED_PARAMETER")
+ constructor(noPointer: NoPointer) {
+ this.pointer = null
+ this.cleanable = UniffiLib.CLEANER.register(this, UniffiCleanAction(pointer))
+ }
{%- match obj.primary_constructor() %}
- {%- when Some with (cons) %}
- constructor({% call kt::arg_list_decl(cons) -%}) :
+ {%- when Some(cons) %}
+ {%- if cons.is_async() %}
+ // Note no constructor generated for this object as it is async.
+ {%- else %}
+ {%- call kt::docstring(cons, 4) %}
+ constructor({% call kt::arg_list(cons, true) -%}) :
this({% call kt::to_ffi_call(cons) %})
+ {%- endif %}
{%- when None %}
{%- endmatch %}
- /**
- * Disconnect the object from the underlying Rust object.
- *
- * It can be called more than once, but once called, interacting with the object
- * causes an `IllegalStateException`.
- *
- * Clients **must** call this method once done with the object, or cause a memory leak.
- */
- override protected fun freeRustArcPtr() {
- rustCall() { status ->
- _UniFFILib.INSTANCE.{{ obj.ffi_object_free().name() }}(this.pointer, status)
+ protected val pointer: Pointer?
+ protected val cleanable: UniffiCleaner.Cleanable
+
+ private val wasDestroyed = AtomicBoolean(false)
+ private val callCounter = AtomicLong(1)
+
+ override fun destroy() {
+ // Only allow a single call to this method.
+ // TODO: maybe we should log a warning if called more than once?
+ if (this.wasDestroyed.compareAndSet(false, true)) {
+ // This decrement always matches the initial count of 1 given at creation time.
+ if (this.callCounter.decrementAndGet() == 0L) {
+ cleanable.clean()
+ }
}
}
- {% for meth in obj.methods() -%}
- {%- match meth.throws_type() -%}
- {%- when Some with (throwable) %}
- @Throws({{ throwable|type_name(ci) }}::class)
- {%- else -%}
- {%- endmatch -%}
- {%- if meth.is_async() %}
- @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
- override suspend fun {{ meth.name()|fn_name }}({%- call kt::arg_list_decl(meth) -%}){% match meth.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
- return uniffiRustCallAsync(
- callWithPointer { thisPtr ->
- _UniFFILib.INSTANCE.{{ meth.ffi_func().name() }}(
- thisPtr,
- {% call kt::arg_list_lowered(meth) %}
- )
- },
- {{ meth|async_poll(ci) }},
- {{ meth|async_complete(ci) }},
- {{ meth|async_free(ci) }},
- // lift function
- {%- match meth.return_type() %}
- {%- when Some(return_type) %}
- { {{ return_type|lift_fn }}(it) },
- {%- when None %}
- { Unit },
- {% endmatch %}
- // Error FFI converter
- {%- match meth.throws_type() %}
- {%- when Some(e) %}
- {{ e|type_name(ci) }}.ErrorHandler,
- {%- when None %}
- NullCallStatusErrorHandler,
- {%- endmatch %}
- )
- }
- {%- else -%}
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) -%}
- override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}): {{ return_type|type_name(ci) }} =
- callWithPointer {
- {%- call kt::to_ffi_call_with_prefix("it", meth) %}
- }.let {
- {{ return_type|lift_fn }}(it)
+ @Synchronized
+ override fun close() {
+ this.destroy()
+ }
+
+ internal inline fun <R> callWithPointer(block: (ptr: Pointer) -> R): R {
+ // Check and increment the call counter, to keep the object alive.
+ // This needs a compare-and-set retry loop in case of concurrent updates.
+ do {
+ val c = this.callCounter.get()
+ if (c == 0L) {
+ throw IllegalStateException("${this.javaClass.simpleName} object has already been destroyed")
+ }
+ if (c == Long.MAX_VALUE) {
+ throw IllegalStateException("${this.javaClass.simpleName} call counter would overflow")
+ }
+ } while (! this.callCounter.compareAndSet(c, c + 1L))
+ // Now we can safely do the method call without the pointer being freed concurrently.
+ try {
+ return block(this.uniffiClonePointer())
+ } finally {
+ // This decrement always matches the increment we performed above.
+ if (this.callCounter.decrementAndGet() == 0L) {
+ cleanable.clean()
+ }
}
+ }
- {%- when None -%}
- override fun {{ meth.name()|fn_name }}({% call kt::arg_list_protocol(meth) %}) =
- callWithPointer {
- {%- call kt::to_ffi_call_with_prefix("it", meth) %}
+ // Use a static inner class instead of a closure so as not to accidentally
+ // capture `this` as part of the cleanable's action.
+ private class UniffiCleanAction(private val pointer: Pointer?) : Runnable {
+ override fun run() {
+ pointer?.let { ptr ->
+ uniffiRustCall { status ->
+ UniffiLib.INSTANCE.{{ obj.ffi_object_free().name() }}(ptr, status)
+ }
+ }
}
- {% endmatch %}
- {% endif %}
+ }
+
+ fun uniffiClonePointer(): Pointer {
+ return uniffiRustCall() { status ->
+ UniffiLib.INSTANCE.{{ obj.ffi_object_clone().name() }}(pointer!!, status)
+ }
+ }
+
+ {% for meth in obj.methods() -%}
+ {%- call kt::func_decl("override", meth, 4) %}
{% endfor %}
+ {%- for tm in obj.uniffi_traits() %}
+ {%- match tm %}
+ {% when UniffiTrait::Display { fmt } %}
+ override fun toString(): String {
+ return {{ fmt.return_type().unwrap()|lift_fn }}({% call kt::to_ffi_call(fmt) %})
+ }
+ {% when UniffiTrait::Eq { eq, ne } %}
+ {# only equals used #}
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is {{ impl_class_name}}) return false
+ return {{ eq.return_type().unwrap()|lift_fn }}({% call kt::to_ffi_call(eq) %})
+ }
+ {% when UniffiTrait::Hash { hash } %}
+ override fun hashCode(): Int {
+ return {{ hash.return_type().unwrap()|lift_fn }}({%- call kt::to_ffi_call(hash) %}).toInt()
+ }
+ {%- else %}
+ {%- endmatch %}
+ {%- endfor %}
+
+ {# XXX - "companion object" confusion? How to have alternate constructors *and* be an error? #}
{% if !obj.alternate_constructors().is_empty() -%}
companion object {
{% for cons in obj.alternate_constructors() -%}
- fun {{ cons.name()|fn_name }}({% call kt::arg_list_decl(cons) %}): {{ type_name }} =
- {{ type_name }}({% call kt::to_ffi_call(cons) %})
+ {% call kt::func_decl("", cons, 4) %}
{% endfor %}
}
+ {% else if is_error %}
+ companion object ErrorHandler : UniffiRustCallStatusErrorHandler<{{ impl_class_name }}> {
+ override fun lift(error_buf: RustBuffer.ByValue): {{ impl_class_name }} {
+ // Due to some mismatches in the ffi converter mechanisms, errors are a RustBuffer.
+ val bb = error_buf.asByteBuffer()
+ if (bb == null) {
+ throw InternalException("?")
+ }
+ return {{ ffi_converter_name }}.read(bb)
+ }
+ }
{% else %}
companion object
{% endif %}
}
-public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> {
- override fun lower(value: {{ type_name }}): Pointer = value.callWithPointer { it }
+{%- if obj.has_callback_interface() %}
+{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %}
+{%- let vtable_methods = obj.vtable_methods() %}
+{%- let ffi_init_callback = obj.ffi_init_callback() %}
+{% include "CallbackInterfaceImpl.kt" %}
+{%- endif %}
+
+public object {{ ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointer> {
+ {%- if obj.has_callback_interface() %}
+ internal val handleMap = UniffiHandleMap<{{ type_name }}>()
+ {%- endif %}
+
+ override fun lower(value: {{ type_name }}): Pointer {
+ {%- if obj.has_callback_interface() %}
+ return Pointer(handleMap.insert(value))
+ {%- else %}
+ return value.uniffiClonePointer()
+ {%- endif %}
+ }
override fun lift(value: Pointer): {{ type_name }} {
- return {{ type_name }}(value)
+ return {{ impl_class_name }}(value)
}
override fun read(buf: ByteBuffer): {{ type_name }} {
@@ -128,7 +285,7 @@ public object {{ obj|ffi_converter_name }}: FfiConverter<{{ type_name }}, Pointe
return lift(Pointer(buf.getLong()))
}
- override fun allocationSize(value: {{ type_name }}) = 8
+ override fun allocationSize(value: {{ type_name }}) = 8UL
override fun write(value: {{ type_name }}, buf: ByteBuffer) {
// The Rust code always expects pointers written as 8 bytes,
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
index 56cb5f87a5..98451e1451 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/OptionalTemplate.kt
@@ -8,11 +8,11 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<{{ inner_type_nam
return {{ inner_type|read_fn }}(buf)
}
- override fun allocationSize(value: {{ inner_type_name }}?): Int {
+ override fun allocationSize(value: {{ inner_type_name }}?): ULong {
if (value == null) {
- return 1
+ return 1UL
} else {
- return 1 + {{ inner_type|allocation_size_fn }}(value)
+ return 1UL + {{ inner_type|allocation_size_fn }}(value)
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md
new file mode 100644
index 0000000000..0e770cb829
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/README.md
@@ -0,0 +1,13 @@
+# Rules for the Kotlin template code
+
+## Naming
+
+Private variables, classes, functions, etc. should be prefixed with `uniffi`, `Uniffi`, or `UNIFFI`.
+This avoids naming collisions with user-defined items.
+Users will not get name collisions as long as they don't use "uniffi", which is reserved for us.
+
+In particular, make sure to use the `uniffi` prefix for any variable names in generated functions.
+If you name a variable something like `result` the code will probably work initially.
+Then it will break later on when a user decides to define a function with a parameter named `result`.
+
+Note: this doesn't apply to items that we want to expose, for example users may want to catch `InternalException` so doesn't get the `Uniffi` prefix.
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
index b588ca1398..bc3028c736 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RecordTemplate.kt
@@ -1,8 +1,11 @@
{%- let rec = ci|get_record_definition(name) %}
+{%- if rec.has_fields() %}
+{%- call kt::docstring(rec, 0) %}
data class {{ type_name }} (
{%- for field in rec.fields() %}
- var {{ field.name()|var_name }}: {{ field|type_name(ci) -}}
+ {%- call kt::docstring(field, 4) %}
+ {% if config.generate_immutable_records() %}val{% else %}var{% endif %} {{ field.name()|var_name }}: {{ field|type_name(ci) -}}
{%- match field.default_value() %}
{%- when Some with(literal) %} = {{ literal|render_literal(field, ci) }}
{%- else %}
@@ -18,21 +21,39 @@ data class {{ type_name }} (
{% endif %}
companion object
}
+{%- else -%}
+{%- call kt::docstring(rec, 0) %}
+class {{ type_name }} {
+ override fun equals(other: Any?): Boolean {
+ return other is {{ type_name }}
+ }
+
+ override fun hashCode(): Int {
+ return javaClass.hashCode()
+ }
+
+ companion object
+}
+{%- endif %}
public object {{ rec|ffi_converter_name }}: FfiConverterRustBuffer<{{ type_name }}> {
override fun read(buf: ByteBuffer): {{ type_name }} {
+ {%- if rec.has_fields() %}
return {{ type_name }}(
{%- for field in rec.fields() %}
{{ field|read_fn }}(buf),
{%- endfor %}
)
+ {%- else %}
+ return {{ type_name }}()
+ {%- endif %}
}
- override fun allocationSize(value: {{ type_name }}) = (
+ override fun allocationSize(value: {{ type_name }}) = {%- if rec.has_fields() %} (
{%- for field in rec.fields() %}
- {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif%}
+ {{ field|allocation_size_fn }}(value.{{ field.name()|var_name }}){% if !loop.last %} +{% endif %}
{%- endfor %}
- )
+ ) {%- else %} 0UL {%- endif %}
override fun write(value: {{ type_name }}, buf: ByteBuffer) {
{%- for field in rec.fields() %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
index dfbea24074..b28f25bfc3 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt
@@ -4,32 +4,41 @@
@Structure.FieldOrder("capacity", "len", "data")
open class RustBuffer : Structure() {
- @JvmField var capacity: Int = 0
- @JvmField var len: Int = 0
+ // Note: `capacity` and `len` are actually `ULong` values, but JVM only supports signed values.
+ // When dealing with these fields, make sure to call `toULong()`.
+ @JvmField var capacity: Long = 0
+ @JvmField var len: Long = 0
@JvmField var data: Pointer? = null
class ByValue: RustBuffer(), Structure.ByValue
class ByReference: RustBuffer(), Structure.ByReference
+ internal fun setValue(other: RustBuffer) {
+ capacity = other.capacity
+ len = other.len
+ data = other.data
+ }
+
companion object {
- internal fun alloc(size: Int = 0) = rustCall() { status ->
- _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size, status)
+ internal fun alloc(size: ULong = 0UL) = uniffiRustCall() { status ->
+ // Note: need to convert the size to a `Long` value to make this work with JVM.
+ UniffiLib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size.toLong(), status)
}.also {
if(it.data == null) {
throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})")
}
}
- internal fun create(capacity: Int, len: Int, data: Pointer?): RustBuffer.ByValue {
+ internal fun create(capacity: ULong, len: ULong, data: Pointer?): RustBuffer.ByValue {
var buf = RustBuffer.ByValue()
- buf.capacity = capacity
- buf.len = len
+ buf.capacity = capacity.toLong()
+ buf.len = len.toLong()
buf.data = data
return buf
}
- internal fun free(buf: RustBuffer.ByValue) = rustCall() { status ->
- _UniFFILib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status)
+ internal fun free(buf: RustBuffer.ByValue) = uniffiRustCall() { status ->
+ UniffiLib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status)
}
}
@@ -53,9 +62,9 @@ class RustBufferByReference : ByReference(16) {
fun setValue(value: RustBuffer.ByValue) {
// NOTE: The offsets are as they are in the C-like struct.
val pointer = getPointer()
- pointer.setInt(0, value.capacity)
- pointer.setInt(4, value.len)
- pointer.setPointer(8, value.data)
+ pointer.setLong(0, value.capacity)
+ pointer.setLong(8, value.len)
+ pointer.setPointer(16, value.data)
}
/**
@@ -64,9 +73,9 @@ class RustBufferByReference : ByReference(16) {
fun getValue(): RustBuffer.ByValue {
val pointer = getPointer()
val value = RustBuffer.ByValue()
- value.writeField("capacity", pointer.getInt(0))
- value.writeField("len", pointer.getInt(4))
- value.writeField("data", pointer.getPointer(8))
+ value.writeField("capacity", pointer.getLong(0))
+ value.writeField("len", pointer.getLong(8))
+ value.writeField("data", pointer.getLong(16))
return value
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
index 876d1bc05e..61f911cb0c 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/SequenceTemplate.kt
@@ -8,15 +8,15 @@ public object {{ ffi_converter_name }}: FfiConverterRustBuffer<List<{{ inner_typ
}
}
- override fun allocationSize(value: List<{{ inner_type_name }}>): Int {
- val sizeForLength = 4
+ override fun allocationSize(value: List<{{ inner_type_name }}>): ULong {
+ val sizeForLength = 4UL
val sizeForItems = value.map { {{ inner_type|allocation_size_fn }}(it) }.sum()
return sizeForLength + sizeForItems
}
override fun write(value: List<{{ inner_type_name }}>, buf: ByteBuffer) {
buf.putInt(value.size)
- value.forEach {
+ value.iterator().forEach {
{{ inner_type|write_fn }}(it, buf)
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
index 68324be4f9..b67435bd1a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/StringHelper.kt
@@ -4,7 +4,7 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
// store our length and avoid writing it out to the buffer.
override fun lift(value: RustBuffer.ByValue): String {
try {
- val byteArr = ByteArray(value.len)
+ val byteArr = ByteArray(value.len.toInt())
value.asByteBuffer()!!.get(byteArr)
return byteArr.toString(Charsets.UTF_8)
} finally {
@@ -31,7 +31,7 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
val byteBuf = toUtf8(value)
// Ideally we'd pass these bytes to `ffi_bytebuffer_from_bytes`, but doing so would require us
// to copy them into a JNA `Memory`. So we might as well directly copy them into a `RustBuffer`.
- val rbuf = RustBuffer.alloc(byteBuf.limit())
+ val rbuf = RustBuffer.alloc(byteBuf.limit().toULong())
rbuf.asByteBuffer()!!.put(byteBuf)
return rbuf
}
@@ -39,9 +39,9 @@ public object FfiConverterString: FfiConverter<String, RustBuffer.ByValue> {
// We aren't sure exactly how many bytes our string will be once it's UTF-8
// encoded. Allocate 3 bytes per UTF-16 code unit which will always be
// enough.
- override fun allocationSize(value: String): Int {
- val sizeForLength = 4
- val sizeForString = value.length * 3
+ override fun allocationSize(value: String): ULong {
+ val sizeForLength = 4UL
+ val sizeForString = value.length.toULong() * 3UL
return sizeForLength + sizeForString
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
index 21069d7ce8..10a450a4bd 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TimestampHelper.kt
@@ -14,7 +14,7 @@ public object FfiConverterTimestamp: FfiConverterRustBuffer<java.time.Instant> {
}
// 8 bytes for seconds, 4 bytes for nanoseconds
- override fun allocationSize(value: java.time.Instant) = 12
+ override fun allocationSize(value: java.time.Instant) = 12UL
override fun write(value: java.time.Instant, buf: ByteBuffer) {
var epochOffset = java.time.Duration.between(java.time.Instant.EPOCH, value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
index 6a841d3484..681c48093a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/TopLevelFunctionTemplate.kt
@@ -1,51 +1 @@
-{%- if func.is_async() %}
-{%- match func.throws_type() -%}
-{%- when Some with (throwable) %}
-@Throws({{ throwable|type_name(ci) }}::class)
-{%- else -%}
-{%- endmatch %}
-
-@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
-suspend fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}){% match func.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
- return uniffiRustCallAsync(
- _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call kt::arg_list_lowered(func) %}),
- {{ func|async_poll(ci) }},
- {{ func|async_complete(ci) }},
- {{ func|async_free(ci) }},
- // lift function
- {%- match func.return_type() %}
- {%- when Some(return_type) %}
- { {{ return_type|lift_fn }}(it) },
- {%- when None %}
- { Unit },
- {% endmatch %}
- // Error FFI converter
- {%- match func.throws_type() %}
- {%- when Some(e) %}
- {{ e|type_name(ci) }}.ErrorHandler,
- {%- when None %}
- NullCallStatusErrorHandler,
- {%- endmatch %}
- )
-}
-
-{%- else %}
-{%- match func.throws_type() -%}
-{%- when Some with (throwable) %}
-@Throws({{ throwable|type_name(ci) }}::class)
-{%- else -%}
-{%- endmatch -%}
-
-{%- match func.return_type() -%}
-{%- when Some with (return_type) %}
-
-fun {{ func.name()|fn_name }}({%- call kt::arg_list_decl(func) -%}): {{ return_type|type_name(ci) }} {
- return {{ return_type|lift_fn }}({% call kt::to_ffi_call(func) %})
-}
-{% when None %}
-
-fun {{ func.name()|fn_name }}({% call kt::arg_list_decl(func) %}) =
- {% call kt::to_ffi_call(func) %}
-
-{% endmatch %}
-{%- endif %}
+{%- call kt::func_decl("", func, 8) %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
index 103d444ea3..c27121b701 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/Types.kt
@@ -1,5 +1,38 @@
{%- import "macros.kt" as kt %}
+// Interface implemented by anything that can contain an object reference.
+//
+// Such types expose a `destroy()` method that must be called to cleanly
+// dispose of the contained objects. Failure to call this method may result
+// in memory leaks.
+//
+// The easiest way to ensure this method is called is to use the `.use`
+// helper method to execute a block and destroy the object at the end.
+interface Disposable {
+ fun destroy()
+ companion object {
+ fun destroy(vararg args: Any?) {
+ args.filterIsInstance<Disposable>()
+ .forEach(Disposable::destroy)
+ }
+ }
+}
+
+inline fun <T : Disposable?, R> T.use(block: (T) -> R) =
+ try {
+ block(this)
+ } finally {
+ try {
+ // N.B. our implementation is on the nullable type `Disposable?`.
+ this?.destroy()
+ } catch (e: Throwable) {
+ // swallow
+ }
+ }
+
+/** Used to instantiate an interface without an actual pointer, for fakes in tests, mostly. */
+object NoPointer
+
{%- for type_ in ci.iter_types() %}
{%- let type_name = type_|type_name(ci) %}
{%- let ffi_converter_name = type_|ffi_converter_name %}
@@ -82,9 +115,6 @@
{%- when Type::CallbackInterface { module_path, name } %}
{% include "CallbackInterfaceTemplate.kt" %}
-{%- when Type::ForeignExecutor %}
-{% include "ForeignExecutorTemplate.kt" %}
-
{%- when Type::Timestamp %}
{% include "TimestampHelper.kt" %}
@@ -104,6 +134,10 @@
{%- if ci.has_async_fns() %}
{# Import types needed for async support #}
{{ self.add_import("kotlin.coroutines.resume") }}
+{{ self.add_import("kotlinx.coroutines.launch") }}
{{ self.add_import("kotlinx.coroutines.suspendCancellableCoroutine") }}
{{ self.add_import("kotlinx.coroutines.CancellableContinuation") }}
+{{ self.add_import("kotlinx.coroutines.DelicateCoroutinesApi") }}
+{{ self.add_import("kotlinx.coroutines.Job") }}
+{{ self.add_import("kotlinx.coroutines.GlobalScope") }}
{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
index 279a8fa91b..b179145b62 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt16Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterUShort: FfiConverter<UShort, Short> {
return value.toShort()
}
- override fun allocationSize(value: UShort) = 2
+ override fun allocationSize(value: UShort) = 2UL
override fun write(value: UShort, buf: ByteBuffer) {
buf.putShort(value.toShort())
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
index da7b5b28d6..202d5bcd5b 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt32Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterUInt: FfiConverter<UInt, Int> {
return value.toInt()
}
- override fun allocationSize(value: UInt) = 4
+ override fun allocationSize(value: UInt) = 4UL
override fun write(value: UInt, buf: ByteBuffer) {
buf.putInt(value.toInt())
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
index 44d27ad36b..9be2a5a69d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt64Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterULong: FfiConverter<ULong, Long> {
return value.toLong()
}
- override fun allocationSize(value: ULong) = 8
+ override fun allocationSize(value: ULong) = 8UL
override fun write(value: ULong, buf: ByteBuffer) {
buf.putLong(value.toLong())
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
index b6d176603e..ee360673e0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/UInt8Helper.kt
@@ -11,7 +11,7 @@ public object FfiConverterUByte: FfiConverter<UByte, Byte> {
return value.toByte()
}
- override fun allocationSize(value: UByte) = 1
+ override fun allocationSize(value: UByte) = 1UL
override fun write(value: UByte, buf: ByteBuffer) {
buf.put(value.toByte())
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
index 6a95d6a66d..7acfdc8861 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/macros.kt
@@ -1,32 +1,91 @@
{#
// Template to call into rust. Used in several places.
-// Variable names in `arg_list_decl` should match up with arg lists
+// Variable names in `arg_list` should match up with arg lists
// passed to rust via `arg_list_lowered`
#}
{%- macro to_ffi_call(func) -%}
- {%- match func.throws_type() %}
- {%- when Some with (e) %}
- rustCallWithError({{ e|type_name(ci) }})
- {%- else %}
- rustCall()
- {%- endmatch %} { _status ->
- _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} _status)
-}
-{%- endmacro -%}
+ {%- if func.takes_self() %}
+ callWithPointer {
+ {%- call to_raw_ffi_call(func) %}
+ }
+ {% else %}
+ {%- call to_raw_ffi_call(func) %}
+ {% endif %}
+{%- endmacro %}
-{%- macro to_ffi_call_with_prefix(prefix, func) %}
+{%- macro to_raw_ffi_call(func) -%}
{%- match func.throws_type() %}
{%- when Some with (e) %}
- rustCallWithError({{ e|type_name(ci) }})
+ uniffiRustCallWithError({{ e|type_name(ci) }})
{%- else %}
- rustCall()
+ uniffiRustCall()
{%- endmatch %} { _status ->
- _UniFFILib.INSTANCE.{{ func.ffi_func().name() }}(
- {{- prefix }},
- {% call arg_list_lowered(func) %}
+ UniffiLib.INSTANCE.{{ func.ffi_func().name() }}(
+ {% if func.takes_self() %}it, {% endif -%}
+ {% call arg_list_lowered(func) -%}
_status)
}
+{%- endmacro -%}
+
+{%- macro func_decl(func_decl, callable, indent) %}
+ {%- call docstring(callable, indent) %}
+ {%- match callable.throws_type() -%}
+ {%- when Some(throwable) %}
+ @Throws({{ throwable|type_name(ci) }}::class)
+ {%- else -%}
+ {%- endmatch -%}
+ {%- if callable.is_async() %}
+ @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
+ {{ func_decl }} suspend fun {{ callable.name()|fn_name }}(
+ {%- call arg_list(callable, !callable.takes_self()) -%}
+ ){% match callable.return_type() %}{% when Some with (return_type) %} : {{ return_type|type_name(ci) }}{% when None %}{%- endmatch %} {
+ return {% call call_async(callable) %}
+ }
+ {%- else -%}
+ {{ func_decl }} fun {{ callable.name()|fn_name }}(
+ {%- call arg_list(callable, !callable.takes_self()) -%}
+ ){%- match callable.return_type() -%}
+ {%- when Some with (return_type) -%}
+ : {{ return_type|type_name(ci) }} {
+ return {{ return_type|lift_fn }}({% call to_ffi_call(callable) %})
+ }
+ {%- when None %}
+ = {% call to_ffi_call(callable) %}
+ {%- endmatch %}
+ {% endif %}
+{% endmacro %}
+
+{%- macro call_async(callable) -%}
+ uniffiRustCallAsync(
+{%- if callable.takes_self() %}
+ callWithPointer { thisPtr ->
+ UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}(
+ thisPtr,
+ {% call arg_list_lowered(callable) %}
+ )
+ },
+{%- else %}
+ UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}({% call arg_list_lowered(callable) %}),
+{%- endif %}
+ {{ callable|async_poll(ci) }},
+ {{ callable|async_complete(ci) }},
+ {{ callable|async_free(ci) }},
+ // lift function
+ {%- match callable.return_type() %}
+ {%- when Some(return_type) %}
+ { {{ return_type|lift_fn }}(it) },
+ {%- when None %}
+ { Unit },
+ {% endmatch %}
+ // Error FFI converter
+ {%- match callable.throws_type() %}
+ {%- when Some(e) %}
+ {{ e|type_name(ci) }}.ErrorHandler,
+ {%- when None %}
+ UniffiNullRustCallStatusErrorHandler,
+ {%- endmatch %}
+ )
{%- endmacro %}
{%- macro arg_list_lowered(func) %}
@@ -37,37 +96,42 @@
{#-
// Arglist as used in kotlin declarations of methods, functions and constructors.
+// If is_decl, then default values be specified.
// Note the var_name and type_name filters.
-#}
-{% macro arg_list_decl(func) %}
- {%- for arg in func.arguments() -%}
+{% macro arg_list(func, is_decl) %}
+{%- for arg in func.arguments() -%}
{{ arg.name()|var_name }}: {{ arg|type_name(ci) }}
- {%- match arg.default_value() %}
- {%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }}
- {%- else %}
- {%- endmatch %}
- {%- if !loop.last %}, {% endif -%}
- {%- endfor %}
+{%- if is_decl %}
+{%- match arg.default_value() %}
+{%- when Some with(literal) %} = {{ literal|render_literal(arg, ci) }}
+{%- else %}
+{%- endmatch %}
+{%- endif %}
+{%- if !loop.last %}, {% endif -%}
+{%- endfor %}
{%- endmacro %}
-{% macro arg_list_protocol(func) %}
- {%- for arg in func.arguments() -%}
- {{ arg.name()|var_name }}: {{ arg|type_name(ci) }}
- {%- if !loop.last %}, {% endif -%}
- {%- endfor %}
-{%- endmacro %}
{#-
-// Arglist as used in the _UniFFILib function declarations.
+// Arglist as used in the UniffiLib function declarations.
// Note unfiltered name but ffi_type_name filters.
-#}
{%- macro arg_list_ffi_decl(func) %}
{%- for arg in func.arguments() %}
{{- arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name_by_value -}},
{%- endfor %}
- {%- if func.has_rust_call_status_arg() %}_uniffi_out_err: RustCallStatus, {% endif %}
+ {%- if func.has_rust_call_status_arg() %}uniffi_out_err: UniffiRustCallStatus, {% endif %}
{%- endmacro -%}
+{% macro field_name(field, field_num) %}
+{%- if field.name().is_empty() -%}
+v{{- field_num -}}
+{%- else -%}
+{{ field.name()|var_name }}
+{%- endif -%}
+{%- endmacro %}
+
// Macro for destroying fields
{%- macro destroy_fields(member) %}
Disposable.destroy(
@@ -75,3 +139,15 @@
this.{{ field.name()|var_name }}{%- if !loop.last %}, {% endif -%}
{% endfor -%})
{%- endmacro -%}
+
+{%- macro docstring_value(maybe_docstring, indent_spaces) %}
+{%- match maybe_docstring %}
+{%- when Some(docstring) %}
+{{ docstring|docstring(indent_spaces) }}
+{%- else %}
+{%- endmatch %}
+{%- endmacro %}
+
+{%- macro docstring(defn, indent_spaces) %}
+{%- call docstring_value(defn.docstring(), indent_spaces) %}
+{%- endmacro %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
index 9ee4229018..2cdc72a5e2 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/templates/wrapper.kt
@@ -1,6 +1,8 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
+{%- call kt::docstring_value(ci.namespace_docstring(), 0) %}
+
@file:Suppress("NAME_SHADOWING")
package {{ config.package_name() }};
@@ -28,6 +30,7 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.CharBuffer
import java.nio.charset.CodingErrorAction
+import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.ConcurrentHashMap
{%- for req in self.imports() %}
@@ -37,6 +40,7 @@ import java.util.concurrent.ConcurrentHashMap
{% include "RustBufferTemplate.kt" %}
{% include "FfiConverterTemplate.kt" %}
{% include "Helpers.kt" %}
+{% include "HandleMap.kt" %}
// Contains loading, initialization code,
// and the FFI Function declarations in a com.sun.jna.Library.
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
index 7b78540741..0824015751 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/kotlin/test.rs
@@ -2,10 +2,8 @@
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/. */
-use crate::{
- bindings::{RunScriptOptions, TargetLanguage},
- library_mode::generate_bindings,
-};
+use crate::bindings::TargetLanguage;
+use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault};
use anyhow::{bail, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use std::env;
@@ -33,14 +31,18 @@ pub fn run_script(
args: Vec<String>,
options: &RunScriptOptions,
) -> Result<()> {
- let script_path = Utf8Path::new(".").join(script_file);
+ let script_path = Utf8Path::new(script_file);
let test_helper = UniFFITestHelper::new(crate_name)?;
- let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?;
+ let out_dir = test_helper.create_out_dir(tmp_dir, script_path)?;
let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?;
generate_bindings(
&cdylib_path,
None,
- &[TargetLanguage::Kotlin],
+ &BindingGeneratorDefault {
+ target_languages: vec![TargetLanguage::Kotlin],
+ try_format_code: false,
+ },
+ None,
&out_dir,
false,
)?;
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs
index b91bcbe18f..16adeca9a5 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs
@@ -3,7 +3,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use super::CodeType;
-use crate::backend::{Literal, Type};
+use crate::{
+ backend::{Literal, Type},
+ bindings::python::gen_python::AsCodeType,
+};
#[derive(Debug)]
pub struct OptionalCodeType {
@@ -33,8 +36,9 @@ impl CodeType for OptionalCodeType {
fn literal(&self, literal: &Literal) -> String {
match literal {
- Literal::Null => "None".into(),
- _ => super::PythonCodeOracle.find(&self.inner).literal(literal),
+ Literal::None => "None".into(),
+ Literal::Some { inner } => super::PythonCodeOracle.find(&self.inner).literal(inner),
+ _ => panic!("Invalid literal for Optional type: {literal:?}"),
}
}
}
@@ -88,7 +92,11 @@ impl MapCodeType {
impl CodeType for MapCodeType {
fn type_label(&self) -> String {
- "dict".to_string()
+ format!(
+ "dict[{}, {}]",
+ self.key.as_codetype().type_label(),
+ self.value.as_codetype().type_label()
+ )
}
fn canonical_name(&self) -> String {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs
deleted file mode 100644
index be3ba1d791..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/executor.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-/* 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/. */
-
-use super::CodeType;
-
-#[derive(Debug)]
-pub struct ForeignExecutorCodeType;
-
-impl CodeType for ForeignExecutorCodeType {
- fn type_label(&self) -> String {
- "asyncio.BaseEventLoop".into()
- }
-
- fn canonical_name(&self) -> String {
- "ForeignExecutor".into()
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs
index 0d19c4bb3c..0a46251d6d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs
@@ -17,7 +17,7 @@ impl ExternalCodeType {
impl CodeType for ExternalCodeType {
fn type_label(&self) -> String {
- self.name.clone()
+ super::PythonCodeOracle.class_name(&self.name)
}
fn canonical_name(&self) -> String {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs
index 8178fcc102..6a10a38e7f 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs
@@ -2,8 +2,9 @@
* 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/. */
-use anyhow::{Context, Result};
+use anyhow::{bail, Context, Result};
use askama::Template;
+use camino::Utf8Path;
use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
@@ -13,20 +14,43 @@ use std::collections::{BTreeSet, HashMap, HashSet};
use std::fmt::Debug;
use crate::backend::TemplateExpression;
+use crate::bindings::python;
use crate::interface::*;
-use crate::BindingsConfig;
+use crate::{BindingGenerator, BindingsConfig};
mod callback_interface;
mod compounds;
mod custom;
mod enum_;
-mod executor;
mod external;
mod miscellany;
mod object;
mod primitives;
mod record;
+pub struct PythonBindingGenerator;
+
+impl BindingGenerator for PythonBindingGenerator {
+ type Config = Config;
+
+ fn write_bindings(
+ &self,
+ ci: &ComponentInterface,
+ config: &Config,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+ ) -> Result<()> {
+ python::write_bindings(config, ci, out_dir, try_format_code)
+ }
+
+ fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> {
+ if cdylib_name.is_none() {
+ bail!("Generate bindings for Python requires a cdylib, but {library_path} was given");
+ }
+ Ok(())
+ }
+}
+
/// A trait tor the implementation.
trait CodeType: Debug {
/// The language specific label used to reference this type. This will be used in
@@ -114,6 +138,8 @@ pub struct Config {
cdylib_name: Option<String>,
#[serde(default)]
custom_types: HashMap<String, CustomTypeConfig>,
+ #[serde(default)]
+ external_packages: HashMap<String, String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
@@ -133,6 +159,16 @@ impl Config {
"uniffi".into()
}
}
+
+ /// Get the package name for a given external namespace.
+ pub fn module_for_namespace(&self, ns: &str) -> String {
+ let ns = ns.to_string().to_snake_case();
+ match self.external_packages.get(&ns) {
+ None => format!(".{ns}"),
+ Some(value) if value.is_empty() => ns,
+ Some(value) => format!("{value}.{ns}"),
+ }
+ }
}
impl BindingsConfig for Config {
@@ -326,7 +362,19 @@ impl PythonCodeOracle {
fixup_keyword(nm.to_string().to_shouty_snake_case())
}
- fn ffi_type_label(ffi_type: &FfiType) -> String {
+ /// Get the idiomatic Python rendering of an FFI callback function name
+ fn ffi_callback_name(&self, nm: &str) -> String {
+ format!("UNIFFI_{}", nm.to_shouty_snake_case())
+ }
+
+ /// Get the idiomatic Python rendering of an FFI struct name
+ fn ffi_struct_name(&self, nm: &str) -> String {
+ // The ctypes docs use both SHOUTY_SNAKE_CASE AND UpperCamelCase for structs. Let's use
+ // UpperCamelCase and reserve shouting for global variables
+ format!("Uniffi{}", nm.to_upper_camel_case())
+ }
+
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
match ffi_type {
FfiType::Int8 => "ctypes.c_int8".to_string(),
FfiType::UInt8 => "ctypes.c_uint8".to_string(),
@@ -338,19 +386,64 @@ impl PythonCodeOracle {
FfiType::UInt64 => "ctypes.c_uint64".to_string(),
FfiType::Float32 => "ctypes.c_float".to_string(),
FfiType::Float64 => "ctypes.c_double".to_string(),
+ FfiType::Handle => "ctypes.c_uint64".to_string(),
FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(),
FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
Some(suffix) => format!("_UniffiRustBuffer{suffix}"),
None => "_UniffiRustBuffer".to_string(),
},
+ FfiType::RustCallStatus => "_UniffiRustCallStatus".to_string(),
FfiType::ForeignBytes => "_UniffiForeignBytes".to_string(),
- FfiType::ForeignCallback => "_UNIFFI_FOREIGN_CALLBACK_T".to_string(),
+ FfiType::Callback(name) => self.ffi_callback_name(name),
+ FfiType::Struct(name) => self.ffi_struct_name(name),
// Pointer to an `asyncio.EventLoop` instance
- FfiType::ForeignExecutorHandle => "ctypes.c_size_t".to_string(),
- FfiType::ForeignExecutorCallback => "_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T".to_string(),
- FfiType::RustFutureHandle => "ctypes.c_void_p".to_string(),
- FfiType::RustFutureContinuationCallback => "_UNIFFI_FUTURE_CONTINUATION_T".to_string(),
- FfiType::RustFutureContinuationData => "ctypes.c_size_t".to_string(),
+ FfiType::Reference(inner) => format!("ctypes.POINTER({})", self.ffi_type_label(inner)),
+ FfiType::VoidPointer => "ctypes.c_void_p".to_string(),
+ }
+ }
+
+ /// Default values for FFI types
+ ///
+ /// Used to set a default return value when returning an error
+ fn ffi_default_value(&self, return_type: Option<&FfiType>) -> String {
+ match return_type {
+ Some(t) => match t {
+ FfiType::UInt8
+ | FfiType::Int8
+ | FfiType::UInt16
+ | FfiType::Int16
+ | FfiType::UInt32
+ | FfiType::Int32
+ | FfiType::UInt64
+ | FfiType::Int64 => "0".to_owned(),
+ FfiType::Float32 | FfiType::Float64 => "0.0".to_owned(),
+ FfiType::RustArcPtr(_) => "ctypes.c_void_p()".to_owned(),
+ FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
+ Some(suffix) => format!("_UniffiRustBuffer{suffix}.default()"),
+ None => "_UniffiRustBuffer.default()".to_owned(),
+ },
+ _ => unimplemented!("FFI return type: {t:?}"),
+ },
+ // When we need to use a value for void returns, we use a `u8` placeholder
+ None => "0".to_owned(),
+ }
+ }
+
+ /// Get the name of the protocol and class name for an object.
+ ///
+ /// If we support callback interfaces, the protocol name is the object name, and the class name is derived from that.
+ /// Otherwise, the class name is the object name and the protocol name is derived from that.
+ ///
+ /// This split determines what types `FfiConverter.lower()` inputs. If we support callback
+ /// interfaces, `lower` must lower anything that implements the protocol. If not, then lower
+ /// only lowers the concrete class.
+ fn object_names(&self, obj: &Object) -> (String, String) {
+ let class_name = self.class_name(obj.name());
+ if obj.has_callback_interface() {
+ let impl_name = format!("{class_name}Impl");
+ (class_name, impl_name)
+ } else {
+ (format!("{class_name}Protocol"), class_name)
}
}
}
@@ -392,7 +485,6 @@ impl<T: AsType> AsCodeType for T {
Type::CallbackInterface { name, .. } => {
Box::new(callback_interface::CallbackInterfaceCodeType::new(name))
}
- Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
Type::Optional { inner_type } => {
Box::new(compounds::OptionalCodeType::new(*inner_type))
}
@@ -429,6 +521,10 @@ pub mod filters {
Ok(format!("{}.lift", ffi_converter_name(as_ct)?))
}
+ pub(super) fn check_lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.check_lower", ffi_converter_name(as_ct)?))
+ }
+
pub(super) fn lower_fn(as_ct: &impl AsCodeType) -> Result<String, askama::Error> {
Ok(format!("{}.lower", ffi_converter_name(as_ct)?))
}
@@ -448,8 +544,18 @@ pub mod filters {
Ok(as_ct.as_codetype().literal(literal))
}
+ // Get the idiomatic Python rendering of an individual enum variant's discriminant
+ pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> {
+ let literal = e.variant_discr(*index).expect("invalid index");
+ Ok(Type::UInt64.as_codetype().literal(&literal))
+ }
+
pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
- Ok(PythonCodeOracle::ffi_type_label(type_))
+ Ok(PythonCodeOracle.ffi_type_label(type_))
+ }
+
+ pub fn ffi_default_value(return_type: Option<FfiType>) -> Result<String, askama::Error> {
+ Ok(PythonCodeOracle.ffi_default_value(return_type.as_ref()))
}
/// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
@@ -471,4 +577,51 @@ pub mod filters {
pub fn enum_variant_py(nm: &str) -> Result<String, askama::Error> {
Ok(PythonCodeOracle.enum_variant_name(nm))
}
+
+ /// Get the idiomatic Python rendering of an FFI callback function name
+ pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(PythonCodeOracle.ffi_callback_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of an FFI struct name
+ pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(PythonCodeOracle.ffi_struct_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of an individual enum variant.
+ pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> {
+ Ok(PythonCodeOracle.object_names(obj))
+ }
+
+ /// Get the idiomatic Python rendering of docstring
+ pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> {
+ let docstring = textwrap::dedent(docstring);
+ // Escape triple quotes to avoid syntax error
+ let escaped = docstring.replace(r#"""""#, r#"\"\"\""#);
+
+ let wrapped = format!("\"\"\"\n{escaped}\n\"\"\"");
+
+ let spaces = usize::try_from(*spaces).unwrap_or_default();
+ Ok(textwrap::indent(&wrapped, &" ".repeat(spaces)))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_docstring_escape() {
+ let docstring = r#""""This is a docstring beginning with triple quotes.
+Contains "quotes" in it.
+It also has a triple quote: """
+And a even longer quote: """"""#;
+
+ let expected = r#""""
+\"\"\"This is a docstring beginning with triple quotes.
+Contains "quotes" in it.
+It also has a triple quote: \"\"\"
+And a even longer quote: \"\"\"""
+""""#;
+
+ assert_eq!(super::filters::docstring(docstring, &0).unwrap(), expected);
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py
index 82aa534b46..26daa9ba5c 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Async.py
@@ -3,13 +3,37 @@ _UNIFFI_RUST_FUTURE_POLL_READY = 0
_UNIFFI_RUST_FUTURE_POLL_MAYBE_READY = 1
# Stores futures for _uniffi_continuation_callback
-_UniffiContinuationPointerManager = _UniffiPointerManager()
+_UniffiContinuationHandleMap = _UniffiHandleMap()
+
+UNIFFI_GLOBAL_EVENT_LOOP = None
+
+"""
+Set the event loop to use for async functions
+
+This is needed if some async functions run outside of the eventloop, for example:
+ - A non-eventloop thread is spawned, maybe from `EventLoop.run_in_executor` or maybe from the
+ Rust code spawning its own thread.
+ - The Rust code calls an async callback method from a sync callback function, using something
+ like `pollster` to block on the async call.
+
+In this case, we need an event loop to run the Python async function, but there's no eventloop set
+for the thread. Use `uniffi_set_event_loop` to force an eventloop to be used in this case.
+"""
+def uniffi_set_event_loop(eventloop: asyncio.BaseEventLoop):
+ global UNIFFI_GLOBAL_EVENT_LOOP
+ UNIFFI_GLOBAL_EVENT_LOOP = eventloop
+
+def _uniffi_get_event_loop():
+ if UNIFFI_GLOBAL_EVENT_LOOP is not None:
+ return UNIFFI_GLOBAL_EVENT_LOOP
+ else:
+ return asyncio.get_running_loop()
# Continuation callback for async functions
# lift the return value or error and resolve the future, causing the async function to resume.
-@_UNIFFI_FUTURE_CONTINUATION_T
+@UNIFFI_RUST_FUTURE_CONTINUATION_CALLBACK
def _uniffi_continuation_callback(future_ptr, poll_code):
- (eventloop, future) = _UniffiContinuationPointerManager.release_pointer(future_ptr)
+ (eventloop, future) = _UniffiContinuationHandleMap.remove(future_ptr)
eventloop.call_soon_threadsafe(_uniffi_set_future_result, future, poll_code)
def _uniffi_set_future_result(future, poll_code):
@@ -18,14 +42,15 @@ def _uniffi_set_future_result(future, poll_code):
async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free, lift_func, error_ffi_converter):
try:
- eventloop = asyncio.get_running_loop()
+ eventloop = _uniffi_get_event_loop()
# Loop and poll until we see a _UNIFFI_RUST_FUTURE_POLL_READY value
while True:
future = eventloop.create_future()
ffi_poll(
rust_future,
- _UniffiContinuationPointerManager.new_pointer((eventloop, future)),
+ _uniffi_continuation_callback,
+ _UniffiContinuationHandleMap.insert((eventloop, future)),
)
poll_code = await future
if poll_code == _UNIFFI_RUST_FUTURE_POLL_READY:
@@ -37,4 +62,53 @@ async def _uniffi_rust_call_async(rust_future, ffi_poll, ffi_complete, ffi_free,
finally:
ffi_free(rust_future)
-_UniffiLib.{{ ci.ffi_rust_future_continuation_callback_set().name() }}(_uniffi_continuation_callback)
+{%- if ci.has_async_callback_interface_definition() %}
+def uniffi_trait_interface_call_async(make_call, handle_success, handle_error):
+ async def make_call_and_call_callback():
+ try:
+ handle_success(await make_call())
+ except Exception as e:
+ print("UniFFI: Unhandled exception in trait interface call", file=sys.stderr)
+ traceback.print_exc(file=sys.stderr)
+ handle_error(
+ _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR,
+ {{ Type::String.borrow()|lower_fn }}(repr(e)),
+ )
+ eventloop = _uniffi_get_event_loop()
+ task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop)
+ handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task))
+ return UniffiForeignFuture(handle, uniffi_foreign_future_free)
+
+def uniffi_trait_interface_call_async_with_error(make_call, handle_success, handle_error, error_type, lower_error):
+ async def make_call_and_call_callback():
+ try:
+ try:
+ handle_success(await make_call())
+ except error_type as e:
+ handle_error(
+ _UniffiRustCallStatus.CALL_ERROR,
+ lower_error(e),
+ )
+ except Exception as e:
+ print("UniFFI: Unhandled exception in trait interface call", file=sys.stderr)
+ traceback.print_exc(file=sys.stderr)
+ handle_error(
+ _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR,
+ {{ Type::String.borrow()|lower_fn }}(repr(e)),
+ )
+ eventloop = _uniffi_get_event_loop()
+ task = asyncio.run_coroutine_threadsafe(make_call_and_call_callback(), eventloop)
+ handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert((eventloop, task))
+ return UniffiForeignFuture(handle, uniffi_foreign_future_free)
+
+UNIFFI_FOREIGN_FUTURE_HANDLE_MAP = _UniffiHandleMap()
+
+@UNIFFI_FOREIGN_FUTURE_FREE
+def uniffi_foreign_future_free(handle):
+ (eventloop, task) = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.remove(handle)
+ eventloop.call_soon(uniffi_foreign_future_do_free, task)
+
+def uniffi_foreign_future_do_free(task):
+ if not task.done():
+ task.cancel()
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py
index 6775e9e132..3f8c5d1d4d 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py
@@ -1,16 +1,20 @@
-class _UniffiConverterBool(_UniffiConverterPrimitive):
+class _UniffiConverterBool:
@classmethod
- def check(cls, value):
+ def check_lower(cls, value):
return not not value
@classmethod
+ def lower(cls, value):
+ return 1 if value else 0
+
+ @staticmethod
+ def lift(value):
+ return value != 0
+
+ @classmethod
def read(cls, buf):
return cls.lift(buf.read_u8())
@classmethod
- def write_unchecked(cls, value, buf):
+ def write(cls, value, buf):
buf.write_u8(value)
-
- @staticmethod
- def lift(value):
- return value != 0
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py
index 196b5b29fa..4d09531322 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py
@@ -7,10 +7,13 @@ class _UniffiConverterBytes(_UniffiConverterRustBuffer):
return buf.read(size)
@staticmethod
- def write(value, buf):
+ def check_lower(value):
try:
memoryview(value)
except TypeError:
raise TypeError("a bytes-like object is required, not {!r}".format(type(value).__name__))
+
+ @staticmethod
+ def write(value, buf):
buf.write_i32(len(value))
buf.write(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py
new file mode 100644
index 0000000000..676f01177a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceImpl.py
@@ -0,0 +1,98 @@
+{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %}
+{%- let trait_impl=format!("UniffiTraitImpl{}", name) %}
+
+# Put all the bits inside a class to keep the top-level namespace clean
+class {{ trait_impl }}:
+ # For each method, generate a callback function to pass to Rust
+ {%- for (ffi_callback, meth) in vtable_methods.iter() %}
+
+ @{{ ffi_callback.name()|ffi_callback_name }}
+ def {{ meth.name()|fn_name }}(
+ {%- for arg in ffi_callback.arguments() %}
+ {{ arg.name()|var_name }},
+ {%- endfor -%}
+ {%- if ffi_callback.has_rust_call_status_arg() %}
+ uniffi_call_status_ptr,
+ {%- endif %}
+ ):
+ uniffi_obj = {{ ffi_converter_name }}._handle_map.get(uniffi_handle)
+ def make_call():
+ args = ({% for arg in meth.arguments() %}{{ arg|lift_fn }}({{ arg.name()|var_name }}), {% endfor %})
+ method = uniffi_obj.{{ meth.name()|fn_name }}
+ return method(*args)
+
+ {% if !meth.is_async() %}
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ def write_return_value(v):
+ uniffi_out_return[0] = {{ return_type|lower_fn }}(v)
+ {%- when None %}
+ write_return_value = lambda v: None
+ {%- endmatch %}
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ _uniffi_trait_interface_call(
+ uniffi_call_status_ptr.contents,
+ make_call,
+ write_return_value,
+ )
+ {%- when Some(error) %}
+ _uniffi_trait_interface_call_with_error(
+ uniffi_call_status_ptr.contents,
+ make_call,
+ write_return_value,
+ {{ error|type_name }},
+ {{ error|lower_fn }},
+ )
+ {%- endmatch %}
+ {%- else %}
+ def handle_success(return_value):
+ uniffi_future_callback(
+ uniffi_callback_data,
+ {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ {{ return_type|lower_fn }}(return_value),
+ {%- when None %}
+ {%- endmatch %}
+ _UniffiRustCallStatus.default()
+ )
+ )
+
+ def handle_error(status_code, rust_buffer):
+ uniffi_future_callback(
+ uniffi_callback_data,
+ {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ {{ meth.return_type().map(FfiType::from)|ffi_default_value }},
+ {%- when None %}
+ {%- endmatch %}
+ _UniffiRustCallStatus(status_code, rust_buffer),
+ )
+ )
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ uniffi_out_return[0] = uniffi_trait_interface_call_async(make_call, handle_success, handle_error)
+ {%- when Some(error) %}
+ uniffi_out_return[0] = uniffi_trait_interface_call_async_with_error(make_call, handle_success, handle_error, {{ error|type_name }}, {{ error|lower_fn }})
+ {%- endmatch %}
+ {%- endif %}
+ {%- endfor %}
+
+ @{{ "CallbackInterfaceFree"|ffi_callback_name }}
+ def uniffi_free(uniffi_handle):
+ {{ ffi_converter_name }}._handle_map.remove(uniffi_handle)
+
+ # Generate the FFI VTable. This has a field for each callback interface method.
+ uniffi_vtable = {{ vtable|ffi_type_name }}(
+ {%- for (_, meth) in vtable_methods.iter() %}
+ {{ meth.name()|fn_name }},
+ {%- endfor %}
+ uniffi_free
+ )
+ # Send Rust a pointer to the VTable. Note: this means we need to keep the struct alive forever,
+ # or else bad things will happen when Rust tries to access it.
+ _UniffiLib.{{ ffi_init_callback.name() }}(ctypes.byref(uniffi_vtable))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py
index 0fe2ab8dc0..d802c90fde 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py
@@ -1,42 +1,3 @@
-import threading
-
-class ConcurrentHandleMap:
- """
- A map where inserting, getting and removing data is synchronized with a lock.
- """
-
- def __init__(self):
- # type Handle = int
- self._left_map = {} # type: Dict[Handle, Any]
- self._right_map = {} # type: Dict[Any, Handle]
-
- self._lock = threading.Lock()
- self._current_handle = 0
- self._stride = 1
-
-
- def insert(self, obj):
- with self._lock:
- if obj in self._right_map:
- return self._right_map[obj]
- else:
- handle = self._current_handle
- self._current_handle += self._stride
- self._left_map[handle] = obj
- self._right_map[obj] = handle
- return handle
-
- def get(self, handle):
- with self._lock:
- return self._left_map.get(handle)
-
- def remove(self, handle):
- with self._lock:
- if handle in self._left_map:
- obj = self._left_map.pop(handle)
- del self._right_map[obj]
- return obj
-
# Magic number for the Rust proxy to call using the same mechanism as every other method,
# to free the callback once it's dropped by Rust.
IDX_CALLBACK_FREE = 0
@@ -45,22 +6,12 @@ _UNIFFI_CALLBACK_SUCCESS = 0
_UNIFFI_CALLBACK_ERROR = 1
_UNIFFI_CALLBACK_UNEXPECTED_ERROR = 2
-class _UniffiConverterCallbackInterface:
- _handle_map = ConcurrentHandleMap()
-
- def __init__(self, cb):
- self._foreign_callback = cb
-
- def drop(self, handle):
- self.__class__._handle_map.remove(handle)
+class UniffiCallbackInterfaceFfiConverter:
+ _handle_map = _UniffiHandleMap()
@classmethod
def lift(cls, handle):
- obj = cls._handle_map.get(handle)
- if not obj:
- raise InternalError("The object in the handle map has been dropped already")
-
- return obj
+ return cls._handle_map.get(handle)
@classmethod
def read(cls, buf):
@@ -68,6 +19,10 @@ class _UniffiConverterCallbackInterface:
cls.lift(handle)
@classmethod
+ def check_lower(cls, cb):
+ pass
+
+ @classmethod
def lower(cls, cb):
handle = cls._handle_map.insert(cb)
return handle
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py
index e0e926757a..a41e58e635 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py
@@ -1,105 +1,13 @@
-{%- let cbi = ci|get_callback_interface_definition(id) %}
-{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %}
+{%- let cbi = ci|get_callback_interface_definition(name) %}
+{%- let ffi_init_callback = cbi.ffi_init_callback() %}
+{%- let protocol_name = type_name.clone() %}
+{%- let protocol_docstring = cbi.docstring() %}
+{%- let vtable = cbi.vtable() %}
+{%- let methods = cbi.methods() %}
+{%- let vtable_methods = cbi.vtable_methods() %}
-{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %}
-
-# Declaration and _UniffiConverters for {{ type_name }} Callback Interface
-
-class {{ type_name }}:
- {% for meth in cbi.methods() -%}
- def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}):
- raise NotImplementedError
-
- {% endfor %}
-
-def py_{{ foreign_callback }}(handle, method, args_data, args_len, buf_ptr):
- {% for meth in cbi.methods() -%}
- {% let method_name = format!("invoke_{}", meth.name())|fn_name %}
- def {{ method_name }}(python_callback, args_stream, buf_ptr):
- {#- Unpacking args from the _UniffiRustBuffer #}
- def makeCall():
- {#- Calling the concrete callback object #}
- {%- if meth.arguments().len() != 0 -%}
- return python_callback.{{ meth.name()|fn_name }}(
- {% for arg in meth.arguments() -%}
- {{ arg|read_fn }}(args_stream)
- {%- if !loop.last %}, {% endif %}
- {% endfor -%}
- )
- {%- else %}
- return python_callback.{{ meth.name()|fn_name }}()
- {%- endif %}
-
- def makeCallAndHandleReturn():
- {%- match meth.return_type() %}
- {%- when Some(return_type) %}
- rval = makeCall()
- with _UniffiRustBuffer.alloc_with_builder() as builder:
- {{ return_type|write_fn }}(rval, builder)
- buf_ptr[0] = builder.finalize()
- {%- when None %}
- makeCall()
- {%- endmatch %}
- return _UNIFFI_CALLBACK_SUCCESS
-
- {%- match meth.throws_type() %}
- {%- when None %}
- return makeCallAndHandleReturn()
- {%- when Some(err) %}
- try:
- return makeCallAndHandleReturn()
- except {{ err|type_name }} as e:
- # Catch errors declared in the UDL file
- with _UniffiRustBuffer.alloc_with_builder() as builder:
- {{ err|write_fn }}(e, builder)
- buf_ptr[0] = builder.finalize()
- return _UNIFFI_CALLBACK_ERROR
- {%- endmatch %}
-
- {% endfor %}
-
- cb = {{ ffi_converter_name }}.lift(handle)
- if not cb:
- raise InternalError("No callback in handlemap; this is a uniffi bug")
-
- if method == IDX_CALLBACK_FREE:
- {{ ffi_converter_name }}.drop(handle)
- # Successfull return
- # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- return _UNIFFI_CALLBACK_SUCCESS
-
- {% for meth in cbi.methods() -%}
- {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
- if method == {{ loop.index }}:
- # Call the method and handle any errors
- # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs` for details
- try:
- return {{ method_name }}(cb, _UniffiRustBufferStream(args_data, args_len), buf_ptr)
- except BaseException as e:
- # Catch unexpected errors
- try:
- # Try to serialize the exception into a String
- buf_ptr[0] = {{ Type::String.borrow()|lower_fn }}(repr(e))
- except:
- # If that fails, just give up
- pass
- return _UNIFFI_CALLBACK_UNEXPECTED_ERROR
- {% endfor %}
-
- # This should never happen, because an out of bounds method index won't
- # ever be used. Once we can catch errors, we should return an InternalException.
- # https://github.com/mozilla/uniffi-rs/issues/351
-
- # An unexpected error happened.
- # See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- return _UNIFFI_CALLBACK_UNEXPECTED_ERROR
-
-# We need to keep this function reference alive:
-# if they get GC'd while in use then UniFFI internals could attempt to call a function
-# that is in freed memory.
-# That would be...uh...bad. Yeah, that's the word. Bad.
-{{ foreign_callback }} = _UNIFFI_FOREIGN_CALLBACK_T(py_{{ foreign_callback }})
-_rust_call(lambda err: _UniffiLib.{{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err))
+{% include "Protocol.py" %}
+{% include "CallbackInterfaceImpl.py" %}
# The _UniffiConverter which transforms the Callbacks in to Handles to pass to Rust.
-{{ ffi_converter_name }} = _UniffiConverterCallbackInterface({{ foreign_callback }})
+{{ ffi_converter_name }} = UniffiCallbackInterfaceFfiConverter()
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py
index 5be6155b84..f75a85dc27 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py
@@ -18,6 +18,10 @@ class _UniffiConverterType{{ name }}:
return {{ builtin|ffi_converter_name }}.lift(value)
@staticmethod
+ def check_lower(value):
+ return {{ builtin|ffi_converter_name }}.check_lower(value)
+
+ @staticmethod
def lower(value):
return {{ builtin|ffi_converter_name }}.lower(value)
@@ -52,6 +56,11 @@ class _UniffiConverterType{{ name }}:
return {{ config.into_custom.render("builtin_value") }}
@staticmethod
+ def check_lower(value):
+ builtin_value = {{ config.from_custom.render("value") }}
+ return {{ builtin|check_lower_fn }}(builtin_value)
+
+ @staticmethod
def lower(value):
builtin_value = {{ config.from_custom.render("value") }}
return {{ builtin|lower_fn }}(builtin_value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py
index 10974e009d..ecb035b7f4 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py
@@ -12,10 +12,14 @@ class _UniffiConverterDuration(_UniffiConverterRustBuffer):
return datetime.timedelta(seconds=seconds, microseconds=microseconds)
@staticmethod
- def write(value, buf):
+ def check_lower(value):
seconds = value.seconds + value.days * 24 * 3600
- nanoseconds = value.microseconds * 1000
if seconds < 0:
raise ValueError("Invalid duration, must be non-negative")
+
+ @staticmethod
+ def write(value, buf):
+ seconds = value.seconds + value.days * 24 * 3600
+ nanoseconds = value.microseconds * 1000
buf.write_i64(seconds)
buf.write_u32(nanoseconds)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
index 84d089baf9..d07dd1c44a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
@@ -7,31 +7,59 @@
{% if e.is_flat() %}
class {{ type_name }}(enum.Enum):
- {% for variant in e.variants() -%}
- {{ variant.name()|enum_variant_py }} = {{ loop.index }}
+ {%- call py::docstring(e, 4) %}
+ {%- for variant in e.variants() %}
+ {{ variant.name()|enum_variant_py }} = {{ e|variant_discr_literal(loop.index0) }}
+ {%- call py::docstring(variant, 4) %}
{% endfor %}
{% else %}
class {{ type_name }}:
+ {%- call py::docstring(e, 4) %}
def __init__(self):
raise RuntimeError("{{ type_name }} cannot be instantiated directly")
# Each enum variant is a nested class of the enum itself.
{% for variant in e.variants() -%}
class {{ variant.name()|enum_variant_py }}:
- {% for field in variant.fields() %}
- {{- field.name()|var_name }}: "{{- field|type_name }}";
+ {%- call py::docstring(variant, 8) %}
+
+ {%- if variant.has_nameless_fields() %}
+ def __init__(self, *values):
+ if len(values) != {{ variant.fields().len() }}:
+ raise TypeError(f"Expected a tuple of len {{ variant.fields().len() }}, found len {len(values)}")
+ {%- for field in variant.fields() %}
+ if not isinstance(values[{{ loop.index0 }}], {{ field|type_name }}):
+ raise TypeError(f"unexpected type for tuple element {{ loop.index0 }} - expected '{{ field|type_name }}', got '{type(values[{{ loop.index0 }}])}'")
+ {%- endfor %}
+ self._values = values
+
+ def __getitem__(self, index):
+ return self._values[index]
+
+ def __str__(self):
+ return f"{{ type_name }}.{{ variant.name()|enum_variant_py }}{self._values!r}"
+
+ def __eq__(self, other):
+ if not other.is_{{ variant.name()|var_name }}():
+ return False
+ return self._values == other._values
+
+ {%- else -%}
+ {%- for field in variant.fields() %}
+ {{ field.name()|var_name }}: "{{ field|type_name }}"
+ {%- call py::docstring(field, 8) %}
{%- endfor %}
@typing.no_type_check
def __init__(self,{% for field in variant.fields() %}{{ field.name()|var_name }}: "{{- field|type_name }}"{% if loop.last %}{% else %}, {% endif %}{% endfor %}):
- {% if variant.has_fields() %}
+ {%- if variant.has_fields() %}
{%- for field in variant.fields() %}
self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
{%- endfor %}
- {% else %}
+ {%- else %}
pass
- {% endif %}
+ {%- endif %}
def __str__(self):
return "{{ type_name }}.{{ variant.name()|enum_variant_py }}({% for field in variant.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
@@ -44,6 +72,7 @@ class {{ type_name }}:
return False
{%- endfor %}
return True
+ {% endif %}
{% endfor %}
# For each variant, we have an `is_NAME` method for easily checking
@@ -81,6 +110,30 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
{%- endfor %}
raise InternalError("Raw enum value doesn't match any cases")
+ @staticmethod
+ def check_lower(value):
+ {%- if e.variants().is_empty() %}
+ pass
+ {%- else %}
+ {%- for variant in e.variants() %}
+ {%- if e.is_flat() %}
+ if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}:
+ {%- else %}
+ if value.is_{{ variant.name()|var_name }}():
+ {%- endif %}
+ {%- for field in variant.fields() %}
+ {%- if variant.has_nameless_fields() %}
+ {{ field|check_lower_fn }}(value._values[{{ loop.index0 }}])
+ {%- else %}
+ {{ field|check_lower_fn }}(value.{{ field.name()|var_name }})
+ {%- endif %}
+ {%- endfor %}
+ return
+ {%- endfor %}
+ raise ValueError(value)
+ {%- endif %}
+
+ @staticmethod
def write(value, buf):
{%- for variant in e.variants() %}
{%- if e.is_flat() %}
@@ -90,7 +143,11 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
if value.is_{{ variant.name()|var_name }}():
buf.write_i32({{ loop.index }})
{%- for field in variant.fields() %}
+ {%- if variant.has_nameless_fields() %}
+ {{ field|write_fn }}(value._values[{{ loop.index0 }}], buf)
+ {%- else %}
{{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endif %}
{%- endfor %}
{%- endif %}
{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py
index 26a1e6452a..0911ff559a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py
@@ -5,6 +5,7 @@
# __dict__. All of this happens in dummy class to avoid polluting the module
# namespace.
class {{ type_name }}(Exception):
+ {%- call py::docstring(e, 4) %}
pass
_UniffiTemp{{ type_name }} = {{ type_name }}
@@ -14,10 +15,14 @@ class {{ type_name }}: # type: ignore
{%- let variant_type_name = variant.name()|class_name -%}
{%- if e.is_flat() %}
class {{ variant_type_name }}(_UniffiTemp{{ type_name }}):
+ {%- call py::docstring(variant, 8) %}
+
def __repr__(self):
return "{{ type_name }}.{{ variant_type_name }}({})".format(repr(str(self)))
{%- else %}
class {{ variant_type_name }}(_UniffiTemp{{ type_name }}):
+ {%- call py::docstring(variant, 8) %}
+
def __init__(self{% for field in variant.fields() %}, {{ field.name()|var_name }}{% endfor %}):
{%- if variant.has_fields() %}
super().__init__(", ".join([
@@ -60,6 +65,20 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
raise InternalError("Raw enum value doesn't match any cases")
@staticmethod
+ def check_lower(value):
+ {%- if e.variants().is_empty() %}
+ pass
+ {%- else %}
+ {%- for variant in e.variants() %}
+ if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}):
+ {%- for field in variant.fields() %}
+ {{ field|check_lower_fn }}(value.{{ field.name()|var_name }})
+ {%- endfor %}
+ return
+ {%- endfor %}
+ {%- endif %}
+
+ @staticmethod
def write(value, buf):
{%- for variant in e.variants() %}
if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}):
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py
index 71e05e8b06..6c0cee85ef 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py
@@ -1,9 +1,9 @@
-{%- let ns = namespace|fn_name %}
+{%- let module = python_config.module_for_namespace(namespace) -%}
# External type {{name}} is in namespace "{{namespace}}", crate {{module_path}}
{%- let ffi_converter_name = "_UniffiConverterType{}"|format(name) %}
-{{ self.add_import_of(ns, ffi_converter_name) }}
-{{ self.add_import_of(ns, name) }} {#- import the type alias itself -#}
+{{ self.add_import_of(module, ffi_converter_name) }}
+{{ self.add_import_of(module, name|class_name) }} {#- import the type alias itself -#}
{%- let rustbuffer_local_name = "_UniffiRustBuffer{}"|format(name) %}
-{{ self.add_import_of_as(ns, "_UniffiRustBuffer", rustbuffer_local_name) }}
+{{ self.add_import_of_as(module, "_UniffiRustBuffer", rustbuffer_local_name) }}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py
index a52107a638..49a1a7286e 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py
@@ -4,5 +4,5 @@ class _UniffiConverterFloat(_UniffiConverterPrimitiveFloat):
return buf.read_float()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_float(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py
index 772f5080e9..e2084c7b13 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py
@@ -4,5 +4,5 @@ class _UniffiConverterDouble(_UniffiConverterPrimitiveFloat):
return buf.read_double()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_double(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py
deleted file mode 100644
index 6a6932fed0..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ForeignExecutorTemplate.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# FFI code for the ForeignExecutor type
-
-{{ self.add_import("asyncio") }}
-
-_UNIFFI_RUST_TASK_CALLBACK_SUCCESS = 0
-_UNIFFI_RUST_TASK_CALLBACK_CANCELLED = 1
-_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS = 0
-_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED = 1
-_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR = 2
-
-class {{ ffi_converter_name }}:
- _pointer_manager = _UniffiPointerManager()
-
- @classmethod
- def lower(cls, eventloop):
- if not isinstance(eventloop, asyncio.BaseEventLoop):
- raise TypeError("_uniffi_executor_callback: Expected EventLoop instance")
- return cls._pointer_manager.new_pointer(eventloop)
-
- @classmethod
- def write(cls, eventloop, buf):
- buf.write_c_size_t(cls.lower(eventloop))
-
- @classmethod
- def read(cls, buf):
- return cls.lift(buf.read_c_size_t())
-
- @classmethod
- def lift(cls, value):
- return cls._pointer_manager.lookup(value)
-
-@_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T
-def _uniffi_executor_callback(eventloop_address, delay, task_ptr, task_data):
- if task_ptr is None:
- {{ ffi_converter_name }}._pointer_manager.release_pointer(eventloop_address)
- return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
- else:
- eventloop = {{ ffi_converter_name }}._pointer_manager.lookup(eventloop_address)
- if eventloop.is_closed():
- return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED
-
- callback = _UNIFFI_RUST_TASK(task_ptr)
- # FIXME: there's no easy way to get a callback when an eventloop is closed. This means that
- # if eventloop is called before the `call_soon_threadsafe()` calls are invoked, the call
- # will never happen and we will probably leak a resource.
- if delay == 0:
- # This can be called from any thread, so make sure to use `call_soon_threadsafe'
- eventloop.call_soon_threadsafe(callback, task_data,
- _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS)
- else:
- # For delayed tasks, we use `call_soon_threadsafe()` + `call_later()` to make the
- # operation threadsafe
- eventloop.call_soon_threadsafe(eventloop.call_later, delay / 1000.0, callback,
- task_data, _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS)
- return _UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
-
-# Register the callback with the scaffolding
-{%- match ci.ffi_foreign_executor_callback_set() %}
-{%- when Some with (fn) %}
-_UniffiLib.{{ fn.name() }}(_uniffi_executor_callback)
-{%- when None %}
-{#- No foreign executor, we don't set anything #}
-{% endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py
new file mode 100644
index 0000000000..f7c13cf745
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/HandleMap.py
@@ -0,0 +1,33 @@
+class _UniffiHandleMap:
+ """
+ A map where inserting, getting and removing data is synchronized with a lock.
+ """
+
+ def __init__(self):
+ # type Handle = int
+ self._map = {} # type: Dict[Handle, Any]
+ self._lock = threading.Lock()
+ self._counter = itertools.count()
+
+ def insert(self, obj):
+ with self._lock:
+ handle = next(self._counter)
+ self._map[handle] = obj
+ return handle
+
+ def get(self, handle):
+ try:
+ with self._lock:
+ return self._map[handle]
+ except KeyError:
+ raise InternalError("UniffiHandleMap.get: Invalid handle")
+
+ def remove(self, handle):
+ try:
+ with self._lock:
+ return self._map.pop(handle)
+ except KeyError:
+ raise InternalError("UniffiHandleMap.remove: Invalid handle")
+
+ def __len__(self):
+ return len(self._map)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py
index dca962f176..5d4bcbba89 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py
@@ -16,15 +16,19 @@ class _UniffiRustCallStatus(ctypes.Structure):
# These match the values from the uniffi::rustcalls module
CALL_SUCCESS = 0
CALL_ERROR = 1
- CALL_PANIC = 2
+ CALL_UNEXPECTED_ERROR = 2
+
+ @staticmethod
+ def default():
+ return _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer.default())
def __str__(self):
if self.code == _UniffiRustCallStatus.CALL_SUCCESS:
return "_UniffiRustCallStatus(CALL_SUCCESS)"
elif self.code == _UniffiRustCallStatus.CALL_ERROR:
return "_UniffiRustCallStatus(CALL_ERROR)"
- elif self.code == _UniffiRustCallStatus.CALL_PANIC:
- return "_UniffiRustCallStatus(CALL_PANIC)"
+ elif self.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR:
+ return "_UniffiRustCallStatus(CALL_UNEXPECTED_ERROR)"
else:
return "_UniffiRustCallStatus(<invalid code>)"
@@ -37,7 +41,7 @@ def _rust_call_with_error(error_ffi_converter, fn, *args):
#
# This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code.
# error_ffi_converter must be set to the _UniffiConverter for the error class that corresponds to the result.
- call_status = _UniffiRustCallStatus(code=_UniffiRustCallStatus.CALL_SUCCESS, error_buf=_UniffiRustBuffer(0, 0, None))
+ call_status = _UniffiRustCallStatus.default()
args_with_error = args + (ctypes.byref(call_status),)
result = fn(*args_with_error)
@@ -53,7 +57,7 @@ def _uniffi_check_call_status(error_ffi_converter, call_status):
raise InternalError("_rust_call_with_error: CALL_ERROR, but error_ffi_converter is None")
else:
raise error_ffi_converter.lift(call_status.error_buf)
- elif call_status.code == _UniffiRustCallStatus.CALL_PANIC:
+ elif call_status.code == _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR:
# When the rust code sees a panic, it tries to construct a _UniffiRustBuffer
# with the message. But if that code panics, then it just sends back
# an empty buffer.
@@ -66,10 +70,20 @@ def _uniffi_check_call_status(error_ffi_converter, call_status):
raise InternalError("Invalid _UniffiRustCallStatus code: {}".format(
call_status.code))
-# A function pointer for a callback as defined by UniFFI.
-# Rust definition `fn(handle: u64, method: u32, args: _UniffiRustBuffer, buf_ptr: *mut _UniffiRustBuffer) -> int`
-_UNIFFI_FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, ctypes.POINTER(ctypes.c_char), ctypes.c_int, ctypes.POINTER(_UniffiRustBuffer))
-
-# UniFFI future continuation
-_UNIFFI_FUTURE_CONTINUATION_T = ctypes.CFUNCTYPE(None, ctypes.c_size_t, ctypes.c_int8)
+def _uniffi_trait_interface_call(call_status, make_call, write_return_value):
+ try:
+ return write_return_value(make_call())
+ except Exception as e:
+ call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR
+ call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e))
+def _uniffi_trait_interface_call_with_error(call_status, make_call, write_return_value, error_type, lower_error):
+ try:
+ try:
+ return write_return_value(make_call())
+ except error_type as e:
+ call_status.code = _UniffiRustCallStatus.CALL_ERROR
+ call_status.error_buf = lower_error(e)
+ except Exception as e:
+ call_status.code = _UniffiRustCallStatus.CALL_UNEXPECTED_ERROR
+ call_status.error_buf = {{ Type::String.borrow()|lower_fn }}(repr(e))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py
index 99f19dc1c0..befa563384 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterInt16(_UniffiConverterPrimitiveInt):
return buf.read_i16()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_i16(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py
index 3b142c8749..70644f6717 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterInt32(_UniffiConverterPrimitiveInt):
return buf.read_i32()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_i32(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py
index 6e94379cbf..232f127bd6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterInt64(_UniffiConverterPrimitiveInt):
return buf.read_i64()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_i64(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py
index 732530e3cb..c1de1625e7 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterInt8(_UniffiConverterPrimitiveInt):
return buf.read_i8()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_i8(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py
index 387227ed09..a09ca28a30 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py
@@ -3,6 +3,12 @@
class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
@classmethod
+ def check_lower(cls, items):
+ for (key, value) in items.items():
+ {{ key_ffi_converter }}.check_lower(key)
+ {{ value_ffi_converter }}.check_lower(value)
+
+ @classmethod
def write(cls, items, buf):
buf.write_i32(len(items))
for (key, value) in items.items():
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py
index fac6cd5564..1929f9aad6 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py
@@ -1,22 +1,6 @@
# Define some ctypes FFI types that we use in the library
"""
-ctypes type for the foreign executor callback. This is a built-in interface for scheduling
-tasks
-
-Args:
- executor: opaque c_size_t value representing the eventloop
- delay: delay in ms
- task: function pointer to the task callback
- task_data: void pointer to the task callback data
-
-Normally we should call task(task_data) after the detail.
-However, when task is NULL this indicates that Rust has dropped the ForeignExecutor and we should
-decrease the EventLoop refcount.
-"""
-_UNIFFI_FOREIGN_EXECUTOR_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int8, ctypes.c_size_t, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p)
-
-"""
Function pointer for a Rust task, which a callback function that takes a opaque pointer
"""
_UNIFFI_RUST_TASK = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int8)
@@ -25,7 +9,7 @@ def _uniffi_future_callback_t(return_type):
"""
Factory function to create callback function types for async functions
"""
- return ctypes.CFUNCTYPE(None, ctypes.c_size_t, return_type, _UniffiRustCallStatus)
+ return ctypes.CFUNCTYPE(None, ctypes.c_uint64, return_type, _UniffiRustCallStatus)
def _uniffi_load_indirect():
"""
@@ -72,12 +56,37 @@ def _uniffi_check_api_checksums(lib):
# This is an implementation detail which will be called internally by the public API.
_UniffiLib = _uniffi_load_indirect()
-{%- for func in ci.iter_ffi_function_definitions() %}
+
+{%- for def in ci.ffi_definitions() %}
+{%- match def %}
+{%- when FfiDefinition::CallbackFunction(callback) %}
+{{ callback.name()|ffi_callback_name }} = ctypes.CFUNCTYPE(
+ {%- match callback.return_type() %}
+ {%- when Some(return_type) %}{{ return_type|ffi_type_name }},
+ {%- when None %}None,
+ {%- endmatch %}
+ {%- for arg in callback.arguments() -%}
+ {{ arg.type_().borrow()|ffi_type_name }},
+ {%- endfor -%}
+ {%- if callback.has_rust_call_status_arg() %}
+ ctypes.POINTER(_UniffiRustCallStatus),
+ {%- endif %}
+)
+{%- when FfiDefinition::Struct(ffi_struct) %}
+class {{ ffi_struct.name()|ffi_struct_name }}(ctypes.Structure):
+ _fields_ = [
+ {%- for field in ffi_struct.fields() %}
+ ("{{ field.name()|var_name }}", {{ field.type_().borrow()|ffi_type_name }}),
+ {%- endfor %}
+ ]
+{%- when FfiDefinition::Function(func) %}
_UniffiLib.{{ func.name() }}.argtypes = (
{%- call py::arg_list_ffi_decl(func) -%}
)
_UniffiLib.{{ func.name() }}.restype = {% match func.return_type() %}{% when Some with (type_) %}{{ type_|ffi_type_name }}{% when None %}None{% endmatch %}
+{%- endmatch %}
{%- endfor %}
+
{# Ensure to call the contract verification only after we defined all functions. -#}
_uniffi_check_contract_api_version(_UniffiLib)
_uniffi_check_api_checksums(_UniffiLib)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py
index 7e98f7c46f..18dca4743a 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py
@@ -1,14 +1,33 @@
{%- let obj = ci|get_object_definition(name) %}
-
-class {{ type_name }}:
+{%- let (protocol_name, impl_name) = obj|object_names %}
+{%- let methods = obj.methods() %}
+{%- let protocol_docstring = obj.docstring() %}
+
+{% include "Protocol.py" %}
+
+{% if ci.is_name_used_as_error(name) %}
+class {{ impl_name }}(Exception):
+{%- else %}
+class {{ impl_name }}:
+{%- endif %}
+ {%- call py::docstring(obj, 4) %}
_pointer: ctypes.c_void_p
{%- match obj.primary_constructor() %}
{%- when Some with (cons) %}
+{%- if cons.is_async() %}
+ def __init__(self, *args, **kw):
+ raise ValueError("async constructors not supported.")
+{%- else %}
def __init__(self, {% call py::arg_list_decl(cons) -%}):
+ {%- call py::docstring(cons, 8) %}
{%- call py::setup_args_extra_indent(cons) %}
self._pointer = {% call py::to_ffi_call(cons) %}
+{%- endif %}
{%- when None %}
+ {# no __init__ means simple construction without a pointer works, which can confuse #}
+ def __init__(self, *args, **kwargs):
+ raise ValueError("This class has no default constructor")
{%- endmatch %}
def __del__(self):
@@ -17,6 +36,9 @@ class {{ type_name }}:
if pointer is not None:
_rust_call(_UniffiLib.{{ obj.ffi_object_free().name() }}, pointer)
+ def _uniffi_clone_pointer(self):
+ return _rust_call(_UniffiLib.{{ obj.ffi_object_clone().name() }}, self._pointer)
+
# Used by alternative constructors or any methods which return this type.
@classmethod
def _make_instance_(cls, pointer):
@@ -29,17 +51,32 @@ class {{ type_name }}:
{%- for cons in obj.alternate_constructors() %}
@classmethod
+{%- if cons.is_async() %}
+ async def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}):
+ {%- call py::docstring(cons, 8) %}
+ {%- call py::setup_args_extra_indent(cons) %}
+
+ return await _uniffi_rust_call_async(
+ _UniffiLib.{{ cons.ffi_func().name() }}({% call py::arg_list_lowered(cons) %}),
+ _UniffiLib.{{ cons.ffi_rust_future_poll(ci) }},
+ _UniffiLib.{{ cons.ffi_rust_future_complete(ci) }},
+ _UniffiLib.{{ cons.ffi_rust_future_free(ci) }},
+ {{ ffi_converter_name }}.lift,
+ {% call py::error_ffi_converter(cons) %}
+ )
+{%- else %}
def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}):
+ {%- call py::docstring(cons, 8) %}
{%- call py::setup_args_extra_indent(cons) %}
# Call the (fallible) function before creating any half-baked object instances.
pointer = {% call py::to_ffi_call(cons) %}
return cls._make_instance_(pointer)
+{%- endif %}
{% endfor %}
{%- for meth in obj.methods() -%}
{%- call py::method_decl(meth.name()|fn_name, meth) %}
-{% endfor %}
-
+{%- endfor %}
{%- for tm in obj.uniffi_traits() -%}
{%- match tm %}
{%- when UniffiTrait::Debug { fmt } %}
@@ -51,37 +88,84 @@ class {{ type_name }}:
if not isinstance(other, {{ type_name }}):
return NotImplemented
- return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", eq) %})
+ return {{ eq.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_clone_pointer()", eq) %})
def __ne__(self, other: object) -> {{ ne.return_type().unwrap()|type_name }}:
if not isinstance(other, {{ type_name }}):
return NotImplemented
- return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._pointer", ne) %})
+ return {{ ne.return_type().unwrap()|lift_fn }}({% call py::to_ffi_call_with_prefix("self._uniffi_clone_pointer()", ne) %})
{%- when UniffiTrait::Hash { hash } %}
{%- call py::method_decl("__hash__", hash) %}
-{% endmatch %}
-{% endfor %}
-
-
-class {{ ffi_converter_name }}:
+{%- endmatch %}
+{%- endfor %}
+
+{%- if obj.has_callback_interface() %}
+{%- let ffi_init_callback = obj.ffi_init_callback() %}
+{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %}
+{%- let vtable_methods = obj.vtable_methods() %}
+{% include "CallbackInterfaceImpl.py" %}
+{%- endif %}
+
+{# Objects as error #}
+{%- if ci.is_name_used_as_error(name) %}
+{# Due to some mismatches in the ffi converter mechanisms, errors are forced to be a RustBuffer #}
+class {{ ffi_converter_name }}__as_error(_UniffiConverterRustBuffer):
@classmethod
def read(cls, buf):
- ptr = buf.read_u64()
- if ptr == 0:
- raise InternalError("Raw pointer value was null")
- return cls.lift(ptr)
+ raise NotImplementedError()
@classmethod
def write(cls, value, buf):
- if not isinstance(value, {{ type_name }}):
- raise TypeError("Expected {{ type_name }} instance, {} found".format(type(value).__name__))
- buf.write_u64(cls.lower(value))
+ raise NotImplementedError()
@staticmethod
def lift(value):
- return {{ type_name }}._make_instance_(value)
+ # Errors are always a rust buffer holding a pointer - which is a "read"
+ with value.consume_with_stream() as stream:
+ return {{ ffi_converter_name }}.read(stream)
@staticmethod
def lower(value):
- return value._pointer
+ raise NotImplementedError()
+
+{%- endif %}
+
+class {{ ffi_converter_name }}:
+ {%- if obj.has_callback_interface() %}
+ _handle_map = _UniffiHandleMap()
+ {%- endif %}
+
+ @staticmethod
+ def lift(value: int):
+ return {{ impl_name }}._make_instance_(value)
+
+ @staticmethod
+ def check_lower(value: {{ type_name }}):
+ {%- if obj.has_callback_interface() %}
+ pass
+ {%- else %}
+ if not isinstance(value, {{ impl_name }}):
+ raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__))
+ {%- endif %}
+
+ @staticmethod
+ def lower(value: {{ protocol_name }}):
+ {%- if obj.has_callback_interface() %}
+ return {{ ffi_converter_name }}._handle_map.insert(value)
+ {%- else %}
+ if not isinstance(value, {{ impl_name }}):
+ raise TypeError("Expected {{ impl_name }} instance, {} found".format(type(value).__name__))
+ return value._uniffi_clone_pointer()
+ {%- endif %}
+
+ @classmethod
+ def read(cls, buf: _UniffiRustBuffer):
+ ptr = buf.read_u64()
+ if ptr == 0:
+ raise InternalError("Raw pointer value was null")
+ return cls.lift(ptr)
+
+ @classmethod
+ def write(cls, value: {{ protocol_name }}, buf: _UniffiRustBuffer):
+ buf.write_u64(cls.lower(value))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py
index e406c51d49..4c07ae3e34 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py
@@ -2,6 +2,11 @@
class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
@classmethod
+ def check_lower(cls, value):
+ if value is not None:
+ {{ inner_ffi_converter }}.check_lower(value)
+
+ @classmethod
def write(cls, value, buf):
if value is None:
buf.write_u8(0)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py
deleted file mode 100644
index 23aa28eab4..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/PointerManager.py
+++ /dev/null
@@ -1,68 +0,0 @@
-class _UniffiPointerManagerCPython:
- """
- Manage giving out pointers to Python objects on CPython
-
- This class is used to generate opaque pointers that reference Python objects to pass to Rust.
- It assumes a CPython platform. See _UniffiPointerManagerGeneral for the alternative.
- """
-
- def new_pointer(self, obj):
- """
- Get a pointer for an object as a ctypes.c_size_t instance
-
- Each call to new_pointer() must be balanced with exactly one call to release_pointer()
-
- This returns a ctypes.c_size_t. This is always the same size as a pointer and can be
- interchanged with pointers for FFI function arguments and return values.
- """
- # IncRef the object since we're going to pass a pointer to Rust
- ctypes.pythonapi.Py_IncRef(ctypes.py_object(obj))
- # id() is the object address on CPython
- # (https://docs.python.org/3/library/functions.html#id)
- return id(obj)
-
- def release_pointer(self, address):
- py_obj = ctypes.cast(address, ctypes.py_object)
- obj = py_obj.value
- ctypes.pythonapi.Py_DecRef(py_obj)
- return obj
-
- def lookup(self, address):
- return ctypes.cast(address, ctypes.py_object).value
-
-class _UniffiPointerManagerGeneral:
- """
- Manage giving out pointers to Python objects on non-CPython platforms
-
- This has the same API as _UniffiPointerManagerCPython, but doesn't assume we're running on
- CPython and is slightly slower.
-
- Instead of using real pointers, it maps integer values to objects and returns the keys as
- c_size_t values.
- """
-
- def __init__(self):
- self._map = {}
- self._lock = threading.Lock()
- self._current_handle = 0
-
- def new_pointer(self, obj):
- with self._lock:
- handle = self._current_handle
- self._current_handle += 1
- self._map[handle] = obj
- return handle
-
- def release_pointer(self, handle):
- with self._lock:
- return self._map.pop(handle)
-
- def lookup(self, handle):
- with self._lock:
- return self._map[handle]
-
-# Pick an pointer manager implementation based on the platform
-if platform.python_implementation() == 'CPython':
- _UniffiPointerManager = _UniffiPointerManagerCPython # type: ignore
-else:
- _UniffiPointerManager = _UniffiPointerManagerGeneral # type: ignore
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py
new file mode 100644
index 0000000000..3b7e93596a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Protocol.py
@@ -0,0 +1,9 @@
+class {{ protocol_name }}(typing.Protocol):
+ {%- call py::docstring_value(protocol_docstring, 4) %}
+ {%- for meth in methods.iter() %}
+ def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}):
+ {%- call py::docstring(meth, 8) %}
+ raise NotImplementedError
+ {%- else %}
+ pass
+ {%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py
index 99a30e120f..0b5634eb52 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py
@@ -1,11 +1,14 @@
{%- let rec = ci|get_record_definition(name) %}
class {{ type_name }}:
- {% for field in rec.fields() %}
- {{- field.name()|var_name }}: "{{- field|type_name }}";
+ {%- call py::docstring(rec, 4) %}
+ {%- for field in rec.fields() %}
+ {{ field.name()|var_name }}: "{{ field|type_name }}"
+ {%- call py::docstring(field, 4) %}
{%- endfor %}
+ {%- if rec.has_fields() %}
@typing.no_type_check
- def __init__(self, {% for field in rec.fields() %}
+ def __init__(self, *, {% for field in rec.fields() %}
{{- field.name()|var_name }}: "{{- field|type_name }}"
{%- if field.default_value().is_some() %} = _DEFAULT{% endif %}
{%- if !loop.last %}, {% endif %}
@@ -22,6 +25,7 @@ class {{ type_name }}:
self.{{ field_name }} = {{ field_name }}
{%- endmatch %}
{%- endfor %}
+ {%- endif %}
def __str__(self):
return "{{ type_name }}({% for field in rec.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in rec.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
@@ -43,7 +47,21 @@ class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
)
@staticmethod
+ def check_lower(value):
+ {%- if rec.fields().is_empty() %}
+ pass
+ {%- else %}
+ {%- for field in rec.fields() %}
+ {{ field|check_lower_fn }}(value.{{ field.name()|var_name }})
+ {%- endfor %}
+ {%- endif %}
+
+ @staticmethod
def write(value, buf):
+ {%- if rec.has_fields() %}
{%- for field in rec.fields() %}
{{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
{%- endfor %}
+ {%- else %}
+ pass
+ {%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py
index daabd5b4b9..4db74fb157 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py
@@ -1,28 +1,16 @@
# Types conforming to `_UniffiConverterPrimitive` pass themselves directly over the FFI.
class _UniffiConverterPrimitive:
@classmethod
- def check(cls, value):
- return value
-
- @classmethod
def lift(cls, value):
return value
@classmethod
def lower(cls, value):
- return cls.lowerUnchecked(cls.check(value))
-
- @classmethod
- def lowerUnchecked(cls, value):
return value
- @classmethod
- def write(cls, value, buf):
- cls.write_unchecked(cls.check(value), buf)
-
class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive):
@classmethod
- def check(cls, value):
+ def check_lower(cls, value):
try:
value = value.__index__()
except Exception:
@@ -31,18 +19,16 @@ class _UniffiConverterPrimitiveInt(_UniffiConverterPrimitive):
raise TypeError("__index__ returned non-int (type {})".format(type(value).__name__))
if not cls.VALUE_MIN <= value < cls.VALUE_MAX:
raise ValueError("{} requires {} <= value < {}".format(cls.CLASS_NAME, cls.VALUE_MIN, cls.VALUE_MAX))
- return super().check(value)
class _UniffiConverterPrimitiveFloat(_UniffiConverterPrimitive):
@classmethod
- def check(cls, value):
+ def check_lower(cls, value):
try:
value = value.__float__()
except Exception:
raise TypeError("must be real number, not {}".format(type(value).__name__))
if not isinstance(value, float):
raise TypeError("__float__ returned non-float (type {})".format(type(value).__name__))
- return super().check(value)
# Helper class for wrapper types that will always go through a _UniffiRustBuffer.
# Classes should inherit from this and implement the `read` and `write` static methods.
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py
index c317a632fc..44e0ba1001 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py
@@ -1,12 +1,16 @@
class _UniffiRustBuffer(ctypes.Structure):
_fields_ = [
- ("capacity", ctypes.c_int32),
- ("len", ctypes.c_int32),
+ ("capacity", ctypes.c_uint64),
+ ("len", ctypes.c_uint64),
("data", ctypes.POINTER(ctypes.c_char)),
]
@staticmethod
+ def default():
+ return _UniffiRustBuffer(0, 0, None)
+
+ @staticmethod
def alloc(size):
return _rust_call(_UniffiLib.{{ ci.ffi_rustbuffer_alloc().name() }}, size)
@@ -137,9 +141,6 @@ class _UniffiRustBufferStream:
def read_double(self):
return self._unpack_from(8, ">d")
- def read_c_size_t(self):
- return self._unpack_from(ctypes.sizeof(ctypes.c_size_t) , "@N")
-
class _UniffiRustBufferBuilder:
"""
Helper for structured writing of bytes into a _UniffiRustBuffer.
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py
index 3c9f5a4596..3c30d9f9f5 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py
@@ -2,6 +2,11 @@
class {{ ffi_converter_name}}(_UniffiConverterRustBuffer):
@classmethod
+ def check_lower(cls, value):
+ for item in value:
+ {{ inner_ffi_converter }}.check_lower(item)
+
+ @classmethod
def write(cls, value, buf):
items = len(value)
buf.write_i32(items)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py
index 40890b6abc..d574edd09f 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py
@@ -1,6 +1,6 @@
class _UniffiConverterString:
@staticmethod
- def check(value):
+ def check_lower(value):
if not isinstance(value, str):
raise TypeError("argument must be str, not {}".format(type(value).__name__))
return value
@@ -15,7 +15,6 @@ class _UniffiConverterString:
@staticmethod
def write(value, buf):
- value = _UniffiConverterString.check(value)
utf8_bytes = value.encode("utf-8")
buf.write_i32(len(utf8_bytes))
buf.write(utf8_bytes)
@@ -27,7 +26,6 @@ class _UniffiConverterString:
@staticmethod
def lower(value):
- value = _UniffiConverterString.check(value)
with _UniffiRustBuffer.alloc_with_builder() as builder:
builder.write(value.encode("utf-8"))
return builder.finalize()
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py
index 8402f6095d..76d7f8bcdb 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py
@@ -18,6 +18,10 @@ class _UniffiConverterTimestamp(_UniffiConverterRustBuffer):
return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) - datetime.timedelta(seconds=-seconds, microseconds=microseconds)
@staticmethod
+ def check_lower(value):
+ pass
+
+ @staticmethod
def write(value, buf):
if value >= datetime.datetime.fromtimestamp(0, datetime.timezone.utc):
sign = 1
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py
index f258b60a1c..230b9e853f 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py
@@ -1,7 +1,15 @@
{%- if func.is_async() %}
-def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
- return _uniffi_rust_call_async(
+{%- match func.return_type() -%}
+{%- when Some with (return_type) %}
+async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}":
+{% when None %}
+async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> None:
+{% endmatch %}
+
+ {%- call py::docstring(func, 4) %}
+ {%- call py::setup_args(func) %}
+ return await _uniffi_rust_call_async(
_UniffiLib.{{ func.ffi_func().name() }}({% call py::arg_list_lowered(func) %}),
_UniffiLib.{{func.ffi_rust_future_poll(ci) }},
_UniffiLib.{{func.ffi_rust_future_complete(ci) }},
@@ -13,13 +21,7 @@ def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
{%- when None %}
lambda val: None,
{% endmatch %}
- # Error FFI converter
- {%- match func.throws_type() %}
- {%- when Some(e) %}
- {{ e|ffi_converter_name }},
- {%- when None %}
- None,
- {%- endmatch %}
+ {% call py::error_ffi_converter(func) %}
)
{%- else %}
@@ -27,11 +29,13 @@ def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
{%- when Some with (return_type) %}
def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> "{{ return_type|type_name }}":
+ {%- call py::docstring(func, 4) %}
{%- call py::setup_args(func) %}
return {{ return_type|lift_fn }}({% call py::to_ffi_call(func) %})
{% when None %}
-def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
+def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}) -> None:
+ {%- call py::docstring(func, 4) %}
{%- call py::setup_args(func) %}
{% call py::to_ffi_call(func) %}
{% endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py
index 5e05314c37..4aaed253e0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py
@@ -85,7 +85,7 @@
{%- when Type::Map { key_type, value_type } %}
{%- include "MapTemplate.py" %}
-{%- when Type::CallbackInterface { name: id, module_path } %}
+{%- when Type::CallbackInterface { name, module_path } %}
{%- include "CallbackInterfaceTemplate.py" %}
{%- when Type::Custom { name, module_path, builtin } %}
@@ -94,9 +94,6 @@
{%- when Type::External { name, module_path, namespace, kind, tagged } %}
{%- include "ExternalTemplate.py" %}
-{%- when Type::ForeignExecutor %}
-{%- include "ForeignExecutorTemplate.py" %}
-
{%- else %}
{%- endmatch %}
{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py
index 081c6731ce..039bf76162 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterUInt16(_UniffiConverterPrimitiveInt):
return buf.read_u16()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_u16(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py
index b80e75177d..1650bf9b60 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterUInt32(_UniffiConverterPrimitiveInt):
return buf.read_u32()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_u32(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py
index 4b87e58547..f354545e26 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterUInt64(_UniffiConverterPrimitiveInt):
return buf.read_u64()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_u64(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py
index 33026706f2..cee130b4d9 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py
@@ -8,5 +8,5 @@ class _UniffiConverterUInt8(_UniffiConverterPrimitiveInt):
return buf.read_u8()
@staticmethod
- def write_unchecked(value, buf):
+ def write(value, buf):
buf.write_u8(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py
index ef3b1bb94d..6818a8c107 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py
@@ -5,27 +5,29 @@
#}
{%- macro to_ffi_call(func) -%}
- {%- match func.throws_type() -%}
- {%- when Some with (e) -%}
-_rust_call_with_error({{ e|ffi_converter_name }},
- {%- else -%}
-_rust_call(
- {%- endmatch -%}
- _UniffiLib.{{ func.ffi_func().name() }},
- {%- call arg_list_lowered(func) -%}
-)
+{%- call _to_ffi_call_with_prefix_arg("", func) %}
{%- endmacro -%}
{%- macro to_ffi_call_with_prefix(prefix, func) -%}
- {%- match func.throws_type() -%}
- {%- when Some with (e) -%}
-_rust_call_with_error(
- {{ e|ffi_converter_name }},
- {%- else -%}
+{%- call _to_ffi_call_with_prefix_arg(format!("{},", prefix), func) %}
+{%- endmacro -%}
+
+{%- macro _to_ffi_call_with_prefix_arg(prefix, func) -%}
+{%- match func.throws_type() -%}
+{%- when Some with (e) -%}
+{%- match e -%}
+{%- when Type::Enum { name, module_path } -%}
+_rust_call_with_error({{ e|ffi_converter_name }},
+{%- when Type::Object { name, module_path, imp } -%}
+_rust_call_with_error({{ e|ffi_converter_name }}__as_error,
+{%- else %}
+# unsupported error type!
+{%- endmatch %}
+{%- else -%}
_rust_call(
- {%- endmatch -%}
+{%- endmatch -%}
_UniffiLib.{{ func.ffi_func().name() }},
- {{- prefix }},
+ {{- prefix }}
{%- call arg_list_lowered(func) -%}
)
{%- endmacro -%}
@@ -37,6 +39,19 @@ _rust_call(
{%- endfor %}
{%- endmacro -%}
+{%- macro docstring_value(maybe_docstring, indent_spaces) %}
+{%- match maybe_docstring %}
+{%- when Some(docstring) %}
+{{ docstring|docstring(indent_spaces) }}
+{{ "" }}
+{%- else %}
+{%- endmatch %}
+{%- endmacro %}
+
+{%- macro docstring(defn, indent_spaces) %}
+{%- call docstring_value(defn.docstring(), indent_spaces) %}
+{%- endmacro %}
+
{#-
// Arglist as used in Python declarations of methods, functions and constructors.
// Note the var_name and type_name filters.
@@ -76,6 +91,7 @@ _rust_call(
if {{ arg.name()|var_name }} is _DEFAULT:
{{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }}
{%- endmatch %}
+ {{ arg|check_lower_fn }}({{ arg.name()|var_name }})
{% endfor -%}
{%- endmacro -%}
@@ -91,6 +107,7 @@ _rust_call(
if {{ arg.name()|var_name }} is _DEFAULT:
{{ arg.name()|var_name }} = {{ literal|literal_py(arg.as_type().borrow()) }}
{%- endmatch %}
+ {{ arg|check_lower_fn }}({{ arg.name()|var_name }})
{% endfor -%}
{%- endmacro -%}
@@ -100,11 +117,18 @@ _rust_call(
{%- macro method_decl(py_method_name, meth) %}
{% if meth.is_async() %}
- def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}):
+{%- match meth.return_type() %}
+{%- when Some with (return_type) %}
+ async def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}":
+{%- when None %}
+ async def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> None:
+{% endmatch %}
+
+ {%- call docstring(meth, 8) %}
{%- call setup_args_extra_indent(meth) %}
- return _uniffi_rust_call_async(
+ return await _uniffi_rust_call_async(
_UniffiLib.{{ meth.ffi_func().name() }}(
- self._pointer, {% call arg_list_lowered(meth) %}
+ self._uniffi_clone_pointer(), {% call arg_list_lowered(meth) %}
),
_UniffiLib.{{ meth.ffi_rust_future_poll(ci) }},
_UniffiLib.{{ meth.ffi_rust_future_complete(ci) }},
@@ -116,13 +140,7 @@ _rust_call(
{%- when None %}
lambda val: None,
{% endmatch %}
- # Error FFI converter
- {%- match meth.throws_type() %}
- {%- when Some(e) %}
- {{ e|ffi_converter_name }},
- {%- when None %}
- None,
- {%- endmatch %}
+ {% call error_ffi_converter(meth) %}
)
{%- else -%}
@@ -131,17 +149,36 @@ _rust_call(
{%- when Some with (return_type) %}
def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> "{{ return_type|type_name }}":
+ {%- call docstring(meth, 8) %}
{%- call setup_args_extra_indent(meth) %}
return {{ return_type|lift_fn }}(
- {% call to_ffi_call_with_prefix("self._pointer", meth) %}
+ {% call to_ffi_call_with_prefix("self._uniffi_clone_pointer()", meth) %}
)
{%- when None %}
- def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}):
+ def {{ py_method_name }}(self, {% call arg_list_decl(meth) %}) -> None:
+ {%- call docstring(meth, 8) %}
{%- call setup_args_extra_indent(meth) %}
- {% call to_ffi_call_with_prefix("self._pointer", meth) %}
+ {% call to_ffi_call_with_prefix("self._uniffi_clone_pointer()", meth) %}
{% endmatch %}
{% endif %}
{% endmacro %}
+
+{%- macro error_ffi_converter(func) %}
+ # Error FFI converter
+{% match func.throws_type() %}
+{%- when Some(e) %}
+{%- match e -%}
+{%- when Type::Enum { name, module_path } -%}
+ {{ e|ffi_converter_name }},
+{%- when Type::Object { name, module_path, imp } -%}
+ {{ e|ffi_converter_name }}__as_error,
+{%- else %}
+ # unsupported error type!
+{%- endmatch %}
+{%- when None %}
+ None,
+{%- endmatch %}
+{% endmacro %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py
index 24c3290ff7..1ccd6821c0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py
@@ -1,3 +1,5 @@
+{%- call py::docstring_value(ci.namespace_docstring(), 0) %}
+
# This file was autogenerated by some hot garbage in the `uniffi` crate.
# Trust me, you don't want to mess with it!
@@ -13,6 +15,7 @@
# compile the rust component. The easiest way to ensure this is to bundle the Python
# helpers directly inline like we're doing here.
+from __future__ import annotations
import os
import sys
import ctypes
@@ -20,6 +23,9 @@ import enum
import struct
import contextlib
import datetime
+import threading
+import itertools
+import traceback
import typing
{%- if ci.has_async_fns() %}
import asyncio
@@ -34,20 +40,20 @@ _DEFAULT = object()
{% include "RustBufferTemplate.py" %}
{% include "Helpers.py" %}
-{% include "PointerManager.py" %}
+{% include "HandleMap.py" %}
{% include "RustBufferHelper.py" %}
# Contains loading, initialization code, and the FFI Function declarations.
{% include "NamespaceLibraryTemplate.py" %}
+# Public interface members begin here.
+{{ type_helper_code }}
+
# Async support
{%- if ci.has_async_fns() %}
{%- include "Async.py" %}
{%- endif %}
-# Public interface members begin here.
-{{ type_helper_code }}
-
{%- for func in ci.function_definitions() %}
{%- include "TopLevelFunctionTemplate.py" %}
{%- endfor %}
@@ -69,6 +75,9 @@ __all__ = [
{%- for c in ci.callback_interface_definitions() %}
"{{ c.name()|class_name }}",
{%- endfor %}
+ {%- if ci.has_async_fns() %}
+ "uniffi_set_event_loop",
+ {%- endif %}
]
{% import "macros.py" as py %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs
index 0fcf09996f..0c23140b33 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs
@@ -2,10 +2,8 @@
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/. */
-use crate::{
- bindings::{RunScriptOptions, TargetLanguage},
- library_mode::generate_bindings,
-};
+use crate::bindings::TargetLanguage;
+use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault};
use anyhow::{Context, Result};
use camino::Utf8Path;
use std::env;
@@ -34,14 +32,18 @@ pub fn run_script(
args: Vec<String>,
_options: &RunScriptOptions,
) -> Result<()> {
- let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let script_path = Utf8Path::new(script_file).canonicalize_utf8()?;
let test_helper = UniFFITestHelper::new(crate_name)?;
let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?;
let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?;
generate_bindings(
&cdylib_path,
None,
- &[TargetLanguage::Python],
+ &BindingGeneratorDefault {
+ target_languages: vec![TargetLanguage::Python],
+ try_format_code: false,
+ },
+ None,
&out_dir,
false,
)?;
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs
index 1f1bf8e299..04841b459c 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs
@@ -2,15 +2,39 @@
* 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/. */
-use anyhow::Result;
+use anyhow::{bail, Result};
use askama::Template;
+use camino::Utf8Path;
use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::collections::HashMap;
+use crate::bindings::ruby;
use crate::interface::*;
-use crate::BindingsConfig;
+use crate::{BindingGenerator, BindingsConfig};
+
+pub struct RubyBindingGenerator;
+impl BindingGenerator for RubyBindingGenerator {
+ type Config = Config;
+
+ fn write_bindings(
+ &self,
+ ci: &ComponentInterface,
+ config: &Config,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+ ) -> Result<()> {
+ ruby::write_bindings(config, ci, out_dir, try_format_code)
+ }
+
+ fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> {
+ if cdylib_name.is_none() {
+ bail!("Generate bindings for Ruby requires a cdylib, but {library_path} was given");
+ }
+ Ok(())
+ }
+}
const RESERVED_WORDS: &[&str] = &[
"alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else",
@@ -57,7 +81,6 @@ pub fn canonical_name(t: &Type) -> String {
Type::CallbackInterface { name, .. } => format!("CallbackInterface{name}"),
Type::Timestamp => "Timestamp".into(),
Type::Duration => "Duration".into(),
- Type::ForeignExecutor => "ForeignExecutor".into(),
// Recursive types.
// These add a prefix to the name of the underlying type.
// The component API definition cannot give names to recursive types, so as long as the
@@ -150,20 +173,20 @@ mod filters {
FfiType::UInt64 => ":uint64".to_string(),
FfiType::Float32 => ":float".to_string(),
FfiType::Float64 => ":double".to_string(),
+ FfiType::Handle => ":uint64".to_string(),
FfiType::RustArcPtr(_) => ":pointer".to_string(),
FfiType::RustBuffer(_) => "RustBuffer.by_value".to_string(),
+ FfiType::RustCallStatus => "RustCallStatus".to_string(),
FfiType::ForeignBytes => "ForeignBytes".to_string(),
- FfiType::ForeignCallback => unimplemented!("Callback interfaces are not implemented"),
- FfiType::ForeignExecutorCallback => {
- unimplemented!("Foreign executors are not implemented")
- }
- FfiType::ForeignExecutorHandle => {
- unimplemented!("Foreign executors are not implemented")
- }
- FfiType::RustFutureHandle
- | FfiType::RustFutureContinuationCallback
- | FfiType::RustFutureContinuationData => {
- unimplemented!("Async functions are not implemented")
+ FfiType::Callback(_) => unimplemented!("FFI Callbacks not implemented"),
+ // Note: this can't just be `unimplemented!()` because some of the FFI function
+ // definitions use references. Those FFI functions aren't actually used, so we just
+ // pick something that runs and makes some sense. Revisit this once the references
+ // are actually implemented.
+ FfiType::Reference(_) => ":pointer".to_string(),
+ FfiType::VoidPointer => ":pointer".to_string(),
+ FfiType::Struct(_) => {
+ unimplemented!("Structs are not implemented")
}
})
}
@@ -179,7 +202,8 @@ mod filters {
}
// use the double-quote form to match with the other languages, and quote escapes.
Literal::String(s) => format!("\"{s}\""),
- Literal::Null => "nil".into(),
+ Literal::None => "nil".into(),
+ Literal::Some { inner } => literal_rb(inner)?,
Literal::EmptySequence => "[]".into(),
Literal::EmptyMap => "{}".into(),
Literal::Enum(v, type_) => match type_ {
@@ -264,7 +288,24 @@ mod filters {
}
Type::External { .. } => panic!("No support for external types, yet"),
Type::Custom { .. } => panic!("No support for custom types, yet"),
- Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"),
+ })
+ }
+
+ pub fn check_lower_rb(nm: &str, type_: &Type) -> Result<String, askama::Error> {
+ Ok(match type_ {
+ Type::Object { name, .. } => {
+ format!("({}.uniffi_check_lower {nm})", class_name_rb(name)?)
+ }
+ Type::Enum { .. }
+ | Type::Record { .. }
+ | Type::Optional { .. }
+ | Type::Sequence { .. }
+ | Type::Map { .. } => format!(
+ "RustBuffer.check_lower_{}({})",
+ class_name_rb(&canonical_name(type_))?,
+ nm
+ ),
+ _ => "".to_owned(),
})
}
@@ -283,7 +324,7 @@ mod filters {
Type::Boolean => format!("({nm} ? 1 : 0)"),
Type::String => format!("RustBuffer.allocFromString({nm})"),
Type::Bytes => format!("RustBuffer.allocFromBytes({nm})"),
- Type::Object { name, .. } => format!("({}._uniffi_lower {nm})", class_name_rb(name)?),
+ Type::Object { name, .. } => format!("({}.uniffi_lower {nm})", class_name_rb(name)?),
Type::CallbackInterface { .. } => {
panic!("No support for lowering callback interfaces yet")
}
@@ -300,7 +341,6 @@ mod filters {
),
Type::External { .. } => panic!("No support for lowering external types, yet"),
Type::Custom { .. } => panic!("No support for lowering custom types, yet"),
- Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"),
})
}
@@ -318,7 +358,7 @@ mod filters {
Type::Boolean => format!("1 == {nm}"),
Type::String => format!("{nm}.consumeIntoString"),
Type::Bytes => format!("{nm}.consumeIntoBytes"),
- Type::Object { name, .. } => format!("{}._uniffi_allocate({nm})", class_name_rb(name)?),
+ Type::Object { name, .. } => format!("{}.uniffi_allocate({nm})", class_name_rb(name)?),
Type::CallbackInterface { .. } => {
panic!("No support for lifting callback interfaces, yet")
}
@@ -341,7 +381,6 @@ mod filters {
),
Type::External { .. } => panic!("No support for lifting external types, yet"),
Type::Custom { .. } => panic!("No support for lifting custom types, yet"),
- Type::ForeignExecutor => unimplemented!("Foreign executors are not implemented"),
})
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb
index 677c5c729b..ba2caf7380 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/ObjectTemplate.rb
@@ -2,18 +2,18 @@ class {{ obj.name()|class_name_rb }}
# A private helper for initializing instances of the class from a raw pointer,
# bypassing any initialization logic and ensuring they are GC'd properly.
- def self._uniffi_allocate(pointer)
+ def self.uniffi_allocate(pointer)
pointer.autorelease = false
inst = allocate
inst.instance_variable_set :@pointer, pointer
- ObjectSpace.define_finalizer(inst, _uniffi_define_finalizer_by_pointer(pointer, inst.object_id))
+ ObjectSpace.define_finalizer(inst, uniffi_define_finalizer_by_pointer(pointer, inst.object_id))
return inst
end
# A private helper for registering an object finalizer.
# N.B. it's important that this does not capture a reference
# to the actual instance, only its underlying pointer.
- def self._uniffi_define_finalizer_by_pointer(pointer, object_id)
+ def self.uniffi_define_finalizer_by_pointer(pointer, object_id)
Proc.new do |_id|
{{ ci.namespace()|class_name_rb }}.rust_call(
:{{ obj.ffi_object_free().name() }},
@@ -25,31 +25,41 @@ class {{ obj.name()|class_name_rb }}
# A private helper for lowering instances into a raw pointer.
# This does an explicit typecheck, because accidentally lowering a different type of
# object in a place where this type is expected, could lead to memory unsafety.
- def self._uniffi_lower(inst)
+ def self.uniffi_check_lower(inst)
if not inst.is_a? self
raise TypeError.new "Expected a {{ obj.name()|class_name_rb }} instance, got #{inst}"
end
- return inst.instance_variable_get :@pointer
+ end
+
+ def uniffi_clone_pointer()
+ return {{ ci.namespace()|class_name_rb }}.rust_call(
+ :{{ obj.ffi_object_clone().name() }},
+ @pointer
+ )
+ end
+
+ def self.uniffi_lower(inst)
+ return inst.uniffi_clone_pointer()
end
{%- match obj.primary_constructor() %}
{%- when Some with (cons) %}
def initialize({% call rb::arg_list_decl(cons) -%})
- {%- call rb::coerce_args_extra_indent(cons) %}
+ {%- call rb::setup_args_extra_indent(cons) %}
pointer = {% call rb::to_ffi_call(cons) %}
@pointer = pointer
- ObjectSpace.define_finalizer(self, self.class._uniffi_define_finalizer_by_pointer(pointer, self.object_id))
+ ObjectSpace.define_finalizer(self, self.class.uniffi_define_finalizer_by_pointer(pointer, self.object_id))
end
{%- when None %}
{%- endmatch %}
{% for cons in obj.alternate_constructors() -%}
def self.{{ cons.name()|fn_name_rb }}({% call rb::arg_list_decl(cons) %})
- {%- call rb::coerce_args_extra_indent(cons) %}
+ {%- call rb::setup_args_extra_indent(cons) %}
# Call the (fallible) function before creating any half-baked object instances.
# Lightly yucky way to bypass the usual "initialize" logic
# and just create a new instance with the required pointer.
- return _uniffi_allocate({% call rb::to_ffi_call(cons) %})
+ return uniffi_allocate({% call rb::to_ffi_call(cons) %})
end
{% endfor %}
@@ -58,15 +68,15 @@ class {{ obj.name()|class_name_rb }}
{%- when Some with (return_type) -%}
def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %})
- {%- call rb::coerce_args_extra_indent(meth) %}
- result = {% call rb::to_ffi_call_with_prefix("@pointer", meth) %}
+ {%- call rb::setup_args_extra_indent(meth) %}
+ result = {% call rb::to_ffi_call_with_prefix("uniffi_clone_pointer()", meth) %}
return {{ "result"|lift_rb(return_type) }}
end
{%- when None -%}
def {{ meth.name()|fn_name_rb }}({% call rb::arg_list_decl(meth) %})
- {%- call rb::coerce_args_extra_indent(meth) %}
- {% call rb::to_ffi_call_with_prefix("@pointer", meth) %}
+ {%- call rb::setup_args_extra_indent(meth) %}
+ {% call rb::to_ffi_call_with_prefix("uniffi_clone_pointer()", meth) %}
end
{% endmatch %}
{% endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb
index c940b31060..b5a201b248 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RecordTemplate.rb
@@ -2,7 +2,12 @@
class {{ rec.name()|class_name_rb }}
attr_reader {% for field in rec.fields() %}:{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{%- endfor %}
- def initialize({% for field in rec.fields() %}{{ field.name()|var_name_rb }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
+ def initialize({% for field in rec.fields() %}{{ field.name()|var_name_rb -}}:
+ {%- match field.default_value() %}
+ {%- when Some with(literal) %} {{ literal|literal_rb }}
+ {%- else %}
+ {%- endmatch %}
+ {%- if loop.last %}{% else %}, {% endif -%}{% endfor %})
{%- for field in rec.fields() %}
@{{ field.name()|var_name_rb }} = {{ field.name()|var_name_rb }}
{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb
index 8749139116..d15c0bbe76 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb
@@ -163,7 +163,7 @@ class RustBufferBuilder
# The Object type {{ object_name }}.
def write_{{ canonical_type_name }}(obj)
- pointer = {{ object_name|class_name_rb}}._uniffi_lower obj
+ pointer = {{ object_name|class_name_rb}}.uniffi_lower obj
pack_into(8, 'Q>', pointer.address)
end
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb
index b085dddf15..f9b0806abc 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferStream.rb
@@ -155,7 +155,7 @@ class RustBufferStream
def read{{ canonical_type_name }}
pointer = FFI::Pointer.new unpack_from 8, 'Q>'
- return {{ object_name|class_name_rb }}._uniffi_allocate(pointer)
+ return {{ object_name|class_name_rb }}.uniffi_allocate(pointer)
end
{% when Type::Enum { name, module_path } -%}
@@ -237,7 +237,7 @@ class RustBufferStream
def read{{ canonical_type_name }}
{{ rec.name()|class_name_rb }}.new(
{%- for field in rec.fields() %}
- read{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}{% if loop.last %}{% else %},{% endif %}
+ {{ field.name()|var_name_rb }}: read{{ canonical_name(field.as_type().borrow()).borrow()|class_name_rb }}{% if loop.last %}{% else %},{% endif %}
{%- endfor %}
)
end
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb
index 0194c9666d..452d9831cd 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/RustBufferTemplate.rb
@@ -1,6 +1,6 @@
class RustBuffer < FFI::Struct
- layout :capacity, :int32,
- :len, :int32,
+ layout :capacity, :uint64,
+ :len, :uint64,
:data, :pointer
def self.alloc(size)
@@ -128,6 +128,12 @@ class RustBuffer < FFI::Struct
{%- let rec = ci|get_record_definition(record_name) -%}
# The Record type {{ record_name }}.
+ def self.check_lower_{{ canonical_type_name }}(v)
+ {%- for field in rec.fields() %}
+ {{ "v.{}"|format(field.name()|var_name_rb)|check_lower_rb(field.as_type().borrow()) }}
+ {%- endfor %}
+ end
+
def self.alloc_from_{{ canonical_type_name }}(v)
RustBuffer.allocWithBuilder do |builder|
builder.write_{{ canonical_type_name }}(v)
@@ -146,6 +152,19 @@ class RustBuffer < FFI::Struct
{%- let e = ci|get_enum_definition(enum_name) -%}
# The Enum type {{ enum_name }}.
+ def self.check_lower_{{ canonical_type_name }}(v)
+ {%- if !e.is_flat() %}
+ {%- for variant in e.variants() %}
+ if v.{{ variant.name()|var_name_rb }}?
+ {%- for field in variant.fields() %}
+ {{ "v.{}"|format(field.name())|check_lower_rb(field.as_type().borrow()) }}
+ {%- endfor %}
+ return
+ end
+ {%- endfor %}
+ {%- endif %}
+ end
+
def self.alloc_from_{{ canonical_type_name }}(v)
RustBuffer.allocWithBuilder do |builder|
builder.write_{{ canonical_type_name }}(v)
@@ -163,6 +182,12 @@ class RustBuffer < FFI::Struct
{% when Type::Optional { inner_type } -%}
# The Optional<T> type for {{ canonical_name(inner_type) }}.
+ def self.check_lower_{{ canonical_type_name }}(v)
+ if not v.nil?
+ {{ "v"|check_lower_rb(inner_type.borrow()) }}
+ end
+ end
+
def self.alloc_from_{{ canonical_type_name }}(v)
RustBuffer.allocWithBuilder do |builder|
builder.write_{{ canonical_type_name }}(v)
@@ -179,6 +204,12 @@ class RustBuffer < FFI::Struct
{% when Type::Sequence { inner_type } -%}
# The Sequence<T> type for {{ canonical_name(inner_type) }}.
+ def self.check_lower_{{ canonical_type_name }}(v)
+ v.each do |item|
+ {{ "item"|check_lower_rb(inner_type.borrow()) }}
+ end
+ end
+
def self.alloc_from_{{ canonical_type_name }}(v)
RustBuffer.allocWithBuilder do |builder|
builder.write_{{ canonical_type_name }}(v)
@@ -195,6 +226,13 @@ class RustBuffer < FFI::Struct
{% when Type::Map { key_type: k, value_type: inner_type } -%}
# The Map<T> type for {{ canonical_name(inner_type) }}.
+ def self.check_lower_{{ canonical_type_name }}(v)
+ v.each do |k, v|
+ {{ "k"|check_lower_rb(k.borrow()) }}
+ {{ "v"|check_lower_rb(inner_type.borrow()) }}
+ end
+ end
+
def self.alloc_from_{{ canonical_type_name }}(v)
RustBuffer.allocWithBuilder do |builder|
builder.write_{{ canonical_type_name }}(v)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb
index 13214cf31b..b6dce0effa 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/TopLevelFunctionTemplate.rb
@@ -2,7 +2,7 @@
{%- when Some with (return_type) %}
def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%})
- {%- call rb::coerce_args(func) %}
+ {%- call rb::setup_args(func) %}
result = {% call rb::to_ffi_call(func) %}
return {{ "result"|lift_rb(return_type) }}
end
@@ -10,7 +10,7 @@ end
{% when None %}
def self.{{ func.name()|fn_name_rb }}({%- call rb::arg_list_decl(func) -%})
- {%- call rb::coerce_args(func) %}
+ {%- call rb::setup_args(func) %}
{% call rb::to_ffi_call(func) %}
end
{% endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb
index 8dc3e5e613..59fa4ef4cc 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/templates/macros.rb
@@ -60,14 +60,16 @@
[{%- for arg in func.arguments() -%}{{ arg.type_().borrow()|type_ffi }}, {% endfor -%} RustCallStatus.by_ref]
{%- endmacro -%}
-{%- macro coerce_args(func) %}
+{%- macro setup_args(func) %}
{%- for arg in func.arguments() %}
- {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) -}}
+ {{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) }}
+ {{ arg.name()|check_lower_rb(arg.as_type().borrow()) }}
{% endfor -%}
{%- endmacro -%}
-{%- macro coerce_args_extra_indent(func) %}
- {%- for arg in func.arguments() %}
+{%- macro setup_args_extra_indent(meth) %}
+ {%- for arg in meth.arguments() %}
{{ arg.name() }} = {{ arg.name()|coerce_rb(ci.namespace()|class_name_rb, arg.as_type().borrow()) }}
+ {{ arg.name()|check_lower_rb(arg.as_type().borrow()) }}
{%- endfor %}
{%- endmacro -%}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs
index 03da37d567..88f770b9dc 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/ruby/test.rs
@@ -4,6 +4,7 @@ License, v. 2.0. If a copy of the MPL was not distributed with this
use crate::bindings::TargetLanguage;
use crate::library_mode::generate_bindings;
+use crate::BindingGeneratorDefault;
use anyhow::{bail, Context, Result};
use camino::Utf8Path;
use std::env;
@@ -30,11 +31,21 @@ pub fn test_script_command(
fixture_name: &str,
script_file: &str,
) -> Result<Command> {
- let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let script_path = Utf8Path::new(script_file).canonicalize_utf8()?;
let test_helper = UniFFITestHelper::new(fixture_name)?;
let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?;
let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?;
- generate_bindings(&cdylib_path, None, &[TargetLanguage::Ruby], &out_dir, false)?;
+ generate_bindings(
+ &cdylib_path,
+ None,
+ &BindingGeneratorDefault {
+ target_languages: vec![TargetLanguage::Ruby],
+ try_format_code: false,
+ },
+ None,
+ &out_dir,
+ false,
+ )?;
let rubypath = env::var_os("RUBYLIB").unwrap_or_else(|| OsString::from(""));
let rubypath = env::join_paths(
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs
index 5d8b37e0af..dab89e0259 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/callback_interface.rs
@@ -6,21 +6,25 @@ use super::CodeType;
#[derive(Debug)]
pub struct CallbackInterfaceCodeType {
- id: String,
+ name: String,
}
impl CallbackInterfaceCodeType {
- pub fn new(id: String) -> Self {
- Self { id }
+ pub fn new(name: String) -> Self {
+ Self { name }
}
}
impl CodeType for CallbackInterfaceCodeType {
fn type_label(&self) -> String {
- super::SwiftCodeOracle.class_name(&self.id)
+ super::SwiftCodeOracle.class_name(&self.name)
}
fn canonical_name(&self) -> String {
format!("CallbackInterface{}", self.type_label())
}
+
+ fn initialization_fn(&self) -> Option<String> {
+ Some(format!("uniffiCallbackInit{}", self.name))
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs
index 8e6dddf3f9..d89fdfd386 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/compounds.rs
@@ -30,8 +30,9 @@ impl CodeType for OptionalCodeType {
fn literal(&self, literal: &Literal) -> String {
match literal {
- Literal::Null => "nil".into(),
- _ => super::SwiftCodeOracle.find(&self.inner).literal(literal),
+ Literal::None => "nil".into(),
+ Literal::Some { inner } => super::SwiftCodeOracle.find(&self.inner).literal(inner),
+ _ => panic!("Invalid literal for Optional type: {literal:?}"),
}
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs
deleted file mode 100644
index b488b004cf..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/executor.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-/* 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/. */
-
-use super::CodeType;
-
-#[derive(Debug)]
-pub struct ForeignExecutorCodeType;
-
-impl CodeType for ForeignExecutorCodeType {
- fn type_label(&self) -> String {
- // On Swift, we define a struct to represent a ForeignExecutor
- "UniFfiForeignExecutor".into()
- }
-
- fn canonical_name(&self) -> String {
- "ForeignExecutor".into()
- }
-
- fn initialization_fn(&self) -> Option<String> {
- Some("uniffiInitForeignExecutor".into())
- }
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs
index 0b6728ba84..3960b7aae1 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/external.rs
@@ -17,7 +17,7 @@ impl ExternalCodeType {
impl CodeType for ExternalCodeType {
fn type_label(&self) -> String {
- self.name.clone()
+ super::SwiftCodeOracle.class_name(&self.name)
}
fn canonical_name(&self) -> String {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
index 12db4afc66..16c1625123 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/mod.rs
@@ -10,25 +10,49 @@ use std::fmt::Debug;
use anyhow::{Context, Result};
use askama::Template;
-use heck::{ToLowerCamelCase, ToUpperCamelCase};
+use camino::Utf8Path;
+use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToUpperCamelCase};
use serde::{Deserialize, Serialize};
use super::Bindings;
use crate::backend::TemplateExpression;
+use crate::bindings::swift;
use crate::interface::*;
-use crate::BindingsConfig;
+use crate::{BindingGenerator, BindingsConfig};
mod callback_interface;
mod compounds;
mod custom;
mod enum_;
-mod executor;
mod external;
mod miscellany;
mod object;
mod primitives;
mod record;
+pub struct SwiftBindingGenerator;
+impl BindingGenerator for SwiftBindingGenerator {
+ type Config = Config;
+
+ fn write_bindings(
+ &self,
+ ci: &ComponentInterface,
+ config: &Config,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+ ) -> Result<()> {
+ swift::write_bindings(config, ci, out_dir, try_format_code)
+ }
+
+ fn check_library_path(
+ &self,
+ _library_path: &Utf8Path,
+ _cdylib_name: Option<&str>,
+ ) -> Result<()> {
+ Ok(())
+ }
+}
+
/// A trait tor the implementation.
trait CodeType: Debug {
/// The language specific label used to reference this type. This will be used in
@@ -196,6 +220,8 @@ pub struct Config {
ffi_module_filename: Option<String>,
generate_module_map: Option<bool>,
omit_argument_labels: Option<bool>,
+ generate_immutable_records: Option<bool>,
+ experimental_sendable_value_types: Option<bool>,
#[serde(default)]
custom_types: HashMap<String, CustomTypeConfig>,
}
@@ -261,6 +287,16 @@ impl Config {
pub fn omit_argument_labels(&self) -> bool {
self.omit_argument_labels.unwrap_or(false)
}
+
+ /// Whether to generate immutable records (`let` instead of `var`)
+ pub fn generate_immutable_records(&self) -> bool {
+ self.generate_immutable_records.unwrap_or(false)
+ }
+
+ /// Whether to mark value types as 'Sendable'
+ pub fn experimental_sendable_value_types(&self) -> bool {
+ self.experimental_sendable_value_types.unwrap_or(false)
+ }
}
impl BindingsConfig for Config {
@@ -400,7 +436,6 @@ pub struct SwiftWrapper<'a> {
ci: &'a ComponentInterface,
type_helper_code: String,
type_imports: BTreeSet<String>,
- has_async_fns: bool,
}
impl<'a> SwiftWrapper<'a> {
pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
@@ -412,7 +447,6 @@ impl<'a> SwiftWrapper<'a> {
ci,
type_helper_code,
type_imports,
- has_async_fns: ci.has_async_fns(),
}
}
@@ -425,10 +459,6 @@ impl<'a> SwiftWrapper<'a> {
.iter_types()
.map(|t| SwiftCodeOracle.find(t))
.filter_map(|ct| ct.initialization_fn())
- .chain(
- self.has_async_fns
- .then(|| "uniffiInitContinuationCallback".into()),
- )
.collect()
}
}
@@ -464,12 +494,11 @@ impl SwiftCodeOracle {
Type::Duration => Box::new(miscellany::DurationCodeType),
Type::Enum { name, .. } => Box::new(enum_::EnumCodeType::new(name)),
- Type::Object { name, .. } => Box::new(object::ObjectCodeType::new(name)),
+ Type::Object { name, imp, .. } => Box::new(object::ObjectCodeType::new(name, imp)),
Type::Record { name, .. } => Box::new(record::RecordCodeType::new(name)),
Type::CallbackInterface { name, .. } => {
Box::new(callback_interface::CallbackInterfaceCodeType::new(name))
}
- Type::ForeignExecutor => Box::new(executor::ForeignExecutorCodeType),
Type::Optional { inner_type } => {
Box::new(compounds::OptionalCodeType::new(*inner_type))
}
@@ -509,7 +538,22 @@ impl SwiftCodeOracle {
nm.to_string().to_lower_camel_case()
}
- fn ffi_type_label_raw(&self, ffi_type: &FfiType) -> String {
+ /// Get the idiomatic Swift rendering of an FFI callback function name
+ fn ffi_callback_name(&self, nm: &str) -> String {
+ format!("Uniffi{}", nm.to_upper_camel_case())
+ }
+
+ /// Get the idiomatic Swift rendering of an FFI struct name
+ fn ffi_struct_name(&self, nm: &str) -> String {
+ format!("Uniffi{}", nm.to_upper_camel_case())
+ }
+
+ /// Get the idiomatic Swift rendering of an if guard name
+ fn if_guard_name(&self, nm: &str) -> String {
+ format!("UNIFFI_FFIDEF_{}", nm.to_shouty_snake_case())
+ }
+
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
match ffi_type {
FfiType::Int8 => "Int8".into(),
FfiType::UInt8 => "UInt8".into(),
@@ -521,40 +565,74 @@ impl SwiftCodeOracle {
FfiType::UInt64 => "UInt64".into(),
FfiType::Float32 => "Float".into(),
FfiType::Float64 => "Double".into(),
+ FfiType::Handle => "UInt64".into(),
FfiType::RustArcPtr(_) => "UnsafeMutableRawPointer".into(),
FfiType::RustBuffer(_) => "RustBuffer".into(),
+ FfiType::RustCallStatus => "RustCallStatus".into(),
FfiType::ForeignBytes => "ForeignBytes".into(),
- FfiType::ForeignCallback => "ForeignCallback".into(),
- FfiType::ForeignExecutorHandle => "Int".into(),
- FfiType::ForeignExecutorCallback => "ForeignExecutorCallback".into(),
- FfiType::RustFutureContinuationCallback => "UniFfiRustFutureContinuation".into(),
- FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
- "UnsafeMutableRawPointer".into()
+ // Note: @escaping is required for Swift versions before 5.7 for callbacks passed into
+ // async functions. Swift 5.7 and later does not require it. We should probably remove
+ // it once we upgrade our minimum requirement to 5.7 or later.
+ FfiType::Callback(name) => format!("@escaping {}", self.ffi_callback_name(name)),
+ FfiType::Struct(name) => self.ffi_struct_name(name),
+ FfiType::Reference(inner) => {
+ format!("UnsafeMutablePointer<{}>", self.ffi_type_label(inner))
}
+ FfiType::VoidPointer => "UnsafeMutableRawPointer".into(),
}
}
- fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
- match ffi_type {
- FfiType::ForeignCallback
- | FfiType::ForeignExecutorCallback
- | FfiType::RustFutureHandle
- | FfiType::RustFutureContinuationCallback
- | FfiType::RustFutureContinuationData => {
- format!("{} _Nonnull", self.ffi_type_label_raw(ffi_type))
- }
- _ => self.ffi_type_label_raw(ffi_type),
+ /// Default values for FFI types
+ ///
+ /// Used to set a default return value when returning an error
+ fn ffi_default_value(&self, return_type: Option<&FfiType>) -> String {
+ match return_type {
+ Some(t) => match t {
+ FfiType::UInt8
+ | FfiType::Int8
+ | FfiType::UInt16
+ | FfiType::Int16
+ | FfiType::UInt32
+ | FfiType::Int32
+ | FfiType::UInt64
+ | FfiType::Int64 => "0".to_owned(),
+ FfiType::Float32 | FfiType::Float64 => "0.0".to_owned(),
+ FfiType::RustArcPtr(_) => "nil".to_owned(),
+ FfiType::RustBuffer(_) => "RustBuffer.empty()".to_owned(),
+ _ => unimplemented!("FFI return type: {t:?}"),
+ },
+ // When we need to use a value for void returns, we use a `u8` placeholder
+ None => "0".to_owned(),
}
}
fn ffi_canonical_name(&self, ffi_type: &FfiType) -> String {
- self.ffi_type_label_raw(ffi_type)
+ self.ffi_type_label(ffi_type)
+ }
+
+ /// Get the name of the protocol and class name for an object.
+ ///
+ /// If we support callback interfaces, the protocol name is the object name, and the class name is derived from that.
+ /// Otherwise, the class name is the object name and the protocol name is derived from that.
+ ///
+ /// This split determines what types `FfiConverter.lower()` inputs. If we support callback
+ /// interfaces, `lower` must lower anything that implements the protocol. If not, then lower
+ /// only lowers the concrete class.
+ fn object_names(&self, obj: &Object) -> (String, String) {
+ let class_name = self.class_name(obj.name());
+ if obj.has_callback_interface() {
+ let impl_name = format!("{class_name}Impl");
+ (class_name, impl_name)
+ } else {
+ (format!("{class_name}Protocol"), class_name)
+ }
}
}
pub mod filters {
use super::*;
pub use crate::backend::filters::*;
+ use uniffi_meta::LiteralMetadata;
fn oracle() -> &'static SwiftCodeOracle {
&SwiftCodeOracle
@@ -564,6 +642,13 @@ pub mod filters {
Ok(oracle().find(&as_type.as_type()).type_label())
}
+ pub fn return_type_name(as_type: Option<&impl AsType>) -> Result<String, askama::Error> {
+ Ok(match as_type {
+ Some(as_type) => oracle().find(&as_type.as_type()).type_label(),
+ None => "()".to_owned(),
+ })
+ }
+
pub fn canonical_name(as_type: &impl AsType) -> Result<String, askama::Error> {
Ok(oracle().find(&as_type.as_type()).canonical_name())
}
@@ -572,6 +657,15 @@ pub mod filters {
Ok(oracle().find(&as_type.as_type()).ffi_converter_name())
}
+ pub fn ffi_error_converter_name(as_type: &impl AsType) -> Result<String, askama::Error> {
+ // special handling for types used as errors.
+ let mut name = oracle().find(&as_type.as_type()).ffi_converter_name();
+ if matches!(&as_type.as_type(), Type::Object { .. }) {
+ name.push_str("__as_error")
+ }
+ Ok(name)
+ }
+
pub fn lower_fn(as_type: &impl AsType) -> Result<String, askama::Error> {
Ok(oracle().find(&as_type.as_type()).lower())
}
@@ -595,6 +689,16 @@ pub mod filters {
Ok(oracle().find(&as_type.as_type()).literal(literal))
}
+ // Get the idiomatic Swift rendering of an individual enum variant's discriminant
+ pub fn variant_discr_literal(e: &Enum, index: &usize) -> Result<String, askama::Error> {
+ let literal = e.variant_discr(*index).expect("invalid index");
+ match literal {
+ LiteralMetadata::UInt(v, _, _) => Ok(v.to_string()),
+ LiteralMetadata::Int(v, _, _) => Ok(v.to_string()),
+ _ => unreachable!("expected an UInt!"),
+ }
+ }
+
/// Get the Swift type for an FFIType
pub fn ffi_type_name(ffi_type: &FfiType) -> Result<String, askama::Error> {
Ok(oracle().ffi_type_label(ffi_type))
@@ -604,6 +708,10 @@ pub mod filters {
Ok(oracle().ffi_canonical_name(ffi_type))
}
+ pub fn ffi_default_value(return_type: Option<FfiType>) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_default_value(return_type.as_ref()))
+ }
+
/// Like `ffi_type_name`, but used in `BridgingHeaderTemplate.h` which uses a slightly different
/// names.
pub fn header_ffi_type_name(ffi_type: &FfiType) -> Result<String, askama::Error> {
@@ -618,18 +726,17 @@ pub mod filters {
FfiType::UInt64 => "uint64_t".into(),
FfiType::Float32 => "float".into(),
FfiType::Float64 => "double".into(),
+ FfiType::Handle => "uint64_t".into(),
FfiType::RustArcPtr(_) => "void*_Nonnull".into(),
FfiType::RustBuffer(_) => "RustBuffer".into(),
+ FfiType::RustCallStatus => "RustCallStatus".into(),
FfiType::ForeignBytes => "ForeignBytes".into(),
- FfiType::ForeignCallback => "ForeignCallback _Nonnull".into(),
- FfiType::ForeignExecutorCallback => "UniFfiForeignExecutorCallback _Nonnull".into(),
- FfiType::ForeignExecutorHandle => "size_t".into(),
- FfiType::RustFutureContinuationCallback => {
- "UniFfiRustFutureContinuation _Nonnull".into()
- }
- FfiType::RustFutureHandle | FfiType::RustFutureContinuationData => {
- "void* _Nonnull".into()
+ FfiType::Callback(name) => {
+ format!("{} _Nonnull", SwiftCodeOracle.ffi_callback_name(name))
}
+ FfiType::Struct(name) => SwiftCodeOracle.ffi_struct_name(name),
+ FfiType::Reference(inner) => format!("{}* _Nonnull", header_ffi_type_name(inner)?),
+ FfiType::VoidPointer => "void* _Nonnull".into(),
})
}
@@ -664,6 +771,30 @@ pub mod filters {
Ok(oracle().enum_variant_name(nm))
}
+ /// Get the idiomatic Swift rendering of an FFI callback function name
+ pub fn ffi_callback_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_callback_name(nm))
+ }
+
+ /// Get the idiomatic Swift rendering of an FFI struct name
+ pub fn ffi_struct_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_struct_name(nm))
+ }
+
+ /// Get the idiomatic Swift rendering of an if guard name
+ pub fn if_guard_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().if_guard_name(nm))
+ }
+
+ /// Get the idiomatic Swift rendering of docstring
+ pub fn docstring(docstring: &str, spaces: &i32) -> Result<String, askama::Error> {
+ let middle = textwrap::indent(&textwrap::dedent(docstring), " * ");
+ let wrapped = format!("/**\n{middle}\n */");
+
+ let spaces = usize::try_from(*spaces).unwrap_or_default();
+ Ok(textwrap::indent(&wrapped, &" ".repeat(spaces)))
+ }
+
pub fn error_handler(result: &ResultType) -> Result<String, askama::Error> {
Ok(match &result.throws_type {
Some(t) => format!("{}.lift", ffi_converter_name(t)?),
@@ -685,4 +816,8 @@ pub mod filters {
}
))
}
+
+ pub fn object_names(obj: &Object) -> Result<(String, String), askama::Error> {
+ Ok(SwiftCodeOracle.object_names(obj))
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs
index ea140c998d..d4497a7b19 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/object.rs
@@ -3,24 +3,32 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use super::CodeType;
+use crate::interface::ObjectImpl;
#[derive(Debug)]
pub struct ObjectCodeType {
- id: String,
+ name: String,
+ imp: ObjectImpl,
}
impl ObjectCodeType {
- pub fn new(id: String) -> Self {
- Self { id }
+ pub fn new(name: String, imp: ObjectImpl) -> Self {
+ Self { name, imp }
}
}
impl CodeType for ObjectCodeType {
fn type_label(&self) -> String {
- super::SwiftCodeOracle.class_name(&self.id)
+ super::SwiftCodeOracle.class_name(&self.name)
}
fn canonical_name(&self) -> String {
- format!("Type{}", self.id)
+ format!("Type{}", self.name)
+ }
+
+ fn initialization_fn(&self) -> Option<String> {
+ self.imp
+ .has_callback_interface()
+ .then(|| format!("uniffiCallbackInit{}", self.name))
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs
index 86424658a3..e0c670520e 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/gen_swift/primitives.rs
@@ -9,7 +9,11 @@ use paste::paste;
fn render_literal(literal: &Literal) -> String {
fn typed_number(type_: &Type, num_str: String) -> String {
- match type_ {
+ let unwrapped_type = match type_ {
+ Type::Optional { inner_type } => inner_type,
+ t => t,
+ };
+ match unwrapped_type {
// special case Int32.
Type::Int32 => num_str,
// otherwise use constructor e.g. UInt8(x)
@@ -29,7 +33,7 @@ fn render_literal(literal: &Literal) -> String {
super::SwiftCodeOracle.find(type_).type_label()
)
}
- _ => panic!("Unexpected literal: {num_str} is not a number"),
+ _ => panic!("Unexpected literal: {num_str} for type: {type_:?}"),
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift
index 695208861d..e16f3108e1 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Async.swift
@@ -1,11 +1,13 @@
private let UNIFFI_RUST_FUTURE_POLL_READY: Int8 = 0
private let UNIFFI_RUST_FUTURE_POLL_MAYBE_READY: Int8 = 1
+fileprivate let uniffiContinuationHandleMap = UniffiHandleMap<UnsafeContinuation<Int8, Never>>()
+
fileprivate func uniffiRustCallAsync<F, T>(
- rustFutureFunc: () -> UnsafeMutableRawPointer,
- pollFunc: (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> (),
- completeFunc: (UnsafeMutableRawPointer, UnsafeMutablePointer<RustCallStatus>) -> F,
- freeFunc: (UnsafeMutableRawPointer) -> (),
+ rustFutureFunc: () -> UInt64,
+ pollFunc: (UInt64, @escaping UniffiRustFutureContinuationCallback, UInt64) -> (),
+ completeFunc: (UInt64, UnsafeMutablePointer<RustCallStatus>) -> F,
+ freeFunc: (UInt64) -> (),
liftFunc: (F) throws -> T,
errorHandler: ((RustBuffer) throws -> Error)?
) async throws -> T {
@@ -19,7 +21,11 @@ fileprivate func uniffiRustCallAsync<F, T>(
var pollResult: Int8;
repeat {
pollResult = await withUnsafeContinuation {
- pollFunc(rustFuture, ContinuationHolder($0).toOpaque())
+ pollFunc(
+ rustFuture,
+ uniffiFutureContinuationCallback,
+ uniffiContinuationHandleMap.insert(obj: $0)
+ )
}
} while pollResult != UNIFFI_RUST_FUTURE_POLL_READY
@@ -31,32 +37,80 @@ fileprivate func uniffiRustCallAsync<F, T>(
// Callback handlers for an async calls. These are invoked by Rust when the future is ready. They
// lift the return value or error and resume the suspended function.
-fileprivate func uniffiFutureContinuationCallback(ptr: UnsafeMutableRawPointer, pollResult: Int8) {
- ContinuationHolder.fromOpaque(ptr).resume(pollResult)
+fileprivate func uniffiFutureContinuationCallback(handle: UInt64, pollResult: Int8) {
+ if let continuation = try? uniffiContinuationHandleMap.remove(handle: handle) {
+ continuation.resume(returning: pollResult)
+ } else {
+ print("uniffiFutureContinuationCallback invalid handle")
+ }
}
-// Wraps UnsafeContinuation in a class so that we can use reference counting when passing it across
-// the FFI
-fileprivate class ContinuationHolder {
- let continuation: UnsafeContinuation<Int8, Never>
-
- init(_ continuation: UnsafeContinuation<Int8, Never>) {
- self.continuation = continuation
+{%- if ci.has_async_callback_interface_definition() %}
+private func uniffiTraitInterfaceCallAsync<T>(
+ makeCall: @escaping () async throws -> T,
+ handleSuccess: @escaping (T) -> (),
+ handleError: @escaping (Int8, RustBuffer) -> ()
+) -> UniffiForeignFuture {
+ let task = Task {
+ do {
+ handleSuccess(try await makeCall())
+ } catch {
+ handleError(CALL_UNEXPECTED_ERROR, {{ Type::String.borrow()|lower_fn }}(String(describing: error)))
+ }
}
+ let handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert(obj: task)
+ return UniffiForeignFuture(handle: handle, free: uniffiForeignFutureFree)
- func resume(_ pollResult: Int8) {
- self.continuation.resume(returning: pollResult)
- }
+}
- func toOpaque() -> UnsafeMutableRawPointer {
- return Unmanaged<ContinuationHolder>.passRetained(self).toOpaque()
+private func uniffiTraitInterfaceCallAsyncWithError<T, E>(
+ makeCall: @escaping () async throws -> T,
+ handleSuccess: @escaping (T) -> (),
+ handleError: @escaping (Int8, RustBuffer) -> (),
+ lowerError: @escaping (E) -> RustBuffer
+) -> UniffiForeignFuture {
+ let task = Task {
+ do {
+ handleSuccess(try await makeCall())
+ } catch let error as E {
+ handleError(CALL_ERROR, lowerError(error))
+ } catch {
+ handleError(CALL_UNEXPECTED_ERROR, {{ Type::String.borrow()|lower_fn }}(String(describing: error)))
+ }
}
+ let handle = UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.insert(obj: task)
+ return UniffiForeignFuture(handle: handle, free: uniffiForeignFutureFree)
+}
+
+// Borrow the callback handle map implementation to store foreign future handles
+// TODO: consolidate the handle-map code (https://github.com/mozilla/uniffi-rs/pull/1823)
+fileprivate var UNIFFI_FOREIGN_FUTURE_HANDLE_MAP = UniffiHandleMap<UniffiForeignFutureTask>()
+
+// Protocol for tasks that handle foreign futures.
+//
+// Defining a protocol allows all tasks to be stored in the same handle map. This can't be done
+// with the task object itself, since has generic parameters.
+protocol UniffiForeignFutureTask {
+ func cancel()
+}
+
+extension Task: UniffiForeignFutureTask {}
- static func fromOpaque(_ ptr: UnsafeRawPointer) -> ContinuationHolder {
- return Unmanaged<ContinuationHolder>.fromOpaque(ptr).takeRetainedValue()
+private func uniffiForeignFutureFree(handle: UInt64) {
+ do {
+ let task = try UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.remove(handle: handle)
+ // Set the cancellation flag on the task. If it's still running, the code can check the
+ // cancellation flag or call `Task.checkCancellation()`. If the task has completed, this is
+ // a no-op.
+ task.cancel()
+ } catch {
+ print("uniffiForeignFutureFree: handle missing from handlemap")
}
}
-fileprivate func uniffiInitContinuationCallback() {
- {{ ci.ffi_rust_future_continuation_callback_set().name() }}(uniffiFutureContinuationCallback)
+// For testing
+public func uniffiForeignFutureHandleCount{{ ci.namespace()|class_name }}() -> Int {
+ UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.count
}
+
+{%- endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h
index 87698e359f..89d98594d3 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/BridgingHeaderTemplate.h
@@ -24,25 +24,11 @@
typedef struct RustBuffer
{
- int32_t capacity;
- int32_t len;
+ uint64_t capacity;
+ uint64_t len;
uint8_t *_Nullable data;
} RustBuffer;
-typedef int32_t (*ForeignCallback)(uint64_t, int32_t, const uint8_t *_Nonnull, int32_t, RustBuffer *_Nonnull);
-
-// Task defined in Rust that Swift executes
-typedef void (*UniFfiRustTaskCallback)(const void * _Nullable, int8_t);
-
-// Callback to execute Rust tasks using a Swift Task
-//
-// Args:
-// executor: ForeignExecutor lowered into a size_t value
-// delay: Delay in MS
-// task: UniFfiRustTaskCallback to call
-// task_data: data to pass the task callback
-typedef int8_t (*UniFfiForeignExecutorCallback)(size_t, uint32_t, UniFfiRustTaskCallback _Nullable, const void * _Nullable);
-
typedef struct ForeignBytes
{
int32_t len;
@@ -59,11 +45,29 @@ typedef struct RustCallStatus {
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
#endif // def UNIFFI_SHARED_H
-// Continuation callback for UniFFI Futures
-typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t);
-
-// Scaffolding functions
-{%- for func in ci.iter_ffi_function_definitions() %}
+{%- for def in ci.ffi_definitions() %}
+#ifndef {{ def.name()|if_guard_name }}
+#define {{ def.name()|if_guard_name }}
+{%- match def %}
+{% when FfiDefinition::CallbackFunction(callback) %}
+typedef
+ {%- match callback.return_type() %}{% when Some(return_type) %} {{ return_type|header_ffi_type_name }} {% when None %} void {% endmatch -%}
+ (*{{ callback.name()|ffi_callback_name }})(
+ {%- for arg in callback.arguments() -%}
+ {{ arg.type_().borrow()|header_ffi_type_name }}
+ {%- if !loop.last || callback.has_rust_call_status_arg() %}, {% endif %}
+ {%- endfor -%}
+ {%- if callback.has_rust_call_status_arg() %}
+ RustCallStatus *_Nonnull uniffiCallStatus
+ {%- endif %}
+ );
+{% when FfiDefinition::Struct(struct) %}
+typedef struct {{ struct.name()|ffi_struct_name }} {
+ {%- for field in struct.fields() %}
+ {{ field.type_().borrow()|header_ffi_type_name }} {{ field.name()|var_name }};
+ {%- endfor %}
+} {{ struct.name()|ffi_struct_name }};
+{% when FfiDefinition::Function(func) %}
{% match func.return_type() -%}{%- when Some with (type_) %}{{ type_|header_ffi_type_name }}{% when None %}void{% endmatch %} {{ func.name() }}(
{%- if func.arguments().len() > 0 %}
{%- for arg in func.arguments() %}
@@ -74,6 +78,8 @@ typedef void (*UniFfiRustFutureContinuation)(void * _Nonnull, int8_t);
{%- if func.has_rust_call_status_arg() %}RustCallStatus *_Nonnull out_status{%- else %}void{% endif %}
{% endif %}
);
+{%- endmatch %}
+#endif
{%- endfor %}
{% import "macros.swift" as swift %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift
new file mode 100644
index 0000000000..74ee372642
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceImpl.swift
@@ -0,0 +1,113 @@
+{%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %}
+{%- let trait_impl=format!("UniffiCallbackInterface{}", name) %}
+
+// Put the implementation in a struct so we don't pollute the top-level namespace
+fileprivate struct {{ trait_impl }} {
+
+ // Create the VTable using a series of closures.
+ // Swift automatically converts these into C callback functions.
+ static var vtable: {{ vtable|ffi_type_name }} = {{ vtable|ffi_type_name }}(
+ {%- for (ffi_callback, meth) in vtable_methods %}
+ {{ meth.name()|fn_name }}: { (
+ {%- for arg in ffi_callback.arguments() %}
+ {{ arg.name()|var_name }}: {{ arg.type_().borrow()|ffi_type_name }}{% if !loop.last || ffi_callback.has_rust_call_status_arg() %},{% endif %}
+ {%- endfor -%}
+ {%- if ffi_callback.has_rust_call_status_arg() %}
+ uniffiCallStatus: UnsafeMutablePointer<RustCallStatus>
+ {%- endif %}
+ ) in
+ let makeCall = {
+ () {% if meth.is_async() %}async {% endif %}throws -> {% match meth.return_type() %}{% when Some(t) %}{{ t|type_name }}{% when None %}(){% endmatch %} in
+ guard let uniffiObj = try? {{ ffi_converter_name }}.handleMap.get(handle: uniffiHandle) else {
+ throw UniffiInternalError.unexpectedStaleHandle
+ }
+ return {% if meth.throws() %}try {% endif %}{% if meth.is_async() %}await {% endif %}uniffiObj.{{ meth.name()|fn_name }}(
+ {%- for arg in meth.arguments() %}
+ {% if !config.omit_argument_labels() %} {{ arg.name()|arg_name }}: {% endif %}try {{ arg|lift_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %}
+ {%- endfor %}
+ )
+ }
+ {%- if !meth.is_async() %}
+
+ {% match meth.return_type() %}
+ {%- when Some(t) %}
+ let writeReturn = { uniffiOutReturn.pointee = {{ t|lower_fn }}($0) }
+ {%- when None %}
+ let writeReturn = { () }
+ {%- endmatch %}
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ uniffiTraitInterfaceCall(
+ callStatus: uniffiCallStatus,
+ makeCall: makeCall,
+ writeReturn: writeReturn
+ )
+ {%- when Some(error_type) %}
+ uniffiTraitInterfaceCallWithError(
+ callStatus: uniffiCallStatus,
+ makeCall: makeCall,
+ writeReturn: writeReturn,
+ lowerError: {{ error_type|lower_fn }}
+ )
+ {%- endmatch %}
+ {%- else %}
+
+ let uniffiHandleSuccess = { (returnValue: {{ meth.return_type()|return_type_name }}) in
+ uniffiFutureCallback(
+ uniffiCallbackData,
+ {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ returnValue: {{ return_type|lower_fn }}(returnValue),
+ {%- when None %}
+ {%- endmatch %}
+ callStatus: RustCallStatus()
+ )
+ )
+ }
+ let uniffiHandleError = { (statusCode, errorBuf) in
+ uniffiFutureCallback(
+ uniffiCallbackData,
+ {{ meth.foreign_future_ffi_result_struct().name()|ffi_struct_name }}(
+ {%- match meth.return_type() %}
+ {%- when Some(return_type) %}
+ returnValue: {{ meth.return_type().map(FfiType::from)|ffi_default_value }},
+ {%- when None %}
+ {%- endmatch %}
+ callStatus: RustCallStatus(code: statusCode, errorBuf: errorBuf)
+ )
+ )
+ }
+
+ {%- match meth.throws_type() %}
+ {%- when None %}
+ let uniffiForeignFuture = uniffiTraitInterfaceCallAsync(
+ makeCall: makeCall,
+ handleSuccess: uniffiHandleSuccess,
+ handleError: uniffiHandleError
+ )
+ {%- when Some(error_type) %}
+ let uniffiForeignFuture = uniffiTraitInterfaceCallAsyncWithError(
+ makeCall: makeCall,
+ handleSuccess: uniffiHandleSuccess,
+ handleError: uniffiHandleError,
+ lowerError: {{ error_type|lower_fn }}
+ )
+ {%- endmatch %}
+ uniffiOutReturn.pointee = uniffiForeignFuture
+ {%- endif %}
+ },
+ {%- endfor %}
+ uniffiFree: { (uniffiHandle: UInt64) -> () in
+ let result = try? {{ ffi_converter_name }}.handleMap.remove(handle: uniffiHandle)
+ if result == nil {
+ print("Uniffi callback interface {{ name }}: handle missing in uniffiFree")
+ }
+ }
+ )
+}
+
+private func {{ callback_init }}() {
+ {{ ffi_init_callback.name() }}(&{{ trait_impl }}.vtable)
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift
index 9ae62d1667..5863c2ad41 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceRuntime.swift
@@ -1,60 +1,3 @@
-fileprivate extension NSLock {
- func withLock<T>(f: () throws -> T) rethrows -> T {
- self.lock()
- defer { self.unlock() }
- return try f()
- }
-}
-
-fileprivate typealias UniFFICallbackHandle = UInt64
-fileprivate class UniFFICallbackHandleMap<T> {
- private var leftMap: [UniFFICallbackHandle: T] = [:]
- private var counter: [UniFFICallbackHandle: UInt64] = [:]
- private var rightMap: [ObjectIdentifier: UniFFICallbackHandle] = [:]
-
- private let lock = NSLock()
- private var currentHandle: UniFFICallbackHandle = 0
- private let stride: UniFFICallbackHandle = 1
-
- func insert(obj: T) -> UniFFICallbackHandle {
- lock.withLock {
- let id = ObjectIdentifier(obj as AnyObject)
- let handle = rightMap[id] ?? {
- currentHandle += stride
- let handle = currentHandle
- leftMap[handle] = obj
- rightMap[id] = handle
- return handle
- }()
- counter[handle] = (counter[handle] ?? 0) + 1
- return handle
- }
- }
-
- func get(handle: UniFFICallbackHandle) -> T? {
- lock.withLock {
- leftMap[handle]
- }
- }
-
- func delete(handle: UniFFICallbackHandle) {
- remove(handle: handle)
- }
-
- @discardableResult
- func remove(handle: UniFFICallbackHandle) -> T? {
- lock.withLock {
- defer { counter[handle] = (counter[handle] ?? 1) - 1 }
- guard counter[handle] == 1 else { return leftMap[handle] }
- let obj = leftMap.removeValue(forKey: handle)
- if let obj = obj {
- rightMap.removeValue(forKey: ObjectIdentifier(obj as AnyObject))
- }
- return obj
- }
- }
-}
-
// Magic number for the Rust proxy to call using the same mechanism as every other method,
// to free the callback once it's dropped by Rust.
private let IDX_CALLBACK_FREE: Int32 = 0
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift
index aec8ded930..7aa1cca9b2 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/CallbackInterfaceTemplate.swift
@@ -1,150 +1,39 @@
{%- let cbi = ci|get_callback_interface_definition(name) %}
-{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %}
-{%- if self.include_once_check("CallbackInterfaceRuntime.swift") %}{%- include "CallbackInterfaceRuntime.swift" %}{%- endif %}
-
-// Declaration and FfiConverters for {{ type_name }} Callback Interface
-
-public protocol {{ type_name }} : AnyObject {
- {% for meth in cbi.methods() -%}
- func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::throws(meth) -%}
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) %} -> {{ return_type|type_name -}}
- {%- else -%}
- {%- endmatch %}
- {% endfor %}
-}
-
-// The ForeignCallback that is passed to Rust.
-fileprivate let {{ foreign_callback }} : ForeignCallback =
- { (handle: UniFFICallbackHandle, method: Int32, argsData: UnsafePointer<UInt8>, argsLen: Int32, out_buf: UnsafeMutablePointer<RustBuffer>) -> Int32 in
- {% for meth in cbi.methods() -%}
- {%- let method_name = format!("invoke_{}", meth.name())|fn_name %}
-
- func {{ method_name }}(_ swiftCallbackInterface: {{ type_name }}, _ argsData: UnsafePointer<UInt8>, _ argsLen: Int32, _ out_buf: UnsafeMutablePointer<RustBuffer>) throws -> Int32 {
- {%- if meth.arguments().len() > 0 %}
- var reader = createReader(data: Data(bytes: argsData, count: Int(argsLen)))
- {%- endif %}
-
- {%- match meth.return_type() %}
- {%- when Some(return_type) %}
- func makeCall() throws -> Int32 {
- let result = {% if meth.throws() %} try{% endif %} swiftCallbackInterface.{{ meth.name()|fn_name }}(
- {% for arg in meth.arguments() -%}
- {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader)
- {%- if !loop.last %}, {% endif %}
- {% endfor -%}
- )
- var writer = [UInt8]()
- {{ return_type|write_fn }}(result, into: &writer)
- out_buf.pointee = RustBuffer(bytes: writer)
- return UNIFFI_CALLBACK_SUCCESS
- }
- {%- when None %}
- func makeCall() throws -> Int32 {
- try swiftCallbackInterface.{{ meth.name()|fn_name }}(
- {% for arg in meth.arguments() -%}
- {% if !config.omit_argument_labels() %}{{ arg.name()|var_name }}: {% endif %} try {{ arg|read_fn }}(from: &reader)
- {%- if !loop.last %}, {% endif %}
- {% endfor -%}
- )
- return UNIFFI_CALLBACK_SUCCESS
- }
- {%- endmatch %}
-
- {%- match meth.throws_type() %}
- {%- when None %}
- return try makeCall()
- {%- when Some(error_type) %}
- do {
- return try makeCall()
- } catch let error as {{ error_type|type_name }} {
- out_buf.pointee = {{ error_type|lower_fn }}(error)
- return UNIFFI_CALLBACK_ERROR
- }
- {%- endmatch %}
- }
- {%- endfor %}
-
-
- switch method {
- case IDX_CALLBACK_FREE:
- {{ ffi_converter_name }}.drop(handle: handle)
- // Sucessful return
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- return UNIFFI_CALLBACK_SUCCESS
- {% for meth in cbi.methods() -%}
- {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
- case {{ loop.index }}:
- let cb: {{ cbi|type_name }}
- do {
- cb = try {{ ffi_converter_name }}.lift(handle)
- } catch {
- out_buf.pointee = {{ Type::String.borrow()|lower_fn }}("{{ cbi.name() }}: Invalid handle")
- return UNIFFI_CALLBACK_UNEXPECTED_ERROR
- }
- do {
- return try {{ method_name }}(cb, argsData, argsLen, out_buf)
- } catch let error {
- out_buf.pointee = {{ Type::String.borrow()|lower_fn }}(String(describing: error))
- return UNIFFI_CALLBACK_UNEXPECTED_ERROR
- }
- {% endfor %}
- // This should never happen, because an out of bounds method index won't
- // ever be used. Once we can catch errors, we should return an InternalError.
- // https://github.com/mozilla/uniffi-rs/issues/351
- default:
- // An unexpected error happened.
- // See docs of ForeignCallback in `uniffi_core/src/ffi/foreigncallbacks.rs`
- return UNIFFI_CALLBACK_UNEXPECTED_ERROR
- }
-}
+{%- let callback_handler = format!("uniffiCallbackHandler{}", name) %}
+{%- let callback_init = format!("uniffiCallbackInit{}", name) %}
+{%- let methods = cbi.methods() %}
+{%- let protocol_name = type_name.clone() %}
+{%- let protocol_docstring = cbi.docstring() %}
+{%- let vtable = cbi.vtable() %}
+{%- let vtable_methods = cbi.vtable_methods() %}
+{%- let ffi_init_callback = cbi.ffi_init_callback() %}
+
+{% include "Protocol.swift" %}
+{% include "CallbackInterfaceImpl.swift" %}
// FfiConverter protocol for callback interfaces
fileprivate struct {{ ffi_converter_name }} {
- private static let initCallbackOnce: () = {
- // Swift ensures this initializer code will once run once, even when accessed by multiple threads.
- try! rustCall { (err: UnsafeMutablePointer<RustCallStatus>) in
- {{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err)
- }
- }()
-
- private static func ensureCallbackinitialized() {
- _ = initCallbackOnce
- }
-
- static func drop(handle: UniFFICallbackHandle) {
- handleMap.remove(handle: handle)
- }
-
- private static var handleMap = UniFFICallbackHandleMap<{{ type_name }}>()
+ fileprivate static var handleMap = UniffiHandleMap<{{ type_name }}>()
}
extension {{ ffi_converter_name }} : FfiConverter {
typealias SwiftType = {{ type_name }}
- // We can use Handle as the FfiType because it's a typealias to UInt64
- typealias FfiType = UniFFICallbackHandle
+ typealias FfiType = UInt64
- public static func lift(_ handle: UniFFICallbackHandle) throws -> SwiftType {
- ensureCallbackinitialized();
- guard let callback = handleMap.get(handle: handle) else {
- throw UniffiInternalError.unexpectedStaleHandle
- }
- return callback
+ public static func lift(_ handle: UInt64) throws -> SwiftType {
+ try handleMap.get(handle: handle)
}
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
- ensureCallbackinitialized();
- let handle: UniFFICallbackHandle = try readInt(&buf)
+ let handle: UInt64 = try readInt(&buf)
return try lift(handle)
}
- public static func lower(_ v: SwiftType) -> UniFFICallbackHandle {
- ensureCallbackinitialized();
+ public static func lower(_ v: SwiftType) -> UInt64 {
return handleMap.insert(obj: v)
}
public static func write(_ v: SwiftType, into buf: inout [UInt8]) {
- ensureCallbackinitialized();
writeInt(&buf, lower(v))
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift
index 99f45290cc..1d8b3cf500 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/EnumTemplate.swift
@@ -1,10 +1,26 @@
// Note that we don't yet support `indirect` for enums.
// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion.
+{%- call swift::docstring(e, 0) %}
+{% match e.variant_discr_type() %}
+{% when None %}
public enum {{ type_name }} {
{% for variant in e.variants() %}
- case {{ variant.name()|enum_variant_swift_quoted }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%}
+ {%- call swift::docstring(variant, 4) %}
+ case {{ variant.name()|enum_variant_swift_quoted }}{% if variant.fields().len() > 0 %}(
+ {%- call swift::field_list_decl(variant, variant.has_nameless_fields()) %}
+ ){% endif -%}
{% endfor %}
}
+{% when Some with (variant_discr_type) %}
+public enum {{ type_name }} : {{ variant_discr_type|type_name }} {
+ {% for variant in e.variants() %}
+ {%- call swift::docstring(variant, 4) %}
+ case {{ variant.name()|enum_variant_swift_quoted }} = {{ e|variant_discr_literal(loop.index0) }}{% if variant.fields().len() > 0 %}(
+ {%- call swift::field_list_decl(variant, variant.has_nameless_fields()) %}
+ ){% endif -%}
+ {% endfor %}
+}
+{% endmatch %}
public struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
typealias SwiftType = {{ type_name }}
@@ -15,7 +31,11 @@ public struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
{% for variant in e.variants() %}
case {{ loop.index }}: return .{{ variant.name()|enum_variant_swift_quoted }}{% if variant.has_fields() %}(
{%- for field in variant.fields() %}
+ {%- if variant.has_nameless_fields() -%}
+ try {{ field|read_fn }}(from: &buf)
+ {%- else -%}
{{ field.name()|arg_name }}: try {{ field|read_fn }}(from: &buf)
+ {%- endif -%}
{%- if !loop.last %}, {% endif %}
{%- endfor %}
){%- endif %}
@@ -28,10 +48,10 @@ public struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
switch value {
{% for variant in e.variants() %}
{% if variant.has_fields() %}
- case let .{{ variant.name()|enum_variant_swift_quoted }}({% for field in variant.fields() %}{{ field.name()|var_name }}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}):
+ case let .{{ variant.name()|enum_variant_swift_quoted }}({% for field in variant.fields() %}{%- call swift::field_name(field, loop.index) -%}{%- if loop.last -%}{%- else -%},{%- endif -%}{% endfor %}):
writeInt(&buf, Int32({{ loop.index }}))
{% for field in variant.fields() -%}
- {{ field|write_fn }}({{ field.name()|var_name }}, into: &buf)
+ {{ field|write_fn }}({% call swift::field_name(field, loop.index) %}, into: &buf)
{% endfor -%}
{% else %}
case .{{ variant.name()|enum_variant_swift_quoted }}:
@@ -55,5 +75,6 @@ public func {{ ffi_converter_name }}_lower(_ value: {{ type_name }}) -> RustBuff
}
{% if !contains_object_references %}
+{% if config.experimental_sendable_value_types() %}extension {{ type_name }}: Sendable {} {% endif %}
extension {{ type_name }}: Equatable, Hashable {}
{% endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift
index 786091395b..0702c477e9 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ErrorTemplate.swift
@@ -1,21 +1,21 @@
+{%- call swift::docstring(e, 0) %}
public enum {{ type_name }} {
{% if e.is_flat() %}
{% for variant in e.variants() %}
- // Simple error enums only carry a message
+ {%- call swift::docstring(variant, 4) %}
case {{ variant.name()|class_name }}(message: String)
{% endfor %}
{%- else %}
{% for variant in e.variants() %}
- case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}({% call swift::field_list_decl(variant) %}){% endif -%}
+ {%- call swift::docstring(variant, 4) %}
+ case {{ variant.name()|class_name }}{% if variant.fields().len() > 0 %}(
+ {%- call swift::field_list_decl(variant, variant.has_nameless_fields()) %}
+ ){% endif -%}
{% endfor %}
{%- endif %}
-
- fileprivate static func uniffiErrorHandler(_ error: RustBuffer) throws -> Error {
- return try {{ ffi_converter_name }}.lift(error)
- }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift
deleted file mode 100644
index 167e4c7546..0000000000
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ForeignExecutorTemplate.swift
+++ /dev/null
@@ -1,69 +0,0 @@
-private let UNIFFI_RUST_TASK_CALLBACK_SUCCESS: Int8 = 0
-private let UNIFFI_RUST_TASK_CALLBACK_CANCELLED: Int8 = 1
-private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS: Int8 = 0
-private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_CANCELED: Int8 = 1
-private let UNIFFI_FOREIGN_EXECUTOR_CALLBACK_ERROR: Int8 = 2
-
-// Encapsulates an executor that can run Rust tasks
-//
-// On Swift, `Task.detached` can handle this we just need to know what priority to send it.
-public struct UniFfiForeignExecutor {
- var priority: TaskPriority
-
- public init(priority: TaskPriority) {
- self.priority = priority
- }
-
- public init() {
- self.priority = Task.currentPriority
- }
-}
-
-fileprivate struct FfiConverterForeignExecutor: FfiConverter {
- typealias SwiftType = UniFfiForeignExecutor
- // Rust uses a pointer to represent the FfiConverterForeignExecutor, but we only need a u8.
- // let's use `Int`, which is equivalent to `size_t`
- typealias FfiType = Int
-
- public static func lift(_ value: FfiType) throws -> SwiftType {
- UniFfiForeignExecutor(priority: TaskPriority(rawValue: numericCast(value)))
- }
- public static func lower(_ value: SwiftType) -> FfiType {
- numericCast(value.priority.rawValue)
- }
-
- public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
- fatalError("FfiConverterForeignExecutor.read not implemented yet")
- }
- public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
- fatalError("FfiConverterForeignExecutor.read not implemented yet")
- }
-}
-
-
-fileprivate func uniffiForeignExecutorCallback(executorHandle: Int, delayMs: UInt32, rustTask: UniFfiRustTaskCallback?, taskData: UnsafeRawPointer?) -> Int8 {
- if let rustTask = rustTask {
- let executor = try! FfiConverterForeignExecutor.lift(executorHandle)
- Task.detached(priority: executor.priority) {
- if delayMs != 0 {
- let nanoseconds: UInt64 = numericCast(delayMs * 1000000)
- try! await Task.sleep(nanoseconds: nanoseconds)
- }
- rustTask(taskData, UNIFFI_RUST_TASK_CALLBACK_SUCCESS)
- }
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
- } else {
- // When rustTask is null, we should drop the foreign executor.
- // However, since its just a value type, we don't need to do anything here.
- return UNIFFI_FOREIGN_EXECUTOR_CALLBACK_SUCCESS
- }
-}
-
-fileprivate func uniffiInitForeignExecutor() {
- {%- match ci.ffi_foreign_executor_callback_set() %}
- {%- when Some with (fn) %}
- {{ fn.name() }}(uniffiForeignExecutorCallback)
- {%- when None %}
- {#- No foreign executor, we don't set anything #}
- {% endmatch %}
-}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift
new file mode 100644
index 0000000000..6de9f085d6
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/HandleMap.swift
@@ -0,0 +1,40 @@
+fileprivate class UniffiHandleMap<T> {
+ private var map: [UInt64: T] = [:]
+ private let lock = NSLock()
+ private var currentHandle: UInt64 = 1
+
+ func insert(obj: T) -> UInt64 {
+ lock.withLock {
+ let handle = currentHandle
+ currentHandle += 1
+ map[handle] = obj
+ return handle
+ }
+ }
+
+ func get(handle: UInt64) throws -> T {
+ try lock.withLock {
+ guard let obj = map[handle] else {
+ throw UniffiInternalError.unexpectedStaleHandle
+ }
+ return obj
+ }
+ }
+
+ @discardableResult
+ func remove(handle: UInt64) throws -> T {
+ try lock.withLock {
+ guard let obj = map.removeValue(forKey: handle) else {
+ throw UniffiInternalError.unexpectedStaleHandle
+ }
+ return obj
+ }
+ }
+
+ var count: Int {
+ get {
+ map.count
+ }
+ }
+}
+
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift
index a34b128e23..cfddf7b313 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Helpers.swift
@@ -26,9 +26,17 @@ fileprivate enum UniffiInternalError: LocalizedError {
}
}
+fileprivate extension NSLock {
+ func withLock<T>(f: () throws -> T) rethrows -> T {
+ self.lock()
+ defer { self.unlock() }
+ return try f()
+ }
+}
+
fileprivate let CALL_SUCCESS: Int8 = 0
fileprivate let CALL_ERROR: Int8 = 1
-fileprivate let CALL_PANIC: Int8 = 2
+fileprivate let CALL_UNEXPECTED_ERROR: Int8 = 2
fileprivate let CALL_CANCELLED: Int8 = 3
fileprivate extension RustCallStatus {
@@ -81,7 +89,7 @@ private func uniffiCheckCallStatus(
throw UniffiInternalError.unexpectedRustCallError
}
- case CALL_PANIC:
+ case CALL_UNEXPECTED_ERROR:
// When the rust code sees a panic, it tries to construct a RustBuffer
// with the message. But if that code panics, then it just sends back
// an empty buffer.
@@ -93,9 +101,39 @@ private func uniffiCheckCallStatus(
}
case CALL_CANCELLED:
- throw CancellationError()
+ fatalError("Cancellation not supported yet")
default:
throw UniffiInternalError.unexpectedRustCallStatusCode
}
}
+
+private func uniffiTraitInterfaceCall<T>(
+ callStatus: UnsafeMutablePointer<RustCallStatus>,
+ makeCall: () throws -> T,
+ writeReturn: (T) -> ()
+) {
+ do {
+ try writeReturn(makeCall())
+ } catch let error {
+ callStatus.pointee.code = CALL_UNEXPECTED_ERROR
+ callStatus.pointee.errorBuf = {{ Type::String.borrow()|lower_fn }}(String(describing: error))
+ }
+}
+
+private func uniffiTraitInterfaceCallWithError<T, E>(
+ callStatus: UnsafeMutablePointer<RustCallStatus>,
+ makeCall: () throws -> T,
+ writeReturn: (T) -> (),
+ lowerError: (E) -> RustBuffer
+) {
+ do {
+ try writeReturn(makeCall())
+ } catch let error as E {
+ callStatus.pointee.code = CALL_ERROR
+ callStatus.pointee.errorBuf = lowerError(error)
+ } catch {
+ callStatus.pointee.code = CALL_UNEXPECTED_ERROR
+ callStatus.pointee.errorBuf = {{ Type::String.borrow()|lower_fn }}(String(describing: error))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift
index 57a77ca6df..0c28bc4c09 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/ObjectTemplate.swift
@@ -1,104 +1,146 @@
{%- let obj = ci|get_object_definition(name) %}
-public protocol {{ obj.name() }}Protocol {
- {% for meth in obj.methods() -%}
- func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::async(meth) %} {% call swift::throws(meth) -%}
- {%- match meth.return_type() -%}
- {%- when Some with (return_type) %} -> {{ return_type|type_name -}}
- {%- else -%}
- {%- endmatch %}
- {% endfor %}
-}
-
-public class {{ type_name }}: {{ obj.name() }}Protocol {
- fileprivate let pointer: UnsafeMutableRawPointer
+{%- let (protocol_name, impl_class_name) = obj|object_names %}
+{%- let methods = obj.methods() %}
+{%- let protocol_docstring = obj.docstring() %}
+
+{%- let is_error = ci.is_name_used_as_error(name) %}
+
+{% include "Protocol.swift" %}
+
+{%- call swift::docstring(obj, 0) %}
+open class {{ impl_class_name }}:
+ {%- for tm in obj.uniffi_traits() %}
+ {%- match tm %}
+ {%- when UniffiTrait::Display { fmt } %}
+ CustomStringConvertible,
+ {%- when UniffiTrait::Debug { fmt } %}
+ CustomDebugStringConvertible,
+ {%- when UniffiTrait::Eq { eq, ne } %}
+ Equatable,
+ {%- when UniffiTrait::Hash { hash } %}
+ Hashable,
+ {%- else %}
+ {%- endmatch %}
+ {%- endfor %}
+ {%- if is_error %}
+ Error,
+ {% endif %}
+ {{ protocol_name }} {
+ fileprivate let pointer: UnsafeMutableRawPointer!
+
+ /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly.
+ public struct NoPointer {
+ public init() {}
+ }
// TODO: We'd like this to be `private` but for Swifty reasons,
// we can't implement `FfiConverter` without making this `required` and we can't
// make it `required` without making it `public`.
- required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
+ required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
self.pointer = pointer
}
+ /// This constructor can be used to instantiate a fake object.
+ /// - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject].
+ ///
+ /// - Warning:
+ /// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash.
+ public init(noPointer: NoPointer) {
+ self.pointer = nil
+ }
+
+ public func uniffiClonePointer() -> UnsafeMutableRawPointer {
+ return try! rustCall { {{ obj.ffi_object_clone().name() }}(self.pointer, $0) }
+ }
+
{%- match obj.primary_constructor() %}
{%- when Some with (cons) %}
- public convenience init({% call swift::arg_list_decl(cons) -%}) {% call swift::throws(cons) %} {
- self.init(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %})
- }
+ {%- call swift::ctor_decl(cons, 4) %}
{%- when None %}
+ // No primary constructor declared for this class.
{%- endmatch %}
deinit {
+ guard let pointer = pointer else {
+ return
+ }
+
try! rustCall { {{ obj.ffi_object_free().name() }}(pointer, $0) }
}
{% for cons in obj.alternate_constructors() %}
-
- public static func {{ cons.name()|fn_name }}({% call swift::arg_list_decl(cons) %}) {% call swift::throws(cons) %} -> {{ type_name }} {
- return {{ type_name }}(unsafeFromRawPointer: {% call swift::to_ffi_call(cons) %})
- }
-
+ {%- call swift::func_decl("public static func", cons, 4) %}
{% endfor %}
- {# // TODO: Maybe merge the two templates (i.e the one with a return type and the one without) #}
{% for meth in obj.methods() -%}
- {%- if meth.is_async() %}
-
- public func {{ meth.name()|fn_name }}({%- call swift::arg_list_decl(meth) -%}) async {% call swift::throws(meth) %}{% match meth.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} {
- return {% call swift::try(meth) %} await uniffiRustCallAsync(
- rustFutureFunc: {
- {{ meth.ffi_func().name() }}(
- self.pointer
- {%- for arg in meth.arguments() -%}
- ,
- {{ arg|lower_fn }}({{ arg.name()|var_name }})
- {%- endfor %}
- )
- },
- pollFunc: {{ meth.ffi_rust_future_poll(ci) }},
- completeFunc: {{ meth.ffi_rust_future_complete(ci) }},
- freeFunc: {{ meth.ffi_rust_future_free(ci) }},
- {%- match meth.return_type() %}
- {%- when Some(return_type) %}
- liftFunc: {{ return_type|lift_fn }},
- {%- when None %}
- liftFunc: { $0 },
- {%- endmatch %}
- {%- match meth.throws_type() %}
- {%- when Some with (e) %}
- errorHandler: {{ e|ffi_converter_name }}.lift
- {%- else %}
- errorHandler: nil
- {% endmatch %}
+ {%- call swift::func_decl("open func", meth, 4) %}
+ {% endfor %}
+
+ {%- for tm in obj.uniffi_traits() %}
+ {%- match tm %}
+ {%- when UniffiTrait::Display { fmt } %}
+ open var description: String {
+ return {% call swift::try(fmt) %} {{ fmt.return_type().unwrap()|lift_fn }}(
+ {% call swift::to_ffi_call(fmt) %}
)
}
-
- {% else -%}
-
- {%- match meth.return_type() -%}
-
- {%- when Some with (return_type) %}
-
- public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} -> {{ return_type|type_name }} {
- return {% call swift::try(meth) %} {{ return_type|lift_fn }}(
- {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %}
+ {%- when UniffiTrait::Debug { fmt } %}
+ open var debugDescription: String {
+ return {% call swift::try(fmt) %} {{ fmt.return_type().unwrap()|lift_fn }}(
+ {% call swift::to_ffi_call(fmt) %}
)
}
-
- {%- when None %}
-
- public func {{ meth.name()|fn_name }}({% call swift::arg_list_decl(meth) %}) {% call swift::throws(meth) %} {
- {% call swift::to_ffi_call_with_prefix("self.pointer", meth) %}
+ {%- when UniffiTrait::Eq { eq, ne } %}
+ public static func == (self: {{ impl_class_name }}, other: {{ impl_class_name }}) -> Bool {
+ return {% call swift::try(eq) %} {{ eq.return_type().unwrap()|lift_fn }}(
+ {% call swift::to_ffi_call(eq) %}
+ )
+ }
+ {%- when UniffiTrait::Hash { hash } %}
+ open func hash(into hasher: inout Hasher) {
+ let val = {% call swift::try(hash) %} {{ hash.return_type().unwrap()|lift_fn }}(
+ {% call swift::to_ffi_call(hash) %}
+ )
+ hasher.combine(val)
}
+ {%- else %}
+ {%- endmatch %}
+ {%- endfor %}
- {%- endmatch -%}
- {%- endif -%}
- {% endfor %}
}
+{%- if obj.has_callback_interface() %}
+{%- let callback_handler = format!("uniffiCallbackInterface{}", name) %}
+{%- let callback_init = format!("uniffiCallbackInit{}", name) %}
+{%- let vtable = obj.vtable().expect("trait interface should have a vtable") %}
+{%- let vtable_methods = obj.vtable_methods() %}
+{%- let ffi_init_callback = obj.ffi_init_callback() %}
+{% include "CallbackInterfaceImpl.swift" %}
+{%- endif %}
+
public struct {{ ffi_converter_name }}: FfiConverter {
+ {%- if obj.has_callback_interface() %}
+ fileprivate static var handleMap = UniffiHandleMap<{{ type_name }}>()
+ {%- endif %}
+
typealias FfiType = UnsafeMutableRawPointer
typealias SwiftType = {{ type_name }}
+ public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} {
+ return {{ impl_class_name }}(unsafeFromRawPointer: pointer)
+ }
+
+ public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer {
+ {%- if obj.has_callback_interface() %}
+ guard let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: handleMap.insert(obj: value))) else {
+ fatalError("Cast to UnsafeMutableRawPointer failed")
+ }
+ return ptr
+ {%- else %}
+ return value.uniffiClonePointer()
+ {%- endif %}
+ }
+
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} {
let v: UInt64 = try readInt(&buf)
// The Rust code won't compile if a pointer won't fit in a UInt64.
@@ -115,15 +157,30 @@ public struct {{ ffi_converter_name }}: FfiConverter {
// The Rust code won't compile if a pointer won't fit in a `UInt64`.
writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value)))))
}
+}
- public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> {{ type_name }} {
- return {{ type_name}}(unsafeFromRawPointer: pointer)
+{# Objects as error #}
+{%- if is_error %}
+{# Due to some mismatches in the ffi converter mechanisms, errors are a RustBuffer holding a pointer #}
+public struct {{ ffi_converter_name }}__as_error: FfiConverterRustBuffer {
+ public static func lift(_ buf: RustBuffer) throws -> {{ type_name }} {
+ var reader = createReader(data: Data(rustBuffer: buf))
+ return try {{ ffi_converter_name }}.read(from: &reader)
}
- public static func lower(_ value: {{ type_name }}) -> UnsafeMutableRawPointer {
- return value.pointer
+ public static func lower(_ value: {{ type_name }}) -> RustBuffer {
+ fatalError("not implemented")
+ }
+
+ public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} {
+ fatalError("not implemented")
+ }
+
+ public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) {
+ fatalError("not implemented")
}
}
+{%- endif %}
{#
We always write these public functions just in case the enum is used as
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift
new file mode 100644
index 0000000000..7df953558a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Protocol.swift
@@ -0,0 +1,12 @@
+{%- call swift::docstring_value(protocol_docstring, 0) %}
+public protocol {{ protocol_name }} : AnyObject {
+ {% for meth in methods.iter() -%}
+ {%- call swift::docstring(meth, 4) %}
+ func {{ meth.name()|fn_name }}({% call swift::arg_list_protocol(meth) %}) {% call swift::async(meth) -%}{% call swift::throws(meth) -%}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) %} -> {{ return_type|type_name -}}
+ {%- else -%}
+ {%- endmatch %}
+ {% endfor %}
+}
+
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift
index 44de9dd358..c262a7a216 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RecordTemplate.swift
@@ -1,12 +1,14 @@
{%- let rec = ci|get_record_definition(name) %}
+{%- call swift::docstring(rec, 0) %}
public struct {{ type_name }} {
{%- for field in rec.fields() %}
- public var {{ field.name()|var_name }}: {{ field|type_name }}
+ {%- call swift::docstring(field, 4) %}
+ public {% if config.generate_immutable_records() %}let{% else %}var{% endif %} {{ field.name()|var_name }}: {{ field|type_name }}
{%- endfor %}
// Default memberwise initializers are never public by default, so we
// declare one manually.
- public init({% call swift::field_list_decl(rec) %}) {
+ public init({% call swift::field_list_decl(rec, false) %}) {
{%- for field in rec.fields() %}
self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
{%- endfor %}
@@ -14,6 +16,7 @@ public struct {{ type_name }} {
}
{% if !contains_object_references %}
+{% if config.experimental_sendable_value_types() %}extension {{ type_name }}: Sendable {} {% endif %}
extension {{ type_name }}: Equatable, Hashable {
public static func ==(lhs: {{ type_name }}, rhs: {{ type_name }}) -> Bool {
{%- for field in rec.fields() %}
@@ -34,12 +37,16 @@ extension {{ type_name }}: Equatable, Hashable {
public struct {{ ffi_converter_name }}: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> {{ type_name }} {
- return try {{ type_name }}(
+ return {%- if rec.has_fields() %}
+ try {{ type_name }}(
{%- for field in rec.fields() %}
- {{ field.name()|arg_name }}: {{ field|read_fn }}(from: &buf)
- {%- if !loop.last %}, {% endif %}
+ {{ field.name()|arg_name }}: {{ field|read_fn }}(from: &buf)
+ {%- if !loop.last %}, {% endif %}
{%- endfor %}
)
+ {%- else %}
+ {{ type_name }}()
+ {%- endif %}
}
public static func write(_ value: {{ type_name }}, into buf: inout [UInt8]) {
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift
index 2f737b6635..a053334a30 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/RustBufferTemplate.swift
@@ -7,6 +7,10 @@ fileprivate extension RustBuffer {
self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data)
}
+ static func empty() -> RustBuffer {
+ RustBuffer(capacity: 0, len:0, data: nil)
+ }
+
static func from(_ ptr: UnsafeBufferPointer<UInt8>) -> RustBuffer {
try! rustCall { {{ ci.ffi_rustbuffer_from_bytes().name() }}(ForeignBytes(bufferPointer: ptr), $0) }
}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift
index a2c6311931..ce946076f7 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/TopLevelFunctionTemplate.swift
@@ -1,48 +1 @@
-{%- if func.is_async() %}
-
-public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) async {% call swift::throws(func) %}{% match func.return_type() %}{% when Some with (return_type) %} -> {{ return_type|type_name }}{% when None %}{% endmatch %} {
- return {% call swift::try(func) %} await uniffiRustCallAsync(
- rustFutureFunc: {
- {{ func.ffi_func().name() }}(
- {%- for arg in func.arguments() %}
- {{ arg|lower_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %}
- {%- endfor %}
- )
- },
- pollFunc: {{ func.ffi_rust_future_poll(ci) }},
- completeFunc: {{ func.ffi_rust_future_complete(ci) }},
- freeFunc: {{ func.ffi_rust_future_free(ci) }},
- {%- match func.return_type() %}
- {%- when Some(return_type) %}
- liftFunc: {{ return_type|lift_fn }},
- {%- when None %}
- liftFunc: { $0 },
- {%- endmatch %}
- {%- match func.throws_type() %}
- {%- when Some with (e) %}
- errorHandler: {{ e|ffi_converter_name }}.lift
- {%- else %}
- errorHandler: nil
- {% endmatch %}
- )
-}
-
-{% else %}
-
-{%- match func.return_type() -%}
-{%- when Some with (return_type) %}
-
-public func {{ func.name()|fn_name }}({%- call swift::arg_list_decl(func) -%}) {% call swift::throws(func) %} -> {{ return_type|type_name }} {
- return {% call swift::try(func) %} {{ return_type|lift_fn }}(
- {% call swift::to_ffi_call(func) %}
- )
-}
-
-{%- when None %}
-
-public func {{ func.name()|fn_name }}({% call swift::arg_list_decl(func) %}) {% call swift::throws(func) %} {
- {% call swift::to_ffi_call(func) %}
-}
-
-{% endmatch %}
-{%- endif %}
+{%- call swift::func_decl("public func", func, 0) %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift
index aba34f4b0b..5e26758f3c 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/Types.swift
@@ -64,9 +64,6 @@
{%- when Type::CallbackInterface { name, module_path } %}
{%- include "CallbackInterfaceTemplate.swift" %}
-{%- when Type::ForeignExecutor %}
-{%- include "ForeignExecutorTemplate.swift" %}
-
{%- when Type::Custom { name, module_path, builtin } %}
{%- include "CustomType.swift" %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift
index 0a125e6f61..8692cd6ff0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/macros.swift
@@ -8,28 +8,97 @@
{%- call try(func) -%}
{%- match func.throws_type() -%}
{%- when Some with (e) -%}
- rustCallWithError({{ e|ffi_converter_name }}.lift) {
+ rustCallWithError({{ e|ffi_error_converter_name }}.lift) {
{%- else -%}
rustCall() {
{%- endmatch %}
- {{ func.ffi_func().name() }}({% call arg_list_lowered(func) -%} $0)
+ {{ func.ffi_func().name() }}(
+ {%- if func.takes_self() %}self.uniffiClonePointer(),{% endif %}
+ {%- call arg_list_lowered(func) -%} $0
+ )
}
{%- endmacro -%}
-{%- macro to_ffi_call_with_prefix(prefix, func) -%}
-{% call try(func) %}
- {%- match func.throws_type() %}
- {%- when Some with (e) %}
- rustCallWithError({{ e|ffi_converter_name }}.lift) {
+// eg, `public func foo_bar() { body }`
+{%- macro func_decl(func_decl, callable, indent) %}
+{%- call docstring(callable, indent) %}
+{{ func_decl }} {{ callable.name()|fn_name }}(
+ {%- call arg_list_decl(callable) -%})
+ {%- call async(callable) %}
+ {%- call throws(callable) %}
+ {%- match callable.return_type() %}
+ {%- when Some with (return_type) %} -> {{ return_type|type_name }}
+ {%- when None %}
+ {%- endmatch %} {
+ {%- call call_body(callable) %}
+}
+{%- endmacro %}
+
+// primary ctor - no name, no return-type.
+{%- macro ctor_decl(callable, indent) %}
+{%- call docstring(callable, indent) %}
+public convenience init(
+ {%- call arg_list_decl(callable) -%}) {%- call async(callable) %} {%- call throws(callable) %} {
+ {%- if callable.is_async() %}
+ let pointer =
+ {%- call call_async(callable) %}
+ {# The async mechanism returns an already constructed self.
+ We work around that by cloning the pointer from that object, then
+ assune the old object dies as there are no other references possible.
+ #}
+ .uniffiClonePointer()
{%- else %}
- rustCall() {
- {% endmatch %}
- {{ func.ffi_func().name() }}(
- {{- prefix }}, {% call arg_list_lowered(func) -%} $0
- )
+ let pointer =
+ {% call to_ffi_call(callable) %}
+ {%- endif %}
+ self.init(unsafeFromRawPointer: pointer)
}
{%- endmacro %}
+{%- macro call_body(callable) %}
+{%- if callable.is_async() %}
+ return {%- call call_async(callable) %}
+{%- else %}
+{%- match callable.return_type() -%}
+{%- when Some with (return_type) %}
+ return {% call try(callable) %} {{ return_type|lift_fn }}({% call to_ffi_call(callable) %})
+{%- when None %}
+{%- call to_ffi_call(callable) %}
+{%- endmatch %}
+{%- endif %}
+
+{%- endmacro %}
+
+{%- macro call_async(callable) %}
+ {% call try(callable) %} await uniffiRustCallAsync(
+ rustFutureFunc: {
+ {{ callable.ffi_func().name() }}(
+ {%- if callable.takes_self() %}
+ self.uniffiClonePointer(){% if !callable.arguments().is_empty() %},{% endif %}
+ {% endif %}
+ {%- for arg in callable.arguments() -%}
+ {{ arg|lower_fn }}({{ arg.name()|var_name }}){% if !loop.last %},{% endif %}
+ {%- endfor %}
+ )
+ },
+ pollFunc: {{ callable.ffi_rust_future_poll(ci) }},
+ completeFunc: {{ callable.ffi_rust_future_complete(ci) }},
+ freeFunc: {{ callable.ffi_rust_future_free(ci) }},
+ {%- match callable.return_type() %}
+ {%- when Some(return_type) %}
+ liftFunc: {{ return_type|lift_fn }},
+ {%- when None %}
+ liftFunc: { $0 },
+ {%- endmatch %}
+ {%- match callable.throws_type() %}
+ {%- when Some with (e) %}
+ errorHandler: {{ e|ffi_error_converter_name }}.lift
+ {%- else %}
+ errorHandler: nil
+ {% endmatch %}
+ )
+{%- endmacro %}
+
{%- macro arg_list_lowered(func) %}
{%- for arg in func.arguments() %}
{{ arg|lower_fn }}({{ arg.name()|var_name }}),
@@ -56,17 +125,30 @@
// Field lists as used in Swift declarations of Records and Enums.
// Note the var_name and type_name filters.
-#}
-{% macro field_list_decl(item) %}
+{% macro field_list_decl(item, has_nameless_fields) %}
{%- for field in item.fields() -%}
+ {%- call docstring(field, 8) %}
+ {%- if has_nameless_fields %}
+ {{- field|type_name -}}
+ {%- if !loop.last -%}, {%- endif -%}
+ {%- else -%}
{{ field.name()|var_name }}: {{ field|type_name -}}
{%- match field.default_value() %}
{%- when Some with(literal) %} = {{ literal|literal_swift(field) }}
{%- else %}
{%- endmatch -%}
{% if !loop.last %}, {% endif %}
+ {%- endif -%}
{%- endfor %}
{%- endmacro %}
+{% macro field_name(field, field_num) %}
+{%- if field.name().is_empty() -%}
+v{{- field_num -}}
+{%- else -%}
+{{ field.name()|var_name }}
+{%- endif -%}
+{%- endmacro %}
{% macro arg_list_protocol(func) %}
{%- for arg in func.arguments() -%}
@@ -75,15 +157,26 @@
{%- endfor %}
{%- endmacro %}
-
{%- macro async(func) %}
-{%- if func.is_async() %}async{% endif %}
+{%- if func.is_async() %}async {% endif %}
{%- endmacro -%}
{%- macro throws(func) %}
-{%- if func.throws() %}throws{% endif %}
+{%- if func.throws() %}throws {% endif %}
{%- endmacro -%}
{%- macro try(func) %}
{%- if func.throws() %}try {% else %}try! {% endif %}
{%- endmacro -%}
+
+{%- macro docstring_value(maybe_docstring, indent_spaces) %}
+{%- match maybe_docstring %}
+{%- when Some(docstring) %}
+{{ docstring|docstring(indent_spaces) }}
+{%- else %}
+{%- endmatch %}
+{%- endmacro %}
+
+{%- macro docstring(defn, indent_spaces) %}
+{%- call docstring_value(defn.docstring(), indent_spaces) %}
+{%- endmacro %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift
index c34d348efb..17fdde74e0 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/templates/wrapper.swift
@@ -1,5 +1,10 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
+
+// swiftlint:disable all
+
+{%- call swift::docstring_value(ci.namespace_docstring(), 0) %}
+
{%- import "macros.swift" as swift %}
import Foundation
{%- for imported_class in self.imports() %}
@@ -15,6 +20,7 @@ import {{ config.ffi_module_name() }}
{% include "RustBufferTemplate.swift" %}
{% include "Helpers.swift" %}
+{% include "HandleMap.swift" %}
// Public interface members begin here.
{{ type_helper_code }}
@@ -66,3 +72,5 @@ private func uniffiEnsureInitialized() {
fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
}
}
+
+// swiftlint:enable all
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs
index c3b2f15277..195a77696b 100644
--- a/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs
+++ b/third_party/rust/uniffi_bindgen/src/bindings/swift/test.rs
@@ -2,10 +2,7 @@
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/. */
-use crate::{
- bindings::{RunScriptOptions, TargetLanguage},
- library_mode::generate_bindings,
-};
+use crate::{bindings::RunScriptOptions, library_mode::generate_bindings, BindingGeneratorDefault};
use anyhow::{bail, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};
@@ -15,6 +12,8 @@ use std::io::Write;
use std::process::{Command, Stdio};
use uniffi_testing::UniFFITestHelper;
+use crate::bindings::TargetLanguage;
+
/// Run Swift tests for a UniFFI test fixture
pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> {
run_script(
@@ -36,7 +35,7 @@ pub fn run_script(
args: Vec<String>,
options: &RunScriptOptions,
) -> Result<()> {
- let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let script_path = Utf8Path::new(script_file).canonicalize_utf8()?;
let test_helper = UniFFITestHelper::new(crate_name)?;
let out_dir = test_helper.create_out_dir(tmp_dir, &script_path)?;
let cdylib_path = test_helper.copy_cdylib_to_out_dir(&out_dir)?;
@@ -126,8 +125,17 @@ struct GeneratedSources {
impl GeneratedSources {
fn new(crate_name: &str, cdylib_path: &Utf8Path, out_dir: &Utf8Path) -> Result<Self> {
- let sources =
- generate_bindings(cdylib_path, None, &[TargetLanguage::Swift], out_dir, false)?;
+ let sources = generate_bindings(
+ cdylib_path,
+ None,
+ &BindingGeneratorDefault {
+ target_languages: vec![TargetLanguage::Swift],
+ try_format_code: false,
+ },
+ None,
+ out_dir,
+ false,
+ )?;
let main_source = sources
.iter()
.find(|s| s.package.name == crate_name)
@@ -169,7 +177,7 @@ fn create_command(program: &str, options: &RunScriptOptions) -> Command {
if !options.show_compiler_messages {
// This prevents most compiler messages, but not remarks
command.arg("-suppress-warnings");
- // This gets the remarks. Note: swift will eventually get a `-supress-remarks` argument,
+ // This gets the remarks. Note: swift will eventually get a `-suppress-remarks` argument,
// maybe we can eventually move to that
command.stderr(Stdio::null());
}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs
index e3bca4f966..f176a7a684 100644
--- a/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs
+++ b/third_party/rust/uniffi_bindgen/src/interface/callbacks.rs
@@ -33,9 +33,12 @@
//! # Ok::<(), anyhow::Error>(())
//! ```
+use std::iter;
+
+use heck::ToUpperCamelCase;
use uniffi_meta::Checksum;
-use super::ffi::{FfiArgument, FfiFunction, FfiType};
+use super::ffi::{FfiArgument, FfiCallbackFunction, FfiField, FfiFunction, FfiStruct, FfiType};
use super::object::Method;
use super::{AsType, Type, TypeIterator};
@@ -52,18 +55,11 @@ pub struct CallbackInterface {
// avoids a weird circular dependency in the calculation.
#[checksum_ignore]
pub(super) ffi_init_callback: FfiFunction,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
}
impl CallbackInterface {
- pub fn new(name: String, module_path: String) -> CallbackInterface {
- CallbackInterface {
- name,
- module_path,
- methods: Default::default(),
- ffi_init_callback: Default::default(),
- }
- }
-
pub fn name(&self) -> &str {
&self.name
}
@@ -77,18 +73,45 @@ impl CallbackInterface {
}
pub(super) fn derive_ffi_funcs(&mut self) {
- self.ffi_init_callback.name =
- uniffi_meta::init_callback_fn_symbol_name(&self.module_path, &self.name);
- self.ffi_init_callback.arguments = vec![FfiArgument {
- name: "callback_stub".to_string(),
- type_: FfiType::ForeignCallback,
- }];
- self.ffi_init_callback.return_type = None;
+ self.ffi_init_callback =
+ FfiFunction::callback_init(&self.module_path, &self.name, vtable_name(&self.name));
+ }
+
+ /// FfiCallbacks to define for our methods.
+ pub fn ffi_callbacks(&self) -> Vec<FfiCallbackFunction> {
+ ffi_callbacks(&self.name, &self.methods)
+ }
+
+ /// The VTable FFI type
+ pub fn vtable(&self) -> FfiType {
+ FfiType::Struct(vtable_name(&self.name))
+ }
+
+ /// the VTable struct to define.
+ pub fn vtable_definition(&self) -> FfiStruct {
+ vtable_struct(&self.name, &self.methods)
+ }
+
+ /// Vec of (ffi_callback, method) pairs
+ pub fn vtable_methods(&self) -> Vec<(FfiCallbackFunction, &Method)> {
+ self.methods
+ .iter()
+ .enumerate()
+ .map(|(i, method)| (method_ffi_callback(&self.name, method, i), method))
+ .collect()
}
pub fn iter_types(&self) -> TypeIterator<'_> {
Box::new(self.methods.iter().flat_map(Method::iter_types))
}
+
+ pub fn docstring(&self) -> Option<&str> {
+ self.docstring.as_deref()
+ }
+
+ pub fn has_async_method(&self) -> bool {
+ self.methods.iter().any(Method::is_async)
+ }
}
impl AsType for CallbackInterface {
@@ -100,6 +123,139 @@ impl AsType for CallbackInterface {
}
}
+impl TryFrom<uniffi_meta::CallbackInterfaceMetadata> for CallbackInterface {
+ type Error = anyhow::Error;
+
+ fn try_from(meta: uniffi_meta::CallbackInterfaceMetadata) -> anyhow::Result<Self> {
+ Ok(Self {
+ name: meta.name,
+ module_path: meta.module_path,
+ methods: Default::default(),
+ ffi_init_callback: Default::default(),
+ docstring: meta.docstring.clone(),
+ })
+ }
+}
+
+/// [FfiCallbackFunction] functions for the methods of a callback/trait interface
+pub fn ffi_callbacks(trait_name: &str, methods: &[Method]) -> Vec<FfiCallbackFunction> {
+ methods
+ .iter()
+ .enumerate()
+ .map(|(i, method)| method_ffi_callback(trait_name, method, i))
+ .collect()
+}
+
+pub fn method_ffi_callback(trait_name: &str, method: &Method, index: usize) -> FfiCallbackFunction {
+ if !method.is_async() {
+ FfiCallbackFunction {
+ name: method_ffi_callback_name(trait_name, index),
+ arguments: iter::once(FfiArgument::new("uniffi_handle", FfiType::UInt64))
+ .chain(method.arguments().into_iter().map(Into::into))
+ .chain(iter::once(match method.return_type() {
+ Some(t) => FfiArgument::new("uniffi_out_return", FfiType::from(t).reference()),
+ None => FfiArgument::new("uniffi_out_return", FfiType::VoidPointer),
+ }))
+ .collect(),
+ has_rust_call_status_arg: true,
+ return_type: None,
+ }
+ } else {
+ let completion_callback =
+ ffi_foreign_future_complete(method.return_type().map(FfiType::from));
+ FfiCallbackFunction {
+ name: method_ffi_callback_name(trait_name, index),
+ arguments: iter::once(FfiArgument::new("uniffi_handle", FfiType::UInt64))
+ .chain(method.arguments().into_iter().map(Into::into))
+ .chain([
+ FfiArgument::new(
+ "uniffi_future_callback",
+ FfiType::Callback(completion_callback.name),
+ ),
+ FfiArgument::new("uniffi_callback_data", FfiType::UInt64),
+ FfiArgument::new(
+ "uniffi_out_return",
+ FfiType::Struct("ForeignFuture".to_owned()).reference(),
+ ),
+ ])
+ .collect(),
+ has_rust_call_status_arg: false,
+ return_type: None,
+ }
+ }
+}
+
+/// Result struct to pass to the completion callback for async methods
+pub fn foreign_future_ffi_result_struct(return_ffi_type: Option<FfiType>) -> FfiStruct {
+ let return_type_name =
+ FfiType::return_type_name(return_ffi_type.as_ref()).to_upper_camel_case();
+ FfiStruct {
+ name: format!("ForeignFutureStruct{return_type_name}"),
+ fields: match return_ffi_type {
+ Some(return_ffi_type) => vec![
+ FfiField::new("return_value", return_ffi_type),
+ FfiField::new("call_status", FfiType::RustCallStatus),
+ ],
+ None => vec![
+ // In Rust, `return_value` is `()` -- a ZST.
+ // ZSTs are not valid in `C`, but they also take up 0 space.
+ // Skip the `return_value` field to make the layout correct.
+ FfiField::new("call_status", FfiType::RustCallStatus),
+ ],
+ },
+ }
+}
+
+/// Definition for callback functions to complete an async callback interface method
+pub fn ffi_foreign_future_complete(return_ffi_type: Option<FfiType>) -> FfiCallbackFunction {
+ let return_type_name =
+ FfiType::return_type_name(return_ffi_type.as_ref()).to_upper_camel_case();
+ FfiCallbackFunction {
+ name: format!("ForeignFutureComplete{return_type_name}"),
+ arguments: vec![
+ FfiArgument::new("callback_data", FfiType::UInt64),
+ FfiArgument::new(
+ "result",
+ FfiType::Struct(format!("ForeignFutureStruct{return_type_name}")),
+ ),
+ ],
+ return_type: None,
+ has_rust_call_status_arg: false,
+ }
+}
+
+/// [FfiStruct] for a callback/trait interface VTable
+///
+/// This struct has a FfiCallbackFunction field for each method, plus extra fields for special
+/// methods
+pub fn vtable_struct(trait_name: &str, methods: &[Method]) -> FfiStruct {
+ FfiStruct {
+ name: vtable_name(trait_name),
+ fields: methods
+ .iter()
+ .enumerate()
+ .map(|(i, method)| {
+ FfiField::new(
+ method.name(),
+ FfiType::Callback(format!("CallbackInterface{trait_name}Method{i}")),
+ )
+ })
+ .chain([FfiField::new(
+ "uniffi_free",
+ FfiType::Callback("CallbackInterfaceFree".to_owned()),
+ )])
+ .collect(),
+ }
+}
+
+pub fn method_ffi_callback_name(trait_name: &str, index: usize) -> String {
+ format!("CallbackInterface{trait_name}Method{index}")
+}
+
+pub fn vtable_name(trait_name: &str) -> String {
+ format!("VTableCallbackInterface{trait_name}")
+}
+
#[cfg(test)]
mod test {
use super::super::ComponentInterface;
@@ -146,4 +302,21 @@ mod test {
assert_eq!(callbacks_two.methods()[0].name(), "two");
assert_eq!(callbacks_two.methods()[1].name(), "too");
}
+
+ #[test]
+ fn test_docstring_callback_interface() {
+ const UDL: &str = r#"
+ namespace test{};
+ /// informative docstring
+ callback interface Testing { };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(
+ ci.get_callback_interface_definition("Testing")
+ .unwrap()
+ .docstring()
+ .unwrap(),
+ "informative docstring"
+ );
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/enum_.rs b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs
index 82baf1dd50..a666cc3605 100644
--- a/third_party/rust/uniffi_bindgen/src/interface/enum_.rs
+++ b/third_party/rust/uniffi_bindgen/src/interface/enum_.rs
@@ -94,7 +94,9 @@
//!
//! ```
//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
-//! # namespace example {};
+//! # namespace example {
+//! # [Throws=Example] void func();
+//! # };
//! # [Error]
//! # enum Example {
//! # "one",
@@ -130,7 +132,9 @@
//!
//! ```
//! # let ci = uniffi_bindgen::interface::ComponentInterface::from_webidl(r##"
-//! # namespace example {};
+//! # namespace example {
+//! # [Throws=Example] void func();
+//! # };
//! # [Error]
//! # interface Example {
//! # one();
@@ -159,7 +163,7 @@ use anyhow::Result;
use uniffi_meta::Checksum;
use super::record::Field;
-use super::{AsType, Type, TypeIterator};
+use super::{AsType, Literal, Type, TypeIterator};
/// Represents an enum with named variants, each of which may have named
/// and typed fields.
@@ -170,6 +174,7 @@ use super::{AsType, Type, TypeIterator};
pub struct Enum {
pub(super) name: String,
pub(super) module_path: String,
+ pub(super) discr_type: Option<Type>,
pub(super) variants: Vec<Variant>,
// NOTE: `flat` is a misleading name and to make matters worse, has 2 different
// meanings depending on the context :(
@@ -189,6 +194,9 @@ pub struct Enum {
// * For an Enum not used as an error but which has no variants with data, `flat` will be
// false when generating the scaffolding but `true` when generating bindings.
pub(super) flat: bool,
+ pub(super) non_exhaustive: bool,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
}
impl Enum {
@@ -200,14 +208,61 @@ impl Enum {
&self.variants
}
+ // Get the literal value to use for the specified variant's discriminant.
+ // Follows Rust's rules when mixing specified and unspecified values; please
+ // file a bug if you find a case where it does not.
+ // However, it *does not* attempt to handle error cases - either cases where
+ // a discriminant is not unique, or where a discriminant would overflow the
+ // repr. The intention is that the Rust compiler itself will fail to build
+ // in those cases, so by the time this get's run we can be confident these
+ // error cases can't exist.
+ pub fn variant_discr(&self, variant_index: usize) -> Result<Literal> {
+ if variant_index >= self.variants.len() {
+ anyhow::bail!("Invalid variant index {variant_index}");
+ }
+ let mut next = 0;
+ let mut this;
+ let mut this_lit = Literal::new_uint(0);
+ for v in self.variants().iter().take(variant_index + 1) {
+ (this, this_lit) = match v.discr {
+ None => (
+ next,
+ if (next as i64) < 0 {
+ Literal::new_int(next as i64)
+ } else {
+ Literal::new_uint(next)
+ },
+ ),
+ Some(Literal::UInt(v, _, _)) => (v, Literal::new_uint(v)),
+ // in-practice, Literal::Int == a negative number.
+ Some(Literal::Int(v, _, _)) => (v as u64, Literal::new_int(v)),
+ _ => anyhow::bail!("Invalid literal type {v:?}"),
+ };
+ next = this.wrapping_add(1);
+ }
+ Ok(this_lit)
+ }
+
+ pub fn variant_discr_type(&self) -> &Option<Type> {
+ &self.discr_type
+ }
+
pub fn is_flat(&self) -> bool {
self.flat
}
+ pub fn is_non_exhaustive(&self) -> bool {
+ self.non_exhaustive
+ }
+
pub fn iter_types(&self) -> TypeIterator<'_> {
Box::new(self.variants.iter().flat_map(Variant::iter_types))
}
+ pub fn docstring(&self) -> Option<&str> {
+ self.docstring.as_deref()
+ }
+
// Sadly can't use TryFrom due to the 'is_flat' complication.
pub fn try_from_meta(meta: uniffi_meta::EnumMetadata, flat: bool) -> Result<Self> {
// This is messy - error enums are considered "flat" if the user
@@ -218,12 +273,15 @@ impl Enum {
Ok(Self {
name: meta.name,
module_path: meta.module_path,
+ discr_type: meta.discr_type,
variants: meta
.variants
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_>>()?,
flat,
+ non_exhaustive: meta.non_exhaustive,
+ docstring: meta.docstring.clone(),
})
}
}
@@ -243,7 +301,10 @@ impl AsType for Enum {
#[derive(Debug, Clone, Default, PartialEq, Eq, Checksum)]
pub struct Variant {
pub(super) name: String,
+ pub(super) discr: Option<Literal>,
pub(super) fields: Vec<Field>,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
}
impl Variant {
@@ -259,6 +320,14 @@ impl Variant {
!self.fields.is_empty()
}
+ pub fn has_nameless_fields(&self) -> bool {
+ self.fields.iter().any(|f| f.name.is_empty())
+ }
+
+ pub fn docstring(&self) -> Option<&str> {
+ self.docstring.as_deref()
+ }
+
pub fn iter_types(&self) -> TypeIterator<'_> {
Box::new(self.fields.iter().flat_map(Field::iter_types))
}
@@ -270,11 +339,13 @@ impl TryFrom<uniffi_meta::VariantMetadata> for Variant {
fn try_from(meta: uniffi_meta::VariantMetadata) -> Result<Self> {
Ok(Self {
name: meta.name,
+ discr: meta.discr,
fields: meta
.fields
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_>>()?,
+ docstring: meta.docstring.clone(),
})
}
}
@@ -447,7 +518,10 @@ mod test {
#[test]
fn test_variants() {
const UDL: &str = r#"
- namespace test{};
+ namespace test{
+ [Throws=Testing]
+ void func();
+ };
[Error]
enum Testing { "one", "two", "three" };
"#;
@@ -486,7 +560,10 @@ mod test {
#[test]
fn test_variant_data() {
const UDL: &str = r#"
- namespace test{};
+ namespace test{
+ [Throws=Testing]
+ void func();
+ };
[Error]
interface Testing {
@@ -564,4 +641,141 @@ mod test {
vec!["Normal", "Error"]
);
}
+
+ fn variant(val: Option<u64>) -> Variant {
+ Variant {
+ name: "v".to_string(),
+ discr: val.map(Literal::new_uint),
+ fields: vec![],
+ docstring: None,
+ }
+ }
+
+ fn check_discrs(e: &mut Enum, vs: Vec<Variant>) -> Vec<u64> {
+ e.variants = vs;
+ (0..e.variants.len())
+ .map(|i| e.variant_discr(i).unwrap())
+ .map(|l| match l {
+ Literal::UInt(v, _, _) => v,
+ _ => unreachable!(),
+ })
+ .collect()
+ }
+
+ #[test]
+ fn test_variant_values() {
+ let mut e = Enum {
+ module_path: "test".to_string(),
+ name: "test".to_string(),
+ discr_type: None,
+ variants: vec![],
+ flat: false,
+ non_exhaustive: false,
+ docstring: None,
+ };
+
+ assert!(e.variant_discr(0).is_err());
+
+ // single values
+ assert_eq!(check_discrs(&mut e, vec![variant(None)]), vec![0]);
+ assert_eq!(check_discrs(&mut e, vec![variant(Some(3))]), vec![3]);
+
+ // no values
+ assert_eq!(
+ check_discrs(&mut e, vec![variant(None), variant(None)]),
+ vec![0, 1]
+ );
+
+ // values
+ assert_eq!(
+ check_discrs(&mut e, vec![variant(Some(1)), variant(Some(3))]),
+ vec![1, 3]
+ );
+
+ // mixed values
+ assert_eq!(
+ check_discrs(&mut e, vec![variant(None), variant(Some(3)), variant(None)]),
+ vec![0, 3, 4]
+ );
+
+ assert_eq!(
+ check_discrs(
+ &mut e,
+ vec![variant(Some(4)), variant(None), variant(Some(1))]
+ ),
+ vec![4, 5, 1]
+ );
+ }
+
+ #[test]
+ fn test_docstring_enum() {
+ const UDL: &str = r#"
+ namespace test{};
+ /// informative docstring
+ enum Testing { "foo" };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(
+ ci.get_enum_definition("Testing")
+ .unwrap()
+ .docstring()
+ .unwrap(),
+ "informative docstring"
+ );
+ }
+
+ #[test]
+ fn test_docstring_enum_variant() {
+ const UDL: &str = r#"
+ namespace test{};
+ enum Testing {
+ /// informative docstring
+ "foo"
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(
+ ci.get_enum_definition("Testing").unwrap().variants()[0]
+ .docstring()
+ .unwrap(),
+ "informative docstring"
+ );
+ }
+
+ #[test]
+ fn test_docstring_associated_enum() {
+ const UDL: &str = r#"
+ namespace test{};
+ /// informative docstring
+ [Enum]
+ interface Testing { };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(
+ ci.get_enum_definition("Testing")
+ .unwrap()
+ .docstring()
+ .unwrap(),
+ "informative docstring"
+ );
+ }
+
+ #[test]
+ fn test_docstring_associated_enum_variant() {
+ const UDL: &str = r#"
+ namespace test{};
+ [Enum]
+ interface Testing {
+ /// informative docstring
+ testing();
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(
+ ci.get_enum_definition("Testing").unwrap().variants()[0]
+ .docstring()
+ .unwrap(),
+ "informative docstring"
+ );
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/ffi.rs b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs
index d18aaf8262..b27cb78477 100644
--- a/third_party/rust/uniffi_bindgen/src/interface/ffi.rs
+++ b/third_party/rust/uniffi_bindgen/src/interface/ffi.rs
@@ -47,20 +47,49 @@ pub enum FfiType {
/// A borrowed reference to some raw bytes owned by foreign language code.
/// The provider of this reference must keep it alive for the duration of the receiving call.
ForeignBytes,
- /// Pointer to a callback function that handles all callbacks on the foreign language side.
- ForeignCallback,
- /// Pointer-sized opaque handle that represents a foreign executor. Foreign bindings can
- /// either use an actual pointer or a usized integer.
- ForeignExecutorHandle,
- /// Pointer to the callback function that's invoked to schedule calls with a ForeignExecutor
- ForeignExecutorCallback,
- /// Pointer to a Rust future
- RustFutureHandle,
- /// Continuation function for a Rust future
- RustFutureContinuationCallback,
- RustFutureContinuationData,
- // TODO: you can imagine a richer structural typesystem here, e.g. `Ref<String>` or something.
- // We don't need that yet and it's possible we never will, so it isn't here for now.
+ /// Pointer to a callback function. The inner value which matches one of the callback
+ /// definitions in [crate::ComponentInterface::ffi_definitions].
+ Callback(String),
+ /// Pointer to a FFI struct (e.g. a VTable). The inner value matches one of the struct
+ /// definitions in [crate::ComponentInterface::ffi_definitions].
+ Struct(String),
+ /// Opaque 64-bit handle
+ ///
+ /// These are used to pass objects across the FFI.
+ Handle,
+ RustCallStatus,
+ /// Pointer to an FfiType.
+ Reference(Box<FfiType>),
+ /// Opaque pointer
+ VoidPointer,
+}
+
+impl FfiType {
+ pub fn reference(self) -> FfiType {
+ FfiType::Reference(Box::new(self))
+ }
+
+ /// Unique name for an FFI return type
+ pub fn return_type_name(return_type: Option<&FfiType>) -> String {
+ match return_type {
+ Some(t) => match t {
+ FfiType::UInt8 => "u8".to_owned(),
+ FfiType::Int8 => "i8".to_owned(),
+ FfiType::UInt16 => "u16".to_owned(),
+ FfiType::Int16 => "i16".to_owned(),
+ FfiType::UInt32 => "u32".to_owned(),
+ FfiType::Int32 => "i32".to_owned(),
+ FfiType::UInt64 => "u64".to_owned(),
+ FfiType::Int64 => "i64".to_owned(),
+ FfiType::Float32 => "f32".to_owned(),
+ FfiType::Float64 => "f64".to_owned(),
+ FfiType::RustArcPtr(_) => "pointer".to_owned(),
+ FfiType::RustBuffer(_) => "rust_buffer".to_owned(),
+ _ => unimplemented!("FFI return type: {t:?}"),
+ },
+ None => "void".to_owned(),
+ }
+ }
}
/// When passing data across the FFI, each `Type` value will be lowered into a corresponding
@@ -94,7 +123,6 @@ impl From<&Type> for FfiType {
Type::Object { name, .. } => FfiType::RustArcPtr(name.to_owned()),
// Callback interfaces are passed as opaque integer handles.
Type::CallbackInterface { .. } => FfiType::UInt64,
- Type::ForeignExecutor => FfiType::ForeignExecutorHandle,
// Other types are serialized into a bytebuffer and deserialized on the other side.
Type::Enum { .. }
| Type::Record { .. }
@@ -107,6 +135,11 @@ impl From<&Type> for FfiType {
name,
kind: ExternalKind::Interface,
..
+ }
+ | Type::External {
+ name,
+ kind: ExternalKind::Trait,
+ ..
} => FfiType::RustArcPtr(name.clone()),
Type::External {
name,
@@ -131,6 +164,24 @@ impl From<&&Type> for FfiType {
}
}
+/// An Ffi definition
+#[derive(Debug, Clone)]
+pub enum FfiDefinition {
+ Function(FfiFunction),
+ CallbackFunction(FfiCallbackFunction),
+ Struct(FfiStruct),
+}
+
+impl FfiDefinition {
+ pub fn name(&self) -> &str {
+ match self {
+ Self::Function(f) => f.name(),
+ Self::CallbackFunction(f) => f.name(),
+ Self::Struct(s) => s.name(),
+ }
+ }
+}
+
/// Represents an "extern C"-style function that will be part of the FFI.
///
/// These can't be declared explicitly in the UDL, but rather, are derived automatically
@@ -150,6 +201,19 @@ pub struct FfiFunction {
}
impl FfiFunction {
+ pub fn callback_init(module_path: &str, trait_name: &str, vtable_name: String) -> Self {
+ Self {
+ name: uniffi_meta::init_callback_vtable_fn_symbol_name(module_path, trait_name),
+ arguments: vec![FfiArgument {
+ name: "vtable".to_string(),
+ type_: FfiType::Struct(vtable_name).reference(),
+ }],
+ return_type: None,
+ has_rust_call_status_arg: false,
+ ..Self::default()
+ }
+ }
+
pub fn name(&self) -> &str {
&self.name
}
@@ -181,7 +245,7 @@ impl FfiFunction {
) {
self.arguments = args.into_iter().collect();
if self.is_async() {
- self.return_type = Some(FfiType::RustFutureHandle);
+ self.return_type = Some(FfiType::Handle);
self.has_rust_call_status_arg = false;
} else {
self.return_type = return_type;
@@ -212,14 +276,113 @@ pub struct FfiArgument {
}
impl FfiArgument {
+ pub fn new(name: impl Into<String>, type_: FfiType) -> Self {
+ Self {
+ name: name.into(),
+ type_,
+ }
+ }
+
pub fn name(&self) -> &str {
&self.name
}
+
pub fn type_(&self) -> FfiType {
self.type_.clone()
}
}
+/// Represents an "extern C"-style callback function
+///
+/// These are defined in the foreign code and passed to Rust as a function pointer.
+#[derive(Debug, Default, Clone)]
+pub struct FfiCallbackFunction {
+ // Name for this function type. This matches the value inside `FfiType::Callback`
+ pub(super) name: String,
+ pub(super) arguments: Vec<FfiArgument>,
+ pub(super) return_type: Option<FfiType>,
+ pub(super) has_rust_call_status_arg: bool,
+}
+
+impl FfiCallbackFunction {
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn arguments(&self) -> Vec<&FfiArgument> {
+ self.arguments.iter().collect()
+ }
+
+ pub fn return_type(&self) -> Option<&FfiType> {
+ self.return_type.as_ref()
+ }
+
+ pub fn has_rust_call_status_arg(&self) -> bool {
+ self.has_rust_call_status_arg
+ }
+}
+
+/// Represents a repr(C) struct used in the FFI
+#[derive(Debug, Default, Clone)]
+pub struct FfiStruct {
+ pub(super) name: String,
+ pub(super) fields: Vec<FfiField>,
+}
+
+impl FfiStruct {
+ /// Get the name of this struct
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ /// Get the fields for this struct
+ pub fn fields(&self) -> &[FfiField] {
+ &self.fields
+ }
+}
+
+/// Represents a field of an [FfiStruct]
+#[derive(Debug, Clone)]
+pub struct FfiField {
+ pub(super) name: String,
+ pub(super) type_: FfiType,
+}
+
+impl FfiField {
+ pub fn new(name: impl Into<String>, type_: FfiType) -> Self {
+ Self {
+ name: name.into(),
+ type_,
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn type_(&self) -> FfiType {
+ self.type_.clone()
+ }
+}
+
+impl From<FfiFunction> for FfiDefinition {
+ fn from(value: FfiFunction) -> FfiDefinition {
+ FfiDefinition::Function(value)
+ }
+}
+
+impl From<FfiStruct> for FfiDefinition {
+ fn from(value: FfiStruct) -> FfiDefinition {
+ FfiDefinition::Struct(value)
+ }
+}
+
+impl From<FfiCallbackFunction> for FfiDefinition {
+ fn from(value: FfiCallbackFunction) -> FfiDefinition {
+ FfiDefinition::CallbackFunction(value)
+ }
+}
+
#[cfg(test)]
mod test {
// There's not really much to test here to be honest,
diff --git a/third_party/rust/uniffi_bindgen/src/interface/function.rs b/third_party/rust/uniffi_bindgen/src/interface/function.rs
index 2d18288c1c..8effc4c876 100644
--- a/third_party/rust/uniffi_bindgen/src/interface/function.rs
+++ b/third_party/rust/uniffi_bindgen/src/interface/function.rs
@@ -59,6 +59,8 @@ pub struct Function {
// avoids a weird circular dependency in the calculation.
#[checksum_ignore]
pub(super) ffi_func: FfiFunction,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
pub(super) throws: Option<Type>,
pub(super) checksum_fn_name: String,
// Force a checksum value, or we'll fallback to the trait.
@@ -128,6 +130,10 @@ impl Function {
.chain(self.return_type.iter().flat_map(Type::iter_types)),
)
}
+
+ pub fn docstring(&self) -> Option<&str> {
+ self.docstring.as_deref()
+ }
}
impl From<uniffi_meta::FnParamMetadata> for Argument {
@@ -163,6 +169,7 @@ impl From<uniffi_meta::FnMetadata> for Function {
arguments,
return_type,
ffi_func,
+ docstring: meta.docstring.clone(),
throws: meta.throws,
checksum_fn_name,
checksum: meta.checksum,
@@ -242,6 +249,9 @@ pub trait Callable {
fn return_type(&self) -> Option<Type>;
fn throws_type(&self) -> Option<Type>;
fn is_async(&self) -> bool;
+ fn takes_self(&self) -> bool {
+ false
+ }
fn result_type(&self) -> ResultType {
ResultType {
return_type: self.return_type(),
@@ -311,6 +321,10 @@ impl<T: Callable> Callable for &T {
fn is_async(&self) -> bool {
(*self).is_async()
}
+
+ fn takes_self(&self) -> bool {
+ (*self).takes_self()
+ }
}
#[cfg(test)]
@@ -364,4 +378,22 @@ mod test {
);
Ok(())
}
+
+ #[test]
+ fn test_docstring_function() {
+ const UDL: &str = r#"
+ namespace test {
+ /// informative docstring
+ void testing();
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(
+ ci.get_function_definition("testing")
+ .unwrap()
+ .docstring()
+ .unwrap(),
+ "informative docstring"
+ );
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/mod.rs b/third_party/rust/uniffi_bindgen/src/interface/mod.rs
index 8e4df2149b..90a941637a 100644
--- a/third_party/rust/uniffi_bindgen/src/interface/mod.rs
+++ b/third_party/rust/uniffi_bindgen/src/interface/mod.rs
@@ -67,7 +67,9 @@ mod record;
pub use record::{Field, Record};
pub mod ffi;
-pub use ffi::{FfiArgument, FfiFunction, FfiType};
+pub use ffi::{
+ FfiArgument, FfiCallbackFunction, FfiDefinition, FfiField, FfiFunction, FfiStruct, FfiType,
+};
pub use uniffi_meta::Radix;
use uniffi_meta::{
ConstructorMetadata, LiteralMetadata, NamespaceMetadata, ObjectMetadata, TraitMethodMetadata,
@@ -139,6 +141,11 @@ impl ComponentInterface {
self.types.namespace
);
}
+
+ if group.namespace_docstring.is_some() {
+ self.types.namespace_docstring = group.namespace_docstring.clone();
+ }
+
// Unconditionally add the String type, which is used by the panic handling
self.types.add_known_type(&uniffi_meta::Type::String)?;
crate::macro_metadata::add_group_to_ci(self, group)?;
@@ -153,6 +160,10 @@ impl ComponentInterface {
&self.types.namespace.name
}
+ pub fn namespace_docstring(&self) -> Option<&str> {
+ self.types.namespace_docstring.as_deref()
+ }
+
pub fn uniffi_contract_version(&self) -> u32 {
// This is set by the scripts in the version-mismatch fixture
let force_version = std::env::var("UNIFFI_FORCE_CONTRACT_VERSION");
@@ -204,6 +215,29 @@ impl ComponentInterface {
self.objects.iter().find(|o| o.name == name)
}
+ fn callback_interface_callback_definitions(
+ &self,
+ ) -> impl IntoIterator<Item = FfiCallbackFunction> + '_ {
+ self.callback_interfaces
+ .iter()
+ .flat_map(|cbi| cbi.ffi_callbacks())
+ .chain(self.objects.iter().flat_map(|o| o.ffi_callbacks()))
+ }
+
+ /// Get the definitions for callback FFI functions
+ ///
+ /// These are defined by the foreign code and invoked by Rust.
+ fn callback_interface_vtable_definitions(&self) -> impl IntoIterator<Item = FfiStruct> + '_ {
+ self.callback_interface_definitions()
+ .iter()
+ .map(|cbi| cbi.vtable_definition())
+ .chain(
+ self.object_definitions()
+ .iter()
+ .flat_map(|o| o.vtable_definition()),
+ )
+ }
+
/// Get the definitions for every Callback Interface type in the interface.
pub fn callback_interface_definitions(&self) -> &[CallbackInterface] {
&self.callback_interfaces
@@ -215,6 +249,17 @@ impl ComponentInterface {
self.callback_interfaces.iter().find(|o| o.name == name)
}
+ /// Get the definitions for every Callback Interface type in the interface.
+ pub fn has_async_callback_interface_definition(&self) -> bool {
+ self.callback_interfaces
+ .iter()
+ .any(|cbi| cbi.has_async_method())
+ || self
+ .objects
+ .iter()
+ .any(|o| o.has_callback_interface() && o.has_async_method())
+ }
+
/// Get the definitions for every Method type in the interface.
pub fn iter_callables(&self) -> impl Iterator<Item = &dyn Callable> {
// Each of the `as &dyn Callable` casts is a trivial cast, but it seems like the clearest
@@ -241,13 +286,19 @@ impl ComponentInterface {
let fielded = !e.is_flat();
// For flat errors, we should only generate read() methods if we need them to support
// callback interface errors
- let used_in_callback_interface = self
+ let used_in_foreign_interface = self
.callback_interface_definitions()
.iter()
.flat_map(|cb| cb.methods())
+ .chain(
+ self.object_definitions()
+ .iter()
+ .filter(|o| o.has_callback_interface())
+ .flat_map(|o| o.methods()),
+ )
.any(|m| m.throws_type() == Some(&e.as_type()));
- self.is_name_used_as_error(&e.name) && (fielded || used_in_callback_interface)
+ self.is_name_used_as_error(&e.name) && (fielded || used_in_foreign_interface)
}
/// Get details about all `Type::External` types.
@@ -304,8 +355,17 @@ impl ComponentInterface {
/// This is important to know in language bindings that cannot integrate object types
/// tightly with the host GC, and hence need to perform manual destruction of objects.
pub fn item_contains_object_references(&self, item: &Type) -> bool {
- self.iter_types_in_item(item)
- .any(|t| matches!(t, Type::Object { .. }))
+ // this is surely broken for external records with object refs?
+ self.iter_types_in_item(item).any(|t| {
+ matches!(
+ t,
+ Type::Object { .. }
+ | Type::External {
+ kind: ExternalKind::Interface,
+ ..
+ }
+ )
+ })
}
/// Check whether the given item contains any (possibly nested) unsigned types
@@ -335,6 +395,13 @@ impl ComponentInterface {
.any(|t| matches!(t, Type::Map { .. }))
}
+ /// Check whether the interface contains any object types
+ pub fn contains_object_types(&self) -> bool {
+ self.types
+ .iter_known_types()
+ .any(|t| matches!(t, Type::Object { .. }))
+ }
+
// The namespace to use in crate-level FFI function definitions. Not used as the ffi
// namespace for types - each type has its own `module_path` which is used for them.
fn ffi_namespace(&self) -> &str {
@@ -364,7 +431,7 @@ impl ComponentInterface {
is_async: false,
arguments: vec![FfiArgument {
name: "size".to_string(),
- type_: FfiType::Int32,
+ type_: FfiType::UInt64,
}],
return_type: Some(FfiType::RustBuffer(None)),
has_rust_call_status_arg: true,
@@ -420,7 +487,7 @@ impl ComponentInterface {
},
FfiArgument {
name: "additional".to_string(),
- type_: FfiType::Int32,
+ type_: FfiType::UInt64,
},
],
return_type: Some(FfiType::RustBuffer(None)),
@@ -429,24 +496,6 @@ impl ComponentInterface {
}
}
- /// Builtin FFI function to set the Rust Future continuation callback
- pub fn ffi_rust_future_continuation_callback_set(&self) -> FfiFunction {
- FfiFunction {
- name: format!(
- "ffi_{}_rust_future_continuation_callback_set",
- self.ffi_namespace()
- ),
- arguments: vec![FfiArgument {
- name: "callback".to_owned(),
- type_: FfiType::RustFutureContinuationCallback,
- }],
- return_type: None,
- is_async: false,
- has_rust_call_status_arg: false,
- is_object_free_function: false,
- }
- }
-
/// Builtin FFI function to poll a Rust future.
pub fn ffi_rust_future_poll(&self, return_ffi_type: Option<FfiType>) -> FfiFunction {
FfiFunction {
@@ -455,12 +504,15 @@ impl ComponentInterface {
arguments: vec![
FfiArgument {
name: "handle".to_owned(),
- type_: FfiType::RustFutureHandle,
+ type_: FfiType::Handle,
+ },
+ FfiArgument {
+ name: "callback".to_owned(),
+ type_: FfiType::Callback("RustFutureContinuationCallback".to_owned()),
},
- // Data to pass to the continuation
FfiArgument {
- name: "uniffi_callback".to_owned(),
- type_: FfiType::RustFutureContinuationData,
+ name: "callback_data".to_owned(),
+ type_: FfiType::Handle,
},
],
return_type: None,
@@ -478,7 +530,7 @@ impl ComponentInterface {
is_async: false,
arguments: vec![FfiArgument {
name: "handle".to_owned(),
- type_: FfiType::RustFutureHandle,
+ type_: FfiType::Handle,
}],
return_type: return_ffi_type,
has_rust_call_status_arg: true,
@@ -493,7 +545,7 @@ impl ComponentInterface {
is_async: false,
arguments: vec![FfiArgument {
name: "handle".to_owned(),
- type_: FfiType::RustFutureHandle,
+ type_: FfiType::Handle,
}],
return_type: None,
has_rust_call_status_arg: false,
@@ -508,7 +560,7 @@ impl ComponentInterface {
is_async: false,
arguments: vec![FfiArgument {
name: "handle".to_owned(),
- type_: FfiType::RustFutureHandle,
+ type_: FfiType::Handle,
}],
return_type: None,
has_rust_call_status_arg: false,
@@ -518,29 +570,17 @@ impl ComponentInterface {
fn rust_future_ffi_fn_name(&self, base_name: &str, return_ffi_type: Option<FfiType>) -> String {
let namespace = self.ffi_namespace();
- match return_ffi_type {
- Some(t) => match t {
- FfiType::UInt8 => format!("ffi_{namespace}_{base_name}_u8"),
- FfiType::Int8 => format!("ffi_{namespace}_{base_name}_i8"),
- FfiType::UInt16 => format!("ffi_{namespace}_{base_name}_u16"),
- FfiType::Int16 => format!("ffi_{namespace}_{base_name}_i16"),
- FfiType::UInt32 => format!("ffi_{namespace}_{base_name}_u32"),
- FfiType::Int32 => format!("ffi_{namespace}_{base_name}_i32"),
- FfiType::UInt64 => format!("ffi_{namespace}_{base_name}_u64"),
- FfiType::Int64 => format!("ffi_{namespace}_{base_name}_i64"),
- FfiType::Float32 => format!("ffi_{namespace}_{base_name}_f32"),
- FfiType::Float64 => format!("ffi_{namespace}_{base_name}_f64"),
- FfiType::RustArcPtr(_) => format!("ffi_{namespace}_{base_name}_pointer"),
- FfiType::RustBuffer(_) => format!("ffi_{namespace}_{base_name}_rust_buffer"),
- _ => unimplemented!("FFI return type: {t:?}"),
- },
- None => format!("ffi_{namespace}_{base_name}_void"),
- }
+ let return_type_name = FfiType::return_type_name(return_ffi_type.as_ref());
+ format!("ffi_{namespace}_{base_name}_{return_type_name}")
}
/// Does this interface contain async functions?
pub fn has_async_fns(&self) -> bool {
self.iter_ffi_function_definitions().any(|f| f.is_async())
+ || self
+ .callback_interfaces
+ .iter()
+ .any(CallbackInterface::has_async_method)
}
/// Iterate over `T` parameters of the `FutureCallback<T>` callbacks in this interface
@@ -561,6 +601,73 @@ impl ComponentInterface {
unique_results.into_iter()
}
+ /// Iterate over all Ffi definitions
+ pub fn ffi_definitions(&self) -> impl Iterator<Item = FfiDefinition> + '_ {
+ // Note: for languages like Python it's important to keep things in dependency order.
+ // For example some FFI function definitions depend on FFI struct definitions, so the
+ // function definitions come last.
+ self.builtin_ffi_definitions()
+ .into_iter()
+ .chain(
+ self.callback_interface_callback_definitions()
+ .into_iter()
+ .map(Into::into),
+ )
+ .chain(
+ self.callback_interface_vtable_definitions()
+ .into_iter()
+ .map(Into::into),
+ )
+ .chain(self.iter_ffi_function_definitions().map(Into::into))
+ }
+
+ fn builtin_ffi_definitions(&self) -> impl IntoIterator<Item = FfiDefinition> + '_ {
+ [
+ FfiCallbackFunction {
+ name: "RustFutureContinuationCallback".to_owned(),
+ arguments: vec![
+ FfiArgument::new("data", FfiType::UInt64),
+ FfiArgument::new("poll_result", FfiType::Int8),
+ ],
+ return_type: None,
+ has_rust_call_status_arg: false,
+ }
+ .into(),
+ FfiCallbackFunction {
+ name: "ForeignFutureFree".to_owned(),
+ arguments: vec![FfiArgument::new("handle", FfiType::UInt64)],
+ return_type: None,
+ has_rust_call_status_arg: false,
+ }
+ .into(),
+ FfiCallbackFunction {
+ name: "CallbackInterfaceFree".to_owned(),
+ arguments: vec![FfiArgument::new("handle", FfiType::UInt64)],
+ return_type: None,
+ has_rust_call_status_arg: false,
+ }
+ .into(),
+ FfiStruct {
+ name: "ForeignFuture".to_owned(),
+ fields: vec![
+ FfiField::new("handle", FfiType::UInt64),
+ FfiField::new("free", FfiType::Callback("ForeignFutureFree".to_owned())),
+ ],
+ }
+ .into(),
+ ]
+ .into_iter()
+ .chain(
+ self.all_possible_return_ffi_types()
+ .flat_map(|return_type| {
+ [
+ callbacks::foreign_future_ffi_result_struct(return_type.clone()).into(),
+ callbacks::ffi_foreign_future_complete(return_type).into(),
+ ]
+ }),
+ )
+ }
+
/// List the definitions of all FFI functions in the interface.
///
/// The set of FFI functions is derived automatically from the set of higher-level types
@@ -569,9 +676,8 @@ impl ComponentInterface {
self.iter_user_ffi_function_definitions()
.cloned()
.chain(self.iter_rust_buffer_ffi_function_definitions())
- .chain(self.iter_futures_ffi_function_definitons())
+ .chain(self.iter_futures_ffi_function_definitions())
.chain(self.iter_checksum_ffi_functions())
- .chain(self.ffi_foreign_executor_callback_set())
.chain([self.ffi_uniffi_contract_version()])
}
@@ -618,9 +724,8 @@ impl ComponentInterface {
.into_iter()
}
- /// List all FFI functions definitions for async functionality.
- pub fn iter_futures_ffi_function_definitons(&self) -> impl Iterator<Item = FfiFunction> + '_ {
- let all_possible_return_ffi_types = [
+ fn all_possible_return_ffi_types(&self) -> impl Iterator<Item = Option<FfiType>> {
+ [
Some(FfiType::UInt8),
Some(FfiType::Int8),
Some(FfiType::UInt16),
@@ -631,46 +736,26 @@ impl ComponentInterface {
Some(FfiType::Int64),
Some(FfiType::Float32),
Some(FfiType::Float64),
- // RustBuffer and RustArcPtr have an inner field which doesn't affect the rust future
- // complete scaffolding function, so we just use a placeholder value here.
+ // RustBuffer and RustArcPtr have an inner field which we have to fill in with a
+ // placeholder value.
Some(FfiType::RustArcPtr("".to_owned())),
Some(FfiType::RustBuffer(None)),
None,
- ];
-
- iter::once(self.ffi_rust_future_continuation_callback_set()).chain(
- all_possible_return_ffi_types
- .into_iter()
- .flat_map(|return_type| {
- [
- self.ffi_rust_future_poll(return_type.clone()),
- self.ffi_rust_future_cancel(return_type.clone()),
- self.ffi_rust_future_free(return_type.clone()),
- self.ffi_rust_future_complete(return_type),
- ]
- }),
- )
+ ]
+ .into_iter()
}
- /// The ffi_foreign_executor_callback_set FFI function
- ///
- /// We only include this in the FFI if the `ForeignExecutor` type is actually used
- pub fn ffi_foreign_executor_callback_set(&self) -> Option<FfiFunction> {
- if self.types.contains(&Type::ForeignExecutor) {
- Some(FfiFunction {
- name: format!("ffi_{}_foreign_executor_callback_set", self.ffi_namespace()),
- arguments: vec![FfiArgument {
- name: "callback".into(),
- type_: FfiType::ForeignExecutorCallback,
- }],
- return_type: None,
- is_async: false,
- has_rust_call_status_arg: false,
- is_object_free_function: false,
+ /// List all FFI functions definitions for async functionality.
+ pub fn iter_futures_ffi_function_definitions(&self) -> impl Iterator<Item = FfiFunction> + '_ {
+ self.all_possible_return_ffi_types()
+ .flat_map(|return_type| {
+ [
+ self.ffi_rust_future_poll(return_type.clone()),
+ self.ffi_rust_future_cancel(return_type.clone()),
+ self.ffi_rust_future_free(return_type.clone()),
+ self.ffi_rust_future_complete(return_type),
+ ]
})
- } else {
- None
- }
}
/// List all API checksums to check
@@ -778,6 +863,8 @@ impl ComponentInterface {
bail!("Conflicting type definition for \"{}\"", defn.name());
}
self.types.add_known_types(defn.iter_types())?;
+ defn.throws_name()
+ .map(|n| self.errors.insert(n.to_string()));
self.functions.push(defn);
Ok(())
@@ -789,6 +876,8 @@ impl ComponentInterface {
let defn: Constructor = meta.into();
self.types.add_known_types(defn.iter_types())?;
+ defn.throws_name()
+ .map(|n| self.errors.insert(n.to_string()));
object.constructors.push(defn);
Ok(())
@@ -800,6 +889,9 @@ impl ComponentInterface {
.ok_or_else(|| anyhow!("add_method_meta: object {} not found", &method.object_name))?;
self.types.add_known_types(method.iter_types())?;
+ method
+ .throws_name()
+ .map(|n| self.errors.insert(n.to_string()));
method.object_impl = object.imp;
object.methods.push(method);
Ok(())
@@ -825,10 +917,6 @@ impl ComponentInterface {
Ok(())
}
- pub(super) fn note_name_used_as_error(&mut self, name: &str) {
- self.errors.insert(name.to_string());
- }
-
pub fn is_name_used_as_error(&self, name: &str) -> bool {
self.errors.contains(name)
}
@@ -856,6 +944,9 @@ impl ComponentInterface {
self.callback_interface_throws_types.insert(error.clone());
}
self.types.add_known_types(method.iter_types())?;
+ method
+ .throws_name()
+ .map(|n| self.errors.insert(n.to_string()));
cbi.methods.push(method);
} else {
self.add_method_meta(meta)?;
@@ -880,31 +971,6 @@ impl ComponentInterface {
bail!("Conflicting type definition for \"{}\"", f.name());
}
}
-
- for ty in self.iter_types() {
- match ty {
- Type::Object { name, .. } => {
- ensure!(
- self.objects.iter().any(|o| o.name == *name),
- "Object `{name}` has no definition"
- );
- }
- Type::Record { name, .. } => {
- ensure!(
- self.records.contains_key(name),
- "Record `{name}` has no definition",
- );
- }
- Type::Enum { name, .. } => {
- ensure!(
- self.enums.contains_key(name),
- "Enum `{name}` has no definition",
- );
- }
- _ => {}
- }
- }
-
Ok(())
}
@@ -1047,7 +1113,7 @@ fn throws_name(throws: &Option<Type>) -> Option<&str> {
// Type has no `name()` method, just `canonical_name()` which isn't what we want.
match throws {
None => None,
- Some(Type::Enum { name, .. }) => Some(name),
+ Some(Type::Enum { name, .. }) | Some(Type::Object { name, .. }) => Some(name),
_ => panic!("unknown throw type: {throws:?}"),
}
}
@@ -1089,35 +1155,50 @@ mod test {
let err = ComponentInterface::from_webidl(UDL2, "crate_name").unwrap_err();
assert_eq!(
err.to_string(),
- "Mismatching definition for enum `Testing`!\nexisting definition: Enum {
+ "Mismatching definition for enum `Testing`!
+existing definition: Enum {
name: \"Testing\",
module_path: \"crate_name\",
+ discr_type: None,
variants: [
Variant {
name: \"one\",
+ discr: None,
fields: [],
+ docstring: None,
},
Variant {
name: \"two\",
+ discr: None,
fields: [],
+ docstring: None,
},
],
flat: true,
+ non_exhaustive: false,
+ docstring: None,
},
new definition: Enum {
name: \"Testing\",
module_path: \"crate_name\",
+ discr_type: None,
variants: [
Variant {
name: \"three\",
+ discr: None,
fields: [],
+ docstring: None,
},
Variant {
name: \"four\",
+ discr: None,
fields: [],
+ docstring: None,
},
],
flat: true,
+ non_exhaustive: false,
+ docstring: None,
}",
);
@@ -1231,4 +1312,25 @@ new definition: Enum {
imp: ObjectImpl::Struct,
}));
}
+
+ #[test]
+ fn test_docstring_namespace() {
+ const UDL: &str = r#"
+ /// informative docstring
+ namespace test{};
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(ci.namespace_docstring().unwrap(), "informative docstring");
+ }
+
+ #[test]
+ fn test_multiline_docstring() {
+ const UDL: &str = r#"
+ /// informative
+ /// docstring
+ namespace test{};
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(ci.namespace_docstring().unwrap(), "informative\ndocstring");
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/object.rs b/third_party/rust/uniffi_bindgen/src/interface/object.rs
index 942032b3c6..2b86e54a45 100644
--- a/third_party/rust/uniffi_bindgen/src/interface/object.rs
+++ b/third_party/rust/uniffi_bindgen/src/interface/object.rs
@@ -57,12 +57,11 @@
//! # Ok::<(), anyhow::Error>(())
//! ```
-use std::iter;
-
use anyhow::Result;
use uniffi_meta::Checksum;
-use super::ffi::{FfiArgument, FfiFunction, FfiType};
+use super::callbacks;
+use super::ffi::{FfiArgument, FfiCallbackFunction, FfiFunction, FfiStruct, FfiType};
use super::function::{Argument, Callable};
use super::{AsType, ObjectImpl, Type, TypeIterator};
@@ -92,14 +91,24 @@ pub struct Object {
// a regular method (albeit with a generated name)
// XXX - this should really be a HashSet, but not enough transient types support hash to make it worthwhile now.
pub(super) uniffi_traits: Vec<UniffiTrait>,
- // We don't include the FfiFunc in the hash calculation, because:
+ // We don't include the FfiFuncs in the hash calculation, because:
// - it is entirely determined by the other fields,
// so excluding it is safe.
// - its `name` property includes a checksum derived from the very
// hash value we're trying to calculate here, so excluding it
// avoids a weird circular dependency in the calculation.
+
+ // FFI function to clone a pointer for this object
+ #[checksum_ignore]
+ pub(super) ffi_func_clone: FfiFunction,
+ // FFI function to free a pointer for this object
#[checksum_ignore]
pub(super) ffi_func_free: FfiFunction,
+ // Ffi function to initialize the foreign callback for trait interfaces
+ #[checksum_ignore]
+ pub(super) ffi_init_callback: Option<FfiFunction>,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
}
impl Object {
@@ -118,6 +127,18 @@ impl Object {
&self.imp
}
+ pub fn is_trait_interface(&self) -> bool {
+ self.imp.is_trait_interface()
+ }
+
+ pub fn has_callback_interface(&self) -> bool {
+ self.imp.has_callback_interface()
+ }
+
+ pub fn has_async_method(&self) -> bool {
+ self.methods.iter().any(Method::is_async)
+ }
+
pub fn constructors(&self) -> Vec<&Constructor> {
self.constructors.iter().collect()
}
@@ -151,12 +172,28 @@ impl Object {
self.uniffi_traits.iter().collect()
}
+ pub fn ffi_object_clone(&self) -> &FfiFunction {
+ &self.ffi_func_clone
+ }
+
pub fn ffi_object_free(&self) -> &FfiFunction {
&self.ffi_func_free
}
+ pub fn ffi_init_callback(&self) -> &FfiFunction {
+ self.ffi_init_callback
+ .as_ref()
+ .unwrap_or_else(|| panic!("No ffi_init_callback set for {}", &self.name))
+ }
+
+ pub fn docstring(&self) -> Option<&str> {
+ self.docstring.as_deref()
+ }
+
pub fn iter_ffi_function_definitions(&self) -> impl Iterator<Item = &FfiFunction> {
- iter::once(&self.ffi_func_free)
+ [&self.ffi_func_clone, &self.ffi_func_free]
+ .into_iter()
+ .chain(&self.ffi_init_callback)
.chain(self.constructors.iter().map(|f| &f.ffi_func))
.chain(self.methods.iter().map(|f| &f.ffi_func))
.chain(
@@ -173,13 +210,26 @@ impl Object {
}
pub fn derive_ffi_funcs(&mut self) -> Result<()> {
+ assert!(!self.ffi_func_clone.name().is_empty());
assert!(!self.ffi_func_free.name().is_empty());
+ self.ffi_func_clone.arguments = vec![FfiArgument {
+ name: "ptr".to_string(),
+ type_: FfiType::RustArcPtr(self.name.to_string()),
+ }];
+ self.ffi_func_clone.return_type = Some(FfiType::RustArcPtr(self.name.to_string()));
self.ffi_func_free.arguments = vec![FfiArgument {
name: "ptr".to_string(),
type_: FfiType::RustArcPtr(self.name.to_string()),
}];
self.ffi_func_free.return_type = None;
self.ffi_func_free.is_object_free_function = true;
+ if self.has_callback_interface() {
+ self.ffi_init_callback = Some(FfiFunction::callback_init(
+ &self.module_path,
+ &self.name,
+ callbacks::vtable_name(&self.name),
+ ));
+ }
for cons in self.constructors.iter_mut() {
cons.derive_ffi_func();
@@ -194,6 +244,41 @@ impl Object {
Ok(())
}
+ /// For trait interfaces, FfiCallbacks to define for our methods, otherwise an empty vec.
+ pub fn ffi_callbacks(&self) -> Vec<FfiCallbackFunction> {
+ if self.is_trait_interface() {
+ callbacks::ffi_callbacks(&self.name, &self.methods)
+ } else {
+ vec![]
+ }
+ }
+
+ /// For trait interfaces, the VTable FFI type
+ pub fn vtable(&self) -> Option<FfiType> {
+ self.is_trait_interface()
+ .then(|| FfiType::Struct(callbacks::vtable_name(&self.name)))
+ }
+
+ /// For trait interfaces, the VTable struct to define. Otherwise None.
+ pub fn vtable_definition(&self) -> Option<FfiStruct> {
+ self.is_trait_interface()
+ .then(|| callbacks::vtable_struct(&self.name, &self.methods))
+ }
+
+ /// Vec of (ffi_callback_name, method) pairs
+ pub fn vtable_methods(&self) -> Vec<(FfiCallbackFunction, &Method)> {
+ self.methods
+ .iter()
+ .enumerate()
+ .map(|(i, method)| {
+ (
+ callbacks::method_ffi_callback(&self.name, method, i),
+ method,
+ )
+ })
+ .collect()
+ }
+
pub fn iter_types(&self) -> TypeIterator<'_> {
Box::new(
self.methods
@@ -218,6 +303,7 @@ impl AsType for Object {
impl From<uniffi_meta::ObjectMetadata> for Object {
fn from(meta: uniffi_meta::ObjectMetadata) -> Self {
+ let ffi_clone_name = meta.clone_ffi_symbol_name();
let ffi_free_name = meta.free_ffi_symbol_name();
Object {
module_path: meta.module_path,
@@ -226,10 +312,16 @@ impl From<uniffi_meta::ObjectMetadata> for Object {
constructors: Default::default(),
methods: Default::default(),
uniffi_traits: Default::default(),
+ ffi_func_clone: FfiFunction {
+ name: ffi_clone_name,
+ ..Default::default()
+ },
ffi_func_free: FfiFunction {
name: ffi_free_name,
..Default::default()
},
+ ffi_init_callback: None,
+ docstring: meta.docstring.clone(),
}
}
}
@@ -263,6 +355,7 @@ pub struct Constructor {
pub(super) name: String,
pub(super) object_name: String,
pub(super) object_module_path: String,
+ pub(super) is_async: bool,
pub(super) arguments: Vec<Argument>,
// We don't include the FFIFunc in the hash calculation, because:
// - it is entirely determined by the other fields,
@@ -272,6 +365,8 @@ pub struct Constructor {
// avoids a weird circular dependency in the calculation.
#[checksum_ignore]
pub(super) ffi_func: FfiFunction,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
pub(super) throws: Option<Type>,
pub(super) checksum_fn_name: String,
// Force a checksum value, or we'll fallback to the trait.
@@ -316,14 +411,20 @@ impl Constructor {
self.throws.as_ref()
}
+ pub fn docstring(&self) -> Option<&str> {
+ self.docstring.as_deref()
+ }
+
pub fn is_primary_constructor(&self) -> bool {
self.name == "new"
}
fn derive_ffi_func(&mut self) {
assert!(!self.ffi_func.name().is_empty());
- self.ffi_func.arguments = self.arguments.iter().map(Into::into).collect();
- self.ffi_func.return_type = Some(FfiType::RustArcPtr(self.object_name.clone()));
+ self.ffi_func.init(
+ Some(FfiType::RustArcPtr(self.object_name.clone())),
+ self.arguments.iter().map(Into::into),
+ );
}
pub fn iter_types(&self) -> TypeIterator<'_> {
@@ -339,14 +440,17 @@ impl From<uniffi_meta::ConstructorMetadata> for Constructor {
let ffi_func = FfiFunction {
name: ffi_name,
+ is_async: meta.is_async,
..FfiFunction::default()
};
Self {
name: meta.name,
object_name: meta.self_name,
+ is_async: meta.is_async,
object_module_path: meta.module_path,
arguments,
ffi_func,
+ docstring: meta.docstring.clone(),
throws: meta.throws.map(Into::into),
checksum_fn_name,
checksum: meta.checksum,
@@ -375,6 +479,8 @@ pub struct Method {
// avoids a weird circular dependency in the calculation.
#[checksum_ignore]
pub(super) ffi_func: FfiFunction,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
pub(super) throws: Option<Type>,
pub(super) takes_self_by_arc: bool,
pub(super) checksum_fn_name: String,
@@ -445,6 +551,10 @@ impl Method {
self.throws.as_ref()
}
+ pub fn docstring(&self) -> Option<&str> {
+ self.docstring.as_deref()
+ }
+
pub fn takes_self_by_arc(&self) -> bool {
self.takes_self_by_arc
}
@@ -466,6 +576,11 @@ impl Method {
.chain(self.return_type.iter().flat_map(Type::iter_types)),
)
}
+
+ /// For async callback interface methods, the FFI struct to pass to the completion function.
+ pub fn foreign_future_ffi_result_struct(&self) -> FfiStruct {
+ callbacks::foreign_future_ffi_result_struct(self.return_type.as_ref().map(FfiType::from))
+ }
}
impl From<uniffi_meta::MethodMetadata> for Method {
@@ -491,6 +606,7 @@ impl From<uniffi_meta::MethodMetadata> for Method {
arguments,
return_type,
ffi_func,
+ docstring: meta.docstring.clone(),
throws: meta.throws.map(Into::into),
takes_self_by_arc: meta.takes_self_by_arc,
checksum_fn_name,
@@ -503,19 +619,22 @@ impl From<uniffi_meta::TraitMethodMetadata> for Method {
fn from(meta: uniffi_meta::TraitMethodMetadata) -> Self {
let ffi_name = meta.ffi_symbol_name();
let checksum_fn_name = meta.checksum_symbol_name();
+ let is_async = meta.is_async;
let return_type = meta.return_type.map(Into::into);
let arguments = meta.inputs.into_iter().map(Into::into).collect();
let ffi_func = FfiFunction {
name: ffi_name,
+ is_async,
..FfiFunction::default()
};
Self {
name: meta.name,
object_name: meta.trait_name,
object_module_path: meta.module_path,
- is_async: false,
+ is_async,
arguments,
return_type,
+ docstring: meta.docstring.clone(),
throws: meta.throws.map(Into::into),
takes_self_by_arc: meta.takes_self_by_arc,
checksum_fn_name,
@@ -583,7 +702,7 @@ impl Callable for Constructor {
}
fn is_async(&self) -> bool {
- false
+ self.is_async
}
}
@@ -603,6 +722,10 @@ impl Callable for Method {
fn is_async(&self) -> bool {
self.is_async
}
+
+ fn takes_self(&self) -> bool {
+ true
+ }
}
#[cfg(test)]
@@ -770,4 +893,62 @@ mod test {
"Trait interfaces can not have constructors: \"new\""
);
}
+
+ #[test]
+ fn test_docstring_object() {
+ const UDL: &str = r#"
+ namespace test{};
+ /// informative docstring
+ interface Testing { };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(
+ ci.get_object_definition("Testing")
+ .unwrap()
+ .docstring()
+ .unwrap(),
+ "informative docstring"
+ );
+ }
+
+ #[test]
+ fn test_docstring_constructor() {
+ const UDL: &str = r#"
+ namespace test{};
+ interface Testing {
+ /// informative docstring
+ constructor();
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(
+ ci.get_object_definition("Testing")
+ .unwrap()
+ .primary_constructor()
+ .unwrap()
+ .docstring()
+ .unwrap(),
+ "informative docstring"
+ );
+ }
+
+ #[test]
+ fn test_docstring_method() {
+ const UDL: &str = r#"
+ namespace test{};
+ interface Testing {
+ /// informative docstring
+ void testing();
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(
+ ci.get_object_definition("Testing")
+ .unwrap()
+ .get_method("testing")
+ .docstring()
+ .unwrap(),
+ "informative docstring"
+ );
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/record.rs b/third_party/rust/uniffi_bindgen/src/interface/record.rs
index 17d3774a49..e9a6004189 100644
--- a/third_party/rust/uniffi_bindgen/src/interface/record.rs
+++ b/third_party/rust/uniffi_bindgen/src/interface/record.rs
@@ -60,6 +60,8 @@ pub struct Record {
pub(super) name: String,
pub(super) module_path: String,
pub(super) fields: Vec<Field>,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
}
impl Record {
@@ -71,9 +73,17 @@ impl Record {
&self.fields
}
+ pub fn docstring(&self) -> Option<&str> {
+ self.docstring.as_deref()
+ }
+
pub fn iter_types(&self) -> TypeIterator<'_> {
Box::new(self.fields.iter().flat_map(Field::iter_types))
}
+
+ pub fn has_fields(&self) -> bool {
+ !self.fields.is_empty()
+ }
}
impl AsType for Record {
@@ -97,6 +107,7 @@ impl TryFrom<uniffi_meta::RecordMetadata> for Record {
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_>>()?,
+ docstring: meta.docstring.clone(),
})
}
}
@@ -107,6 +118,8 @@ pub struct Field {
pub(super) name: String,
pub(super) type_: Type,
pub(super) default: Option<Literal>,
+ #[checksum_ignore]
+ pub(super) docstring: Option<String>,
}
impl Field {
@@ -118,6 +131,10 @@ impl Field {
self.default.as_ref()
}
+ pub fn docstring(&self) -> Option<&str> {
+ self.docstring.as_deref()
+ }
+
pub fn iter_types(&self) -> TypeIterator<'_> {
self.type_.iter_types()
}
@@ -140,6 +157,7 @@ impl TryFrom<uniffi_meta::FieldMetadata> for Field {
name,
type_,
default,
+ docstring: meta.docstring.clone(),
})
}
}
@@ -227,4 +245,39 @@ mod test {
.iter_types()
.any(|t| matches!(t, Type::Record { name, .. } if name == "Testing")));
}
+
+ #[test]
+ fn test_docstring_record() {
+ const UDL: &str = r#"
+ namespace test{};
+ /// informative docstring
+ dictionary Testing { };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(
+ ci.get_record_definition("Testing")
+ .unwrap()
+ .docstring()
+ .unwrap(),
+ "informative docstring"
+ );
+ }
+
+ #[test]
+ fn test_docstring_record_field() {
+ const UDL: &str = r#"
+ namespace test{};
+ dictionary Testing {
+ /// informative docstring
+ i32 testing;
+ };
+ "#;
+ let ci = ComponentInterface::from_webidl(UDL, "crate_name").unwrap();
+ assert_eq!(
+ ci.get_record_definition("Testing").unwrap().fields()[0]
+ .docstring()
+ .unwrap(),
+ "informative docstring"
+ );
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/interface/universe.rs b/third_party/rust/uniffi_bindgen/src/interface/universe.rs
index e69d86e44f..70bc61f8a9 100644
--- a/third_party/rust/uniffi_bindgen/src/interface/universe.rs
+++ b/third_party/rust/uniffi_bindgen/src/interface/universe.rs
@@ -25,6 +25,7 @@ pub use uniffi_meta::{AsType, ExternalKind, NamespaceMetadata, ObjectImpl, Type,
pub(crate) struct TypeUniverse {
/// The unique prefixes that we'll use for namespacing when exposing this component's API.
pub namespace: NamespaceMetadata,
+ pub namespace_docstring: Option<String>,
// Named type definitions (including aliases).
type_definitions: HashMap<String, Type>,
@@ -83,9 +84,6 @@ impl TypeUniverse {
Type::Bytes => self.add_type_definition("bytes", type_)?,
Type::Timestamp => self.add_type_definition("timestamp", type_)?,
Type::Duration => self.add_type_definition("duration", type_)?,
- Type::ForeignExecutor => {
- self.add_type_definition("ForeignExecutor", type_)?;
- }
Type::Object { name, .. }
| Type::Record { name, .. }
| Type::Enum { name, .. }
@@ -118,6 +116,7 @@ impl TypeUniverse {
Ok(())
}
+ #[cfg(test)]
/// Check if a [Type] is present
pub fn contains(&self, type_: &Type) -> bool {
self.all_known_types.contains(type_)
diff --git a/third_party/rust/uniffi_bindgen/src/lib.rs b/third_party/rust/uniffi_bindgen/src/lib.rs
index 019b24022f..dfc90b32a6 100644
--- a/third_party/rust/uniffi_bindgen/src/lib.rs
+++ b/third_party/rust/uniffi_bindgen/src/lib.rs
@@ -58,9 +58,8 @@
//!
//! ### 3) Generate and include component scaffolding from the UDL file
//!
-//! First you will need to install `uniffi-bindgen` on your system using `cargo install uniffi_bindgen`.
-//! Then add to your crate `uniffi_build` under `[build-dependencies]`.
-//! Finally, add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding`
+//! Add to your crate `uniffi_build` under `[build-dependencies]`,
+//! then add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding`
//! to process your `.udl` file. This will generate some Rust code to be included in the top-level source
//! code of your crate. If your UDL file is named `example.udl`, then your build script would call:
//!
@@ -77,12 +76,13 @@
//!
//! ### 4) Generate foreign language bindings for the library
//!
-//! The `uniffi-bindgen` utility provides a command-line tool that can produce code to
+//! You will need ensure a local `uniffi-bindgen` - see <https://mozilla.github.io/uniffi-rs/tutorial/foreign_language_bindings.html>
+//! This utility provides a command-line tool that can produce code to
//! consume the Rust library in any of several supported languages.
//! It is done by calling (in kotlin for example):
//!
//! ```text
-//! uniffi-bindgen --language kotlin ./src/example.udl
+//! cargo run --bin -p uniffi-bindgen --language kotlin ./src/example.udl
//! ```
//!
//! This will produce a file `example.kt` in the same directory as the .udl file, containing kotlin bindings
@@ -160,15 +160,16 @@ pub trait BindingGenerator: Sized {
ci: &ComponentInterface,
config: &Self::Config,
out_dir: &Utf8Path,
+ try_format_code: bool,
) -> Result<()>;
/// Check if `library_path` used by library mode is valid for this generator
fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()>;
}
-struct BindingGeneratorDefault {
- target_languages: Vec<TargetLanguage>,
- try_format_code: bool,
+pub struct BindingGeneratorDefault {
+ pub target_languages: Vec<TargetLanguage>,
+ pub try_format_code: bool,
}
impl BindingGenerator for BindingGeneratorDefault {
@@ -179,6 +180,7 @@ impl BindingGenerator for BindingGeneratorDefault {
ci: &ComponentInterface,
config: &Self::Config,
out_dir: &Utf8Path,
+ _try_format_code: bool,
) -> Result<()> {
for &language in &self.target_languages {
bindings::write_bindings(
@@ -219,12 +221,13 @@ impl BindingGenerator for BindingGeneratorDefault {
/// - `library_file`: The path to a dynamic library to attempt to extract the definitions from and extend the component interface with. No extensions to component interface occur if it's [`None`]
/// - `crate_name`: Override the default crate name that is guessed from UDL file path.
pub fn generate_external_bindings<T: BindingGenerator>(
- binding_generator: T,
+ binding_generator: &T,
udl_file: impl AsRef<Utf8Path>,
config_file_override: Option<impl AsRef<Utf8Path>>,
out_dir_override: Option<impl AsRef<Utf8Path>>,
library_file: Option<impl AsRef<Utf8Path>>,
crate_name: Option<&str>,
+ try_format_code: bool,
) -> Result<()> {
let crate_name = crate_name
.map(|c| Ok(c.to_string()))
@@ -253,7 +256,7 @@ pub fn generate_external_bindings<T: BindingGenerator>(
udl_file.as_ref(),
out_dir_override.as_ref().map(|p| p.as_ref()),
)?;
- binding_generator.write_bindings(&component, &config, &out_dir)
+ binding_generator.write_bindings(&component, &config, &out_dir, try_format_code)
}
// Generate the infrastructural Rust code for implementing the UDL interface,
@@ -301,25 +304,23 @@ fn generate_component_scaffolding_inner(
// Generate the bindings in the target languages that call the scaffolding
// Rust code.
-pub fn generate_bindings(
+pub fn generate_bindings<T: BindingGenerator>(
udl_file: &Utf8Path,
config_file_override: Option<&Utf8Path>,
- target_languages: Vec<TargetLanguage>,
+ binding_generator: T,
out_dir_override: Option<&Utf8Path>,
library_file: Option<&Utf8Path>,
crate_name: Option<&str>,
try_format_code: bool,
) -> Result<()> {
generate_external_bindings(
- BindingGeneratorDefault {
- target_languages,
- try_format_code,
- },
+ &binding_generator,
udl_file,
config_file_override,
out_dir_override,
library_file,
crate_name,
+ try_format_code,
)
}
@@ -417,22 +418,53 @@ fn format_code_with_rustfmt(path: &Utf8Path) -> Result<()> {
Ok(())
}
+/// Load TOML from file if the file exists.
+fn load_toml_file(source: Option<&Utf8Path>) -> Result<Option<toml::value::Table>> {
+ if let Some(source) = source {
+ if source.exists() {
+ let contents =
+ fs::read_to_string(source).with_context(|| format!("read file: {:?}", source))?;
+ return Ok(Some(
+ toml::de::from_str(&contents)
+ .with_context(|| format!("parse toml: {:?}", source))?,
+ ));
+ }
+ }
+
+ Ok(None)
+}
+
+/// Load the default `uniffi.toml` config, merge TOML trees with `config_file_override` if specified.
fn load_initial_config<Config: DeserializeOwned>(
crate_root: &Utf8Path,
config_file_override: Option<&Utf8Path>,
) -> Result<Config> {
- let path = match config_file_override {
- Some(cfg) => Some(cfg.to_owned()),
- None => crate_root.join("uniffi.toml").canonicalize_utf8().ok(),
- };
- let toml_config = match path {
- Some(path) => {
- let contents = fs::read_to_string(path).context("Failed to read config file")?;
- toml::de::from_str(&contents)?
+ let mut config = load_toml_file(Some(crate_root.join("uniffi.toml").as_path()))
+ .context("default config")?
+ .unwrap_or(toml::value::Table::default());
+
+ let override_config = load_toml_file(config_file_override).context("override config")?;
+ if let Some(override_config) = override_config {
+ merge_toml(&mut config, override_config);
+ }
+
+ Ok(toml::Value::from(config).try_into()?)
+}
+
+fn merge_toml(a: &mut toml::value::Table, b: toml::value::Table) {
+ for (key, value) in b.into_iter() {
+ match a.get_mut(&key) {
+ Some(existing_value) => match (existing_value, value) {
+ (toml::Value::Table(ref mut t0), toml::Value::Table(t1)) => {
+ merge_toml(t0, t1);
+ }
+ (v, value) => *v = value,
+ },
+ None => {
+ a.insert(key, value);
+ }
}
- None => toml::Value::from(toml::value::Table::default()),
- };
- Ok(toml_config.try_into()?)
+ }
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
@@ -516,4 +548,56 @@ mod test {
let not_a_crate_root = &this_crate_root.join("src/templates");
assert!(guess_crate_root(&not_a_crate_root.join("src/example.udl")).is_err());
}
+
+ #[test]
+ fn test_merge_toml() {
+ let default = r#"
+ foo = "foo"
+ bar = "bar"
+
+ [table1]
+ foo = "foo"
+ bar = "bar"
+ "#;
+ let mut default = toml::de::from_str(default).unwrap();
+
+ let override_toml = r#"
+ # update key
+ bar = "BAR"
+ # insert new key
+ baz = "BAZ"
+
+ [table1]
+ # update key
+ bar = "BAR"
+ # insert new key
+ baz = "BAZ"
+
+ # new table
+ [table1.table2]
+ bar = "BAR"
+ baz = "BAZ"
+ "#;
+ let override_toml = toml::de::from_str(override_toml).unwrap();
+
+ let expected = r#"
+ foo = "foo"
+ bar = "BAR"
+ baz = "BAZ"
+
+ [table1]
+ foo = "foo"
+ bar = "BAR"
+ baz = "BAZ"
+
+ [table1.table2]
+ bar = "BAR"
+ baz = "BAZ"
+ "#;
+ let expected: toml::value::Table = toml::de::from_str(expected).unwrap();
+
+ merge_toml(&mut default, override_toml);
+
+ assert_eq!(&expected, &default);
+ }
}
diff --git a/third_party/rust/uniffi_bindgen/src/library_mode.rs b/third_party/rust/uniffi_bindgen/src/library_mode.rs
index f170ea5e91..c460c03d9f 100644
--- a/third_party/rust/uniffi_bindgen/src/library_mode.rs
+++ b/third_party/rust/uniffi_bindgen/src/library_mode.rs
@@ -16,8 +16,8 @@
/// - UniFFI can figure out the package/module names for each crate, eliminating the external
/// package maps.
use crate::{
- bindings::TargetLanguage, load_initial_config, macro_metadata, BindingGenerator,
- BindingGeneratorDefault, BindingsConfig, ComponentInterface, Result,
+ load_initial_config, macro_metadata, BindingGenerator, BindingsConfig, ComponentInterface,
+ Result,
};
use anyhow::{bail, Context};
use camino::Utf8Path;
@@ -33,21 +33,21 @@ use uniffi_meta::{
/// Generate foreign bindings
///
/// Returns the list of sources used to generate the bindings, in no particular order.
-pub fn generate_bindings(
+pub fn generate_bindings<T: BindingGenerator + ?Sized>(
library_path: &Utf8Path,
crate_name: Option<String>,
- target_languages: &[TargetLanguage],
+ binding_generator: &T,
+ config_file_override: Option<&Utf8Path>,
out_dir: &Utf8Path,
try_format_code: bool,
-) -> Result<Vec<Source<crate::Config>>> {
+) -> Result<Vec<Source<T::Config>>> {
generate_external_bindings(
- BindingGeneratorDefault {
- target_languages: target_languages.into(),
- try_format_code,
- },
+ binding_generator,
library_path,
- crate_name,
+ crate_name.clone(),
+ config_file_override,
out_dir,
+ try_format_code,
)
}
@@ -55,10 +55,12 @@ pub fn generate_bindings(
///
/// Returns the list of sources used to generate the bindings, in no particular order.
pub fn generate_external_bindings<T: BindingGenerator>(
- binding_generator: T,
+ binding_generator: &T,
library_path: &Utf8Path,
crate_name: Option<String>,
+ config_file_override: Option<&Utf8Path>,
out_dir: &Utf8Path,
+ try_format_code: bool,
) -> Result<Vec<Source<T::Config>>> {
let cargo_metadata = MetadataCommand::new()
.exec()
@@ -66,7 +68,12 @@ pub fn generate_external_bindings<T: BindingGenerator>(
let cdylib_name = calc_cdylib_name(library_path);
binding_generator.check_library_path(library_path, cdylib_name)?;
- let mut sources = find_sources(&cargo_metadata, library_path, cdylib_name)?;
+ let mut sources = find_sources(
+ &cargo_metadata,
+ library_path,
+ cdylib_name,
+ config_file_override,
+ )?;
for i in 0..sources.len() {
// Partition up the sources list because we're eventually going to call
// `update_from_dependency_configs()` which requires an exclusive reference to one source and
@@ -101,7 +108,7 @@ pub fn generate_external_bindings<T: BindingGenerator>(
}
for source in sources.iter() {
- binding_generator.write_bindings(&source.ci, &source.config, out_dir)?;
+ binding_generator.write_bindings(&source.ci, &source.config, out_dir, try_format_code)?;
}
Ok(sources)
@@ -118,10 +125,10 @@ pub struct Source<Config: BindingsConfig> {
// If `library_path` is a C dynamic library, return its name
pub fn calc_cdylib_name(library_path: &Utf8Path) -> Option<&str> {
- let cdylib_extentions = [".so", ".dll", ".dylib"];
+ let cdylib_extensions = [".so", ".dll", ".dylib"];
let filename = library_path.file_name()?;
let filename = filename.strip_prefix("lib").unwrap_or(filename);
- for ext in cdylib_extentions {
+ for ext in cdylib_extensions {
if let Some(f) = filename.strip_suffix(ext) {
return Some(f);
}
@@ -133,6 +140,7 @@ fn find_sources<Config: BindingsConfig>(
cargo_metadata: &cargo_metadata::Metadata,
library_path: &Utf8Path,
cdylib_name: Option<&str>,
+ config_file_override: Option<&Utf8Path>,
) -> Result<Vec<Source<Config>>> {
let items = macro_metadata::extract_from_library(library_path)?;
let mut metadata_groups = create_metadata_groups(&items);
@@ -178,7 +186,7 @@ fn find_sources<Config: BindingsConfig>(
ci.add_metadata(metadata)?;
};
ci.add_metadata(group)?;
- let mut config = load_initial_config::<Config>(crate_root, None)?;
+ let mut config = load_initial_config::<Config>(crate_root, config_file_override)?;
if let Some(cdylib_name) = cdylib_name {
config.update_from_cdylib_name(cdylib_name);
}
diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs
index 7ce6c3a70b..69fad1980e 100644
--- a/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs
+++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/ci.rs
@@ -4,9 +4,7 @@
use crate::interface::{CallbackInterface, ComponentInterface, Enum, Record, Type};
use anyhow::{bail, Context};
-use uniffi_meta::{
- create_metadata_groups, group_metadata, EnumMetadata, ErrorMetadata, Metadata, MetadataGroup,
-};
+use uniffi_meta::{create_metadata_groups, group_metadata, EnumMetadata, Metadata, MetadataGroup};
/// Add Metadata items to the ComponentInterface
///
@@ -98,7 +96,9 @@ fn add_item_to_ci(iface: &mut ComponentInterface, item: Metadata) -> anyhow::Res
iface.add_record_definition(record)?;
}
Metadata::Enum(meta) => {
- let flat = meta.variants.iter().all(|v| v.fields.is_empty());
+ let flat = meta
+ .forced_flatness
+ .unwrap_or_else(|| meta.variants.iter().all(|v| v.fields.is_empty()));
add_enum_to_ci(iface, meta, flat)?;
}
Metadata::Object(meta) => {
@@ -117,22 +117,11 @@ fn add_item_to_ci(iface: &mut ComponentInterface, item: Metadata) -> anyhow::Res
module_path: meta.module_path.clone(),
name: meta.name.clone(),
})?;
- iface.add_callback_interface_definition(CallbackInterface::new(
- meta.name,
- meta.module_path,
- ));
+ iface.add_callback_interface_definition(CallbackInterface::try_from(meta)?);
}
Metadata::TraitMethod(meta) => {
iface.add_trait_method_meta(meta)?;
}
- Metadata::Error(meta) => {
- iface.note_name_used_as_error(meta.name());
- match meta {
- ErrorMetadata::Enum { enum_, is_flat } => {
- add_enum_to_ci(iface, enum_, is_flat)?;
- }
- };
- }
Metadata::CustomType(meta) => {
iface.types.add_known_type(&Type::Custom {
module_path: meta.module_path.clone(),
diff --git a/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs
index 25b5ef17ba..6d440919f1 100644
--- a/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs
+++ b/third_party/rust/uniffi_bindgen/src/macro_metadata/extract.rs
@@ -30,7 +30,7 @@ fn extract_from_bytes(file_data: &[u8]) -> anyhow::Result<Vec<Metadata>> {
Object::PE(pe) => extract_from_pe(pe, file_data),
Object::Mach(mach) => extract_from_mach(mach, file_data),
Object::Archive(archive) => extract_from_archive(archive, file_data),
- Object::Unknown(_) => bail!("Unknown library format"),
+ _ => bail!("Unknown library format"),
}
}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs
index f3759cf6fa..7fd81831aa 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/mod.rs
@@ -45,7 +45,6 @@ mod filters {
format!("std::sync::Arc<{}>", imp.rust_name_for(name))
}
Type::CallbackInterface { name, .. } => format!("Box<dyn r#{name}>"),
- Type::ForeignExecutor => "::uniffi::ForeignExecutor".into(),
Type::Optional { inner_type } => {
format!("std::option::Option<{}>", type_rs(inner_type)?)
}
@@ -64,41 +63,12 @@ mod filters {
kind: ExternalKind::Interface,
..
} => format!("::std::sync::Arc<r#{name}>"),
- Type::External { name, .. } => format!("r#{name}"),
- })
- }
-
- // Map a type to Rust code that specifies the FfiConverter implementation.
- //
- // This outputs something like `<MyStruct as Lift<crate::UniFfiTag>>`
- pub fn ffi_trait(type_: &Type, trait_name: &str) -> Result<String, askama::Error> {
- Ok(match type_ {
Type::External {
name,
- kind: ExternalKind::Interface,
+ kind: ExternalKind::Trait,
..
- } => {
- format!("<::std::sync::Arc<r#{name}> as ::uniffi::{trait_name}<crate::UniFfiTag>>")
- }
- _ => format!(
- "<{} as ::uniffi::{trait_name}<crate::UniFfiTag>>",
- type_rs(type_)?
- ),
- })
- }
-
- pub fn return_type<T: Callable>(callable: &T) -> Result<String, askama::Error> {
- let return_type = match callable.return_type() {
- Some(t) => type_rs(&t)?,
- None => "()".to_string(),
- };
- match callable.throws_type() {
- Some(t) => type_rs(&t)?,
- None => "()".to_string(),
- };
- Ok(match callable.throws_type() {
- Some(e) => format!("::std::result::Result<{return_type}, {}>", type_rs(&e)?),
- None => return_type,
+ } => format!("::std::sync::Arc<dyn r#{name}>"),
+ Type::External { name, .. } => format!("r#{name}"),
})
}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs
index 64c69e4d8e..658f4c8de5 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/CallbackInterfaceTemplate.rs
@@ -1,82 +1,17 @@
-{#
-// For each Callback Interface definition, we assume that there is a corresponding trait defined in Rust client code.
-// If the UDL callback interface and Rust trait's methods don't match, the Rust compiler will complain.
-// We generate:
-// * an init function to accept that `ForeignCallback` from the foreign language, and stores it.
-// * a holder for a `ForeignCallback`, of type `uniffi::ForeignCallbackInternals`.
-// * a proxy `struct` which implements the `trait` that the Callback Interface corresponds to. This
-// is the object that client code interacts with.
-// - for each method, arguments will be packed into a `RustBuffer` and sent over the `ForeignCallback` to be
-// unpacked and called. The return value is packed into another `RustBuffer` and sent back to Rust.
-// - a `Drop` `impl`, which tells the foreign language to forget about the real callback object.
-#}
-{% let trait_name = cbi.name() -%}
-{% let trait_impl = format!("UniFFICallbackHandler{}", trait_name) %}
-{% let foreign_callback_internals = format!("foreign_callback_{}_internals", trait_name)|upper -%}
-
-// Register a foreign callback for getting across the FFI.
-#[doc(hidden)]
-static {{ foreign_callback_internals }}: uniffi::ForeignCallbackInternals = uniffi::ForeignCallbackInternals::new();
-
-#[doc(hidden)]
-#[no_mangle]
-pub extern "C" fn {{ cbi.ffi_init_callback().name() }}(callback: uniffi::ForeignCallback, _: &mut uniffi::RustCallStatus) {
- {{ foreign_callback_internals }}.set_callback(callback);
- // The call status should be initialized to CALL_SUCCESS, so no need to modify it.
-}
-
-// Make an implementation which will shell out to the foreign language.
-#[doc(hidden)]
-#[derive(Debug)]
-struct {{ trait_impl }} {
- handle: u64
-}
-
-impl {{ trait_impl }} {
- fn new(handle: u64) -> Self {
- Self { handle }
- }
-}
-
-impl Drop for {{ trait_impl }} {
- fn drop(&mut self) {
- {{ foreign_callback_internals }}.invoke_callback::<(), crate::UniFfiTag>(
- self.handle, uniffi::IDX_CALLBACK_FREE, Default::default()
- )
- }
-}
-
-uniffi::deps::static_assertions::assert_impl_all!({{ trait_impl }}: Send);
-
-impl r#{{ trait_name }} for {{ trait_impl }} {
+#[::uniffi::export_for_udl(callback_interface)]
+pub trait r#{{ cbi.name() }} {
{%- for meth in cbi.methods() %}
-
- {#- Method declaration #}
- fn r#{{ meth.name() -}}
- ({% call rs::arg_list_decl_with_prefix("&self", meth) %})
- {%- match (meth.return_type(), meth.throws_type()) %}
- {%- when (Some(return_type), None) %} -> {{ return_type.borrow()|type_rs }}
- {%- when (Some(return_type), Some(err)) %} -> ::std::result::Result<{{ return_type.borrow()|type_rs }}, {{ err|type_rs }}>
- {%- when (None, Some(err)) %} -> ::std::result::Result<(), {{ err|type_rs }}>
- {% else -%}
- {%- endmatch -%} {
- {#- Method body #}
-
- {#- Packing args into a RustBuffer #}
- {% if meth.arguments().len() == 0 -%}
- let args_buf = Vec::new();
- {% else -%}
- let mut args_buf = Vec::new();
- {% endif -%}
+ fn r#{{ meth.name() }}(
+ {% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %},
{%- for arg in meth.arguments() %}
- {{ arg.as_type().borrow()|ffi_trait("Lower") }}::write(r#{{ arg.name() }}, &mut args_buf);
- {%- endfor -%}
- let args_rbuf = uniffi::RustBuffer::from_vec(args_buf);
-
- {#- Calling into foreign code. #}
- {{ foreign_callback_internals }}.invoke_callback::<{{ meth|return_type }}, crate::UniFfiTag>(self.handle, {{ loop.index }}, args_rbuf)
- }
- {%- endfor %}
+ r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
+ {%- endfor %}
+ )
+ {%- match (meth.return_type(), meth.throws_type()) %}
+ {%- when (Some(return_type), None) %} -> {{ return_type|type_rs }};
+ {%- when (Some(return_type), Some(error_type)) %} -> ::std::result::Result::<{{ return_type|type_rs }}, {{ error_type|type_rs }}>;
+ {%- when (None, Some(error_type)) %} -> ::std::result::Result::<(), {{ error_type|type_rs }}>;
+ {%- when (None, None) %};
+ {%- endmatch %}
+ {% endfor %}
}
-
-::uniffi::scaffolding_ffi_converter_callback_interface!(r#{{ trait_name }}, {{ trait_impl }});
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs
index 6b9f96f224..f918ef2f3a 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/EnumTemplate.rs
@@ -1,13 +1,12 @@
{#
-// For each enum declared in the UDL, we assume the caller has provided a corresponding
-// rust `enum`. We provide the traits for sending it across the FFI, which will fail to
-// compile if the provided struct has a different shape to the one declared in the UDL.
-//
-// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's
-// public so other crates can refer to it via an `[External='crate'] typedef`
+// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent.
#}
-#[::uniffi::derive_enum_for_udl]
+#[::uniffi::derive_enum_for_udl(
+ {%- if e.is_non_exhaustive() -%}
+ non_exhaustive,
+ {%- endif %}
+)]
enum r#{{ e.name() }} {
{%- for variant in e.variants() %}
r#{{ variant.name() }} {
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs
index 94538ecaa8..64f48e2334 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ErrorTemplate.rs
@@ -1,10 +1,5 @@
{#
-// For each error declared in the UDL, we assume the caller has provided a corresponding
-// rust `enum`. We provide the traits for sending it across the FFI, which will fail to
-// compile if the provided struct has a different shape to the one declared in the UDL.
-//
-// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's
-// public so other crates can refer to it via an `[External='crate'] typedef`
+// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent.
#}
#[::uniffi::derive_error_for_udl(
@@ -14,6 +9,9 @@
with_try_read,
{%- endif %}
{%- endif %}
+ {%- if e.is_non_exhaustive() -%}
+ non_exhaustive,
+ {%- endif %}
)]
enum r#{{ e.name() }} {
{%- for variant in e.variants() %}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs
index ade1578897..d67e172cc2 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ExternalTypesTemplate.rs
@@ -10,6 +10,8 @@
::uniffi::ffi_converter_forward!(r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag);
{%- when ExternalKind::Interface %}
::uniffi::ffi_converter_arc_forward!(r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag);
+{%- when ExternalKind::Trait %}
+::uniffi::ffi_converter_arc_forward!(dyn r#{{ name }}, ::{{ crate_name|crate_name_rs }}::UniFfiTag, crate::UniFfiTag);
{%- endmatch %}
{% endif %}
{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs
index e2445c670d..e752878af5 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/ObjectTemplate.rs
@@ -1,24 +1,15 @@
-// For each Object definition, we assume the caller has provided an appropriately-shaped `struct T`
-// with an `impl` for each method on the object. We create an `Arc<T>` for "safely" handing out
-// references to these structs to foreign language code, and we provide a `pub extern "C"` function
-// corresponding to each method.
-//
-// (Note that "safely" is in "scare quotes" - that's because we use functions on an `Arc` that
-// that are inherently unsafe, but the code we generate is safe in practice.)
-//
-// If the caller's implementation of the struct does not match with the methods or types specified
-// in the UDL, then the rust compiler will complain with a (hopefully at least somewhat helpful!)
-// error message when processing this generated code.
+{#
+// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent.
+#}
-{%- match obj.imp() -%}
-{%- when ObjectImpl::Trait %}
-#[::uniffi::export_for_udl]
+{%- if obj.is_trait_interface() %}
+#[::uniffi::export_for_udl{% if obj.has_callback_interface() %}(with_foreign){% endif %}]
pub trait r#{{ obj.name() }} {
{%- for meth in obj.methods() %}
- fn {{ meth.name() }}(
+ {% if meth.is_async() %}async {% endif %}fn r#{{ meth.name() }}(
{% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %},
{%- for arg in meth.arguments() %}
- {{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
+ r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
{%- endfor %}
)
{%- match (meth.return_type(), meth.throws_type()) %}
@@ -29,7 +20,7 @@ pub trait r#{{ obj.name() }} {
{%- endmatch %}
{% endfor %}
}
-{% when ObjectImpl::Struct %}
+{%- else %}
{%- for tm in obj.uniffi_traits() %}
{% match tm %}
{% when UniffiTrait::Debug { fmt }%}
@@ -46,9 +37,10 @@ pub trait r#{{ obj.name() }} {
struct {{ obj.rust_name() }} { }
{%- for cons in obj.constructors() %}
-#[::uniffi::export_for_udl(constructor)]
+#[::uniffi::export_for_udl]
impl {{ obj.rust_name() }} {
- pub fn r#{{ cons.name() }}(
+ #[uniffi::constructor]
+ pub {% if cons.is_async() %}async {% endif %}fn r#{{ cons.name() }}(
{%- for arg in cons.arguments() %}
r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
{%- endfor %}
@@ -68,7 +60,7 @@ impl {{ obj.rust_name() }} {
{%- for meth in obj.methods() %}
#[::uniffi::export_for_udl]
impl {{ obj.rust_name() }} {
- pub fn r#{{ meth.name() }}(
+ pub {% if meth.is_async() %}async {% endif %}fn r#{{ meth.name() }}(
{% if meth.takes_self_by_arc()%}self: Arc<Self>{% else %}&self{% endif %},
{%- for arg in meth.arguments() %}
r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
@@ -86,4 +78,4 @@ impl {{ obj.rust_name() }} {
}
{%- endfor %}
-{% endmatch %}
+{% endif %}
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs
index 85e131dd8c..a7affdf7b8 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/RecordTemplate.rs
@@ -1,11 +1,5 @@
{#
-// For each record declared in the UDL, we assume the caller has provided a corresponding
-// rust `struct` with the declared fields. We provide the traits for sending it across the FFI.
-// If the caller's struct does not match the shape and types declared in the UDL then the rust
-// compiler will complain with a type error.
-//
-// We define a unit-struct to implement the trait to sidestep Rust's orphan rule (ADR-0006). It's
-// public so other crates can refer to it via an `[External='crate'] typedef`
+// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent.
#}
#[::uniffi::derive_record_for_udl]
diff --git a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs
index eeee0f5ee2..27f3686b9f 100644
--- a/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs
+++ b/third_party/rust/uniffi_bindgen/src/scaffolding/templates/TopLevelFunctionTemplate.rs
@@ -1,5 +1,8 @@
+{#
+// Forward work to `uniffi_macros` This keeps macro-based and UDL-based generated code consistent.
+#}
#[::uniffi::export_for_udl]
-pub fn r#{{ func.name() }}(
+pub {% if func.is_async() %}async {% endif %}fn r#{{ func.name() }}(
{%- for arg in func.arguments() %}
r#{{ arg.name() }}: {% if arg.by_ref() %}&{% endif %}{{ arg.as_type().borrow()|type_rs }},
{%- endfor %}
diff --git a/third_party/rust/uniffi_build/.cargo-checksum.json b/third_party/rust/uniffi_build/.cargo-checksum.json
index 8e585bfa95..a9005a0f58 100644
--- a/third_party/rust/uniffi_build/.cargo-checksum.json
+++ b/third_party/rust/uniffi_build/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"a6db989e5a3d597219df0a9c94541130b7db607efd8606043cd1187971020639","src/lib.rs":"47ff3d1a18456164414af1c20cd5df129401e5257cc15552ecc39afed8970707"},"package":"001964dd3682d600084b3aaf75acf9c3426699bc27b65e96bb32d175a31c74e9"} \ No newline at end of file
+{"files":{"Cargo.toml":"8fcf43ff5e6c1281a1ee5f9ed796b0f8115bd39ca9ce5b2d0c32e88d9eb2038f","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","src/lib.rs":"47ff3d1a18456164414af1c20cd5df129401e5257cc15552ecc39afed8970707"},"package":"45cba427aeb7b3a8b54830c4c915079a7a3c62608dd03dddba1d867a8a023eb4"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_build/Cargo.toml b/third_party/rust/uniffi_build/Cargo.toml
index 3fe7ee5cf0..fed51c34ca 100644
--- a/third_party/rust/uniffi_build/Cargo.toml
+++ b/third_party/rust/uniffi_build/Cargo.toml
@@ -12,11 +12,12 @@
[package]
edition = "2021"
name = "uniffi_build"
-version = "0.25.3"
+version = "0.27.1"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
description = "a multi-language bindings generator for rust (build script helpers)"
homepage = "https://mozilla.github.io/uniffi-rs"
documentation = "https://mozilla.github.io/uniffi-rs"
+readme = "README.md"
keywords = [
"ffi",
"bindgen",
@@ -31,7 +32,7 @@ version = "1"
version = "1.0.8"
[dependencies.uniffi_bindgen]
-version = "=0.25.3"
+version = "=0.27.1"
default-features = false
[features]
diff --git a/third_party/rust/uniffi_build/README.md b/third_party/rust/uniffi_build/README.md
new file mode 100644
index 0000000000..64ac3486a3
--- /dev/null
+++ b/third_party/rust/uniffi_build/README.md
@@ -0,0 +1,81 @@
+# UniFFI - a multi-language bindings generator for Rust
+
+UniFFI is a toolkit for building cross-platform software components in Rust.
+
+For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/)
+or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components).
+
+By writing your core business logic in Rust and describing its interface in an "object model",
+you can use UniFFI to help you:
+
+* Compile your Rust code into a shared library for use on different target platforms.
+* Generate bindings to load and use the library from different target languages.
+
+You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html).
+
+UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers;
+written once in Rust, auto-generated bindings allow that functionality to be called
+from both Kotlin (for Android apps) and Swift (for iOS apps).
+It also has a growing community of users shipping various cool things to many users.
+
+UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**.
+Additional foreign language bindings can be developed externally and we welcome contributions to list them here.
+See [Third-party foreign language bindings](#third-party-foreign-language-bindings).
+
+## User Guide
+
+You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/).
+
+We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on.
+We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time.
+
+### Etymology and Pronunciation
+
+ˈjuːnɪfaɪ. Pronounced to rhyme with "unify".
+
+A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages.
+
+uni - [Latin ūni-, from ūnus, one]
+FFI - [Abbreviation, Foreign Function Interface]
+
+## Alternative tools
+
+Other tools we know of which try and solve a similarly shaped problem are:
+
+* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of
+ the different approach taken by that tool](docs/diplomat-and-macros.md)
+* [Interoptopus](https://github.com/ralfbiedert/interoptopus/)
+
+(Please open a PR if you think other tools should be listed!)
+
+## Third-party foreign language bindings
+
+* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native.
+* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go)
+* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs)
+* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart)
+
+### External resources
+
+There are a few third-party resources that make it easier to work with UniFFI:
+
+* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/).
+* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts.
+* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful.
+* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure.
+
+(Please open a PR if you think other resources should be listed!)
+
+## Contributing
+
+If this tool sounds interesting to you, please help us develop it! You can:
+
+* View the [contributor guidelines](./docs/contributing.md).
+* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub.
+* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org)
+ room on Matrix.
+
+## Code of Conduct
+
+This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md).
diff --git a/third_party/rust/uniffi_checksum_derive/.cargo-checksum.json b/third_party/rust/uniffi_checksum_derive/.cargo-checksum.json
index 9df6eba7b4..5e211378a0 100644
--- a/third_party/rust/uniffi_checksum_derive/.cargo-checksum.json
+++ b/third_party/rust/uniffi_checksum_derive/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"028104be15a1d73a8ca192a30865b26827344808a95b21e3798e4c9406ebc4f1","src/lib.rs":"44d2e2c595b14d33d16c71dfe4ef42ad0b9e010a878ee2ec49c2e929d60275ba"},"package":"55137c122f712d9330fd985d66fa61bdc381752e89c35708c13ce63049a3002c"} \ No newline at end of file
+{"files":{"Cargo.toml":"da89504b9007c2a1ea0e498a2e8ec6baeb0ff7391363cd9007e383247637792c","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","src/lib.rs":"44d2e2c595b14d33d16c71dfe4ef42ad0b9e010a878ee2ec49c2e929d60275ba"},"package":"ae7e5a6c33b1dec3f255f57ec0b6af0f0b2bb3021868be1d5eec7a38e2905ebc"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_checksum_derive/Cargo.toml b/third_party/rust/uniffi_checksum_derive/Cargo.toml
index a3c4ed7ca3..681c8846e1 100644
--- a/third_party/rust/uniffi_checksum_derive/Cargo.toml
+++ b/third_party/rust/uniffi_checksum_derive/Cargo.toml
@@ -12,11 +12,12 @@
[package]
edition = "2021"
name = "uniffi_checksum_derive"
-version = "0.25.3"
+version = "0.27.1"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
description = "a multi-language bindings generator for rust (checksum custom derive)"
homepage = "https://mozilla.github.io/uniffi-rs"
documentation = "https://mozilla.github.io/uniffi-rs"
+readme = "README.md"
keywords = [
"ffi",
"bindgen",
diff --git a/third_party/rust/uniffi_checksum_derive/README.md b/third_party/rust/uniffi_checksum_derive/README.md
new file mode 100644
index 0000000000..64ac3486a3
--- /dev/null
+++ b/third_party/rust/uniffi_checksum_derive/README.md
@@ -0,0 +1,81 @@
+# UniFFI - a multi-language bindings generator for Rust
+
+UniFFI is a toolkit for building cross-platform software components in Rust.
+
+For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/)
+or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components).
+
+By writing your core business logic in Rust and describing its interface in an "object model",
+you can use UniFFI to help you:
+
+* Compile your Rust code into a shared library for use on different target platforms.
+* Generate bindings to load and use the library from different target languages.
+
+You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html).
+
+UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers;
+written once in Rust, auto-generated bindings allow that functionality to be called
+from both Kotlin (for Android apps) and Swift (for iOS apps).
+It also has a growing community of users shipping various cool things to many users.
+
+UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**.
+Additional foreign language bindings can be developed externally and we welcome contributions to list them here.
+See [Third-party foreign language bindings](#third-party-foreign-language-bindings).
+
+## User Guide
+
+You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/).
+
+We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on.
+We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time.
+
+### Etymology and Pronunciation
+
+ˈjuːnɪfaɪ. Pronounced to rhyme with "unify".
+
+A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages.
+
+uni - [Latin ūni-, from ūnus, one]
+FFI - [Abbreviation, Foreign Function Interface]
+
+## Alternative tools
+
+Other tools we know of which try and solve a similarly shaped problem are:
+
+* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of
+ the different approach taken by that tool](docs/diplomat-and-macros.md)
+* [Interoptopus](https://github.com/ralfbiedert/interoptopus/)
+
+(Please open a PR if you think other tools should be listed!)
+
+## Third-party foreign language bindings
+
+* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native.
+* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go)
+* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs)
+* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart)
+
+### External resources
+
+There are a few third-party resources that make it easier to work with UniFFI:
+
+* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/).
+* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts.
+* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful.
+* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure.
+
+(Please open a PR if you think other resources should be listed!)
+
+## Contributing
+
+If this tool sounds interesting to you, please help us develop it! You can:
+
+* View the [contributor guidelines](./docs/contributing.md).
+* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub.
+* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org)
+ room on Matrix.
+
+## Code of Conduct
+
+This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md).
diff --git a/third_party/rust/uniffi_core/.cargo-checksum.json b/third_party/rust/uniffi_core/.cargo-checksum.json
index 59804f7c89..573f7a72c9 100644
--- a/third_party/rust/uniffi_core/.cargo-checksum.json
+++ b/third_party/rust/uniffi_core/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"b074f0db902264714faf879e99bdbc07df1550d75694f96751b499f98fecd16d","release.toml":"b150796411fc6ff90b481218cb50f8ac7c07f5845aebdb8e17877d47e55b05b9","src/ffi/callbackinterface.rs":"9e8650f0df087bf5e030a13d28f4990079e53613e656789b4b539d937a7fd288","src/ffi/ffidefault.rs":"f1ce099b92adbb12b160d513bae93342c7b6d806d7f6ebb665067db10af9a681","src/ffi/foreignbytes.rs":"d2b46e1a6317aa64801b855e0d12af6bcdef118d8036603d11c3cdaf6f35fdfe","src/ffi/foreigncallbacks.rs":"af8129a69ef23b92859e1cca0d666c95f0ed2c1fb2797f4495d824b65f774d03","src/ffi/foreignexecutor.rs":"123687921ce6dfb7f5bfa0736a630cfeff7f376b776ea03fc651da21ffd1cab8","src/ffi/mod.rs":"8117b08bbb7af3e97f66ed69c9690b60e8da0d6d8940349c7b9659a47cd8c92f","src/ffi/rustbuffer.rs":"8cc1f94b9ecba52b911da6a68155921c1b7f51b899d9874ddbc281a379941473","src/ffi/rustcalls.rs":"7caaa35ba8898c4b4983f07cefa80584ba00e753a11d496e578c80abe0cabe8b","src/ffi/rustfuture.rs":"d240426c8c8b83e3f6a2c0013e905298611287b2bb2022eb8161532209c635ca","src/ffi_converter_impls.rs":"82c1b47e02718610f2a5556997cd29ba5d8daf149d6353f470be0d9b971d968a","src/ffi_converter_traits.rs":"646c0d4aeb807d3e40db4d289f909030d0b2684087871a7d40d337680096b7d6","src/lib.rs":"4ad1a2899944a20e80a55d1c7bd01ff28395ace743a083c65847e6ea216fc5c8","src/metadata.rs":"6520ffcf2568a0d95f0f854acb6fc8aeaae26ef1f23fc576c2c50db72aa30eee","src/panichook.rs":"9f49c7994a8e5489c1105c488bb3f8c5571bc5f813e7be90441eca15da5c9851"},"package":"6121a127a3af1665cd90d12dd2b3683c2643c5103281d0fed5838324ca1fad5b"} \ No newline at end of file
+{"files":{"Cargo.toml":"c8969fbc6e8f6694e260ab78c94f9b4195d61afb7836b4c130b542d3b91b9200","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","release.toml":"b150796411fc6ff90b481218cb50f8ac7c07f5845aebdb8e17877d47e55b05b9","src/ffi/callbackinterface.rs":"f0184cf76bd86abb2815d260a87f85bd7060f5373ac6ef6f71955ece2a5075af","src/ffi/ffidefault.rs":"0db83fbcbc274c4c0daf7fb27833400568839b77a3496155840734c511d801e0","src/ffi/foreignbytes.rs":"d2b46e1a6317aa64801b855e0d12af6bcdef118d8036603d11c3cdaf6f35fdfe","src/ffi/foreigncallbacks.rs":"2b820a34b78705f5debc302a25c64d515a4aa7b3bdade083f4c1cfa2803664ae","src/ffi/foreignfuture.rs":"c1d621e41ea6af0c1d3959b46af8567c3fdc4164e7a82d635fcbb1da2c0737ac","src/ffi/handle.rs":"91f91469a81cb19edebb8bba433df62658cc66f6b54d5dc8520eb5793a85abd9","src/ffi/mod.rs":"30eea545299747838bf11b0698cfb71cedd3ca04d8cfb703c53198fcc44045c1","src/ffi/rustbuffer.rs":"0e725347f916834b17156413f406d5ca6c064b2cbc7437b051fe6692ad72c2aa","src/ffi/rustcalls.rs":"51c6499871c7d5eb4f80cabc806f26dd1df3b1090a2419d0d967aa9c5299a0a6","src/ffi/rustfuture/future.rs":"426cd0ad3c8cf008a7052a7d89856b6c6d5053b94e24325f5666d0281a40ec7f","src/ffi/rustfuture/mod.rs":"44568267e591f5b37f77acfdd6e60ae55ce48ab0a17fd81af3aeb31baa3d53e6","src/ffi/rustfuture/scheduler.rs":"c6484fff14c04596df5f306f2090366435dcff92561d317fde1ea9c097a9576b","src/ffi/rustfuture/tests.rs":"211241fb484a3a103eb0418e7d295850ea021bcd583fa1488f5efc68f33d5ab8","src/ffi_converter_impls.rs":"397c813f2e765462d7a7be524e6ac75e813a91a8ffd11c7e7df05f853213f77b","src/ffi_converter_traits.rs":"24c8cf6ada9b2f63b265e62c0f9092d640e533d0d7234e9156f92c3d1902f430","src/lib.rs":"1f6a031bbb160dfe46455a8bc24596f63b1e478f45579bfff62a62f58900bee4","src/metadata.rs":"83e463c377c0f501e58ac4eb5cc47c433c1473cecd47305fa89283e736b48d96","src/panichook.rs":"9f49c7994a8e5489c1105c488bb3f8c5571bc5f813e7be90441eca15da5c9851"},"package":"0ea3eb5474d50fc149b7e4d86b9c5bd4a61dcc167f0683902bf18ae7bbb3deef"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_core/Cargo.toml b/third_party/rust/uniffi_core/Cargo.toml
index 4d4cbe2758..ce36a2168a 100644
--- a/third_party/rust/uniffi_core/Cargo.toml
+++ b/third_party/rust/uniffi_core/Cargo.toml
@@ -12,11 +12,12 @@
[package]
edition = "2021"
name = "uniffi_core"
-version = "0.25.3"
+version = "0.27.1"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
description = "a multi-language bindings generator for rust (runtime support code)"
homepage = "https://mozilla.github.io/uniffi-rs"
documentation = "https://mozilla.github.io/uniffi-rs"
+readme = "README.md"
keywords = [
"ffi",
"bindgen",
@@ -44,7 +45,7 @@ version = "0.4"
version = "1.10.0"
[dependencies.oneshot]
-version = "0.1.5"
+version = "0.1.6"
features = ["async"]
package = "oneshot-uniffi"
@@ -56,5 +57,4 @@ version = "1.1.0"
[features]
default = []
-extern-rustbuffer = []
tokio = ["dep:async-compat"]
diff --git a/third_party/rust/uniffi_core/README.md b/third_party/rust/uniffi_core/README.md
new file mode 100644
index 0000000000..64ac3486a3
--- /dev/null
+++ b/third_party/rust/uniffi_core/README.md
@@ -0,0 +1,81 @@
+# UniFFI - a multi-language bindings generator for Rust
+
+UniFFI is a toolkit for building cross-platform software components in Rust.
+
+For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/)
+or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components).
+
+By writing your core business logic in Rust and describing its interface in an "object model",
+you can use UniFFI to help you:
+
+* Compile your Rust code into a shared library for use on different target platforms.
+* Generate bindings to load and use the library from different target languages.
+
+You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html).
+
+UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers;
+written once in Rust, auto-generated bindings allow that functionality to be called
+from both Kotlin (for Android apps) and Swift (for iOS apps).
+It also has a growing community of users shipping various cool things to many users.
+
+UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**.
+Additional foreign language bindings can be developed externally and we welcome contributions to list them here.
+See [Third-party foreign language bindings](#third-party-foreign-language-bindings).
+
+## User Guide
+
+You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/).
+
+We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on.
+We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time.
+
+### Etymology and Pronunciation
+
+ˈjuːnɪfaɪ. Pronounced to rhyme with "unify".
+
+A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages.
+
+uni - [Latin ūni-, from ūnus, one]
+FFI - [Abbreviation, Foreign Function Interface]
+
+## Alternative tools
+
+Other tools we know of which try and solve a similarly shaped problem are:
+
+* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of
+ the different approach taken by that tool](docs/diplomat-and-macros.md)
+* [Interoptopus](https://github.com/ralfbiedert/interoptopus/)
+
+(Please open a PR if you think other tools should be listed!)
+
+## Third-party foreign language bindings
+
+* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native.
+* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go)
+* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs)
+* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart)
+
+### External resources
+
+There are a few third-party resources that make it easier to work with UniFFI:
+
+* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/).
+* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts.
+* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful.
+* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure.
+
+(Please open a PR if you think other resources should be listed!)
+
+## Contributing
+
+If this tool sounds interesting to you, please help us develop it! You can:
+
+* View the [contributor guidelines](./docs/contributing.md).
+* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub.
+* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org)
+ room on Matrix.
+
+## Code of Conduct
+
+This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md).
diff --git a/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs b/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs
index 7be66880bb..e7a4faab64 100644
--- a/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs
+++ b/third_party/rust/uniffi_core/src/ffi/callbackinterface.rs
@@ -91,121 +91,20 @@
//!
//! Uniffi generates a protocol or interface in client code in the foreign language must implement.
//!
-//! For each callback interface, a `CallbackInternals` (on the Foreign Language side) and `ForeignCallbackInternals`
-//! (on Rust side) manages the process through a `ForeignCallback`. There is one `ForeignCallback` per callback interface.
+//! For each callback interface, UniFFI defines a VTable.
+//! This is a `repr(C)` struct where each field is a `repr(C)` callback function pointer.
+//! There is one field for each method, plus an extra field for the `uniffi_free` method.
+//! The foreign code registers one VTable per callback interface with Rust.
//!
-//! Passing a callback interface implementation from foreign language (e.g. `AndroidKeychain`) into Rust causes the
-//! `KeychainCallbackInternals` to store the instance in a handlemap.
-//!
-//! The object handle is passed over to Rust, and used to instantiate a struct `KeychainProxy` which implements
-//! the trait. This proxy implementation is generate by Uniffi. The `KeychainProxy` object is then passed to
-//! client code as `Box<dyn Keychain>`.
-//!
-//! Methods on `KeychainProxy` objects (e.g. `self.keychain.get("username".into())`) encode the arguments into a `RustBuffer`.
-//! Using the `ForeignCallback`, it calls the `CallbackInternals` object on the foreign language side using the
-//! object handle, and the method selector.
-//!
-//! The `CallbackInternals` object unpacks the arguments from the passed buffer, gets the object out from the handlemap,
-//! and calls the actual implementation of the method.
-//!
-//! If there's a return value, it is packed up in to another `RustBuffer` and used as the return value for
-//! `ForeignCallback`. The caller of `ForeignCallback`, the `KeychainProxy` unpacks the returned buffer into the correct
-//! type and then returns to client code.
+//! VTable methods have a similar signature to Rust scaffolding functions.
+//! The one difference is that values are returned via an out pointer to work around a Python bug (https://bugs.python.org/issue5710).
//!
+//! The foreign object that implements the interface is represented by an opaque handle.
+//! UniFFI generates a struct that implements the trait by calling VTable methods, passing the handle as the first parameter.
+//! When the struct is dropped, the `uniffi_free` method is called.
-use crate::{ForeignCallback, ForeignCallbackCell, Lift, LiftReturn, RustBuffer};
use std::fmt;
-/// The method index used by the Drop trait to communicate to the foreign language side that Rust has finished with it,
-/// and it can be deleted from the handle map.
-pub const IDX_CALLBACK_FREE: u32 = 0;
-
-/// Result of a foreign callback invocation
-#[repr(i32)]
-#[derive(Debug, PartialEq, Eq)]
-pub enum CallbackResult {
- /// Successful call.
- /// The return value is serialized to `buf_ptr`.
- Success = 0,
- /// Expected error.
- /// This is returned when a foreign method throws an exception that corresponds to the Rust Err half of a Result.
- /// The error value is serialized to `buf_ptr`.
- Error = 1,
- /// Unexpected error.
- /// An error message string is serialized to `buf_ptr`.
- UnexpectedError = 2,
-}
-
-impl TryFrom<i32> for CallbackResult {
- // On errors we return the unconverted value
- type Error = i32;
-
- fn try_from(value: i32) -> Result<Self, i32> {
- match value {
- 0 => Ok(Self::Success),
- 1 => Ok(Self::Error),
- 2 => Ok(Self::UnexpectedError),
- n => Err(n),
- }
- }
-}
-
-/// Struct to hold a foreign callback.
-pub struct ForeignCallbackInternals {
- callback_cell: ForeignCallbackCell,
-}
-
-impl ForeignCallbackInternals {
- pub const fn new() -> Self {
- ForeignCallbackInternals {
- callback_cell: ForeignCallbackCell::new(),
- }
- }
-
- pub fn set_callback(&self, callback: ForeignCallback) {
- self.callback_cell.set(callback);
- }
-
- /// Invoke a callback interface method on the foreign side and return the result
- pub fn invoke_callback<R, UniFfiTag>(&self, handle: u64, method: u32, args: RustBuffer) -> R
- where
- R: LiftReturn<UniFfiTag>,
- {
- let mut ret_rbuf = RustBuffer::new();
- let callback = self.callback_cell.get();
- let raw_result = unsafe {
- callback(
- handle,
- method,
- args.data_pointer(),
- args.len() as i32,
- &mut ret_rbuf,
- )
- };
- let result = CallbackResult::try_from(raw_result)
- .unwrap_or_else(|code| panic!("Callback failed with unexpected return code: {code}"));
- match result {
- CallbackResult::Success => R::lift_callback_return(ret_rbuf),
- CallbackResult::Error => R::lift_callback_error(ret_rbuf),
- CallbackResult::UnexpectedError => {
- let reason = if !ret_rbuf.is_empty() {
- match <String as Lift<UniFfiTag>>::try_lift(ret_rbuf) {
- Ok(s) => s,
- Err(e) => {
- log::error!("{{ trait_name }} Error reading ret_buf: {e}");
- String::from("[Error reading reason]")
- }
- }
- } else {
- RustBuffer::destroy(ret_rbuf);
- String::from("[Unknown Reason]")
- };
- R::handle_callback_unexpected_error(UnexpectedUniFFICallbackError { reason })
- }
- }
- }
-}
-
/// Used when internal/unexpected error happened when calling a foreign callback, for example when
/// a unknown exception is raised
///
@@ -216,8 +115,10 @@ pub struct UnexpectedUniFFICallbackError {
}
impl UnexpectedUniFFICallbackError {
- pub fn from_reason(reason: String) -> Self {
- Self { reason }
+ pub fn new(reason: impl fmt::Display) -> Self {
+ Self {
+ reason: reason.to_string(),
+ }
}
}
diff --git a/third_party/rust/uniffi_core/src/ffi/ffidefault.rs b/third_party/rust/uniffi_core/src/ffi/ffidefault.rs
index 1f86f6b13b..a992ab7384 100644
--- a/third_party/rust/uniffi_core/src/ffi/ffidefault.rs
+++ b/third_party/rust/uniffi_core/src/ffi/ffidefault.rs
@@ -39,6 +39,12 @@ impl FfiDefault for () {
fn ffi_default() {}
}
+impl FfiDefault for crate::Handle {
+ fn ffi_default() -> Self {
+ Self::default()
+ }
+}
+
impl FfiDefault for *const std::ffi::c_void {
fn ffi_default() -> Self {
std::ptr::null()
@@ -51,9 +57,10 @@ impl FfiDefault for crate::RustBuffer {
}
}
-impl FfiDefault for crate::ForeignExecutorHandle {
+impl FfiDefault for crate::ForeignFuture {
fn ffi_default() -> Self {
- Self(std::ptr::null())
+ extern "C" fn free(_handle: u64) {}
+ crate::ForeignFuture { handle: 0, free }
}
}
diff --git a/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs b/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs
index ac2463cd8e..326ff12747 100644
--- a/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs
+++ b/third_party/rust/uniffi_core/src/ffi/foreigncallbacks.rs
@@ -8,96 +8,32 @@
//! code loads the exported library. For each callback type, we also define a "cell" type for
//! storing the callback.
-use std::sync::atomic::{AtomicUsize, Ordering};
-
-use crate::{ForeignExecutorHandle, RustBuffer, RustTaskCallback};
-
-/// ForeignCallback is the Rust representation of a foreign language function.
-/// It is the basis for all callbacks interfaces. It is registered exactly once per callback interface,
-/// at library start up time.
-/// Calling this method is only done by generated objects which mirror callback interfaces objects in the foreign language.
-///
-/// * The `handle` is the key into a handle map on the other side of the FFI used to look up the foreign language object
-/// that implements the callback interface/trait.
-/// * The `method` selector specifies the method that will be called on the object, by looking it up in a list of methods from
-/// the IDL. The list is 1 indexed. Note that the list of methods is generated by UniFFI from the IDL and used in all
-/// bindings, so we can rely on the method list being stable within the same run of UniFFI.
-/// * `args_data` and `args_len` represents a serialized buffer of arguments to the function. The scaffolding code
-/// writes the callback arguments to this buffer, in order, using `FfiConverter.write()`. The bindings code reads the
-/// arguments from the buffer and passes them to the user's callback.
-/// * `buf_ptr` is a pointer to where the resulting buffer will be written. UniFFI will allocate a
-/// buffer to write the result into.
-/// * Callbacks return one of the `CallbackResult` values
-/// Note: The output buffer might still contain 0 bytes of data.
-pub type ForeignCallback = unsafe extern "C" fn(
- handle: u64,
- method: u32,
- args_data: *const u8,
- args_len: i32,
- buf_ptr: *mut RustBuffer,
-) -> i32;
-
-/// Callback to schedule a Rust call with a `ForeignExecutor`. The bindings code registers exactly
-/// one of these with the Rust code.
-///
-/// Delay is an approximate amount of ms to wait before scheduling the call. Delay is usually 0,
-/// which means schedule sometime soon.
-///
-/// As a special case, when Rust drops the foreign executor, with `task=null`. The foreign
-/// bindings should release the reference to the executor that was reserved for Rust.
-///
-/// This callback can be invoked from any thread, including threads created by Rust.
-///
-/// The callback should return one of the `ForeignExecutorCallbackResult` values.
-pub type ForeignExecutorCallback = extern "C" fn(
- executor: ForeignExecutorHandle,
- delay: u32,
- task: Option<RustTaskCallback>,
- task_data: *const (),
-) -> i8;
-
-/// Store a [ForeignCallback] pointer
-pub(crate) struct ForeignCallbackCell(AtomicUsize);
-
-/// Store a [ForeignExecutorCallback] pointer
-pub(crate) struct ForeignExecutorCallbackCell(AtomicUsize);
-
-/// Macro to define foreign callback types as well as the callback cell.
-macro_rules! impl_foreign_callback_cell {
- ($callback_type:ident, $cell_type:ident) => {
- // Overly-paranoid sanity checking to ensure that these types are
- // convertible between each-other. `transmute` actually should check this for
- // us too, but this helps document the invariants we rely on in this code.
- //
- // Note that these are guaranteed by
- // https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html
- // and thus this is a little paranoid.
- static_assertions::assert_eq_size!(usize, $callback_type);
- static_assertions::assert_eq_size!(usize, Option<$callback_type>);
-
- impl $cell_type {
- pub const fn new() -> Self {
- Self(AtomicUsize::new(0))
- }
-
- pub fn set(&self, callback: $callback_type) {
- // Store the pointer using Ordering::Relaxed. This is sufficient since callback
- // should be set at startup, before there's any chance of using them.
- self.0.store(callback as usize, Ordering::Relaxed);
- }
-
- pub fn get(&self) -> $callback_type {
- let ptr_value = self.0.load(Ordering::Relaxed);
- unsafe {
- // SAFETY: self.0 was set in `set` from our function pointer type, so
- // it's safe to transmute it back here.
- ::std::mem::transmute::<usize, Option<$callback_type>>(ptr_value)
- .expect("Bug: callback not set. This is likely a uniffi bug.")
- }
- }
+use std::{
+ ptr::{null_mut, NonNull},
+ sync::atomic::{AtomicPtr, Ordering},
+};
+
+// Cell type that stores any NonNull<T>
+#[doc(hidden)]
+pub struct UniffiForeignPointerCell<T>(AtomicPtr<T>);
+
+impl<T> UniffiForeignPointerCell<T> {
+ pub const fn new() -> Self {
+ Self(AtomicPtr::new(null_mut()))
+ }
+
+ pub fn set(&self, callback: NonNull<T>) {
+ self.0.store(callback.as_ptr(), Ordering::Relaxed);
+ }
+
+ pub fn get(&self) -> &T {
+ unsafe {
+ NonNull::new(self.0.load(Ordering::Relaxed))
+ .expect("Foreign pointer not set. This is likely a uniffi bug.")
+ .as_mut()
}
- };
+ }
}
-impl_foreign_callback_cell!(ForeignCallback, ForeignCallbackCell);
-impl_foreign_callback_cell!(ForeignExecutorCallback, ForeignExecutorCallbackCell);
+unsafe impl<T> Send for UniffiForeignPointerCell<T> {}
+unsafe impl<T> Sync for UniffiForeignPointerCell<T> {}
diff --git a/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs b/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs
deleted file mode 100644
index 7b1cb9bd80..0000000000
--- a/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs
+++ /dev/null
@@ -1,487 +0,0 @@
-/* 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/. */
-
-//! Schedule tasks using a foreign executor.
-
-use std::panic;
-
-use crate::{ForeignExecutorCallback, ForeignExecutorCallbackCell};
-
-/// Opaque handle for a foreign task executor.
-///
-/// Foreign code can either use an actual pointer, or use an integer value casted to it.
-#[repr(transparent)]
-#[derive(Clone, Copy, Debug)]
-pub struct ForeignExecutorHandle(pub(crate) *const ());
-
-// Implement Send + Sync for `ForeignExecutor`. The foreign bindings code is responsible for
-// making the `ForeignExecutorCallback` thread-safe.
-unsafe impl Send for ForeignExecutorHandle {}
-
-unsafe impl Sync for ForeignExecutorHandle {}
-
-/// Result code returned by `ForeignExecutorCallback`
-#[repr(i8)]
-#[derive(Debug, PartialEq, Eq)]
-pub enum ForeignExecutorCallbackResult {
- /// Callback was scheduled successfully
- Success = 0,
- /// Callback couldn't be scheduled because the foreign executor is canceled/closed.
- Cancelled = 1,
- /// Callback couldn't be scheduled because of some other error
- Error = 2,
-}
-
-impl ForeignExecutorCallbackResult {
- /// Check the result code for the foreign executor callback
- ///
- /// If the result was `ForeignExecutorCallbackResult.Success`, this method returns `true`.
- ///
- /// If not, this method returns `false`, logging errors for any unexpected return values
- pub fn check_result_code(result: i8) -> bool {
- match result {
- n if n == ForeignExecutorCallbackResult::Success as i8 => true,
- n if n == ForeignExecutorCallbackResult::Cancelled as i8 => false,
- n if n == ForeignExecutorCallbackResult::Error as i8 => {
- log::error!(
- "ForeignExecutorCallbackResult::Error returned by foreign executor callback"
- );
- false
- }
- n => {
- log::error!("Unknown code ({n}) returned by foreign executor callback");
- false
- }
- }
- }
-}
-
-// Option<RustTaskCallback> should use the null pointer optimization and be represented in C as a
-// regular pointer. Let's check that.
-static_assertions::assert_eq_size!(usize, Option<RustTaskCallback>);
-
-/// Callback for a Rust task, this is what the foreign executor invokes
-///
-/// The task will be passed the `task_data` passed to `ForeignExecutorCallback` in addition to one
-/// of the `RustTaskCallbackCode` values.
-pub type RustTaskCallback = extern "C" fn(*const (), RustTaskCallbackCode);
-
-/// Passed to a `RustTaskCallback` function when the executor invokes them.
-///
-/// Every `RustTaskCallback` will be invoked eventually, this code is used to distinguish the times
-/// when it's invoked successfully vs times when the callback is being called because the foreign
-/// executor has been cancelled / shutdown
-#[repr(i8)]
-#[derive(Debug, PartialEq, Eq)]
-pub enum RustTaskCallbackCode {
- /// Successful task callback invocation
- Success = 0,
- /// The `ForeignExecutor` has been cancelled.
- ///
- /// This signals that any progress using the executor should be halted. In particular, Futures
- /// should not continue to progress.
- Cancelled = 1,
-}
-
-static FOREIGN_EXECUTOR_CALLBACK: ForeignExecutorCallbackCell = ForeignExecutorCallbackCell::new();
-
-/// Set the global ForeignExecutorCallback. This is called by the foreign bindings, normally
-/// during initialization.
-pub fn foreign_executor_callback_set(callback: ForeignExecutorCallback) {
- FOREIGN_EXECUTOR_CALLBACK.set(callback);
-}
-
-/// Schedule Rust calls using a foreign executor
-#[derive(Debug)]
-pub struct ForeignExecutor {
- pub(crate) handle: ForeignExecutorHandle,
-}
-
-impl ForeignExecutor {
- pub fn new(executor: ForeignExecutorHandle) -> Self {
- Self { handle: executor }
- }
-
- /// Schedule a closure to be run.
- ///
- /// This method can be used for "fire-and-forget" style calls, where the calling code doesn't
- /// need to await the result.
- ///
- /// Closure requirements:
- /// - Send: since the closure will likely run on a different thread
- /// - 'static: since it runs at an arbitrary time, so all references need to be 'static
- /// - panic::UnwindSafe: if the closure panics, it should not corrupt any data
- pub fn schedule<F: FnOnce() + Send + 'static + panic::UnwindSafe>(&self, delay: u32, task: F) {
- let leaked_ptr: *mut F = Box::leak(Box::new(task));
- if !schedule_raw(
- self.handle,
- delay,
- schedule_callback::<F>,
- leaked_ptr as *const (),
- ) {
- // If schedule_raw() failed, drop the leaked box since `schedule_callback()` has not been
- // scheduled to run.
- unsafe {
- drop(Box::<F>::from_raw(leaked_ptr));
- };
- }
- }
-
- /// Schedule a closure to be run and get a Future for the result
- ///
- /// Closure requirements:
- /// - Send: since the closure will likely run on a different thread
- /// - 'static: since it runs at an arbitrary time, so all references need to be 'static
- /// - panic::UnwindSafe: if the closure panics, it should not corrupt any data
- pub async fn run<F, T>(&self, delay: u32, closure: F) -> T
- where
- F: FnOnce() -> T + Send + 'static + panic::UnwindSafe,
- T: Send + 'static,
- {
- // Create a oneshot channel to handle the future
- let (sender, receiver) = oneshot::channel();
- // We can use `AssertUnwindSafe` here because:
- // - The closure is unwind safe
- // - `Sender` is not marked unwind safe, maybe this is just an oversight in the oneshot
- // library. However, calling `send()` and dropping the Sender should certainly be
- // unwind safe. `send()` should probably not panic at all and if it does it shouldn't
- // do it in a way that breaks the Receiver.
- // - Calling `expect` may result in a panic, but this should should not break either the
- // Sender or Receiver.
- self.schedule(
- delay,
- panic::AssertUnwindSafe(move || {
- sender.send(closure()).expect("Error sending future result")
- }),
- );
- receiver.await.expect("Error receiving future result")
- }
-}
-
-/// Low-level schedule interface
-///
-/// When using this function, take care to ensure that the `ForeignExecutor` that holds the
-/// `ForeignExecutorHandle` has not been dropped.
-///
-/// Returns true if the callback was successfully scheduled
-pub(crate) fn schedule_raw(
- handle: ForeignExecutorHandle,
- delay: u32,
- callback: RustTaskCallback,
- data: *const (),
-) -> bool {
- let result_code = (FOREIGN_EXECUTOR_CALLBACK.get())(handle, delay, Some(callback), data);
- ForeignExecutorCallbackResult::check_result_code(result_code)
-}
-
-impl Drop for ForeignExecutor {
- fn drop(&mut self) {
- (FOREIGN_EXECUTOR_CALLBACK.get())(self.handle, 0, None, std::ptr::null());
- }
-}
-
-extern "C" fn schedule_callback<F>(data: *const (), status_code: RustTaskCallbackCode)
-where
- F: FnOnce() + Send + 'static + panic::UnwindSafe,
-{
- // No matter what, we need to call Box::from_raw() to balance the Box::leak() call.
- let task = unsafe { Box::from_raw(data as *mut F) };
- // Skip running the task for the `RustTaskCallbackCode::Cancelled` code
- if status_code == RustTaskCallbackCode::Success {
- run_task(task);
- }
-}
-
-/// Run a scheduled task, catching any panics.
-///
-/// If there are panics, then we will log a warning and return None.
-fn run_task<F: FnOnce() -> T + panic::UnwindSafe, T>(task: F) -> Option<T> {
- match panic::catch_unwind(task) {
- Ok(v) => Some(v),
- Err(cause) => {
- let message = if let Some(s) = cause.downcast_ref::<&'static str>() {
- (*s).to_string()
- } else if let Some(s) = cause.downcast_ref::<String>() {
- s.clone()
- } else {
- "Unknown panic!".to_string()
- };
- log::warn!("Error calling UniFFI callback function: {message}");
- None
- }
- }
-}
-
-#[cfg(test)]
-pub use test::MockEventLoop;
-
-#[cfg(test)]
-mod test {
- use super::*;
- use std::{
- future::Future,
- pin::Pin,
- sync::{
- atomic::{AtomicU32, Ordering},
- Arc, Mutex, Once,
- },
- task::{Context, Poll, Wake, Waker},
- };
-
- /// Simulate an event loop / task queue / coroutine scope on the foreign side
- ///
- /// This simply collects scheduled calls into a Vec for testing purposes.
- ///
- /// Most of the MockEventLoop methods are `pub` since it's also used by the `rustfuture` tests.
- pub struct MockEventLoop {
- // Wrap everything in a mutex since we typically share access to MockEventLoop via an Arc
- inner: Mutex<MockEventLoopInner>,
- }
-
- pub struct MockEventLoopInner {
- // calls that have been scheduled
- calls: Vec<(u32, Option<RustTaskCallback>, *const ())>,
- // has the event loop been shutdown?
- is_shutdown: bool,
- }
-
- unsafe impl Send for MockEventLoopInner {}
-
- static FOREIGN_EXECUTOR_CALLBACK_INIT: Once = Once::new();
-
- impl MockEventLoop {
- pub fn new() -> Arc<Self> {
- // Make sure we install a foreign executor callback that can deal with mock event loops
- FOREIGN_EXECUTOR_CALLBACK_INIT
- .call_once(|| foreign_executor_callback_set(mock_executor_callback));
-
- Arc::new(Self {
- inner: Mutex::new(MockEventLoopInner {
- calls: vec![],
- is_shutdown: false,
- }),
- })
- }
-
- /// Create a new ForeignExecutorHandle
- pub fn new_handle(self: &Arc<Self>) -> ForeignExecutorHandle {
- // To keep the memory management simple, we simply leak an arc reference for this. We
- // only create a handful of these in the tests so there's no need for proper cleanup.
- ForeignExecutorHandle(Arc::into_raw(Arc::clone(self)) as *const ())
- }
-
- pub fn new_executor(self: &Arc<Self>) -> ForeignExecutor {
- ForeignExecutor {
- handle: self.new_handle(),
- }
- }
-
- /// Get the current number of scheduled calls
- pub fn call_count(&self) -> usize {
- self.inner.lock().unwrap().calls.len()
- }
-
- /// Get the last scheduled call
- pub fn last_call(&self) -> (u32, Option<RustTaskCallback>, *const ()) {
- self.inner
- .lock()
- .unwrap()
- .calls
- .last()
- .cloned()
- .expect("no calls scheduled")
- }
-
- /// Run all currently scheduled calls
- pub fn run_all_calls(&self) {
- let mut inner = self.inner.lock().unwrap();
- let is_shutdown = inner.is_shutdown;
- for (_delay, callback, data) in inner.calls.drain(..) {
- if !is_shutdown {
- callback.unwrap()(data, RustTaskCallbackCode::Success);
- } else {
- callback.unwrap()(data, RustTaskCallbackCode::Cancelled);
- }
- }
- }
-
- /// Shutdown the eventloop, causing scheduled calls and future calls to be cancelled
- pub fn shutdown(&self) {
- self.inner.lock().unwrap().is_shutdown = true;
- }
- }
-
- // `ForeignExecutorCallback` that we install for testing
- extern "C" fn mock_executor_callback(
- handle: ForeignExecutorHandle,
- delay: u32,
- task: Option<RustTaskCallback>,
- task_data: *const (),
- ) -> i8 {
- let eventloop = handle.0 as *const MockEventLoop;
- let mut inner = unsafe { (*eventloop).inner.lock().unwrap() };
- if inner.is_shutdown {
- ForeignExecutorCallbackResult::Cancelled as i8
- } else {
- inner.calls.push((delay, task, task_data));
- ForeignExecutorCallbackResult::Success as i8
- }
- }
-
- #[test]
- fn test_schedule_raw() {
- extern "C" fn callback(data: *const (), _status_code: RustTaskCallbackCode) {
- unsafe {
- *(data as *mut u32) += 1;
- }
- }
-
- let eventloop = MockEventLoop::new();
-
- let value: u32 = 0;
- assert_eq!(eventloop.call_count(), 0);
-
- schedule_raw(
- eventloop.new_handle(),
- 0,
- callback,
- &value as *const u32 as *const (),
- );
- assert_eq!(eventloop.call_count(), 1);
- assert_eq!(value, 0);
-
- eventloop.run_all_calls();
- assert_eq!(eventloop.call_count(), 0);
- assert_eq!(value, 1);
- }
-
- #[test]
- fn test_schedule() {
- let eventloop = MockEventLoop::new();
- let executor = eventloop.new_executor();
- let value = Arc::new(AtomicU32::new(0));
- assert_eq!(eventloop.call_count(), 0);
-
- let value2 = value.clone();
- executor.schedule(0, move || {
- value2.fetch_add(1, Ordering::Relaxed);
- });
- assert_eq!(eventloop.call_count(), 1);
- assert_eq!(value.load(Ordering::Relaxed), 0);
-
- eventloop.run_all_calls();
- assert_eq!(eventloop.call_count(), 0);
- assert_eq!(value.load(Ordering::Relaxed), 1);
- }
-
- #[derive(Default)]
- struct MockWaker {
- wake_count: AtomicU32,
- }
-
- impl Wake for MockWaker {
- fn wake(self: Arc<Self>) {
- self.wake_count.fetch_add(1, Ordering::Relaxed);
- }
- }
-
- #[test]
- fn test_run() {
- let eventloop = MockEventLoop::new();
- let executor = eventloop.new_executor();
- let mock_waker = Arc::new(MockWaker::default());
- let waker = Waker::from(mock_waker.clone());
- let mut context = Context::from_waker(&waker);
- assert_eq!(eventloop.call_count(), 0);
-
- let mut future = executor.run(0, move || "test-return-value");
- unsafe {
- assert_eq!(
- Pin::new_unchecked(&mut future).poll(&mut context),
- Poll::Pending
- );
- }
- assert_eq!(eventloop.call_count(), 1);
- assert_eq!(mock_waker.wake_count.load(Ordering::Relaxed), 0);
-
- eventloop.run_all_calls();
- assert_eq!(eventloop.call_count(), 0);
- assert_eq!(mock_waker.wake_count.load(Ordering::Relaxed), 1);
- unsafe {
- assert_eq!(
- Pin::new_unchecked(&mut future).poll(&mut context),
- Poll::Ready("test-return-value")
- );
- }
- }
-
- #[test]
- fn test_drop() {
- let eventloop = MockEventLoop::new();
- let executor = eventloop.new_executor();
-
- drop(executor);
- // Calling drop should schedule a call with null task data.
- assert_eq!(eventloop.call_count(), 1);
- assert_eq!(eventloop.last_call().1, None);
- }
-
- // Test that cancelled calls never run
- #[test]
- fn test_cancelled_call() {
- let eventloop = MockEventLoop::new();
- let executor = eventloop.new_executor();
- // Create a shared counter
- let counter = Arc::new(AtomicU32::new(0));
- // schedule increments using both `schedule()` and run()`
- let counter_clone = Arc::clone(&counter);
- executor.schedule(0, move || {
- counter_clone.fetch_add(1, Ordering::Relaxed);
- });
- let counter_clone = Arc::clone(&counter);
- let future = executor.run(0, move || {
- counter_clone.fetch_add(1, Ordering::Relaxed);
- });
- // shutdown the eventloop before the scheduled call gets a chance to run.
- eventloop.shutdown();
- // `run_all_calls()` will cause the scheduled task callbacks to run, but will pass
- // `RustTaskCallbackCode::Cancelled` to it. This drop the scheduled closure without executing
- // it.
- eventloop.run_all_calls();
-
- assert_eq!(counter.load(Ordering::Relaxed), 0);
- drop(future);
- }
-
- // Test that when scheduled calls are cancelled, the closures are dropped properly
- #[test]
- fn test_cancellation_drops_closures() {
- let eventloop = MockEventLoop::new();
- let executor = eventloop.new_executor();
-
- // Create an Arc<> that we will move into the closures to test if they are dropped or not
- let arc = Arc::new(0);
- let arc_clone = Arc::clone(&arc);
- executor.schedule(0, move || assert_eq!(*arc_clone, 0));
- let arc_clone = Arc::clone(&arc);
- let future = executor.run(0, move || assert_eq!(*arc_clone, 0));
-
- // shutdown the eventloop and run the (cancelled) scheduled calls.
- eventloop.shutdown();
- eventloop.run_all_calls();
- // try to schedule some more calls now that the loop has been shutdown
- let arc_clone = Arc::clone(&arc);
- executor.schedule(0, move || assert_eq!(*arc_clone, 0));
- let arc_clone = Arc::clone(&arc);
- let future2 = executor.run(0, move || assert_eq!(*arc_clone, 0));
-
- // Drop the futures so they don't hold on to any references
- drop(future);
- drop(future2);
-
- // All of these closures should have been dropped by now, there only remaining arc
- // reference should be the original
- assert_eq!(Arc::strong_count(&arc), 1);
- }
-}
diff --git a/third_party/rust/uniffi_core/src/ffi/foreignfuture.rs b/third_party/rust/uniffi_core/src/ffi/foreignfuture.rs
new file mode 100644
index 0000000000..be6a214e84
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/foreignfuture.rs
@@ -0,0 +1,241 @@
+/* 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 module defines a Rust Future that wraps an async foreign function call.
+//!
+//! The general idea is to create a [oneshot::Channel], hand the sender to the foreign side, and
+//! await the receiver side on the Rust side.
+//!
+//! The foreign side should:
+//! * Input a [ForeignFutureCallback] and a `u64` handle in their scaffolding function.
+//! This is the sender, converted to a raw pointer, and an extern "C" function that sends the result.
+//! * Return a [ForeignFuture], which represents the foreign task object corresponding to the async function.
+//! * Call the [ForeignFutureCallback] when the async function completes with:
+//! * The `u64` handle initially passed in
+//! * The `ForeignFutureResult` for the call
+//! * Wait for the [ForeignFutureHandle::free] function to be called to free the task object.
+//! If this is called before the task completes, then the task will be cancelled.
+
+use crate::{LiftReturn, RustCallStatus, UnexpectedUniFFICallbackError};
+
+/// Handle for a foreign future
+pub type ForeignFutureHandle = u64;
+
+/// Handle for a callback data associated with a foreign future.
+pub type ForeignFutureCallbackData = *mut ();
+
+/// Callback that's passed to a foreign async functions.
+///
+/// See `LiftReturn` trait for how this is implemented.
+pub type ForeignFutureCallback<FfiType> =
+ extern "C" fn(oneshot_handle: u64, ForeignFutureResult<FfiType>);
+
+/// C struct that represents the result of a foreign future
+#[repr(C)]
+pub struct ForeignFutureResult<T> {
+ // Note: for void returns, T is `()`, which isn't directly representable with C since it's a ZST.
+ // Foreign code should treat that case as if there was no `return_value` field.
+ return_value: T,
+ call_status: RustCallStatus,
+}
+
+/// Perform a call to a foreign async method
+
+/// C struct that represents the foreign future.
+///
+/// This is what's returned by the async scaffolding functions.
+#[repr(C)]
+pub struct ForeignFuture {
+ pub handle: ForeignFutureHandle,
+ pub free: extern "C" fn(handle: ForeignFutureHandle),
+}
+
+impl Drop for ForeignFuture {
+ fn drop(&mut self) {
+ (self.free)(self.handle)
+ }
+}
+
+unsafe impl Send for ForeignFuture {}
+
+pub async fn foreign_async_call<F, T, UT>(call_scaffolding_function: F) -> T
+where
+ F: FnOnce(ForeignFutureCallback<T::ReturnType>, u64) -> ForeignFuture,
+ T: LiftReturn<UT>,
+{
+ let (sender, receiver) = oneshot::channel::<ForeignFutureResult<T::ReturnType>>();
+ // Keep the ForeignFuture around, even though we don't ever use it.
+ // The important thing is that the ForeignFuture will be dropped when this Future is.
+ let _foreign_future =
+ call_scaffolding_function(foreign_future_complete::<T, UT>, sender.into_raw() as u64);
+ match receiver.await {
+ Ok(result) => T::lift_foreign_return(result.return_value, result.call_status),
+ Err(e) => {
+ // This shouldn't happen in practice, but we can do our best to recover
+ T::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(format!(
+ "Error awaiting foreign future: {e}"
+ )))
+ }
+ }
+}
+
+pub extern "C" fn foreign_future_complete<T: LiftReturn<UT>, UT>(
+ oneshot_handle: u64,
+ result: ForeignFutureResult<T::ReturnType>,
+) {
+ let channel = unsafe { oneshot::Sender::from_raw(oneshot_handle as *mut ()) };
+ // Ignore errors in send.
+ //
+ // Error means the receiver was already dropped which will happen when the future is cancelled.
+ let _ = channel.send(result);
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::{Lower, RustBuffer};
+ use once_cell::sync::OnceCell;
+ use std::{
+ future::Future,
+ pin::Pin,
+ sync::{
+ atomic::{AtomicU32, Ordering},
+ Arc,
+ },
+ task::{Context, Poll, Wake},
+ };
+
+ struct MockForeignFuture {
+ freed: Arc<AtomicU32>,
+ callback_info: Arc<OnceCell<(ForeignFutureCallback<RustBuffer>, u64)>>,
+ rust_future: Option<Pin<Box<dyn Future<Output = String>>>>,
+ }
+
+ impl MockForeignFuture {
+ fn new() -> Self {
+ let callback_info = Arc::new(OnceCell::new());
+ let freed = Arc::new(AtomicU32::new(0));
+
+ let rust_future: Pin<Box<dyn Future<Output = String>>> = {
+ let callback_info = callback_info.clone();
+ let freed = freed.clone();
+ Box::pin(foreign_async_call::<_, String, crate::UniFfiTag>(
+ move |callback, data| {
+ callback_info.set((callback, data)).unwrap();
+ ForeignFuture {
+ handle: Arc::into_raw(freed) as *mut () as u64,
+ free: Self::free,
+ }
+ },
+ ))
+ };
+ let rust_future = Some(rust_future);
+ let mut mock_foreign_future = Self {
+ freed,
+ callback_info,
+ rust_future,
+ };
+ // Poll the future once, to start it up. This ensures that `callback_info` is set.
+ let _ = mock_foreign_future.poll();
+ mock_foreign_future
+ }
+
+ fn poll(&mut self) -> Poll<String> {
+ let waker = Arc::new(NoopWaker).into();
+ let mut context = Context::from_waker(&waker);
+ self.rust_future
+ .as_mut()
+ .unwrap()
+ .as_mut()
+ .poll(&mut context)
+ }
+
+ fn complete_success(&self, value: String) {
+ let (callback, data) = self.callback_info.get().unwrap();
+ callback(
+ *data,
+ ForeignFutureResult {
+ return_value: <String as Lower<crate::UniFfiTag>>::lower(value),
+ call_status: RustCallStatus::new(),
+ },
+ );
+ }
+
+ fn complete_error(&self, error_message: String) {
+ let (callback, data) = self.callback_info.get().unwrap();
+ callback(
+ *data,
+ ForeignFutureResult {
+ return_value: RustBuffer::default(),
+ call_status: RustCallStatus::error(error_message),
+ },
+ );
+ }
+
+ fn drop_future(&mut self) {
+ self.rust_future = None
+ }
+
+ fn free_count(&self) -> u32 {
+ self.freed.load(Ordering::Relaxed)
+ }
+
+ extern "C" fn free(handle: u64) {
+ let flag = unsafe { Arc::from_raw(handle as *mut AtomicU32) };
+ flag.fetch_add(1, Ordering::Relaxed);
+ }
+ }
+
+ struct NoopWaker;
+
+ impl Wake for NoopWaker {
+ fn wake(self: Arc<Self>) {}
+ }
+
+ #[test]
+ fn test_foreign_future() {
+ let mut mock_foreign_future = MockForeignFuture::new();
+ assert_eq!(mock_foreign_future.poll(), Poll::Pending);
+ mock_foreign_future.complete_success("It worked!".to_owned());
+ assert_eq!(
+ mock_foreign_future.poll(),
+ Poll::Ready("It worked!".to_owned())
+ );
+ // Since the future is complete, it should free the foreign future
+ assert_eq!(mock_foreign_future.free_count(), 1);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_foreign_future_error() {
+ let mut mock_foreign_future = MockForeignFuture::new();
+ assert_eq!(mock_foreign_future.poll(), Poll::Pending);
+ mock_foreign_future.complete_error("It Failed!".to_owned());
+ let _ = mock_foreign_future.poll();
+ }
+
+ #[test]
+ fn test_drop_after_complete() {
+ let mut mock_foreign_future = MockForeignFuture::new();
+ mock_foreign_future.complete_success("It worked!".to_owned());
+ assert_eq!(mock_foreign_future.free_count(), 0);
+ assert_eq!(
+ mock_foreign_future.poll(),
+ Poll::Ready("It worked!".to_owned())
+ );
+ // Dropping the future after it's complete should not panic, and not cause a double-free
+ mock_foreign_future.drop_future();
+ assert_eq!(mock_foreign_future.free_count(), 1);
+ }
+
+ #[test]
+ fn test_drop_before_complete() {
+ let mut mock_foreign_future = MockForeignFuture::new();
+ mock_foreign_future.complete_success("It worked!".to_owned());
+ // Dropping the future before it's complete should cancel the future
+ assert_eq!(mock_foreign_future.free_count(), 0);
+ mock_foreign_future.drop_future();
+ assert_eq!(mock_foreign_future.free_count(), 1);
+ }
+}
diff --git a/third_party/rust/uniffi_core/src/ffi/handle.rs b/third_party/rust/uniffi_core/src/ffi/handle.rs
new file mode 100644
index 0000000000..8ee2f46c35
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/handle.rs
@@ -0,0 +1,46 @@
+/* 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/. */
+
+/// Object handle
+///
+/// Handles opaque `u64` values used to pass objects across the FFI, both for objects implemented in
+/// Rust and ones implemented in the foreign language.
+///
+/// Rust handles are generated by leaking a raw pointer
+/// Foreign handles are generated with a handle map that only generates odd values.
+/// For all currently supported architectures and hopefully any ones we add in the future:
+/// * 0 is an invalid value.
+/// * The lowest bit will always be set for foreign handles and never set for Rust ones (since the
+/// leaked pointer will be aligned).
+///
+/// Rust handles are mainly managed is through the [crate::HandleAlloc] trait.
+#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
+#[repr(transparent)]
+pub struct Handle(u64);
+
+impl Handle {
+ pub fn from_pointer<T>(ptr: *const T) -> Self {
+ Self(ptr as u64)
+ }
+
+ pub fn as_pointer<T>(&self) -> *const T {
+ self.0 as *const T
+ }
+
+ pub fn from_raw(raw: u64) -> Option<Self> {
+ if raw == 0 {
+ None
+ } else {
+ Some(Self(raw))
+ }
+ }
+
+ pub fn from_raw_unchecked(raw: u64) -> Self {
+ Self(raw)
+ }
+
+ pub fn as_raw(&self) -> u64 {
+ self.0
+ }
+}
diff --git a/third_party/rust/uniffi_core/src/ffi/mod.rs b/third_party/rust/uniffi_core/src/ffi/mod.rs
index b606323297..acaf2b0d06 100644
--- a/third_party/rust/uniffi_core/src/ffi/mod.rs
+++ b/third_party/rust/uniffi_core/src/ffi/mod.rs
@@ -8,7 +8,8 @@ pub mod callbackinterface;
pub mod ffidefault;
pub mod foreignbytes;
pub mod foreigncallbacks;
-pub mod foreignexecutor;
+pub mod foreignfuture;
+pub mod handle;
pub mod rustbuffer;
pub mod rustcalls;
pub mod rustfuture;
@@ -17,7 +18,8 @@ pub use callbackinterface::*;
pub use ffidefault::FfiDefault;
pub use foreignbytes::*;
pub use foreigncallbacks::*;
-pub use foreignexecutor::*;
+pub use foreignfuture::*;
+pub use handle::*;
pub use rustbuffer::*;
pub use rustcalls::*;
pub use rustfuture::*;
diff --git a/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs b/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs
index e09e3be89a..8b2972968c 100644
--- a/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs
+++ b/third_party/rust/uniffi_core/src/ffi/rustbuffer.rs
@@ -52,11 +52,11 @@ use crate::ffi::{rust_call, ForeignBytes, RustCallStatus};
#[derive(Debug)]
pub struct RustBuffer {
/// The allocated capacity of the underlying `Vec<u8>`.
- /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA.
- capacity: i32,
+ /// In Rust this is a `usize`, but we use an `u64` to keep the foreign binding code simple.
+ capacity: u64,
/// The occupied length of the underlying `Vec<u8>`.
- /// In Rust this is a `usize`, but we use an `i32` for compatibility with JNA.
- len: i32,
+ /// In Rust this is a `usize`, but we use an `u64` to keep the foreign binding code simple.
+ len: u64,
/// The pointer to the allocated buffer of the `Vec<u8>`.
data: *mut u8,
}
@@ -84,7 +84,7 @@ impl RustBuffer {
/// # Safety
///
/// You must ensure that the raw parts uphold the documented invariants of this class.
- pub unsafe fn from_raw_parts(data: *mut u8, len: i32, capacity: i32) -> Self {
+ pub unsafe fn from_raw_parts(data: *mut u8, len: u64, capacity: u64) -> Self {
Self {
capacity,
len,
@@ -126,12 +126,8 @@ impl RustBuffer {
///
/// Panics if the requested size is too large to fit in an `i32`, and
/// hence would risk incompatibility with some foreign-language code.
- pub fn new_with_size(size: usize) -> Self {
- assert!(
- size < i32::MAX as usize,
- "RustBuffer requested size too large"
- );
- Self::from_vec(vec![0u8; size])
+ pub fn new_with_size(size: u64) -> Self {
+ Self::from_vec(vec![0u8; size as usize])
}
/// Consumes a `Vec<u8>` and returns its raw parts as a `RustBuffer`.
@@ -144,8 +140,8 @@ impl RustBuffer {
/// Panics if the vector's length or capacity are too large to fit in an `i32`,
/// and hence would risk incompatibility with some foreign-language code.
pub fn from_vec(v: Vec<u8>) -> Self {
- let capacity = i32::try_from(v.capacity()).expect("buffer capacity cannot fit into a i32.");
- let len = i32::try_from(v.len()).expect("buffer length cannot fit into a i32.");
+ let capacity = u64::try_from(v.capacity()).expect("buffer capacity cannot fit into a u64.");
+ let len = u64::try_from(v.len()).expect("buffer length cannot fit into a u64.");
let mut v = std::mem::ManuallyDrop::new(v);
unsafe { Self::from_raw_parts(v.as_mut_ptr(), len, capacity) }
}
@@ -198,39 +194,18 @@ impl Default for RustBuffer {
}
}
-// extern "C" functions for the RustBuffer functionality.
+// Functions for the RustBuffer functionality.
//
-// These are used in two ways:
-// 1. Code that statically links to UniFFI can use these directly to handle RustBuffer
-// allocation/destruction. The plan is to use this for the Firefox desktop JS bindings.
-//
-// 2. The scaffolding code re-exports these functions, prefixed with the component name and UDL
-// hash This creates a separate set of functions for each UniFFIed component, which is needed
-// in the case where we create multiple dylib artifacts since each dylib will have its own
-// allocator.
+// The scaffolding code re-exports these functions, prefixed with the component name and UDL hash
+// This creates a separate set of functions for each UniFFIed component, which is needed in the
+// case where we create multiple dylib artifacts since each dylib will have its own allocator.
/// This helper allocates a new byte buffer owned by the Rust code, and returns it
/// to the foreign-language code as a `RustBuffer` struct. Callers must eventually
/// free the resulting buffer, either by explicitly calling [`uniffi_rustbuffer_free`] defined
/// below, or by passing ownership of the buffer back into Rust code.
-#[cfg(feature = "extern-rustbuffer")]
-#[no_mangle]
-pub extern "C" fn uniffi_rustbuffer_alloc(
- size: i32,
- call_status: &mut RustCallStatus,
-) -> RustBuffer {
- _uniffi_rustbuffer_alloc(size, call_status)
-}
-
-#[cfg(not(feature = "extern-rustbuffer"))]
-pub fn uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> RustBuffer {
- _uniffi_rustbuffer_alloc(size, call_status)
-}
-
-fn _uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> RustBuffer {
- rust_call(call_status, || {
- Ok(RustBuffer::new_with_size(size.max(0) as usize))
- })
+pub fn uniffi_rustbuffer_alloc(size: u64, call_status: &mut RustCallStatus) -> RustBuffer {
+ rust_call(call_status, || Ok(RustBuffer::new_with_size(size)))
}
/// This helper copies bytes owned by the foreign-language code into a new byte buffer owned
@@ -241,27 +216,10 @@ fn _uniffi_rustbuffer_alloc(size: i32, call_status: &mut RustCallStatus) -> Rust
/// # Safety
/// This function will dereference a provided pointer in order to copy bytes from it, so
/// make sure the `ForeignBytes` struct contains a valid pointer and length.
-#[cfg(feature = "extern-rustbuffer")]
-#[no_mangle]
-pub extern "C" fn uniffi_rustbuffer_from_bytes(
- bytes: ForeignBytes,
- call_status: &mut RustCallStatus,
-) -> RustBuffer {
- _uniffi_rustbuffer_from_bytes(bytes, call_status)
-}
-
-#[cfg(not(feature = "extern-rustbuffer"))]
pub fn uniffi_rustbuffer_from_bytes(
bytes: ForeignBytes,
call_status: &mut RustCallStatus,
) -> RustBuffer {
- _uniffi_rustbuffer_from_bytes(bytes, call_status)
-}
-
-fn _uniffi_rustbuffer_from_bytes(
- bytes: ForeignBytes,
- call_status: &mut RustCallStatus,
-) -> RustBuffer {
rust_call(call_status, || {
let bytes = bytes.as_slice();
Ok(RustBuffer::from_vec(bytes.to_vec()))
@@ -274,18 +232,7 @@ fn _uniffi_rustbuffer_from_bytes(
/// The argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call
/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or
/// corrupting the allocator state.
-#[cfg(feature = "extern-rustbuffer")]
-#[no_mangle]
-pub extern "C" fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) {
- _uniffi_rustbuffer_free(buf, call_status)
-}
-
-#[cfg(not(feature = "extern-rustbuffer"))]
pub fn uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) {
- _uniffi_rustbuffer_free(buf, call_status)
-}
-
-fn _uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) {
rust_call(call_status, || {
RustBuffer::destroy(buf);
Ok(())
@@ -307,28 +254,9 @@ fn _uniffi_rustbuffer_free(buf: RustBuffer, call_status: &mut RustCallStatus) {
/// The first argument *must* be a uniquely-owned `RustBuffer` previously obtained from a call
/// into the Rust code that returned a buffer, or you'll risk freeing unowned memory or
/// corrupting the allocator state.
-#[cfg(feature = "extern-rustbuffer")]
-#[no_mangle]
-pub extern "C" fn uniffi_rustbuffer_reserve(
- buf: RustBuffer,
- additional: i32,
- call_status: &mut RustCallStatus,
-) -> RustBuffer {
- _uniffi_rustbuffer_reserve(buf, additional, call_status)
-}
-
-#[cfg(not(feature = "extern-rustbuffer"))]
pub fn uniffi_rustbuffer_reserve(
buf: RustBuffer,
- additional: i32,
- call_status: &mut RustCallStatus,
-) -> RustBuffer {
- _uniffi_rustbuffer_reserve(buf, additional, call_status)
-}
-
-fn _uniffi_rustbuffer_reserve(
- buf: RustBuffer,
- additional: i32,
+ additional: u64,
call_status: &mut RustCallStatus,
) -> RustBuffer {
rust_call(call_status, || {
@@ -394,24 +322,6 @@ mod test {
#[test]
#[should_panic]
- fn test_rustbuffer_provided_capacity_must_be_non_negative() {
- // We guard against foreign-language code providing this kind of invalid struct.
- let mut v = vec![0u8, 1, 2];
- let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), 3, -7) };
- rbuf.destroy_into_vec();
- }
-
- #[test]
- #[should_panic]
- fn test_rustbuffer_provided_len_must_be_non_negative() {
- // We guard against foreign-language code providing this kind of invalid struct.
- let mut v = vec![0u8, 1, 2];
- let rbuf = unsafe { RustBuffer::from_raw_parts(v.as_mut_ptr(), -1, 3) };
- rbuf.destroy_into_vec();
- }
-
- #[test]
- #[should_panic]
fn test_rustbuffer_provided_len_must_not_exceed_capacity() {
// We guard against foreign-language code providing this kind of invalid struct.
let mut v = vec![0u8, 1, 2];
diff --git a/third_party/rust/uniffi_core/src/ffi/rustcalls.rs b/third_party/rust/uniffi_core/src/ffi/rustcalls.rs
index 53265393c0..16b0c76f2e 100644
--- a/third_party/rust/uniffi_core/src/ffi/rustcalls.rs
+++ b/third_party/rust/uniffi_core/src/ffi/rustcalls.rs
@@ -56,6 +56,13 @@ pub struct RustCallStatus {
}
impl RustCallStatus {
+ pub fn new() -> Self {
+ Self {
+ code: RustCallStatusCode::Success,
+ error_buf: MaybeUninit::new(RustBuffer::new()),
+ }
+ }
+
pub fn cancelled() -> Self {
Self {
code: RustCallStatusCode::Cancelled,
@@ -102,7 +109,7 @@ pub enum RustCallStatusCode {
/// Handle a scaffolding calls
///
/// `callback` is responsible for making the actual Rust call and returning a special result type:
-/// - For successfull calls, return `Ok(value)`
+/// - For successful calls, return `Ok(value)`
/// - For errors that should be translated into thrown exceptions in the foreign code, serialize
/// the error into a `RustBuffer`, then return `Ok(buf)`
/// - The success type, must implement `FfiDefault`.
diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture.rs
deleted file mode 100644
index 0c1a24174b..0000000000
--- a/third_party/rust/uniffi_core/src/ffi/rustfuture.rs
+++ /dev/null
@@ -1,735 +0,0 @@
-/* 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/. */
-
-//! [`RustFuture`] represents a [`Future`] that can be sent to the foreign code over FFI.
-//!
-//! This type is not instantiated directly, but via the procedural macros, such as `#[uniffi::export]`.
-//!
-//! # The big picture
-//!
-//! We implement async foreign functions using a simplified version of the Future API:
-//!
-//! 0. At startup, register a [RustFutureContinuationCallback] by calling
-//! rust_future_continuation_callback_set.
-//! 1. Call the scaffolding function to get a [RustFutureHandle]
-//! 2a. In a loop:
-//! - Call [rust_future_poll]
-//! - Suspend the function until the [rust_future_poll] continuation function is called
-//! - If the continuation was function was called with [RustFuturePoll::Ready], then break
-//! otherwise continue.
-//! 2b. If the async function is cancelled, then call [rust_future_cancel]. This causes the
-//! continuation function to be called with [RustFuturePoll::Ready] and the [RustFuture] to
-//! enter a cancelled state.
-//! 3. Call [rust_future_complete] to get the result of the future.
-//! 4. Call [rust_future_free] to free the future, ideally in a finally block. This:
-//! - Releases any resources held by the future
-//! - Calls any continuation callbacks that have not been called yet
-//!
-//! Note: Technically, the foreign code calls the scaffolding versions of the `rust_future_*`
-//! functions. These are generated by the scaffolding macro, specially prefixed, and extern "C",
-//! and manually monomorphized in the case of [rust_future_complete]. See
-//! `uniffi_macros/src/setup_scaffolding.rs` for details.
-//!
-//! ## How does `Future` work exactly?
-//!
-//! A [`Future`] in Rust does nothing. When calling an async function, it just
-//! returns a `Future` but nothing has happened yet. To start the computation,
-//! the future must be polled. It returns [`Poll::Ready(r)`][`Poll::Ready`] if
-//! the result is ready, [`Poll::Pending`] otherwise. `Poll::Pending` basically
-//! means:
-//!
-//! > Please, try to poll me later, maybe the result will be ready!
-//!
-//! This model is very different than what other languages do, but it can actually
-//! be translated quite easily, fortunately for us!
-//!
-//! But… wait a minute… who is responsible to poll the `Future` if a `Future` does
-//! nothing? Well, it's _the executor_. The executor is responsible _to drive_ the
-//! `Future`: that's where they are polled.
-//!
-//! But… wait another minute… how does the executor know when to poll a [`Future`]?
-//! Does it poll them randomly in an endless loop? Well, no, actually it depends
-//! on the executor! A well-designed `Future` and executor work as follows.
-//! Normally, when [`Future::poll`] is called, a [`Context`] argument is
-//! passed to it. It contains a [`Waker`]. The [`Waker`] is built on top of a
-//! [`RawWaker`] which implements whatever is necessary. Usually, a waker will
-//! signal the executor to poll a particular `Future`. A `Future` will clone
-//! or pass-by-ref the waker to somewhere, as a callback, a completion, a
-//! function, or anything, to the system that is responsible to notify when a
-//! task is completed. So, to recap, the waker is _not_ responsible for waking the
-//! `Future`, it _is_ responsible for _signaling_ the executor that a particular
-//! `Future` should be polled again. That's why the documentation of
-//! [`Poll::Pending`] specifies:
-//!
-//! > When a function returns `Pending`, the function must also ensure that the
-//! > current task is scheduled to be awoken when progress can be made.
-//!
-//! “awakening” is done by using the `Waker`.
-//!
-//! [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html
-//! [`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll
-//! [`Pol::Ready`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready
-//! [`Poll::Pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending
-//! [`Context`]: https://doc.rust-lang.org/std/task/struct.Context.html
-//! [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html
-//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html
-
-use std::{
- future::Future,
- marker::PhantomData,
- mem,
- ops::Deref,
- panic,
- pin::Pin,
- sync::{Arc, Mutex},
- task::{Context, Poll, Wake},
-};
-
-use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus};
-
-/// Result code for [rust_future_poll]. This is passed to the continuation function.
-#[repr(i8)]
-#[derive(Debug, PartialEq, Eq)]
-pub enum RustFuturePoll {
- /// The future is ready and is waiting for [rust_future_complete] to be called
- Ready = 0,
- /// The future might be ready and [rust_future_poll] should be called again
- MaybeReady = 1,
-}
-
-/// Foreign callback that's passed to [rust_future_poll]
-///
-/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again
-/// to continue progress on the future.
-pub type RustFutureContinuationCallback = extern "C" fn(callback_data: *const (), RustFuturePoll);
-
-/// Opaque handle for a Rust future that's stored by the foreign language code
-#[repr(transparent)]
-pub struct RustFutureHandle(*const ());
-
-// === Public FFI API ===
-
-/// Create a new [RustFutureHandle]
-///
-/// For each exported async function, UniFFI will create a scaffolding function that uses this to
-/// create the [RustFutureHandle] to pass to the foreign code.
-pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> RustFutureHandle
-where
- // F is the future type returned by the exported async function. It needs to be Send + `static
- // since it will move between threads for an indeterminate amount of time as the foreign
- // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`,
- // since we synchronize all access to the values.
- F: Future<Output = T> + Send + 'static,
- // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send +
- // 'static for the same reason as F.
- T: LowerReturn<UT> + Send + 'static,
- // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy.
- UT: Send + 'static,
-{
- // Create a RustFuture and coerce to `Arc<dyn RustFutureFfi>`, which is what we use to
- // implement the FFI
- let future_ffi = RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>>;
- // Box the Arc, to convert the wide pointer into a normal sized pointer so that we can pass it
- // to the foreign code.
- let boxed_ffi = Box::new(future_ffi);
- // We can now create a RustFutureHandle
- RustFutureHandle(Box::into_raw(boxed_ffi) as *mut ())
-}
-
-/// Poll a Rust future
-///
-/// When the future is ready to progress the continuation will be called with the `data` value and
-/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called
-/// exactly once.
-///
-/// # Safety
-///
-/// The [RustFutureHandle] must not previously have been passed to [rust_future_free]
-pub unsafe fn rust_future_poll<ReturnType>(
- handle: RustFutureHandle,
- callback: RustFutureContinuationCallback,
- data: *const (),
-) {
- let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>);
- future.clone().ffi_poll(callback, data)
-}
-
-/// Cancel a Rust future
-///
-/// Any current and future continuations will be immediately called with RustFuturePoll::Ready.
-///
-/// This is needed for languages like Swift, which continuation to wait for the continuation to be
-/// called when tasks are cancelled.
-///
-/// # Safety
-///
-/// The [RustFutureHandle] must not previously have been passed to [rust_future_free]
-pub unsafe fn rust_future_cancel<ReturnType>(handle: RustFutureHandle) {
- let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>);
- future.clone().ffi_cancel()
-}
-
-/// Complete a Rust future
-///
-/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for
-/// each supported FFI type.
-///
-/// # Safety
-///
-/// - The [RustFutureHandle] must not previously have been passed to [rust_future_free]
-/// - The `T` param must correctly correspond to the [rust_future_new] call. It must
-/// be `<Output as LowerReturn<UT>>::ReturnType`
-pub unsafe fn rust_future_complete<ReturnType>(
- handle: RustFutureHandle,
- out_status: &mut RustCallStatus,
-) -> ReturnType {
- let future = &*(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>);
- future.ffi_complete(out_status)
-}
-
-/// Free a Rust future, dropping the strong reference and releasing all references held by the
-/// future.
-///
-/// # Safety
-///
-/// The [RustFutureHandle] must not previously have been passed to [rust_future_free]
-pub unsafe fn rust_future_free<ReturnType>(handle: RustFutureHandle) {
- let future = Box::from_raw(handle.0 as *mut Arc<dyn RustFutureFfi<ReturnType>>);
- future.ffi_free()
-}
-
-/// Thread-safe storage for [RustFutureContinuationCallback] data
-///
-/// The basic guarantee is that all data pointers passed in are passed out exactly once to the
-/// foreign continuation callback. This enables us to uphold the [rust_future_poll] guarantee.
-///
-/// [ContinuationDataCell] also tracks cancellation, which is closely tied to continuation data.
-#[derive(Debug)]
-enum ContinuationDataCell {
- /// No continuations set, neither wake() nor cancel() called.
- Empty,
- /// `wake()` was called when there was no continuation set. The next time `store` is called,
- /// the continuation should be immediately invoked with `RustFuturePoll::MaybeReady`
- Waked,
- /// The future has been cancelled, any future `store` calls should immediately result in the
- /// continuation being called with `RustFuturePoll::Ready`.
- Cancelled,
- /// Continuation set, the next time `wake()` is called is called, we should invoke it.
- Set(RustFutureContinuationCallback, *const ()),
-}
-
-impl ContinuationDataCell {
- fn new() -> Self {
- Self::Empty
- }
-
- /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or
- /// `Cancelled` state, call the continuation immediately with the data.
- fn store(&mut self, callback: RustFutureContinuationCallback, data: *const ()) {
- match self {
- Self::Empty => *self = Self::Set(callback, data),
- Self::Set(old_callback, old_data) => {
- log::error!(
- "store: observed `Self::Set` state. Is poll() being called from multiple threads at once?"
- );
- old_callback(*old_data, RustFuturePoll::Ready);
- *self = Self::Set(callback, data);
- }
- Self::Waked => {
- *self = Self::Empty;
- callback(data, RustFuturePoll::MaybeReady);
- }
- Self::Cancelled => {
- callback(data, RustFuturePoll::Ready);
- }
- }
- }
-
- fn wake(&mut self) {
- match self {
- // If we had a continuation set, then call it and transition to the `Empty` state.
- Self::Set(callback, old_data) => {
- let old_data = *old_data;
- let callback = *callback;
- *self = Self::Empty;
- callback(old_data, RustFuturePoll::MaybeReady);
- }
- // If we were in the `Empty` state, then transition to `Waked`. The next time `store`
- // is called, we will immediately call the continuation.
- Self::Empty => *self = Self::Waked,
- // This is a no-op if we were in the `Cancelled` or `Waked` state.
- _ => (),
- }
- }
-
- fn cancel(&mut self) {
- if let Self::Set(callback, old_data) = mem::replace(self, Self::Cancelled) {
- callback(old_data, RustFuturePoll::Ready);
- }
- }
-
- fn is_cancelled(&self) -> bool {
- matches!(self, Self::Cancelled)
- }
-}
-
-// ContinuationDataCell is Send + Sync as long we handle the *const () pointer correctly
-
-unsafe impl Send for ContinuationDataCell {}
-unsafe impl Sync for ContinuationDataCell {}
-
-/// Wraps the actual future we're polling
-struct WrappedFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
- // Note: this could be a single enum, but that would make it easy to mess up the future pinning
- // guarantee. For example you might want to call `std::mem::take()` to try to get the result,
- // but if the future happened to be stored that would move and break all internal references.
- future: Option<F>,
- result: Option<Result<T::ReturnType, RustCallStatus>>,
-}
-
-impl<F, T, UT> WrappedFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
- fn new(future: F) -> Self {
- Self {
- future: Some(future),
- result: None,
- }
- }
-
- // Poll the future and check if it's ready or not
- fn poll(&mut self, context: &mut Context<'_>) -> bool {
- if self.result.is_some() {
- true
- } else if let Some(future) = &mut self.future {
- // SAFETY: We can call Pin::new_unchecked because:
- // - This is the only time we get a &mut to `self.future`
- // - We never poll the future after it's moved (for example by using take())
- // - We never move RustFuture, which contains us.
- // - RustFuture is private to this module so no other code can move it.
- let pinned = unsafe { Pin::new_unchecked(future) };
- // Run the poll and lift the result if it's ready
- let mut out_status = RustCallStatus::default();
- let result: Option<Poll<T::ReturnType>> = rust_call_with_out_status(
- &mut out_status,
- // This closure uses a `&mut F` value, which means it's not UnwindSafe by
- // default. If the future panics, it may be in an invalid state.
- //
- // However, we can safely use `AssertUnwindSafe` since a panic will lead the `None`
- // case below and we will never poll the future again.
- panic::AssertUnwindSafe(|| match pinned.poll(context) {
- Poll::Pending => Ok(Poll::Pending),
- Poll::Ready(v) => T::lower_return(v).map(Poll::Ready),
- }),
- );
- match result {
- Some(Poll::Pending) => false,
- Some(Poll::Ready(v)) => {
- self.future = None;
- self.result = Some(Ok(v));
- true
- }
- None => {
- self.future = None;
- self.result = Some(Err(out_status));
- true
- }
- }
- } else {
- log::error!("poll with neither future nor result set");
- true
- }
- }
-
- fn complete(&mut self, out_status: &mut RustCallStatus) -> T::ReturnType {
- let mut return_value = T::ReturnType::ffi_default();
- match self.result.take() {
- Some(Ok(v)) => return_value = v,
- Some(Err(call_status)) => *out_status = call_status,
- None => *out_status = RustCallStatus::cancelled(),
- }
- self.free();
- return_value
- }
-
- fn free(&mut self) {
- self.future = None;
- self.result = None;
- }
-}
-
-// If F and T are Send, then WrappedFuture is too
-//
-// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising
-// that we will treat the raw pointer properly, for example by not returning it twice.
-unsafe impl<F, T, UT> Send for WrappedFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
-}
-
-/// Future that the foreign code is awaiting
-struct RustFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
- // This Mutex should never block if our code is working correctly, since there should not be
- // multiple threads calling [Self::poll] and/or [Self::complete] at the same time.
- future: Mutex<WrappedFuture<F, T, UT>>,
- continuation_data: Mutex<ContinuationDataCell>,
- // UT is used as the generic parameter for [LowerReturn].
- // Let's model this with PhantomData as a function that inputs a UT value.
- _phantom: PhantomData<fn(UT) -> ()>,
-}
-
-impl<F, T, UT> RustFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
- fn new(future: F, _tag: UT) -> Arc<Self> {
- Arc::new(Self {
- future: Mutex::new(WrappedFuture::new(future)),
- continuation_data: Mutex::new(ContinuationDataCell::new()),
- _phantom: PhantomData,
- })
- }
-
- fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) {
- let ready = self.is_cancelled() || {
- let mut locked = self.future.lock().unwrap();
- let waker: std::task::Waker = Arc::clone(&self).into();
- locked.poll(&mut Context::from_waker(&waker))
- };
- if ready {
- callback(data, RustFuturePoll::Ready)
- } else {
- self.continuation_data.lock().unwrap().store(callback, data);
- }
- }
-
- fn is_cancelled(&self) -> bool {
- self.continuation_data.lock().unwrap().is_cancelled()
- }
-
- fn wake(&self) {
- self.continuation_data.lock().unwrap().wake();
- }
-
- fn cancel(&self) {
- self.continuation_data.lock().unwrap().cancel();
- }
-
- fn complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType {
- self.future.lock().unwrap().complete(call_status)
- }
-
- fn free(self: Arc<Self>) {
- // Call cancel() to send any leftover data to the continuation callback
- self.continuation_data.lock().unwrap().cancel();
- // Ensure we drop our inner future, releasing all held references
- self.future.lock().unwrap().free();
- }
-}
-
-impl<F, T, UT> Wake for RustFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
- fn wake(self: Arc<Self>) {
- self.deref().wake()
- }
-
- fn wake_by_ref(self: &Arc<Self>) {
- self.deref().wake()
- }
-}
-
-/// RustFuture FFI trait. This allows `Arc<RustFuture<F, T, UT>>` to be cast to
-/// `Arc<dyn RustFutureFfi<T::ReturnType>>`, which is needed to implement the public FFI API. In particular, this
-/// allows you to use RustFuture functionality without knowing the concrete Future type, which is
-/// unnamable.
-///
-/// This is parametrized on the ReturnType rather than the `T` directly, to reduce the number of
-/// scaffolding functions we need to generate. If it was parametrized on `T`, then we would need
-/// to create a poll, cancel, complete, and free scaffolding function for each exported async
-/// function. That would add ~1kb binary size per exported function based on a quick estimate on a
-/// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and
-/// only create those functions for each of the 13 possible FFI return types.
-#[doc(hidden)]
-trait RustFutureFfi<ReturnType> {
- fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ());
- fn ffi_cancel(&self);
- fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType;
- fn ffi_free(self: Arc<Self>);
-}
-
-impl<F, T, UT> RustFutureFfi<T::ReturnType> for RustFuture<F, T, UT>
-where
- // See rust_future_new for an explanation of these trait bounds
- F: Future<Output = T> + Send + 'static,
- T: LowerReturn<UT> + Send + 'static,
- UT: Send + 'static,
-{
- fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: *const ()) {
- self.poll(callback, data)
- }
-
- fn ffi_cancel(&self) {
- self.cancel()
- }
-
- fn ffi_complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType {
- self.complete(call_status)
- }
-
- fn ffi_free(self: Arc<Self>) {
- self.free();
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode};
- use once_cell::sync::OnceCell;
- use std::task::Waker;
-
- // Sender/Receiver pair that we use for testing
- struct Channel {
- result: Option<Result<String, TestError>>,
- waker: Option<Waker>,
- }
-
- struct Sender(Arc<Mutex<Channel>>);
-
- impl Sender {
- fn wake(&self) {
- let inner = self.0.lock().unwrap();
- if let Some(waker) = &inner.waker {
- waker.wake_by_ref();
- }
- }
-
- fn send(&self, value: Result<String, TestError>) {
- let mut inner = self.0.lock().unwrap();
- if inner.result.replace(value).is_some() {
- panic!("value already sent");
- }
- if let Some(waker) = &inner.waker {
- waker.wake_by_ref();
- }
- }
- }
-
- struct Receiver(Arc<Mutex<Channel>>);
-
- impl Future for Receiver {
- type Output = Result<String, TestError>;
-
- fn poll(
- self: Pin<&mut Self>,
- context: &mut Context<'_>,
- ) -> Poll<Result<String, TestError>> {
- let mut inner = self.0.lock().unwrap();
- match &inner.result {
- Some(v) => Poll::Ready(v.clone()),
- None => {
- inner.waker = Some(context.waker().clone());
- Poll::Pending
- }
- }
- }
- }
-
- // Create a sender and rust future that we can use for testing
- fn channel() -> (Sender, Arc<dyn RustFutureFfi<RustBuffer>>) {
- let channel = Arc::new(Mutex::new(Channel {
- result: None,
- waker: None,
- }));
- let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag);
- (Sender(channel), rust_future)
- }
-
- /// Poll a Rust future and get an OnceCell that's set when the continuation is called
- fn poll(rust_future: &Arc<dyn RustFutureFfi<RustBuffer>>) -> Arc<OnceCell<RustFuturePoll>> {
- let cell = Arc::new(OnceCell::new());
- let cell_ptr = Arc::into_raw(cell.clone()) as *const ();
- rust_future.clone().ffi_poll(poll_continuation, cell_ptr);
- cell
- }
-
- extern "C" fn poll_continuation(data: *const (), code: RustFuturePoll) {
- let cell = unsafe { Arc::from_raw(data as *const OnceCell<RustFuturePoll>) };
- cell.set(code).expect("Error setting OnceCell");
- }
-
- fn complete(rust_future: Arc<dyn RustFutureFfi<RustBuffer>>) -> (RustBuffer, RustCallStatus) {
- let mut out_status_code = RustCallStatus::default();
- let return_value = rust_future.ffi_complete(&mut out_status_code);
- (return_value, out_status_code)
- }
-
- #[test]
- fn test_success() {
- let (sender, rust_future) = channel();
-
- // Test polling the rust future before it's ready
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), None);
- sender.wake();
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
-
- // Test polling the rust future when it's ready
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), None);
- sender.send(Ok("All done".into()));
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
-
- // Future polls should immediately return ready
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
-
- // Complete the future
- let (return_buf, call_status) = complete(rust_future);
- assert_eq!(call_status.code, RustCallStatusCode::Success);
- assert_eq!(
- <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(),
- "All done"
- );
- }
-
- #[test]
- fn test_error() {
- let (sender, rust_future) = channel();
-
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), None);
- sender.send(Err("Something went wrong".into()));
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
-
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
-
- let (_, call_status) = complete(rust_future);
- assert_eq!(call_status.code, RustCallStatusCode::Error);
- unsafe {
- assert_eq!(
- <TestError as Lift<crate::UniFfiTag>>::try_lift_from_rust_buffer(
- call_status.error_buf.assume_init()
- )
- .unwrap(),
- TestError::from("Something went wrong"),
- )
- }
- }
-
- // Once `complete` is called, the inner future should be released, even if wakers still hold a
- // reference to the RustFuture
- #[test]
- fn test_cancel() {
- let (_sender, rust_future) = channel();
-
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), None);
- rust_future.ffi_cancel();
- // Cancellation should immediately invoke the callback with RustFuturePoll::Ready
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
-
- // Future polls should immediately invoke the callback with RustFuturePoll::Ready
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
-
- let (_, call_status) = complete(rust_future);
- assert_eq!(call_status.code, RustCallStatusCode::Cancelled);
- }
-
- // Once `free` is called, the inner future should be released, even if wakers still hold a
- // reference to the RustFuture
- #[test]
- fn test_release_future() {
- let (sender, rust_future) = channel();
- // Create a weak reference to the channel to use to check if rust_future has dropped its
- // future.
- let channel_weak = Arc::downgrade(&sender.0);
- drop(sender);
- // Create an extra ref to rust_future, simulating a waker that still holds a reference to
- // it
- let rust_future2 = rust_future.clone();
-
- // Complete the rust future
- rust_future.ffi_free();
- // Even though rust_future is still alive, the channel shouldn't be
- assert!(Arc::strong_count(&rust_future2) > 0);
- assert_eq!(channel_weak.strong_count(), 0);
- assert!(channel_weak.upgrade().is_none());
- }
-
- // If `free` is called with a continuation still stored, we should call it them then.
- //
- // This shouldn't happen in practice, but it seems like good defensive programming
- #[test]
- fn test_complete_with_stored_continuation() {
- let (_sender, rust_future) = channel();
-
- let continuation_result = poll(&rust_future);
- rust_future.ffi_free();
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
- }
-
- // Test what happens if we see a `wake()` call while we're polling the future. This can
- // happen, for example, with futures that are handled by a tokio thread pool. We should
- // schedule another poll of the future in this case.
- #[test]
- fn test_wake_during_poll() {
- let mut first_time = true;
- let future = std::future::poll_fn(move |ctx| {
- if first_time {
- first_time = false;
- // Wake the future while we are in the middle of polling it
- ctx.waker().clone().wake();
- Poll::Pending
- } else {
- // The second time we're polled, we're ready
- Poll::Ready("All done".to_owned())
- }
- });
- let rust_future: Arc<dyn RustFutureFfi<RustBuffer>> =
- RustFuture::new(future, crate::UniFfiTag);
- let continuation_result = poll(&rust_future);
- // The continuation function should called immediately
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
- // A second poll should finish the future
- let continuation_result = poll(&rust_future);
- assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
- let (return_buf, call_status) = complete(rust_future);
- assert_eq!(call_status.code, RustCallStatusCode::Success);
- assert_eq!(
- <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(),
- "All done"
- );
- }
-}
diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs
new file mode 100644
index 0000000000..93c34e7543
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/future.rs
@@ -0,0 +1,320 @@
+/* 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/. */
+
+//! [`RustFuture`] represents a [`Future`] that can be sent to the foreign code over FFI.
+//!
+//! This type is not instantiated directly, but via the procedural macros, such as `#[uniffi::export]`.
+//!
+//! # The big picture
+//!
+//! We implement async foreign functions using a simplified version of the Future API:
+//!
+//! 0. At startup, register a [RustFutureContinuationCallback] by calling
+//! rust_future_continuation_callback_set.
+//! 1. Call the scaffolding function to get a [Handle]
+//! 2a. In a loop:
+//! - Call [rust_future_poll]
+//! - Suspend the function until the [rust_future_poll] continuation function is called
+//! - If the continuation was function was called with [RustFuturePoll::Ready], then break
+//! otherwise continue.
+//! 2b. If the async function is cancelled, then call [rust_future_cancel]. This causes the
+//! continuation function to be called with [RustFuturePoll::Ready] and the [RustFuture] to
+//! enter a cancelled state.
+//! 3. Call [rust_future_complete] to get the result of the future.
+//! 4. Call [rust_future_free] to free the future, ideally in a finally block. This:
+//! - Releases any resources held by the future
+//! - Calls any continuation callbacks that have not been called yet
+//!
+//! Note: Technically, the foreign code calls the scaffolding versions of the `rust_future_*`
+//! functions. These are generated by the scaffolding macro, specially prefixed, and extern "C",
+//! and manually monomorphized in the case of [rust_future_complete]. See
+//! `uniffi_macros/src/setup_scaffolding.rs` for details.
+//!
+//! ## How does `Future` work exactly?
+//!
+//! A [`Future`] in Rust does nothing. When calling an async function, it just
+//! returns a `Future` but nothing has happened yet. To start the computation,
+//! the future must be polled. It returns [`Poll::Ready(r)`][`Poll::Ready`] if
+//! the result is ready, [`Poll::Pending`] otherwise. `Poll::Pending` basically
+//! means:
+//!
+//! > Please, try to poll me later, maybe the result will be ready!
+//!
+//! This model is very different than what other languages do, but it can actually
+//! be translated quite easily, fortunately for us!
+//!
+//! But… wait a minute… who is responsible to poll the `Future` if a `Future` does
+//! nothing? Well, it's _the executor_. The executor is responsible _to drive_ the
+//! `Future`: that's where they are polled.
+//!
+//! But… wait another minute… how does the executor know when to poll a [`Future`]?
+//! Does it poll them randomly in an endless loop? Well, no, actually it depends
+//! on the executor! A well-designed `Future` and executor work as follows.
+//! Normally, when [`Future::poll`] is called, a [`Context`] argument is
+//! passed to it. It contains a [`Waker`]. The [`Waker`] is built on top of a
+//! [`RawWaker`] which implements whatever is necessary. Usually, a waker will
+//! signal the executor to poll a particular `Future`. A `Future` will clone
+//! or pass-by-ref the waker to somewhere, as a callback, a completion, a
+//! function, or anything, to the system that is responsible to notify when a
+//! task is completed. So, to recap, the waker is _not_ responsible for waking the
+//! `Future`, it _is_ responsible for _signaling_ the executor that a particular
+//! `Future` should be polled again. That's why the documentation of
+//! [`Poll::Pending`] specifies:
+//!
+//! > When a function returns `Pending`, the function must also ensure that the
+//! > current task is scheduled to be awoken when progress can be made.
+//!
+//! “awakening” is done by using the `Waker`.
+//!
+//! [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html
+//! [`Future::poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#tymethod.poll
+//! [`Pol::Ready`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Ready
+//! [`Poll::Pending`]: https://doc.rust-lang.org/std/task/enum.Poll.html#variant.Pending
+//! [`Context`]: https://doc.rust-lang.org/std/task/struct.Context.html
+//! [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html
+//! [`RawWaker`]: https://doc.rust-lang.org/std/task/struct.RawWaker.html
+
+use std::{
+ future::Future,
+ marker::PhantomData,
+ ops::Deref,
+ panic,
+ pin::Pin,
+ sync::{Arc, Mutex},
+ task::{Context, Poll, Wake},
+};
+
+use super::{RustFutureContinuationCallback, RustFuturePoll, Scheduler};
+use crate::{rust_call_with_out_status, FfiDefault, LowerReturn, RustCallStatus};
+
+/// Wraps the actual future we're polling
+struct WrappedFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+ // Note: this could be a single enum, but that would make it easy to mess up the future pinning
+ // guarantee. For example you might want to call `std::mem::take()` to try to get the result,
+ // but if the future happened to be stored that would move and break all internal references.
+ future: Option<F>,
+ result: Option<Result<T::ReturnType, RustCallStatus>>,
+}
+
+impl<F, T, UT> WrappedFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+ fn new(future: F) -> Self {
+ Self {
+ future: Some(future),
+ result: None,
+ }
+ }
+
+ // Poll the future and check if it's ready or not
+ fn poll(&mut self, context: &mut Context<'_>) -> bool {
+ if self.result.is_some() {
+ true
+ } else if let Some(future) = &mut self.future {
+ // SAFETY: We can call Pin::new_unchecked because:
+ // - This is the only time we get a &mut to `self.future`
+ // - We never poll the future after it's moved (for example by using take())
+ // - We never move RustFuture, which contains us.
+ // - RustFuture is private to this module so no other code can move it.
+ let pinned = unsafe { Pin::new_unchecked(future) };
+ // Run the poll and lift the result if it's ready
+ let mut out_status = RustCallStatus::default();
+ let result: Option<Poll<T::ReturnType>> = rust_call_with_out_status(
+ &mut out_status,
+ // This closure uses a `&mut F` value, which means it's not UnwindSafe by
+ // default. If the future panics, it may be in an invalid state.
+ //
+ // However, we can safely use `AssertUnwindSafe` since a panic will lead the `None`
+ // case below and we will never poll the future again.
+ panic::AssertUnwindSafe(|| match pinned.poll(context) {
+ Poll::Pending => Ok(Poll::Pending),
+ Poll::Ready(v) => T::lower_return(v).map(Poll::Ready),
+ }),
+ );
+ match result {
+ Some(Poll::Pending) => false,
+ Some(Poll::Ready(v)) => {
+ self.future = None;
+ self.result = Some(Ok(v));
+ true
+ }
+ None => {
+ self.future = None;
+ self.result = Some(Err(out_status));
+ true
+ }
+ }
+ } else {
+ log::error!("poll with neither future nor result set");
+ true
+ }
+ }
+
+ fn complete(&mut self, out_status: &mut RustCallStatus) -> T::ReturnType {
+ let mut return_value = T::ReturnType::ffi_default();
+ match self.result.take() {
+ Some(Ok(v)) => return_value = v,
+ Some(Err(call_status)) => *out_status = call_status,
+ None => *out_status = RustCallStatus::cancelled(),
+ }
+ self.free();
+ return_value
+ }
+
+ fn free(&mut self) {
+ self.future = None;
+ self.result = None;
+ }
+}
+
+// If F and T are Send, then WrappedFuture is too
+//
+// Rust will not mark it Send by default when T::ReturnType is a raw pointer. This is promising
+// that we will treat the raw pointer properly, for example by not returning it twice.
+unsafe impl<F, T, UT> Send for WrappedFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+}
+
+/// Future that the foreign code is awaiting
+pub(super) struct RustFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+ // This Mutex should never block if our code is working correctly, since there should not be
+ // multiple threads calling [Self::poll] and/or [Self::complete] at the same time.
+ future: Mutex<WrappedFuture<F, T, UT>>,
+ scheduler: Mutex<Scheduler>,
+ // UT is used as the generic parameter for [LowerReturn].
+ // Let's model this with PhantomData as a function that inputs a UT value.
+ _phantom: PhantomData<fn(UT) -> ()>,
+}
+
+impl<F, T, UT> RustFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+ pub(super) fn new(future: F, _tag: UT) -> Arc<Self> {
+ Arc::new(Self {
+ future: Mutex::new(WrappedFuture::new(future)),
+ scheduler: Mutex::new(Scheduler::new()),
+ _phantom: PhantomData,
+ })
+ }
+
+ pub(super) fn poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64) {
+ let ready = self.is_cancelled() || {
+ let mut locked = self.future.lock().unwrap();
+ let waker: std::task::Waker = Arc::clone(&self).into();
+ locked.poll(&mut Context::from_waker(&waker))
+ };
+ if ready {
+ callback(data, RustFuturePoll::Ready)
+ } else {
+ self.scheduler.lock().unwrap().store(callback, data);
+ }
+ }
+
+ pub(super) fn is_cancelled(&self) -> bool {
+ self.scheduler.lock().unwrap().is_cancelled()
+ }
+
+ pub(super) fn wake(&self) {
+ self.scheduler.lock().unwrap().wake();
+ }
+
+ pub(super) fn cancel(&self) {
+ self.scheduler.lock().unwrap().cancel();
+ }
+
+ pub(super) fn complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType {
+ self.future.lock().unwrap().complete(call_status)
+ }
+
+ pub(super) fn free(self: Arc<Self>) {
+ // Call cancel() to send any leftover data to the continuation callback
+ self.scheduler.lock().unwrap().cancel();
+ // Ensure we drop our inner future, releasing all held references
+ self.future.lock().unwrap().free();
+ }
+}
+
+impl<F, T, UT> Wake for RustFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+ fn wake(self: Arc<Self>) {
+ self.deref().wake()
+ }
+
+ fn wake_by_ref(self: &Arc<Self>) {
+ self.deref().wake()
+ }
+}
+
+/// RustFuture FFI trait. This allows `Arc<RustFuture<F, T, UT>>` to be cast to
+/// `Arc<dyn RustFutureFfi<T::ReturnType>>`, which is needed to implement the public FFI API. In particular, this
+/// allows you to use RustFuture functionality without knowing the concrete Future type, which is
+/// unnamable.
+///
+/// This is parametrized on the ReturnType rather than the `T` directly, to reduce the number of
+/// scaffolding functions we need to generate. If it was parametrized on `T`, then we would need
+/// to create a poll, cancel, complete, and free scaffolding function for each exported async
+/// function. That would add ~1kb binary size per exported function based on a quick estimate on a
+/// x86-64 machine . By parametrizing on `T::ReturnType` we can instead monomorphize by hand and
+/// only create those functions for each of the 13 possible FFI return types.
+#[doc(hidden)]
+pub trait RustFutureFfi<ReturnType>: Send + Sync {
+ fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64);
+ fn ffi_cancel(&self);
+ fn ffi_complete(&self, call_status: &mut RustCallStatus) -> ReturnType;
+ fn ffi_free(self: Arc<Self>);
+}
+
+impl<F, T, UT> RustFutureFfi<T::ReturnType> for RustFuture<F, T, UT>
+where
+ // See rust_future_new for an explanation of these trait bounds
+ F: Future<Output = T> + Send + 'static,
+ T: LowerReturn<UT> + Send + 'static,
+ UT: Send + 'static,
+{
+ fn ffi_poll(self: Arc<Self>, callback: RustFutureContinuationCallback, data: u64) {
+ self.poll(callback, data)
+ }
+
+ fn ffi_cancel(&self) {
+ self.cancel()
+ }
+
+ fn ffi_complete(&self, call_status: &mut RustCallStatus) -> T::ReturnType {
+ self.complete(call_status)
+ }
+
+ fn ffi_free(self: Arc<Self>) {
+ self.free();
+ }
+}
diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs
new file mode 100644
index 0000000000..3d3505e5ef
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/mod.rs
@@ -0,0 +1,141 @@
+/* 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/. */
+
+use std::{future::Future, sync::Arc};
+
+mod future;
+mod scheduler;
+use future::*;
+use scheduler::*;
+
+#[cfg(test)]
+mod tests;
+
+use crate::{derive_ffi_traits, Handle, HandleAlloc, LowerReturn, RustCallStatus};
+
+/// Result code for [rust_future_poll]. This is passed to the continuation function.
+#[repr(i8)]
+#[derive(Debug, PartialEq, Eq)]
+pub enum RustFuturePoll {
+ /// The future is ready and is waiting for [rust_future_complete] to be called
+ Ready = 0,
+ /// The future might be ready and [rust_future_poll] should be called again
+ MaybeReady = 1,
+}
+
+/// Foreign callback that's passed to [rust_future_poll]
+///
+/// The Rust side of things calls this when the foreign side should call [rust_future_poll] again
+/// to continue progress on the future.
+pub type RustFutureContinuationCallback = extern "C" fn(callback_data: u64, RustFuturePoll);
+
+// === Public FFI API ===
+
+/// Create a new [Handle] for a Rust future
+///
+/// For each exported async function, UniFFI will create a scaffolding function that uses this to
+/// create the [Handle] to pass to the foreign code.
+pub fn rust_future_new<F, T, UT>(future: F, tag: UT) -> Handle
+where
+ // F is the future type returned by the exported async function. It needs to be Send + `static
+ // since it will move between threads for an indeterminate amount of time as the foreign
+ // executor calls polls it and the Rust executor wakes it. It does not need to by `Sync`,
+ // since we synchronize all access to the values.
+ F: Future<Output = T> + Send + 'static,
+ // T is the output of the Future. It needs to implement [LowerReturn]. Also it must be Send +
+ // 'static for the same reason as F.
+ T: LowerReturn<UT> + Send + 'static,
+ // The UniFfiTag ZST. The Send + 'static bound is to keep rustc happy.
+ UT: Send + 'static,
+ // Needed to allocate a handle
+ dyn RustFutureFfi<T::ReturnType>: HandleAlloc<UT>,
+{
+ <dyn RustFutureFfi<T::ReturnType> as HandleAlloc<UT>>::new_handle(
+ RustFuture::new(future, tag) as Arc<dyn RustFutureFfi<T::ReturnType>>
+ )
+}
+
+/// Poll a Rust future
+///
+/// When the future is ready to progress the continuation will be called with the `data` value and
+/// a [RustFuturePoll] value. For each [rust_future_poll] call the continuation will be called
+/// exactly once.
+///
+/// # Safety
+///
+/// The [Handle] must not previously have been passed to [rust_future_free]
+pub unsafe fn rust_future_poll<ReturnType, UT>(
+ handle: Handle,
+ callback: RustFutureContinuationCallback,
+ data: u64,
+) where
+ dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>,
+{
+ <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_poll(callback, data)
+}
+
+/// Cancel a Rust future
+///
+/// Any current and future continuations will be immediately called with RustFuturePoll::Ready.
+///
+/// This is needed for languages like Swift, which continuation to wait for the continuation to be
+/// called when tasks are cancelled.
+///
+/// # Safety
+///
+/// The [Handle] must not previously have been passed to [rust_future_free]
+pub unsafe fn rust_future_cancel<ReturnType, UT>(handle: Handle)
+where
+ dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>,
+{
+ <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_cancel()
+}
+
+/// Complete a Rust future
+///
+/// Note: the actually extern "C" scaffolding functions can't be generic, so we generate one for
+/// each supported FFI type.
+///
+/// # Safety
+///
+/// - The [Handle] must not previously have been passed to [rust_future_free]
+/// - The `T` param must correctly correspond to the [rust_future_new] call. It must
+/// be `<Output as LowerReturn<UT>>::ReturnType`
+pub unsafe fn rust_future_complete<ReturnType, UT>(
+ handle: Handle,
+ out_status: &mut RustCallStatus,
+) -> ReturnType
+where
+ dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>,
+{
+ <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::get_arc(handle).ffi_complete(out_status)
+}
+
+/// Free a Rust future, dropping the strong reference and releasing all references held by the
+/// future.
+///
+/// # Safety
+///
+/// The [Handle] must not previously have been passed to [rust_future_free]
+pub unsafe fn rust_future_free<ReturnType, UT>(handle: Handle)
+where
+ dyn RustFutureFfi<ReturnType>: HandleAlloc<UT>,
+{
+ <dyn RustFutureFfi<ReturnType> as HandleAlloc<UT>>::consume_handle(handle).ffi_free()
+}
+
+// Derive HandleAlloc for dyn RustFutureFfi<T> for all FFI return types
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u8>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i8>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u16>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i16>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u32>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i32>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<u64>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<i64>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<f32>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<f64>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<*const std::ffi::c_void>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<crate::RustBuffer>);
+derive_ffi_traits!(impl<UT> HandleAlloc<UT> for dyn RustFutureFfi<()>);
diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs
new file mode 100644
index 0000000000..629ee0c109
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/scheduler.rs
@@ -0,0 +1,96 @@
+/* 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/. */
+
+use std::mem;
+
+use super::{RustFutureContinuationCallback, RustFuturePoll};
+
+/// Schedules a [crate::RustFuture] by managing the continuation data
+///
+/// This struct manages the continuation callback and data that comes from the foreign side. It
+/// is responsible for calling the continuation callback when the future is ready to be woken up.
+///
+/// The basic guarantees are:
+///
+/// * Each callback will be invoked exactly once, with its associated data.
+/// * If `wake()` is called, the callback will be invoked to wake up the future -- either
+/// immediately or the next time we get a callback.
+/// * If `cancel()` is called, the same will happen and the schedule will stay in the cancelled
+/// state, invoking any future callbacks as soon as they're stored.
+
+#[derive(Debug)]
+pub(super) enum Scheduler {
+ /// No continuations set, neither wake() nor cancel() called.
+ Empty,
+ /// `wake()` was called when there was no continuation set. The next time `store` is called,
+ /// the continuation should be immediately invoked with `RustFuturePoll::MaybeReady`
+ Waked,
+ /// The future has been cancelled, any future `store` calls should immediately result in the
+ /// continuation being called with `RustFuturePoll::Ready`.
+ Cancelled,
+ /// Continuation set, the next time `wake()` is called is called, we should invoke it.
+ Set(RustFutureContinuationCallback, u64),
+}
+
+impl Scheduler {
+ pub(super) fn new() -> Self {
+ Self::Empty
+ }
+
+ /// Store new continuation data if we are in the `Empty` state. If we are in the `Waked` or
+ /// `Cancelled` state, call the continuation immediately with the data.
+ pub(super) fn store(&mut self, callback: RustFutureContinuationCallback, data: u64) {
+ match self {
+ Self::Empty => *self = Self::Set(callback, data),
+ Self::Set(old_callback, old_data) => {
+ log::error!(
+ "store: observed `Self::Set` state. Is poll() being called from multiple threads at once?"
+ );
+ old_callback(*old_data, RustFuturePoll::Ready);
+ *self = Self::Set(callback, data);
+ }
+ Self::Waked => {
+ *self = Self::Empty;
+ callback(data, RustFuturePoll::MaybeReady);
+ }
+ Self::Cancelled => {
+ callback(data, RustFuturePoll::Ready);
+ }
+ }
+ }
+
+ pub(super) fn wake(&mut self) {
+ match self {
+ // If we had a continuation set, then call it and transition to the `Empty` state.
+ Self::Set(callback, old_data) => {
+ let old_data = *old_data;
+ let callback = *callback;
+ *self = Self::Empty;
+ callback(old_data, RustFuturePoll::MaybeReady);
+ }
+ // If we were in the `Empty` state, then transition to `Waked`. The next time `store`
+ // is called, we will immediately call the continuation.
+ Self::Empty => *self = Self::Waked,
+ // This is a no-op if we were in the `Cancelled` or `Waked` state.
+ _ => (),
+ }
+ }
+
+ pub(super) fn cancel(&mut self) {
+ if let Self::Set(callback, old_data) = mem::replace(self, Self::Cancelled) {
+ callback(old_data, RustFuturePoll::Ready);
+ }
+ }
+
+ pub(super) fn is_cancelled(&self) -> bool {
+ matches!(self, Self::Cancelled)
+ }
+}
+
+// The `*const ()` data pointer references an object on the foreign side.
+// This object must be `Sync` in Rust terminology -- it must be safe for us to pass the pointer to the continuation callback from any thread.
+// If the foreign side upholds their side of the contract, then `Scheduler` is Send + Sync.
+
+unsafe impl Send for Scheduler {}
+unsafe impl Sync for Scheduler {}
diff --git a/third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs b/third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs
new file mode 100644
index 0000000000..886ee27c71
--- /dev/null
+++ b/third_party/rust/uniffi_core/src/ffi/rustfuture/tests.rs
@@ -0,0 +1,223 @@
+use once_cell::sync::OnceCell;
+use std::{
+ future::Future,
+ panic,
+ pin::Pin,
+ sync::{Arc, Mutex},
+ task::{Context, Poll, Waker},
+};
+
+use super::*;
+use crate::{test_util::TestError, Lift, RustBuffer, RustCallStatusCode};
+
+// Sender/Receiver pair that we use for testing
+struct Channel {
+ result: Option<Result<String, TestError>>,
+ waker: Option<Waker>,
+}
+
+struct Sender(Arc<Mutex<Channel>>);
+
+impl Sender {
+ fn wake(&self) {
+ let inner = self.0.lock().unwrap();
+ if let Some(waker) = &inner.waker {
+ waker.wake_by_ref();
+ }
+ }
+
+ fn send(&self, value: Result<String, TestError>) {
+ let mut inner = self.0.lock().unwrap();
+ if inner.result.replace(value).is_some() {
+ panic!("value already sent");
+ }
+ if let Some(waker) = &inner.waker {
+ waker.wake_by_ref();
+ }
+ }
+}
+
+struct Receiver(Arc<Mutex<Channel>>);
+
+impl Future for Receiver {
+ type Output = Result<String, TestError>;
+
+ fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<String, TestError>> {
+ let mut inner = self.0.lock().unwrap();
+ match &inner.result {
+ Some(v) => Poll::Ready(v.clone()),
+ None => {
+ inner.waker = Some(context.waker().clone());
+ Poll::Pending
+ }
+ }
+ }
+}
+
+// Create a sender and rust future that we can use for testing
+fn channel() -> (Sender, Arc<dyn RustFutureFfi<RustBuffer>>) {
+ let channel = Arc::new(Mutex::new(Channel {
+ result: None,
+ waker: None,
+ }));
+ let rust_future = RustFuture::new(Receiver(channel.clone()), crate::UniFfiTag);
+ (Sender(channel), rust_future)
+}
+
+/// Poll a Rust future and get an OnceCell that's set when the continuation is called
+fn poll(rust_future: &Arc<dyn RustFutureFfi<RustBuffer>>) -> Arc<OnceCell<RustFuturePoll>> {
+ let cell = Arc::new(OnceCell::new());
+ let handle = Arc::into_raw(cell.clone()) as u64;
+ rust_future.clone().ffi_poll(poll_continuation, handle);
+ cell
+}
+
+extern "C" fn poll_continuation(data: u64, code: RustFuturePoll) {
+ let cell = unsafe { Arc::from_raw(data as *const OnceCell<RustFuturePoll>) };
+ cell.set(code).expect("Error setting OnceCell");
+}
+
+fn complete(rust_future: Arc<dyn RustFutureFfi<RustBuffer>>) -> (RustBuffer, RustCallStatus) {
+ let mut out_status_code = RustCallStatus::default();
+ let return_value = rust_future.ffi_complete(&mut out_status_code);
+ (return_value, out_status_code)
+}
+
+#[test]
+fn test_success() {
+ let (sender, rust_future) = channel();
+
+ // Test polling the rust future before it's ready
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), None);
+ sender.wake();
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
+
+ // Test polling the rust future when it's ready
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), None);
+ sender.send(Ok("All done".into()));
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
+
+ // Future polls should immediately return ready
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
+
+ // Complete the future
+ let (return_buf, call_status) = complete(rust_future);
+ assert_eq!(call_status.code, RustCallStatusCode::Success);
+ assert_eq!(
+ <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(),
+ "All done"
+ );
+}
+
+#[test]
+fn test_error() {
+ let (sender, rust_future) = channel();
+
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), None);
+ sender.send(Err("Something went wrong".into()));
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
+
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
+
+ let (_, call_status) = complete(rust_future);
+ assert_eq!(call_status.code, RustCallStatusCode::Error);
+ unsafe {
+ assert_eq!(
+ <TestError as Lift<crate::UniFfiTag>>::try_lift_from_rust_buffer(
+ call_status.error_buf.assume_init()
+ )
+ .unwrap(),
+ TestError::from("Something went wrong"),
+ )
+ }
+}
+
+// Once `complete` is called, the inner future should be released, even if wakers still hold a
+// reference to the RustFuture
+#[test]
+fn test_cancel() {
+ let (_sender, rust_future) = channel();
+
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), None);
+ rust_future.ffi_cancel();
+ // Cancellation should immediately invoke the callback with RustFuturePoll::Ready
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
+
+ // Future polls should immediately invoke the callback with RustFuturePoll::Ready
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
+
+ let (_, call_status) = complete(rust_future);
+ assert_eq!(call_status.code, RustCallStatusCode::Cancelled);
+}
+
+// Once `free` is called, the inner future should be released, even if wakers still hold a
+// reference to the RustFuture
+#[test]
+fn test_release_future() {
+ let (sender, rust_future) = channel();
+ // Create a weak reference to the channel to use to check if rust_future has dropped its
+ // future.
+ let channel_weak = Arc::downgrade(&sender.0);
+ drop(sender);
+ // Create an extra ref to rust_future, simulating a waker that still holds a reference to
+ // it
+ let rust_future2 = rust_future.clone();
+
+ // Complete the rust future
+ rust_future.ffi_free();
+ // Even though rust_future is still alive, the channel shouldn't be
+ assert!(Arc::strong_count(&rust_future2) > 0);
+ assert_eq!(channel_weak.strong_count(), 0);
+ assert!(channel_weak.upgrade().is_none());
+}
+
+// If `free` is called with a continuation still stored, we should call it them then.
+//
+// This shouldn't happen in practice, but it seems like good defensive programming
+#[test]
+fn test_complete_with_stored_continuation() {
+ let (_sender, rust_future) = channel();
+
+ let continuation_result = poll(&rust_future);
+ rust_future.ffi_free();
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
+}
+
+// Test what happens if we see a `wake()` call while we're polling the future. This can
+// happen, for example, with futures that are handled by a tokio thread pool. We should
+// schedule another poll of the future in this case.
+#[test]
+fn test_wake_during_poll() {
+ let mut first_time = true;
+ let future = std::future::poll_fn(move |ctx| {
+ if first_time {
+ first_time = false;
+ // Wake the future while we are in the middle of polling it
+ ctx.waker().clone().wake();
+ Poll::Pending
+ } else {
+ // The second time we're polled, we're ready
+ Poll::Ready("All done".to_owned())
+ }
+ });
+ let rust_future: Arc<dyn RustFutureFfi<RustBuffer>> = RustFuture::new(future, crate::UniFfiTag);
+ let continuation_result = poll(&rust_future);
+ // The continuation function should called immediately
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::MaybeReady));
+ // A second poll should finish the future
+ let continuation_result = poll(&rust_future);
+ assert_eq!(continuation_result.get(), Some(&RustFuturePoll::Ready));
+ let (return_buf, call_status) = complete(rust_future);
+ assert_eq!(call_status.code, RustCallStatusCode::Success);
+ assert_eq!(
+ <String as Lift<crate::UniFfiTag>>::try_lift(return_buf).unwrap(),
+ "All done"
+ );
+}
diff --git a/third_party/rust/uniffi_core/src/ffi_converter_impls.rs b/third_party/rust/uniffi_core/src/ffi_converter_impls.rs
index af18f3873b..aec093154a 100644
--- a/third_party/rust/uniffi_core/src/ffi_converter_impls.rs
+++ b/third_party/rust/uniffi_core/src/ffi_converter_impls.rs
@@ -20,11 +20,11 @@
///
/// This crate needs to implement `FFIConverter<UT>` on `UniFfiTag` instances for all UniFFI
/// consumer crates. To do this, it defines blanket impls like `impl<UT> FFIConverter<UT> for u8`.
-/// "UT" means an abitrary `UniFfiTag` type.
+/// "UT" means an arbitrary `UniFfiTag` type.
use crate::{
check_remaining, derive_ffi_traits, ffi_converter_rust_buffer_lift_and_lower, metadata,
- ConvertError, FfiConverter, ForeignExecutor, Lift, LiftReturn, Lower, LowerReturn,
- MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError,
+ ConvertError, FfiConverter, Lift, LiftRef, LiftReturn, Lower, LowerReturn, MetadataBuffer,
+ Result, RustBuffer, UnexpectedUniFFICallbackError,
};
use anyhow::bail;
use bytes::buf::{Buf, BufMut};
@@ -405,47 +405,6 @@ where
.concat(V::TYPE_ID_META);
}
-/// FFI support for [ForeignExecutor]
-///
-/// These are passed over the FFI as opaque pointer-sized types representing the foreign executor.
-/// The foreign bindings may use an actual pointer to the executor object, or a usized integer
-/// handle.
-unsafe impl<UT> FfiConverter<UT> for ForeignExecutor {
- type FfiType = crate::ForeignExecutorHandle;
-
- // Passing these back to the foreign bindings is currently not supported
- fn lower(executor: Self) -> Self::FfiType {
- executor.handle
- }
-
- fn write(executor: Self, buf: &mut Vec<u8>) {
- // Use native endian when writing these values, so they can be casted to pointer values
- match std::mem::size_of::<usize>() {
- // Use native endian when reading these values, so they can be casted to pointer values
- 4 => buf.put_u32_ne(executor.handle.0 as u32),
- 8 => buf.put_u64_ne(executor.handle.0 as u64),
- n => panic!("Invalid usize width: {n}"),
- };
- }
-
- fn try_lift(executor: Self::FfiType) -> Result<Self> {
- Ok(ForeignExecutor::new(executor))
- }
-
- fn try_read(buf: &mut &[u8]) -> Result<Self> {
- let usize_val = match std::mem::size_of::<usize>() {
- // Use native endian when reading these values, so they can be casted to pointer values
- 4 => buf.get_u32_ne() as usize,
- 8 => buf.get_u64_ne() as usize,
- n => panic!("Invalid usize width: {n}"),
- };
- <Self as FfiConverter<UT>>::try_lift(crate::ForeignExecutorHandle(usize_val as *const ()))
- }
-
- const TYPE_ID_META: MetadataBuffer =
- MetadataBuffer::from_code(metadata::codes::TYPE_FOREIGN_EXECUTOR);
-}
-
derive_ffi_traits!(blanket u8);
derive_ffi_traits!(blanket i8);
derive_ffi_traits!(blanket u16);
@@ -460,7 +419,6 @@ derive_ffi_traits!(blanket bool);
derive_ffi_traits!(blanket String);
derive_ffi_traits!(blanket Duration);
derive_ffi_traits!(blanket SystemTime);
-derive_ffi_traits!(blanket ForeignExecutor);
// For composite types, derive LowerReturn, LiftReturn, etc, from Lift/Lower.
//
@@ -498,7 +456,11 @@ unsafe impl<UT> LowerReturn<UT> for () {
}
unsafe impl<UT> LiftReturn<UT> for () {
- fn lift_callback_return(_buf: RustBuffer) -> Self {}
+ type ReturnType = ();
+
+ fn try_lift_successful_return(_: ()) -> Result<Self> {
+ Ok(())
+ }
const TYPE_ID_META: MetadataBuffer = MetadataBuffer::from_code(metadata::codes::TYPE_UNIT);
}
@@ -535,13 +497,15 @@ where
unsafe impl<UT, R, E> LiftReturn<UT> for Result<R, E>
where
R: LiftReturn<UT>,
- E: Lift<UT> + ConvertError<UT>,
+ E: Lift<UT, FfiType = RustBuffer> + ConvertError<UT>,
{
- fn lift_callback_return(buf: RustBuffer) -> Self {
- Ok(R::lift_callback_return(buf))
+ type ReturnType = R::ReturnType;
+
+ fn try_lift_successful_return(v: R::ReturnType) -> Result<Self> {
+ R::try_lift_successful_return(v).map(Ok)
}
- fn lift_callback_error(buf: RustBuffer) -> Self {
+ fn lift_error(buf: RustBuffer) -> Self {
match E::try_lift_from_rust_buffer(buf) {
Ok(lifted_error) => Err(lifted_error),
Err(anyhow_error) => {
@@ -560,3 +524,14 @@ where
.concat(R::TYPE_ID_META)
.concat(E::TYPE_ID_META);
}
+
+unsafe impl<T, UT> LiftRef<UT> for [T]
+where
+ T: Lift<UT>,
+{
+ type LiftType = Vec<T>;
+}
+
+unsafe impl<UT> LiftRef<UT> for str {
+ type LiftType = String;
+}
diff --git a/third_party/rust/uniffi_core/src/ffi_converter_traits.rs b/third_party/rust/uniffi_core/src/ffi_converter_traits.rs
index 3b5914e32f..4e7b9e06fa 100644
--- a/third_party/rust/uniffi_core/src/ffi_converter_traits.rs
+++ b/third_party/rust/uniffi_core/src/ffi_converter_traits.rs
@@ -51,7 +51,10 @@ use std::{borrow::Borrow, sync::Arc};
use anyhow::bail;
use bytes::Buf;
-use crate::{FfiDefault, MetadataBuffer, Result, RustBuffer, UnexpectedUniFFICallbackError};
+use crate::{
+ FfiDefault, Handle, MetadataBuffer, Result, RustBuffer, RustCallStatus, RustCallStatusCode,
+ UnexpectedUniFFICallbackError,
+};
/// Generalized FFI conversions
///
@@ -302,14 +305,41 @@ pub unsafe trait LowerReturn<UT>: Sized {
/// These traits should not be used directly, only in generated code, and the generated code should
/// have fixture tests to test that everything works correctly together.
pub unsafe trait LiftReturn<UT>: Sized {
- /// Lift a Rust value for a callback interface method result
- fn lift_callback_return(buf: RustBuffer) -> Self;
+ /// FFI return type for trait interfaces
+ type ReturnType;
+
+ /// Lift a successfully returned value from a trait interface
+ fn try_lift_successful_return(v: Self::ReturnType) -> Result<Self>;
+
+ /// Lift a foreign returned value from a trait interface
+ ///
+ /// When we call a foreign-implemented trait interface method, we pass a &mut RustCallStatus
+ /// and get [Self::ReturnType] returned. This method takes both of those and lifts `Self` from
+ /// it.
+ fn lift_foreign_return(ffi_return: Self::ReturnType, call_status: RustCallStatus) -> Self {
+ match call_status.code {
+ RustCallStatusCode::Success => Self::try_lift_successful_return(ffi_return)
+ .unwrap_or_else(|e| {
+ Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e))
+ }),
+ RustCallStatusCode::Error => {
+ Self::lift_error(unsafe { call_status.error_buf.assume_init() })
+ }
+ _ => {
+ let e = <String as FfiConverter<crate::UniFfiTag>>::try_lift(unsafe {
+ call_status.error_buf.assume_init()
+ })
+ .unwrap_or_else(|e| format!("(Error lifting message: {e}"));
+ Self::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(e))
+ }
+ }
+ }
/// Lift a Rust value for a callback interface method error result
///
/// This is called for "expected errors" -- the callback method returns a Result<> type and the
/// foreign code throws an exception that corresponds to the error type.
- fn lift_callback_error(_buf: RustBuffer) -> Self {
+ fn lift_error(_buf: RustBuffer) -> Self {
panic!("Callback interface method returned unexpected error")
}
@@ -351,6 +381,66 @@ pub trait ConvertError<UT>: Sized {
fn try_convert_unexpected_callback_error(e: UnexpectedUniFFICallbackError) -> Result<Self>;
}
+/// Manage handles for `Arc<Self>` instances
+///
+/// Handles are used to manage objects that are passed across the FFI. They general usage is:
+///
+/// * Rust creates an `Arc<>`
+/// * Rust uses `new_handle` to create a handle that represents the Arc reference
+/// * Rust passes the handle to the foreign code as a `u64`
+/// * The foreign code passes the handle back to `Rust` to refer to the object:
+/// * Handle are usually passed as borrowed values. When an FFI function inputs a handle as an
+/// argument, the foreign code simply passes a copy of the `u64` to Rust, which calls `get_arc`
+/// to get a new `Arc<>` clone for it.
+/// * Handles are returned as owned values. When an FFI function returns a handle, the foreign
+/// code either stops using the handle after returning it or calls `clone_handle` and returns
+/// the clone.
+/// * Eventually the foreign code may destroy their handle by passing it into a "free" FFI
+/// function. This functions input an owned handle and consume it.
+///
+/// The foreign code also defines their own handles. These represent foreign objects that are
+/// passed to Rust. Using foreign handles is essentially the same as above, but in reverse.
+///
+/// Handles must always be `Send` and the objects they reference must always be `Sync`.
+/// This means that it must be safe to send handles to other threads and use them there.
+///
+/// Note: this only needs to be derived for unsized types, there's a blanket impl for `T: Sized`.
+///
+/// ## Safety
+///
+/// All traits are unsafe (implementing it requires `unsafe impl`) because we can't guarantee
+/// that it's safe to pass your type out to foreign-language code and back again. Buggy
+/// implementations of this trait might violate some assumptions made by the generated code,
+/// or might not match with the corresponding code in the generated foreign-language bindings.
+/// These traits should not be used directly, only in generated code, and the generated code should
+/// have fixture tests to test that everything works correctly together.
+/// `&T` using the Arc.
+pub unsafe trait HandleAlloc<UT>: Send + Sync {
+ /// Create a new handle for an Arc value
+ ///
+ /// Use this to lower an Arc into a handle value before passing it across the FFI.
+ /// The newly-created handle will have reference count = 1.
+ fn new_handle(value: Arc<Self>) -> Handle;
+
+ /// Clone a handle
+ ///
+ /// This creates a new handle from an existing one.
+ /// It's used when the foreign code wants to pass back an owned handle and still keep a copy
+ /// for themselves.
+ fn clone_handle(handle: Handle) -> Handle;
+
+ /// Get a clone of the `Arc<>` using a "borrowed" handle.
+ ///
+ /// Take care that the handle can not be destroyed between when it's passed and when
+ /// `get_arc()` is called. #1797 is a cautionary tale.
+ fn get_arc(handle: Handle) -> Arc<Self> {
+ Self::consume_handle(Self::clone_handle(handle))
+ }
+
+ /// Consume a handle, getting back the initial `Arc<>`
+ fn consume_handle(handle: Handle) -> Arc<Self>;
+}
+
/// Derive FFI traits
///
/// This can be used to derive:
@@ -439,9 +529,10 @@ macro_rules! derive_ffi_traits {
(impl $(<$($generic:ident),*>)? $(::uniffi::)? LiftReturn<$ut:path> for $ty:ty $(where $($where:tt)*)?) => {
unsafe impl $(<$($generic),*>)* $crate::LiftReturn<$ut> for $ty $(where $($where)*)*
{
- fn lift_callback_return(buf: $crate::RustBuffer) -> Self {
- <Self as $crate::Lift<$ut>>::try_lift_from_rust_buffer(buf)
- .expect("Error reading callback interface result")
+ type ReturnType = <Self as $crate::Lift<$ut>>::FfiType;
+
+ fn try_lift_successful_return(v: Self::ReturnType) -> $crate::Result<Self> {
+ <Self as $crate::Lift<$ut>>::try_lift(v)
}
const TYPE_ID_META: $crate::MetadataBuffer = <Self as $crate::Lift<$ut>>::TYPE_ID_META;
@@ -463,4 +554,50 @@ macro_rules! derive_ffi_traits {
}
}
};
+
+ (impl $(<$($generic:ident),*>)? $(::uniffi::)? HandleAlloc<$ut:path> for $ty:ty $(where $($where:tt)*)?) => {
+ // Derived HandleAlloc implementation.
+ //
+ // This is only needed for !Sized types like `dyn Trait`, below is a blanket implementation
+ // for any sized type.
+ unsafe impl $(<$($generic),*>)* $crate::HandleAlloc<$ut> for $ty $(where $($where)*)*
+ {
+ // To implement HandleAlloc for an unsized type, wrap it with a second Arc which
+ // converts the wide pointer into a normal pointer.
+
+ fn new_handle(value: ::std::sync::Arc<Self>) -> $crate::Handle {
+ $crate::Handle::from_pointer(::std::sync::Arc::into_raw(::std::sync::Arc::new(value)))
+ }
+
+ fn clone_handle(handle: $crate::Handle) -> $crate::Handle {
+ unsafe {
+ ::std::sync::Arc::<::std::sync::Arc<Self>>::increment_strong_count(handle.as_pointer::<::std::sync::Arc<Self>>());
+ }
+ handle
+ }
+
+ fn consume_handle(handle: $crate::Handle) -> ::std::sync::Arc<Self> {
+ unsafe {
+ ::std::sync::Arc::<Self>::clone(
+ &std::sync::Arc::<::std::sync::Arc::<Self>>::from_raw(handle.as_pointer::<::std::sync::Arc<Self>>())
+ )
+ }
+ }
+ }
+ };
+}
+
+unsafe impl<T: Send + Sync, UT> HandleAlloc<UT> for T {
+ fn new_handle(value: Arc<Self>) -> Handle {
+ Handle::from_pointer(Arc::into_raw(value))
+ }
+
+ fn clone_handle(handle: Handle) -> Handle {
+ unsafe { Arc::increment_strong_count(handle.as_pointer::<T>()) };
+ handle
+ }
+
+ fn consume_handle(handle: Handle) -> Arc<Self> {
+ unsafe { Arc::from_raw(handle.as_pointer()) }
+ }
}
diff --git a/third_party/rust/uniffi_core/src/lib.rs b/third_party/rust/uniffi_core/src/lib.rs
index c84b403dce..1f3a2403f8 100644
--- a/third_party/rust/uniffi_core/src/lib.rs
+++ b/third_party/rust/uniffi_core/src/lib.rs
@@ -45,7 +45,8 @@ pub mod metadata;
pub use ffi::*;
pub use ffi_converter_traits::{
- ConvertError, FfiConverter, FfiConverterArc, Lift, LiftRef, LiftReturn, Lower, LowerReturn,
+ ConvertError, FfiConverter, FfiConverterArc, HandleAlloc, Lift, LiftRef, LiftReturn, Lower,
+ LowerReturn,
};
pub use metadata::*;
@@ -57,9 +58,8 @@ pub mod deps {
pub use async_compat;
pub use bytes;
pub use log;
+ pub use oneshot;
pub use static_assertions;
- // Export this dependency for the 0.25 branch so that we can use it in `setup_scaffolding.rs`
- pub use once_cell;
}
mod panichook;
diff --git a/third_party/rust/uniffi_core/src/metadata.rs b/third_party/rust/uniffi_core/src/metadata.rs
index 770d2b36d5..dc61a1bfcb 100644
--- a/third_party/rust/uniffi_core/src/metadata.rs
+++ b/third_party/rust/uniffi_core/src/metadata.rs
@@ -32,13 +32,14 @@ pub mod codes {
pub const RECORD: u8 = 2;
pub const ENUM: u8 = 3;
pub const INTERFACE: u8 = 4;
- pub const ERROR: u8 = 5;
pub const NAMESPACE: u8 = 6;
pub const CONSTRUCTOR: u8 = 7;
pub const UDL_FILE: u8 = 8;
pub const CALLBACK_INTERFACE: u8 = 9;
pub const TRAIT_METHOD: u8 = 10;
pub const UNIFFI_TRAIT: u8 = 11;
+ pub const TRAIT_INTERFACE: u8 = 12;
+ pub const CALLBACK_TRAIT_INTERFACE: u8 = 13;
pub const UNKNOWN: u8 = 255;
// Type codes
@@ -66,20 +67,24 @@ pub mod codes {
pub const TYPE_CALLBACK_INTERFACE: u8 = 21;
pub const TYPE_CUSTOM: u8 = 22;
pub const TYPE_RESULT: u8 = 23;
- pub const TYPE_FUTURE: u8 = 24;
- pub const TYPE_FOREIGN_EXECUTOR: u8 = 25;
+ pub const TYPE_TRAIT_INTERFACE: u8 = 24;
+ pub const TYPE_CALLBACK_TRAIT_INTERFACE: u8 = 25;
pub const TYPE_UNIT: u8 = 255;
- // Literal codes for LiteralMetadata - note that we don't support
- // all variants in the "emit/reader" context.
+ // Literal codes for LiteralMetadata
pub const LIT_STR: u8 = 0;
pub const LIT_INT: u8 = 1;
pub const LIT_FLOAT: u8 = 2;
pub const LIT_BOOL: u8 = 3;
- pub const LIT_NULL: u8 = 4;
+ pub const LIT_NONE: u8 = 4;
+ pub const LIT_SOME: u8 = 5;
+ pub const LIT_EMPTY_SEQ: u8 = 6;
}
-const BUF_SIZE: usize = 4096;
+// For large errors (e.g. enums) a buffer size of ~4k - ~8k
+// is not enough. See issues on Github: #1968 and #2041 and
+// for an example see fixture/large-error
+const BUF_SIZE: usize = 16384;
// This struct is a kludge around the fact that Rust const generic support doesn't quite handle our
// needs.
@@ -168,7 +173,17 @@ impl MetadataBuffer {
self.concat_value(value as u8)
}
- // Concatenate a string to this buffer.
+ // Option<bool>
+ pub const fn concat_option_bool(self, value: Option<bool>) -> Self {
+ self.concat_value(match value {
+ None => 0,
+ Some(false) => 1,
+ Some(true) => 2,
+ })
+ }
+
+ // Concatenate a string to this buffer. The maximum string length is 255 bytes. For longer strings,
+ // use `concat_long_str()`.
//
// Strings are encoded as a `u8` length, followed by the utf8 data.
//
@@ -189,6 +204,28 @@ impl MetadataBuffer {
self
}
+ // Concatenate a longer string to this buffer.
+ //
+ // Strings are encoded as a `u16` length, followed by the utf8 data.
+ //
+ // This consumes self, which is convenient for the proc-macro code and also allows us to avoid
+ // allocated an extra buffer.
+ pub const fn concat_long_str(mut self, string: &str) -> Self {
+ assert!(self.size + string.len() + 1 < BUF_SIZE);
+ let [lo, hi] = (string.len() as u16).to_le_bytes();
+ self.bytes[self.size] = lo;
+ self.bytes[self.size + 1] = hi;
+ self.size += 2;
+ let bytes = string.as_bytes();
+ let mut i = 0;
+ while i < bytes.len() {
+ self.bytes[self.size] = bytes[i];
+ self.size += 1;
+ i += 1;
+ }
+ self
+ }
+
// Create an array from this MetadataBuffer
//
// SIZE should always be `self.size`. This is part of the kludge to hold us over until Rust
diff --git a/third_party/rust/uniffi_macros/.cargo-checksum.json b/third_party/rust/uniffi_macros/.cargo-checksum.json
index 96e44ac74e..9db049289c 100644
--- a/third_party/rust/uniffi_macros/.cargo-checksum.json
+++ b/third_party/rust/uniffi_macros/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"376811e12479a0cced9375a12a2e3186da053b3c7520374bf6f2b54d23282082","src/custom.rs":"36cd6c2eeb8efdc34e59dff634a22e79471ab17f49ceb0f131da5f144313f7e4","src/enum_.rs":"2d3181ef22468deb4e8f48b7b1ed9421134604c2c226d5084c453ebb2cedbfd5","src/error.rs":"0b5beb8a2c8c93c30c56f2f80538bf1f1c8e6a99b2b1c934ad12a4feb75a2fc0","src/export.rs":"6d05417f0b10a9d6df9e96a6ed771c89a5e59e6e52d1ce812025bfe68e9f717e","src/export/attributes.rs":"53a27264882ab0a802a0ee109a2ea3f3456d4c83c85eaa5c0f5912d4486ab843","src/export/callback_interface.rs":"ad2782b7ca930dc067c391394480362be1fbf331d8786be089c0a87415c85a88","src/export/item.rs":"a7b74e6400ec6c5e8fb09d8842ce718b9555d75de13fdf5fecbab2fceeec7cbf","src/export/scaffolding.rs":"8ab2b9b0c5ad5b5477963843b7a58d496344da7de1a2a4b07f30f22275c8f3c9","src/export/utrait.rs":"ce4a3d629aaf0b44b8c5ce6794c5d5b0d7f86f46f0dd6b6ecb134514be330f0d","src/fnsig.rs":"886ceec806b429c7d86fe00d0d84f7b04e21142605f7a61d182f9f616210cd2b","src/lib.rs":"501c736647eff2705c5565f80e554d2e440cceceed95044c9fe147fc309afb48","src/object.rs":"0a14d6b8ccb4faef93a1f61a97ac7d47a80b6581383f4a6e0a4789f53e66e630","src/record.rs":"fbff287bb2d0b7a9eb35ef3161fbd1abbc21f3aa08e88bd4242dd12acbfa3ee9","src/setup_scaffolding.rs":"60b48a56fae16cf01824586b90e6440da3362135d98fc07aaed624c57f806163","src/test.rs":"1673f282bb35d6b0740ad0e5f11826c2852d7a0db29604c2258f457415b537e8","src/util.rs":"217ecef0e4dabd158a7597aa3d00d94477993a90b388afbbc0fb39e14f6b013e"},"package":"11cf7a58f101fcedafa5b77ea037999b88748607f0ef3a33eaa0efc5392e92e4"} \ No newline at end of file
+{"files":{"Cargo.toml":"a292239ca3c72852768fdf0e7bc2dd6386af7bf1ab0ef56dff01e1c9e781b2ca","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","src/custom.rs":"36cd6c2eeb8efdc34e59dff634a22e79471ab17f49ceb0f131da5f144313f7e4","src/default.rs":"77466ac54da69094bcdccc5927d0980b1e9dd0095647ca825830673c48847a53","src/enum_.rs":"afe0a6534d8e7f68047e3f1afad9369d5d5650f4c7555e8d4173f24126c715ba","src/error.rs":"30168378da9a23e6530ffe68647bf6618d07a0aaa236d5009137a922798a0e88","src/export.rs":"42c5e784c1dccc796c8b6ea29c2dc1811e48a531488a3ed0e2a59330778a7e41","src/export/attributes.rs":"c848f8c309c4cf7a168f038834752dc4816b5c853768d7c331ea4cd5ce0841b7","src/export/callback_interface.rs":"794b0665dc7eb02ea854c61c8bb2781e0b4ac1de646d95a8fd7791f770f2e6e3","src/export/item.rs":"4e86875692c2d2993fde12e78dbde2cbffa5675ede143577d5620126401efe05","src/export/scaffolding.rs":"b25167d2213b6d6c5ba653622f26791e8c3e74a5ecce6512ec27009fc8bf68e4","src/export/trait_interface.rs":"f07f9908ee28661de4586d89b693f3d93dae5e5cba8a089eff25f20bbf6b373b","src/export/utrait.rs":"b55533d3eef8262944d3c0d9a3a9cba0615d2d5af8608f0919abc7699989e2a8","src/fnsig.rs":"5e434a1cc87166c5245424bb14e896eb766bf680d4d50d4b8536852f91487d7c","src/lib.rs":"a28bbfd2d1dc835306ff6072f75761bb6b3a158477bba966057776c527fe6d70","src/object.rs":"5419ed64c8120aef811a77c2205f58a7a537bdf34ae04f9c92dd3aaa176eed39","src/record.rs":"29072542cc2f3e027bd7c59b45ba913458f8213d1b2b33bc70d140baa98fcdc8","src/setup_scaffolding.rs":"173fdc916967d54bd6532def16d12e5bb85467813a46a031d3338b77625756bb","src/test.rs":"1673f282bb35d6b0740ad0e5f11826c2852d7a0db29604c2258f457415b537e8","src/util.rs":"a2c3693343e78dffb2a7f7b39eeb9b7f298b66688f1766a7c08113cf9431ef4c"},"package":"18331d35003f46f0d04047fbe4227291815b83a937a8c32bc057f990962182c4"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_macros/Cargo.toml b/third_party/rust/uniffi_macros/Cargo.toml
index 9d3908ae8d..5ae193e392 100644
--- a/third_party/rust/uniffi_macros/Cargo.toml
+++ b/third_party/rust/uniffi_macros/Cargo.toml
@@ -12,11 +12,12 @@
[package]
edition = "2021"
name = "uniffi_macros"
-version = "0.25.3"
+version = "0.27.1"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
description = "a multi-language bindings generator for rust (convenience macros)"
homepage = "https://mozilla.github.io/uniffi-rs"
documentation = "https://mozilla.github.io/uniffi-rs"
+readme = "README.md"
keywords = [
"ffi",
"bindgen",
@@ -47,6 +48,7 @@ version = "1.0"
[dependencies.serde]
version = "1.0.136"
+features = ["derive"]
[dependencies.syn]
version = "2.0"
@@ -59,11 +61,13 @@ features = [
version = "0.5.9"
[dependencies.uniffi_build]
-version = "=0.25.3"
+version = "=0.27.1"
+optional = true
[dependencies.uniffi_meta]
-version = "=0.25.3"
+version = "=0.27.1"
[features]
default = []
nightly = []
+trybuild = ["dep:uniffi_build"]
diff --git a/third_party/rust/uniffi_macros/README.md b/third_party/rust/uniffi_macros/README.md
new file mode 100644
index 0000000000..64ac3486a3
--- /dev/null
+++ b/third_party/rust/uniffi_macros/README.md
@@ -0,0 +1,81 @@
+# UniFFI - a multi-language bindings generator for Rust
+
+UniFFI is a toolkit for building cross-platform software components in Rust.
+
+For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/)
+or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components).
+
+By writing your core business logic in Rust and describing its interface in an "object model",
+you can use UniFFI to help you:
+
+* Compile your Rust code into a shared library for use on different target platforms.
+* Generate bindings to load and use the library from different target languages.
+
+You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html).
+
+UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers;
+written once in Rust, auto-generated bindings allow that functionality to be called
+from both Kotlin (for Android apps) and Swift (for iOS apps).
+It also has a growing community of users shipping various cool things to many users.
+
+UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**.
+Additional foreign language bindings can be developed externally and we welcome contributions to list them here.
+See [Third-party foreign language bindings](#third-party-foreign-language-bindings).
+
+## User Guide
+
+You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/).
+
+We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on.
+We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time.
+
+### Etymology and Pronunciation
+
+ˈjuːnɪfaɪ. Pronounced to rhyme with "unify".
+
+A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages.
+
+uni - [Latin ūni-, from ūnus, one]
+FFI - [Abbreviation, Foreign Function Interface]
+
+## Alternative tools
+
+Other tools we know of which try and solve a similarly shaped problem are:
+
+* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of
+ the different approach taken by that tool](docs/diplomat-and-macros.md)
+* [Interoptopus](https://github.com/ralfbiedert/interoptopus/)
+
+(Please open a PR if you think other tools should be listed!)
+
+## Third-party foreign language bindings
+
+* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native.
+* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go)
+* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs)
+* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart)
+
+### External resources
+
+There are a few third-party resources that make it easier to work with UniFFI:
+
+* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/).
+* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts.
+* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful.
+* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure.
+
+(Please open a PR if you think other resources should be listed!)
+
+## Contributing
+
+If this tool sounds interesting to you, please help us develop it! You can:
+
+* View the [contributor guidelines](./docs/contributing.md).
+* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub.
+* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org)
+ room on Matrix.
+
+## Code of Conduct
+
+This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md).
diff --git a/third_party/rust/uniffi_macros/src/default.rs b/third_party/rust/uniffi_macros/src/default.rs
new file mode 100644
index 0000000000..000c205845
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/default.rs
@@ -0,0 +1,133 @@
+/* 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/. */
+
+use crate::util::kw;
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+use syn::{
+ bracketed, parenthesized,
+ parse::{Nothing, Parse, ParseStream},
+ token::{Bracket, Paren},
+ Lit,
+};
+
+/// Default value
+#[derive(Clone)]
+pub enum DefaultValue {
+ Literal(Lit),
+ None(kw::None),
+ Some {
+ some: kw::Some,
+ paren: Paren,
+ inner: Box<DefaultValue>,
+ },
+ EmptySeq(Bracket),
+}
+
+impl ToTokens for DefaultValue {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ match self {
+ DefaultValue::Literal(lit) => lit.to_tokens(tokens),
+ DefaultValue::None(kw) => kw.to_tokens(tokens),
+ DefaultValue::Some { inner, .. } => tokens.extend(quote! { Some(#inner) }),
+ DefaultValue::EmptySeq(_) => tokens.extend(quote! { [] }),
+ }
+ }
+}
+
+impl Parse for DefaultValue {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::None) {
+ let none_kw: kw::None = input.parse()?;
+ Ok(Self::None(none_kw))
+ } else if lookahead.peek(kw::Some) {
+ let some: kw::Some = input.parse()?;
+ let content;
+ let paren = parenthesized!(content in input);
+ Ok(Self::Some {
+ some,
+ paren,
+ inner: content.parse()?,
+ })
+ } else if lookahead.peek(Bracket) {
+ let content;
+ let bracket = bracketed!(content in input);
+ content.parse::<Nothing>()?;
+ Ok(Self::EmptySeq(bracket))
+ } else {
+ Ok(Self::Literal(input.parse()?))
+ }
+ }
+}
+
+impl DefaultValue {
+ fn metadata_calls(&self) -> syn::Result<TokenStream> {
+ match self {
+ DefaultValue::Literal(Lit::Int(i)) if !i.suffix().is_empty() => Err(
+ syn::Error::new_spanned(i, "integer literals with suffix not supported here"),
+ ),
+ DefaultValue::Literal(Lit::Float(f)) if !f.suffix().is_empty() => Err(
+ syn::Error::new_spanned(f, "float literals with suffix not supported here"),
+ ),
+
+ DefaultValue::Literal(Lit::Str(s)) => Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_STR)
+ .concat_str(#s)
+ }),
+ DefaultValue::Literal(Lit::Int(i)) => {
+ let digits = i.base10_digits();
+ Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_INT)
+ .concat_str(#digits)
+ })
+ }
+ DefaultValue::Literal(Lit::Float(f)) => {
+ let digits = f.base10_digits();
+ Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_FLOAT)
+ .concat_str(#digits)
+ })
+ }
+ DefaultValue::Literal(Lit::Bool(b)) => Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_BOOL)
+ .concat_bool(#b)
+ }),
+
+ DefaultValue::Literal(_) => Err(syn::Error::new_spanned(
+ self,
+ "this type of literal is not currently supported as a default",
+ )),
+
+ DefaultValue::EmptySeq(_) => Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_EMPTY_SEQ)
+ }),
+
+ DefaultValue::None(_) => Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_NONE)
+ }),
+
+ DefaultValue::Some { inner, .. } => {
+ let inner_calls = inner.metadata_calls()?;
+ Ok(quote! {
+ .concat_value(::uniffi::metadata::codes::LIT_SOME)
+ #inner_calls
+ })
+ }
+ }
+ }
+}
+
+pub fn default_value_metadata_calls(default: &Option<DefaultValue>) -> syn::Result<TokenStream> {
+ Ok(match default {
+ Some(default) => {
+ let metadata_calls = default.metadata_calls()?;
+ quote! {
+ .concat_bool(true)
+ #metadata_calls
+ }
+ }
+ None => quote! { .concat_bool(false) },
+ })
+}
diff --git a/third_party/rust/uniffi_macros/src/enum_.rs b/third_party/rust/uniffi_macros/src/enum_.rs
index 32abfa08cc..fd98da3129 100644
--- a/third_party/rust/uniffi_macros/src/enum_.rs
+++ b/third_party/rust/uniffi_macros/src/enum_.rs
@@ -1,13 +1,47 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
-use syn::{Data, DataEnum, DeriveInput, Field, Index};
+use syn::{
+ parse::{Parse, ParseStream},
+ spanned::Spanned,
+ Attribute, Data, DataEnum, DeriveInput, Expr, Index, Lit, Variant,
+};
use crate::util::{
- create_metadata_items, derive_all_ffi_traits, ident_to_string, mod_path, tagged_impl_header,
- try_metadata_value_from_usize, try_read_field,
+ create_metadata_items, derive_all_ffi_traits, either_attribute_arg, extract_docstring,
+ ident_to_string, kw, mod_path, parse_comma_separated, tagged_impl_header,
+ try_metadata_value_from_usize, try_read_field, AttributeSliceExt, UniffiAttributeArgs,
};
-pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> {
+fn extract_repr(attrs: &[Attribute]) -> syn::Result<Option<Ident>> {
+ let mut result = None;
+ for attr in attrs {
+ if attr.path().is_ident("repr") {
+ attr.parse_nested_meta(|meta| {
+ result = match meta.path.get_ident() {
+ Some(i) => {
+ let s = i.to_string();
+ match s.as_str() {
+ "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32"
+ | "i64" | "isize" => Some(i.clone()),
+ // while the default repr for an enum is `isize` we don't apply that default here.
+ _ => None,
+ }
+ }
+ _ => None,
+ };
+ Ok(())
+ })?
+ }
+ }
+ Ok(result)
+}
+
+pub fn expand_enum(
+ input: DeriveInput,
+ // Attributes from #[derive_error_for_udl()], if we are in udl mode
+ attr_from_udl_mode: Option<EnumAttr>,
+ udl_mode: bool,
+) -> syn::Result<TokenStream> {
let enum_ = match input.data {
Data::Enum(e) => e,
_ => {
@@ -18,10 +52,17 @@ pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStrea
}
};
let ident = &input.ident;
- let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, udl_mode);
+ let docstring = extract_docstring(&input.attrs)?;
+ let discr_type = extract_repr(&input.attrs)?;
+ let mut attr: EnumAttr = input.attrs.parse_uniffi_attr_args()?;
+ if let Some(attr_from_udl_mode) = attr_from_udl_mode {
+ attr = attr.merge(attr_from_udl_mode)?;
+ }
+ let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, udl_mode, &attr);
let meta_static_var = (!udl_mode).then(|| {
- enum_meta_static_var(ident, &enum_).unwrap_or_else(syn::Error::into_compile_error)
+ enum_meta_static_var(ident, docstring, discr_type, &enum_, &attr)
+ .unwrap_or_else(syn::Error::into_compile_error)
});
Ok(quote! {
@@ -34,11 +75,13 @@ pub(crate) fn enum_ffi_converter_impl(
ident: &Ident,
enum_: &DataEnum,
udl_mode: bool,
+ attr: &EnumAttr,
) -> TokenStream {
enum_or_error_ffi_converter_impl(
ident,
enum_,
udl_mode,
+ attr,
quote! { ::uniffi::metadata::codes::TYPE_ENUM },
)
}
@@ -47,11 +90,13 @@ pub(crate) fn rich_error_ffi_converter_impl(
ident: &Ident,
enum_: &DataEnum,
udl_mode: bool,
+ attr: &EnumAttr,
) -> TokenStream {
enum_or_error_ffi_converter_impl(
ident,
enum_,
udl_mode,
+ attr,
quote! { ::uniffi::metadata::codes::TYPE_ENUM },
)
}
@@ -60,6 +105,7 @@ fn enum_or_error_ffi_converter_impl(
ident: &Ident,
enum_: &DataEnum,
udl_mode: bool,
+ attr: &EnumAttr,
metadata_type_code: TokenStream,
) -> TokenStream {
let name = ident_to_string(ident);
@@ -69,19 +115,50 @@ fn enum_or_error_ffi_converter_impl(
Ok(p) => p,
Err(e) => return e.into_compile_error(),
};
- let write_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| {
- let v_ident = &v.ident;
- let fields = v.fields.iter().map(|f| &f.ident);
- let idx = Index::from(i + 1);
- let write_fields = v.fields.iter().map(write_field);
+ let mut write_match_arms: Vec<_> = enum_
+ .variants
+ .iter()
+ .enumerate()
+ .map(|(i, v)| {
+ let v_ident = &v.ident;
+ let field_idents = v
+ .fields
+ .iter()
+ .enumerate()
+ .map(|(i, f)| {
+ f.ident
+ .clone()
+ .unwrap_or_else(|| Ident::new(&format!("e{i}"), f.span()))
+ })
+ .collect::<Vec<Ident>>();
+ let idx = Index::from(i + 1);
+ let write_fields =
+ std::iter::zip(v.fields.iter(), field_idents.iter()).map(|(f, ident)| {
+ let ty = &f.ty;
+ quote! {
+ <#ty as ::uniffi::Lower<crate::UniFfiTag>>::write(#ident, buf);
+ }
+ });
+ let is_tuple = v.fields.iter().any(|f| f.ident.is_none());
+ let fields = if is_tuple {
+ quote! { ( #(#field_idents),* ) }
+ } else {
+ quote! { { #(#field_idents),* } }
+ };
- quote! {
- Self::#v_ident { #(#fields),* } => {
- ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx);
- #(#write_fields)*
+ quote! {
+ Self::#v_ident #fields => {
+ ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx);
+ #(#write_fields)*
+ }
}
- }
- });
+ })
+ .collect();
+ if attr.non_exhaustive.is_some() {
+ write_match_arms.push(quote! {
+ _ => panic!("Unexpected variant in non-exhaustive enum"),
+ })
+ }
let write_impl = quote! {
match obj { #(#write_match_arms)* }
};
@@ -89,10 +166,17 @@ fn enum_or_error_ffi_converter_impl(
let try_read_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| {
let idx = Index::from(i + 1);
let v_ident = &v.ident;
+ let is_tuple = v.fields.iter().any(|f| f.ident.is_none());
let try_read_fields = v.fields.iter().map(try_read_field);
- quote! {
- #idx => Self::#v_ident { #(#try_read_fields)* },
+ if is_tuple {
+ quote! {
+ #idx => Self::#v_ident ( #(#try_read_fields)* ),
+ }
+ } else {
+ quote! {
+ #idx => Self::#v_ident { #(#try_read_fields)* },
+ }
}
});
let error_format_string = format!("Invalid {ident} enum value: {{}}");
@@ -127,69 +211,161 @@ fn enum_or_error_ffi_converter_impl(
}
}
-fn write_field(f: &Field) -> TokenStream {
- let ident = &f.ident;
- let ty = &f.ty;
-
- quote! {
- <#ty as ::uniffi::Lower<crate::UniFfiTag>>::write(#ident, buf);
- }
-}
-
-pub(crate) fn enum_meta_static_var(ident: &Ident, enum_: &DataEnum) -> syn::Result<TokenStream> {
+pub(crate) fn enum_meta_static_var(
+ ident: &Ident,
+ docstring: String,
+ discr_type: Option<Ident>,
+ enum_: &DataEnum,
+ attr: &EnumAttr,
+) -> syn::Result<TokenStream> {
let name = ident_to_string(ident);
let module_path = mod_path()?;
+ let non_exhaustive = attr.non_exhaustive.is_some();
let mut metadata_expr = quote! {
::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM)
.concat_str(#module_path)
.concat_str(#name)
+ .concat_option_bool(None) // forced_flatness
};
+ metadata_expr.extend(match discr_type {
+ None => quote! { .concat_bool(false) },
+ Some(t) => quote! { .concat_bool(true).concat(<#t as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META) }
+ });
metadata_expr.extend(variant_metadata(enum_)?);
+ metadata_expr.extend(quote! {
+ .concat_bool(#non_exhaustive)
+ .concat_long_str(#docstring)
+ });
Ok(create_metadata_items("enum", &name, metadata_expr, None))
}
+fn variant_value(v: &Variant) -> syn::Result<TokenStream> {
+ let Some((_, e)) = &v.discriminant else {
+ return Ok(quote! { .concat_bool(false) });
+ };
+ // Attempting to expose an enum value which we don't understand is a hard-error
+ // rather than silently ignoring it. If we had the ability to emit a warning that
+ // might make more sense.
+
+ // We can't sanely handle most expressions other than literals, but we can handle
+ // negative literals.
+ let mut negate = false;
+ let lit = match e {
+ Expr::Lit(lit) => lit,
+ Expr::Unary(expr_unary) if matches!(expr_unary.op, syn::UnOp::Neg(_)) => {
+ negate = true;
+ match *expr_unary.expr {
+ Expr::Lit(ref lit) => lit,
+ _ => {
+ return Err(syn::Error::new_spanned(
+ e,
+ "UniFFI disciminant values must be a literal",
+ ));
+ }
+ }
+ }
+ _ => {
+ return Err(syn::Error::new_spanned(
+ e,
+ "UniFFI disciminant values must be a literal",
+ ));
+ }
+ };
+ let Lit::Int(ref intlit) = lit.lit else {
+ return Err(syn::Error::new_spanned(
+ v,
+ "UniFFI disciminant values must be a literal integer",
+ ));
+ };
+ if !intlit.suffix().is_empty() {
+ return Err(syn::Error::new_spanned(
+ intlit,
+ "integer literals with suffix not supported by UniFFI here",
+ ));
+ }
+ let digits = if negate {
+ format!("-{}", intlit.base10_digits())
+ } else {
+ intlit.base10_digits().to_string()
+ };
+ Ok(quote! {
+ .concat_bool(true)
+ .concat_value(::uniffi::metadata::codes::LIT_INT)
+ .concat_str(#digits)
+ })
+}
+
pub fn variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>> {
let variants_len =
try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?;
std::iter::once(Ok(quote! { .concat_value(#variants_len) }))
- .chain(
- enum_.variants
+ .chain(enum_.variants.iter().map(|v| {
+ let fields_len = try_metadata_value_from_usize(
+ v.fields.len(),
+ "UniFFI limits enum variants to 256 fields",
+ )?;
+
+ let field_names = v
+ .fields
.iter()
- .map(|v| {
- let fields_len = try_metadata_value_from_usize(
- v.fields.len(),
- "UniFFI limits enum variants to 256 fields",
- )?;
-
- let field_names = v.fields
- .iter()
- .map(|f| {
- f.ident
- .as_ref()
- .ok_or_else(||
- syn::Error::new_spanned(
- v,
- "UniFFI only supports enum variants with named fields (or no fields at all)",
- )
- )
- .map(ident_to_string)
- })
- .collect::<syn::Result<Vec<_>>>()?;
-
- let name = ident_to_string(&v.ident);
- let field_types = v.fields.iter().map(|f| &f.ty);
- Ok(quote! {
- .concat_str(#name)
- .concat_value(#fields_len)
- #(
- .concat_str(#field_names)
- .concat(<#field_types as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META)
- // field defaults not yet supported for enums
- .concat_bool(false)
- )*
- })
- })
- )
+ .map(|f| f.ident.as_ref().map(ident_to_string).unwrap_or_default())
+ .collect::<Vec<_>>();
+
+ let name = ident_to_string(&v.ident);
+ let value_tokens = variant_value(v)?;
+ let docstring = extract_docstring(&v.attrs)?;
+ let field_types = v.fields.iter().map(|f| &f.ty);
+ let field_docstrings = v
+ .fields
+ .iter()
+ .map(|f| extract_docstring(&f.attrs))
+ .collect::<syn::Result<Vec<_>>>()?;
+
+ Ok(quote! {
+ .concat_str(#name)
+ #value_tokens
+ .concat_value(#fields_len)
+ #(
+ .concat_str(#field_names)
+ .concat(<#field_types as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META)
+ // field defaults not yet supported for enums
+ .concat_bool(false)
+ .concat_long_str(#field_docstrings)
+ )*
+ .concat_long_str(#docstring)
+ })
+ }))
.collect()
}
+
+#[derive(Default)]
+pub struct EnumAttr {
+ pub non_exhaustive: Option<kw::non_exhaustive>,
+}
+
+// So ErrorAttr can be used with `parse_macro_input!`
+impl Parse for EnumAttr {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ parse_comma_separated(input)
+ }
+}
+
+impl UniffiAttributeArgs for EnumAttr {
+ fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::non_exhaustive) {
+ Ok(Self {
+ non_exhaustive: input.parse()?,
+ })
+ } else {
+ Err(lookahead.error())
+ }
+ }
+
+ fn merge(self, other: Self) -> syn::Result<Self> {
+ Ok(Self {
+ non_exhaustive: either_attribute_arg(self.non_exhaustive, other.non_exhaustive)?,
+ })
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/error.rs b/third_party/rust/uniffi_macros/src/error.rs
index a2ee7cf603..804b438003 100644
--- a/third_party/rust/uniffi_macros/src/error.rs
+++ b/third_party/rust/uniffi_macros/src/error.rs
@@ -6,11 +6,11 @@ use syn::{
};
use crate::{
- enum_::{rich_error_ffi_converter_impl, variant_metadata},
+ enum_::{rich_error_ffi_converter_impl, variant_metadata, EnumAttr},
util::{
- chain, create_metadata_items, derive_ffi_traits, either_attribute_arg, ident_to_string, kw,
- mod_path, parse_comma_separated, tagged_impl_header, try_metadata_value_from_usize,
- AttributeSliceExt, UniffiAttributeArgs,
+ chain, create_metadata_items, derive_ffi_traits, either_attribute_arg, extract_docstring,
+ ident_to_string, kw, mod_path, parse_comma_separated, tagged_impl_header,
+ try_metadata_value_from_usize, AttributeSliceExt, UniffiAttributeArgs,
},
};
@@ -30,13 +30,14 @@ pub fn expand_error(
}
};
let ident = &input.ident;
+ let docstring = extract_docstring(&input.attrs)?;
let mut attr: ErrorAttr = input.attrs.parse_uniffi_attr_args()?;
if let Some(attr_from_udl_mode) = attr_from_udl_mode {
attr = attr.merge(attr_from_udl_mode)?;
}
- let ffi_converter_impl = error_ffi_converter_impl(ident, &enum_, &attr, udl_mode);
+ let ffi_converter_impl = error_ffi_converter_impl(ident, &enum_, &attr, udl_mode)?;
let meta_static_var = (!udl_mode).then(|| {
- error_meta_static_var(ident, &enum_, attr.flat.is_some())
+ error_meta_static_var(ident, docstring, &enum_, &attr)
.unwrap_or_else(syn::Error::into_compile_error)
});
@@ -67,23 +68,23 @@ fn error_ffi_converter_impl(
enum_: &DataEnum,
attr: &ErrorAttr,
udl_mode: bool,
-) -> TokenStream {
- if attr.flat.is_some() {
- flat_error_ffi_converter_impl(ident, enum_, udl_mode, attr.with_try_read.is_some())
+) -> syn::Result<TokenStream> {
+ Ok(if attr.flat.is_some() {
+ flat_error_ffi_converter_impl(ident, enum_, udl_mode, attr)
} else {
- rich_error_ffi_converter_impl(ident, enum_, udl_mode)
- }
+ rich_error_ffi_converter_impl(ident, enum_, udl_mode, &attr.clone().try_into()?)
+ })
}
// FfiConverters for "flat errors"
//
-// These are errors where we only lower the to_string() value, rather than any assocated data.
+// These are errors where we only lower the to_string() value, rather than any associated data.
// We lower the to_string() value unconditionally, whether the enum has associated data or not.
fn flat_error_ffi_converter_impl(
ident: &Ident,
enum_: &DataEnum,
udl_mode: bool,
- implement_lift: bool,
+ attr: &ErrorAttr,
) -> TokenStream {
let name = ident_to_string(ident);
let lower_impl_spec = tagged_impl_header("Lower", ident, udl_mode);
@@ -95,7 +96,7 @@ fn flat_error_ffi_converter_impl(
};
let lower_impl = {
- let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| {
+ let mut match_arms: Vec<_> = enum_.variants.iter().enumerate().map(|(i, v)| {
let v_ident = &v.ident;
let idx = Index::from(i + 1);
@@ -105,7 +106,12 @@ fn flat_error_ffi_converter_impl(
<::std::string::String as ::uniffi::Lower<crate::UniFfiTag>>::write(error_msg, buf);
}
}
- });
+ }).collect();
+ if attr.non_exhaustive.is_some() {
+ match_arms.push(quote! {
+ _ => panic!("Unexpected variant in non-exhaustive enum"),
+ })
+ }
quote! {
#[automatically_derived]
@@ -128,7 +134,7 @@ fn flat_error_ffi_converter_impl(
}
};
- let lift_impl = if implement_lift {
+ let lift_impl = if attr.with_try_read.is_some() {
let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| {
let v_ident = &v.ident;
let idx = Index::from(i + 1);
@@ -192,42 +198,53 @@ fn flat_error_ffi_converter_impl(
pub(crate) fn error_meta_static_var(
ident: &Ident,
+ docstring: String,
enum_: &DataEnum,
- flat: bool,
+ attr: &ErrorAttr,
) -> syn::Result<TokenStream> {
let name = ident_to_string(ident);
let module_path = mod_path()?;
+ let flat = attr.flat.is_some();
+ let non_exhaustive = attr.non_exhaustive.is_some();
let mut metadata_expr = quote! {
- ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ERROR)
- // first our is-flat flag
- .concat_bool(#flat)
- // followed by an enum
+ ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM)
.concat_str(#module_path)
.concat_str(#name)
+ .concat_option_bool(Some(#flat))
+ .concat_bool(false) // discr_type: None
};
if flat {
metadata_expr.extend(flat_error_variant_metadata(enum_)?)
} else {
metadata_expr.extend(variant_metadata(enum_)?);
}
+ metadata_expr.extend(quote! {
+ .concat_bool(#non_exhaustive)
+ .concat_long_str(#docstring)
+ });
Ok(create_metadata_items("error", &name, metadata_expr, None))
}
pub fn flat_error_variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>> {
let variants_len =
try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?;
- Ok(std::iter::once(quote! { .concat_value(#variants_len) })
+ std::iter::once(Ok(quote! { .concat_value(#variants_len) }))
.chain(enum_.variants.iter().map(|v| {
let name = ident_to_string(&v.ident);
- quote! { .concat_str(#name) }
+ let docstring = extract_docstring(&v.attrs)?;
+ Ok(quote! {
+ .concat_str(#name)
+ .concat_long_str(#docstring)
+ })
}))
- .collect())
+ .collect()
}
-#[derive(Default)]
+#[derive(Clone, Default)]
pub struct ErrorAttr {
- flat: Option<kw::flat_error>,
- with_try_read: Option<kw::with_try_read>,
+ pub flat: Option<kw::flat_error>,
+ pub with_try_read: Option<kw::with_try_read>,
+ pub non_exhaustive: Option<kw::non_exhaustive>,
}
impl UniffiAttributeArgs for ErrorAttr {
@@ -243,8 +260,13 @@ impl UniffiAttributeArgs for ErrorAttr {
with_try_read: input.parse()?,
..Self::default()
})
+ } else if lookahead.peek(kw::non_exhaustive) {
+ Ok(Self {
+ non_exhaustive: input.parse()?,
+ ..Self::default()
+ })
} else if lookahead.peek(kw::handle_unknown_callback_error) {
- // Not used anymore, but still lallowed
+ // Not used anymore, but still allowed
Ok(Self::default())
} else {
Err(lookahead.error())
@@ -255,6 +277,7 @@ impl UniffiAttributeArgs for ErrorAttr {
Ok(Self {
flat: either_attribute_arg(self.flat, other.flat)?,
with_try_read: either_attribute_arg(self.with_try_read, other.with_try_read)?,
+ non_exhaustive: either_attribute_arg(self.non_exhaustive, other.non_exhaustive)?,
})
}
}
@@ -265,3 +288,25 @@ impl Parse for ErrorAttr {
parse_comma_separated(input)
}
}
+
+impl TryFrom<ErrorAttr> for EnumAttr {
+ type Error = syn::Error;
+
+ fn try_from(error_attr: ErrorAttr) -> Result<Self, Self::Error> {
+ if error_attr.flat.is_some() {
+ Err(syn::Error::new(
+ Span::call_site(),
+ "flat attribute not valid for rich enum errors",
+ ))
+ } else if error_attr.with_try_read.is_some() {
+ Err(syn::Error::new(
+ Span::call_site(),
+ "with_try_read attribute not valid for rich enum errors",
+ ))
+ } else {
+ Ok(EnumAttr {
+ non_exhaustive: error_attr.non_exhaustive,
+ })
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/export.rs b/third_party/rust/uniffi_macros/src/export.rs
index bbb16acf90..41657a639e 100644
--- a/third_party/rust/uniffi_macros/src/export.rs
+++ b/third_party/rust/uniffi_macros/src/export.rs
@@ -2,7 +2,7 @@
* 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/. */
-use proc_macro2::{Ident, Span, TokenStream};
+use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{visit_mut::VisitMut, Item, Type};
@@ -10,6 +10,7 @@ mod attributes;
mod callback_interface;
mod item;
mod scaffolding;
+mod trait_interface;
mod utrait;
use self::{
@@ -18,20 +19,16 @@ use self::{
gen_constructor_scaffolding, gen_ffi_function, gen_fn_scaffolding, gen_method_scaffolding,
},
};
-use crate::{
- object::interface_meta_static_var,
- util::{ident_to_string, mod_path, tagged_impl_header},
-};
-pub use attributes::ExportAttributeArguments;
+use crate::util::{ident_to_string, mod_path};
+pub use attributes::{DefaultMap, ExportFnArgs, ExportedImplFnArgs};
pub use callback_interface::ffi_converter_callback_interface_impl;
-use uniffi_meta::free_fn_symbol_name;
// TODO(jplatte): Ensure no generics, …
// TODO(jplatte): Aggregate errors instead of short-circuiting, wherever possible
pub(crate) fn expand_export(
mut item: Item,
- args: ExportAttributeArguments,
+ all_args: proc_macro::TokenStream,
udl_mode: bool,
) -> syn::Result<TokenStream> {
let mod_path = mod_path()?;
@@ -41,11 +38,17 @@ pub(crate) fn expand_export(
// new functions outside of the `impl`).
rewrite_self_type(&mut item);
- let metadata = ExportItem::new(item, &args)?;
+ let metadata = ExportItem::new(item, all_args)?;
match metadata {
- ExportItem::Function { sig } => gen_fn_scaffolding(sig, &args, udl_mode),
- ExportItem::Impl { items, self_ident } => {
+ ExportItem::Function { sig, args } => {
+ gen_fn_scaffolding(sig, &args.async_runtime, udl_mode)
+ }
+ ExportItem::Impl {
+ items,
+ self_ident,
+ args,
+ } => {
if let Some(rt) = &args.async_runtime {
if items
.iter()
@@ -61,8 +64,12 @@ pub(crate) fn expand_export(
let item_tokens: TokenStream = items
.into_iter()
.map(|item| match item {
- ImplItem::Constructor(sig) => gen_constructor_scaffolding(sig, &args, udl_mode),
- ImplItem::Method(sig) => gen_method_scaffolding(sig, &args, udl_mode),
+ ImplItem::Constructor(sig) => {
+ gen_constructor_scaffolding(sig, &args.async_runtime, udl_mode)
+ }
+ ImplItem::Method(sig) => {
+ gen_method_scaffolding(sig, &args.async_runtime, udl_mode)
+ }
})
.collect::<syn::Result<_>>()?;
Ok(quote_spanned! { self_ident.span() => #item_tokens })
@@ -70,111 +77,51 @@ pub(crate) fn expand_export(
ExportItem::Trait {
items,
self_ident,
- callback_interface: false,
- } => {
- if let Some(rt) = args.async_runtime {
- return Err(syn::Error::new_spanned(rt, "not supported for traits"));
- }
-
- let name = ident_to_string(&self_ident);
- let free_fn_ident =
- Ident::new(&free_fn_symbol_name(&mod_path, &name), Span::call_site());
-
- let free_tokens = quote! {
- #[doc(hidden)]
- #[no_mangle]
- pub extern "C" fn #free_fn_ident(
- ptr: *const ::std::ffi::c_void,
- call_status: &mut ::uniffi::RustCallStatus
- ) {
- uniffi::rust_call(call_status, || {
- assert!(!ptr.is_null());
- drop(unsafe { ::std::boxed::Box::from_raw(ptr as *mut std::sync::Arc<dyn #self_ident>) });
- Ok(())
- });
- }
- };
-
- let impl_tokens: TokenStream = items
- .into_iter()
- .map(|item| match item {
- ImplItem::Method(sig) => {
- if sig.is_async {
- return Err(syn::Error::new(
- sig.span,
- "async trait methods are not supported",
- ));
- }
- gen_method_scaffolding(sig, &args, udl_mode)
- }
- _ => unreachable!("traits have no constructors"),
- })
- .collect::<syn::Result<_>>()?;
-
- let meta_static_var = (!udl_mode).then(|| {
- interface_meta_static_var(&self_ident, true, &mod_path)
- .unwrap_or_else(syn::Error::into_compile_error)
- });
- let ffi_converter_tokens = ffi_converter_trait_impl(&self_ident, false);
-
- Ok(quote_spanned! { self_ident.span() =>
- #meta_static_var
- #free_tokens
- #ffi_converter_tokens
- #impl_tokens
- })
- }
+ with_foreign,
+ callback_interface_only: false,
+ docstring,
+ args,
+ } => trait_interface::gen_trait_scaffolding(
+ &mod_path,
+ args,
+ self_ident,
+ items,
+ udl_mode,
+ with_foreign,
+ docstring,
+ ),
ExportItem::Trait {
items,
self_ident,
- callback_interface: true,
+ callback_interface_only: true,
+ docstring,
+ ..
} => {
let trait_name = ident_to_string(&self_ident);
- let trait_impl_ident = Ident::new(
- &format!("UniFFICallbackHandler{trait_name}"),
- Span::call_site(),
- );
- let internals_ident = Ident::new(
- &format!(
- "UNIFFI_FOREIGN_CALLBACK_INTERNALS_{}",
- trait_name.to_ascii_uppercase()
- ),
- Span::call_site(),
- );
-
- let trait_impl = callback_interface::trait_impl(
- &trait_impl_ident,
- &self_ident,
- &internals_ident,
- &items,
- )
- .unwrap_or_else(|e| e.into_compile_error());
- let metadata_items = callback_interface::metadata_items(&self_ident, &items, &mod_path)
- .unwrap_or_else(|e| vec![e.into_compile_error()]);
-
- let init_ident = Ident::new(
- &uniffi_meta::init_callback_fn_symbol_name(&mod_path, &trait_name),
- Span::call_site(),
- );
+ let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name);
+ let trait_impl = callback_interface::trait_impl(&mod_path, &self_ident, &items)
+ .unwrap_or_else(|e| e.into_compile_error());
+ let metadata_items = (!udl_mode).then(|| {
+ let items =
+ callback_interface::metadata_items(&self_ident, &items, &mod_path, docstring)
+ .unwrap_or_else(|e| vec![e.into_compile_error()]);
+ quote! { #(#items)* }
+ });
+ let ffi_converter_tokens =
+ ffi_converter_callback_interface_impl(&self_ident, &trait_impl_ident, udl_mode);
Ok(quote! {
- #[doc(hidden)]
- static #internals_ident: ::uniffi::ForeignCallbackInternals = ::uniffi::ForeignCallbackInternals::new();
-
- #[doc(hidden)]
- #[no_mangle]
- pub extern "C" fn #init_ident(callback: ::uniffi::ForeignCallback, _: &mut ::uniffi::RustCallStatus) {
- #internals_ident.set_callback(callback);
- }
-
#trait_impl
- #(#metadata_items)*
+ #ffi_converter_tokens
+
+ #metadata_items
})
}
ExportItem::Struct {
self_ident,
uniffi_traits,
+ ..
} => {
assert!(!udl_mode);
utrait::expand_uniffi_trait_export(self_ident, uniffi_traits)
@@ -182,62 +129,6 @@ pub(crate) fn expand_export(
}
}
-pub(crate) fn ffi_converter_trait_impl(trait_ident: &Ident, udl_mode: bool) -> TokenStream {
- let impl_spec = tagged_impl_header("FfiConverterArc", &quote! { dyn #trait_ident }, udl_mode);
- let lift_ref_impl_spec = tagged_impl_header("LiftRef", &quote! { dyn #trait_ident }, udl_mode);
- let name = ident_to_string(trait_ident);
- let mod_path = match mod_path() {
- Ok(p) => p,
- Err(e) => return e.into_compile_error(),
- };
-
- quote! {
- // All traits must be `Sync + Send`. The generated scaffolding will fail to compile
- // if they are not, but unfortunately it fails with an unactionably obscure error message.
- // By asserting the requirement explicitly, we help Rust produce a more scrutable error message
- // and thus help the user debug why the requirement isn't being met.
- uniffi::deps::static_assertions::assert_impl_all!(dyn #trait_ident: Sync, Send);
-
- unsafe #impl_spec {
- type FfiType = *const ::std::os::raw::c_void;
-
- fn lower(obj: ::std::sync::Arc<Self>) -> Self::FfiType {
- ::std::boxed::Box::into_raw(::std::boxed::Box::new(obj)) as *const ::std::os::raw::c_void
- }
-
- fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc<Self>> {
- let foreign_arc = ::std::boxed::Box::leak(unsafe { Box::from_raw(v as *mut ::std::sync::Arc<Self>) });
- // Take a clone for our own use.
- Ok(::std::sync::Arc::clone(foreign_arc))
- }
-
- fn write(obj: ::std::sync::Arc<Self>, buf: &mut Vec<u8>) {
- ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8);
- ::uniffi::deps::bytes::BufMut::put_u64(
- buf,
- <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(obj) as u64,
- );
- }
-
- fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc<Self>> {
- ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8);
- ::uniffi::check_remaining(buf, 8)?;
- <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::try_lift(
- ::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType)
- }
-
- const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_INTERFACE)
- .concat_str(#mod_path)
- .concat_str(#name)
- .concat_bool(true);
- }
-
- unsafe #lift_ref_impl_spec {
- type LiftType = ::std::sync::Arc<dyn #trait_ident>;
- }
- }
-}
-
/// Rewrite Self type alias usage in an impl block to the type itself.
///
/// For example,
diff --git a/third_party/rust/uniffi_macros/src/export/attributes.rs b/third_party/rust/uniffi_macros/src/export/attributes.rs
index c3edcd5920..be7e8902e4 100644
--- a/third_party/rust/uniffi_macros/src/export/attributes.rs
+++ b/third_party/rust/uniffi_macros/src/export/attributes.rs
@@ -1,31 +1,33 @@
-use crate::util::{either_attribute_arg, kw, parse_comma_separated, UniffiAttributeArgs};
+use std::collections::{HashMap, HashSet};
+
+use crate::{
+ default::DefaultValue,
+ util::{either_attribute_arg, kw, parse_comma_separated, UniffiAttributeArgs},
+};
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{
+ parenthesized,
parse::{Parse, ParseStream},
- Attribute, LitStr, Meta, PathArguments, PathSegment, Token,
+ Attribute, Ident, LitStr, Meta, PathArguments, PathSegment, Token,
};
+use uniffi_meta::UniffiTraitDiscriminants;
#[derive(Default)]
-pub struct ExportAttributeArguments {
+pub struct ExportTraitArgs {
pub(crate) async_runtime: Option<AsyncRuntime>,
pub(crate) callback_interface: Option<kw::callback_interface>,
- pub(crate) constructor: Option<kw::constructor>,
- // tried to make this a vec but that got messy quickly...
- pub(crate) trait_debug: Option<kw::Debug>,
- pub(crate) trait_display: Option<kw::Display>,
- pub(crate) trait_hash: Option<kw::Hash>,
- pub(crate) trait_eq: Option<kw::Eq>,
+ pub(crate) with_foreign: Option<kw::with_foreign>,
}
-impl Parse for ExportAttributeArguments {
+impl Parse for ExportTraitArgs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
parse_comma_separated(input)
}
}
-impl UniffiAttributeArgs for ExportAttributeArguments {
+impl UniffiAttributeArgs for ExportTraitArgs {
fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::async_runtime) {
@@ -40,52 +42,175 @@ impl UniffiAttributeArgs for ExportAttributeArguments {
callback_interface: input.parse()?,
..Self::default()
})
- } else if lookahead.peek(kw::constructor) {
+ } else if lookahead.peek(kw::with_foreign) {
Ok(Self {
- constructor: input.parse()?,
+ with_foreign: input.parse()?,
..Self::default()
})
- } else if lookahead.peek(kw::Debug) {
+ } else {
+ Ok(Self::default())
+ }
+ }
+
+ fn merge(self, other: Self) -> syn::Result<Self> {
+ let merged = Self {
+ async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?,
+ callback_interface: either_attribute_arg(
+ self.callback_interface,
+ other.callback_interface,
+ )?,
+ with_foreign: either_attribute_arg(self.with_foreign, other.with_foreign)?,
+ };
+ if merged.callback_interface.is_some() && merged.with_foreign.is_some() {
+ return Err(syn::Error::new(
+ merged.callback_interface.unwrap().span,
+ "`callback_interface` and `with_foreign` are mutually exclusive",
+ ));
+ }
+ Ok(merged)
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct ExportFnArgs {
+ pub(crate) async_runtime: Option<AsyncRuntime>,
+ pub(crate) name: Option<String>,
+ pub(crate) defaults: DefaultMap,
+}
+
+impl Parse for ExportFnArgs {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ parse_comma_separated(input)
+ }
+}
+
+impl UniffiAttributeArgs for ExportFnArgs {
+ fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::async_runtime) {
+ let _: kw::async_runtime = input.parse()?;
+ let _: Token![=] = input.parse()?;
Ok(Self {
- trait_debug: input.parse()?,
+ async_runtime: Some(input.parse()?),
..Self::default()
})
- } else if lookahead.peek(kw::Display) {
+ } else if lookahead.peek(kw::name) {
+ let _: kw::name = input.parse()?;
+ let _: Token![=] = input.parse()?;
+ let name = Some(input.parse::<LitStr>()?.value());
Ok(Self {
- trait_display: input.parse()?,
+ name,
..Self::default()
})
- } else if lookahead.peek(kw::Hash) {
+ } else if lookahead.peek(kw::default) {
Ok(Self {
- trait_hash: input.parse()?,
+ defaults: DefaultMap::parse(input)?,
..Self::default()
})
- } else if lookahead.peek(kw::Eq) {
+ } else {
+ Err(syn::Error::new(
+ input.span(),
+ format!("uniffi::export attribute `{input}` is not supported here."),
+ ))
+ }
+ }
+
+ fn merge(self, other: Self) -> syn::Result<Self> {
+ Ok(Self {
+ async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?,
+ name: either_attribute_arg(self.name, other.name)?,
+ defaults: self.defaults.merge(other.defaults),
+ })
+ }
+}
+
+#[derive(Default)]
+pub struct ExportImplArgs {
+ pub(crate) async_runtime: Option<AsyncRuntime>,
+}
+
+impl Parse for ExportImplArgs {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ parse_comma_separated(input)
+ }
+}
+
+impl UniffiAttributeArgs for ExportImplArgs {
+ fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::async_runtime) {
+ let _: kw::async_runtime = input.parse()?;
+ let _: Token![=] = input.parse()?;
Ok(Self {
- trait_eq: input.parse()?,
- ..Self::default()
+ async_runtime: Some(input.parse()?),
})
} else {
- Ok(Self::default())
+ Err(syn::Error::new(
+ input.span(),
+ format!("uniffi::export attribute `{input}` is not supported here."),
+ ))
}
}
fn merge(self, other: Self) -> syn::Result<Self> {
Ok(Self {
async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?,
- callback_interface: either_attribute_arg(
- self.callback_interface,
- other.callback_interface,
- )?,
- constructor: either_attribute_arg(self.constructor, other.constructor)?,
- trait_debug: either_attribute_arg(self.trait_debug, other.trait_debug)?,
- trait_display: either_attribute_arg(self.trait_display, other.trait_display)?,
- trait_hash: either_attribute_arg(self.trait_hash, other.trait_hash)?,
- trait_eq: either_attribute_arg(self.trait_eq, other.trait_eq)?,
})
}
}
+#[derive(Default)]
+pub struct ExportStructArgs {
+ pub(crate) traits: HashSet<UniffiTraitDiscriminants>,
+}
+
+impl Parse for ExportStructArgs {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ parse_comma_separated(input)
+ }
+}
+
+impl UniffiAttributeArgs for ExportStructArgs {
+ fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::Debug) {
+ input.parse::<Option<kw::Debug>>()?;
+ Ok(Self {
+ traits: HashSet::from([UniffiTraitDiscriminants::Debug]),
+ })
+ } else if lookahead.peek(kw::Display) {
+ input.parse::<Option<kw::Display>>()?;
+ Ok(Self {
+ traits: HashSet::from([UniffiTraitDiscriminants::Display]),
+ })
+ } else if lookahead.peek(kw::Hash) {
+ input.parse::<Option<kw::Hash>>()?;
+ Ok(Self {
+ traits: HashSet::from([UniffiTraitDiscriminants::Hash]),
+ })
+ } else if lookahead.peek(kw::Eq) {
+ input.parse::<Option<kw::Eq>>()?;
+ Ok(Self {
+ traits: HashSet::from([UniffiTraitDiscriminants::Eq]),
+ })
+ } else {
+ Err(syn::Error::new(
+ input.span(),
+ format!(
+ "uniffi::export struct attributes must be builtin trait names; `{input}` is invalid"
+ ),
+ ))
+ }
+ }
+
+ fn merge(self, other: Self) -> syn::Result<Self> {
+ let mut traits = self.traits;
+ traits.extend(other.traits);
+ Ok(Self { traits })
+ }
+}
+
+#[derive(Clone)]
pub(crate) enum AsyncRuntime {
Tokio(LitStr),
}
@@ -111,9 +236,57 @@ impl ToTokens for AsyncRuntime {
}
}
+/// Arguments for function inside an impl block
+///
+/// This stores the parsed arguments for `uniffi::constructor` and `uniffi::method`
+#[derive(Clone, Default)]
+pub struct ExportedImplFnArgs {
+ pub(crate) name: Option<String>,
+ pub(crate) defaults: DefaultMap,
+}
+
+impl Parse for ExportedImplFnArgs {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ parse_comma_separated(input)
+ }
+}
+
+impl UniffiAttributeArgs for ExportedImplFnArgs {
+ fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
+ let lookahead = input.lookahead1();
+ if lookahead.peek(kw::name) {
+ let _: kw::name = input.parse()?;
+ let _: Token![=] = input.parse()?;
+ let name = Some(input.parse::<LitStr>()?.value());
+ Ok(Self {
+ name,
+ ..Self::default()
+ })
+ } else if lookahead.peek(kw::default) {
+ Ok(Self {
+ defaults: DefaultMap::parse(input)?,
+ ..Self::default()
+ })
+ } else {
+ Err(syn::Error::new(
+ input.span(),
+ format!("uniffi::constructor/method attribute `{input}` is not supported here."),
+ ))
+ }
+ }
+
+ fn merge(self, other: Self) -> syn::Result<Self> {
+ Ok(Self {
+ name: either_attribute_arg(self.name, other.name)?,
+ defaults: self.defaults.merge(other.defaults),
+ })
+ }
+}
+
#[derive(Default)]
pub(super) struct ExportedImplFnAttributes {
pub constructor: bool,
+ pub args: ExportedImplFnArgs,
}
impl ExportedImplFnAttributes {
@@ -130,12 +303,11 @@ impl ExportedImplFnAttributes {
}
ensure_no_path_args(fst)?;
- if let Meta::List(_) | Meta::NameValue(_) = &attr.meta {
- return Err(syn::Error::new_spanned(
- &attr.meta,
- "attribute arguments are not currently recognized in this position",
- ));
- }
+ let args = match &attr.meta {
+ Meta::List(_) => attr.parse_args::<ExportedImplFnArgs>()?,
+ _ => Default::default(),
+ };
+ this.args = args;
if segs.len() != 2 {
return Err(syn::Error::new_spanned(
@@ -156,6 +328,14 @@ impl ExportedImplFnAttributes {
}
this.constructor = true;
}
+ "method" => {
+ if this.constructor {
+ return Err(syn::Error::new_spanned(
+ attr,
+ "confused constructor/method attributes",
+ ));
+ }
+ }
_ => return Err(syn::Error::new_spanned(snd, "unknown uniffi attribute")),
}
}
@@ -171,3 +351,53 @@ fn ensure_no_path_args(seg: &PathSegment) -> syn::Result<()> {
Err(syn::Error::new_spanned(&seg.arguments, "unexpected syntax"))
}
}
+
+/// Maps arguments to defaults for functions
+#[derive(Clone, Default)]
+pub struct DefaultMap {
+ map: HashMap<Ident, DefaultValue>,
+}
+
+impl DefaultMap {
+ pub fn merge(self, other: Self) -> Self {
+ let mut map = self.map;
+ map.extend(other.map);
+ Self { map }
+ }
+
+ pub fn remove(&mut self, ident: &Ident) -> Option<DefaultValue> {
+ self.map.remove(ident)
+ }
+
+ pub fn idents(&self) -> Vec<&Ident> {
+ self.map.keys().collect()
+ }
+}
+
+impl Parse for DefaultMap {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let _: kw::default = input.parse()?;
+ let content;
+ let _ = parenthesized!(content in input);
+ let pairs = content.parse_terminated(DefaultPair::parse, Token![,])?;
+ Ok(Self {
+ map: pairs.into_iter().map(|p| (p.name, p.value)).collect(),
+ })
+ }
+}
+
+pub struct DefaultPair {
+ pub name: Ident,
+ pub eq_token: Token![=],
+ pub value: DefaultValue,
+}
+
+impl Parse for DefaultPair {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ Ok(Self {
+ name: input.parse()?,
+ eq_token: input.parse()?,
+ value: input.parse()?,
+ })
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/export/callback_interface.rs b/third_party/rust/uniffi_macros/src/export/callback_interface.rs
index 2f2561bbc2..fe145384ec 100644
--- a/third_party/rust/uniffi_macros/src/export/callback_interface.rs
+++ b/third_party/rust/uniffi_macros/src/export/callback_interface.rs
@@ -5,69 +5,137 @@
use crate::{
export::ImplItem,
fnsig::{FnKind, FnSignature, ReceiverArg},
- util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header},
+ util::{
+ create_metadata_items, derive_ffi_traits, ident_to_string, mod_path, tagged_impl_header,
+ },
};
use proc_macro2::{Span, TokenStream};
-use quote::quote;
+use quote::{format_ident, quote};
use std::iter;
use syn::Ident;
+/// Generate a trait impl that calls foreign callbacks
+///
+/// This generates:
+/// * A `repr(C)` VTable struct where each field is the FFI function for the trait method.
+/// * A FFI function for foreign code to set their VTable for the interface
+/// * An implementation of the trait using that VTable
pub(super) fn trait_impl(
- ident: &Ident,
+ mod_path: &str,
trait_ident: &Ident,
- internals_ident: &Ident,
items: &[ImplItem],
) -> syn::Result<TokenStream> {
- let trait_impl_methods = items
+ let trait_name = ident_to_string(trait_ident);
+ let trait_impl_ident = trait_impl_ident(&trait_name);
+ let vtable_type = format_ident!("UniFfiTraitVtable{trait_name}");
+ let vtable_cell = format_ident!("UNIFFI_TRAIT_CELL_{}", trait_name.to_uppercase());
+ let init_ident = Ident::new(
+ &uniffi_meta::init_callback_vtable_fn_symbol_name(mod_path, &trait_name),
+ Span::call_site(),
+ );
+ let methods = items
.iter()
.map(|item| match item {
- ImplItem::Method(sig) => gen_method_impl(sig, internals_ident),
- _ => unreachable!("traits have no constructors"),
+ ImplItem::Constructor(sig) => Err(syn::Error::new(
+ sig.span,
+ "Constructors not allowed in trait interfaces",
+ )),
+ ImplItem::Method(sig) => Ok(sig),
})
- .collect::<syn::Result<TokenStream>>()?;
- let ffi_converter_tokens = ffi_converter_callback_interface_impl(trait_ident, ident, false);
+ .collect::<syn::Result<Vec<_>>>()?;
+
+ let vtable_fields = methods.iter()
+ .map(|sig| {
+ let ident = &sig.ident;
+ let param_names = sig.scaffolding_param_names();
+ let param_types = sig.scaffolding_param_types();
+ let lift_return = sig.lift_return_impl();
+ if !sig.is_async {
+ quote! {
+ #ident: extern "C" fn(
+ uniffi_handle: u64,
+ #(#param_names: #param_types,)*
+ uniffi_out_return: &mut #lift_return::ReturnType,
+ uniffi_out_call_status: &mut ::uniffi::RustCallStatus,
+ ),
+ }
+ } else {
+ quote! {
+ #ident: extern "C" fn(
+ uniffi_handle: u64,
+ #(#param_names: #param_types,)*
+ uniffi_future_callback: ::uniffi::ForeignFutureCallback<#lift_return::ReturnType>,
+ uniffi_callback_data: u64,
+ uniffi_out_return: &mut ::uniffi::ForeignFuture,
+ ),
+ }
+ }
+ });
+
+ let trait_impl_methods = methods
+ .iter()
+ .map(|sig| gen_method_impl(sig, &vtable_cell))
+ .collect::<syn::Result<Vec<_>>>()?;
+ let has_async_method = methods.iter().any(|m| m.is_async);
+ let impl_attributes = has_async_method.then(|| quote! { #[::async_trait::async_trait] });
Ok(quote! {
- #[doc(hidden)]
+ struct #vtable_type {
+ #(#vtable_fields)*
+ uniffi_free: extern "C" fn(handle: u64),
+ }
+
+ static #vtable_cell: ::uniffi::UniffiForeignPointerCell::<#vtable_type> = ::uniffi::UniffiForeignPointerCell::<#vtable_type>::new();
+
+ #[no_mangle]
+ extern "C" fn #init_ident(vtable: ::std::ptr::NonNull<#vtable_type>) {
+ #vtable_cell.set(vtable);
+ }
+
#[derive(Debug)]
- struct #ident {
+ struct #trait_impl_ident {
handle: u64,
}
- impl #ident {
+ impl #trait_impl_ident {
fn new(handle: u64) -> Self {
Self { handle }
}
}
- impl ::std::ops::Drop for #ident {
- fn drop(&mut self) {
- #internals_ident.invoke_callback::<(), crate::UniFfiTag>(
- self.handle, uniffi::IDX_CALLBACK_FREE, Default::default()
- )
- }
- }
-
- ::uniffi::deps::static_assertions::assert_impl_all!(#ident: Send);
+ ::uniffi::deps::static_assertions::assert_impl_all!(#trait_impl_ident: ::core::marker::Send);
- impl #trait_ident for #ident {
- #trait_impl_methods
+ #impl_attributes
+ impl #trait_ident for #trait_impl_ident {
+ #(#trait_impl_methods)*
}
- #ffi_converter_tokens
+ impl ::std::ops::Drop for #trait_impl_ident {
+ fn drop(&mut self) {
+ let vtable = #vtable_cell.get();
+ (vtable.uniffi_free)(self.handle);
+ }
+ }
})
}
+pub fn trait_impl_ident(trait_name: &str) -> Ident {
+ Ident::new(
+ &format!("UniFFICallbackHandler{trait_name}"),
+ Span::call_site(),
+ )
+}
+
pub fn ffi_converter_callback_interface_impl(
trait_ident: &Ident,
trait_impl_ident: &Ident,
udl_mode: bool,
) -> TokenStream {
- let name = ident_to_string(trait_ident);
+ let trait_name = ident_to_string(trait_ident);
let dyn_trait = quote! { dyn #trait_ident };
let box_dyn_trait = quote! { ::std::boxed::Box<#dyn_trait> };
let lift_impl_spec = tagged_impl_header("Lift", &box_dyn_trait, udl_mode);
- let lift_ref_impl_spec = tagged_impl_header("LiftRef", &dyn_trait, udl_mode);
+ let derive_ffi_traits = derive_ffi_traits(&box_dyn_trait, udl_mode, &["LiftRef", "LiftReturn"]);
let mod_path = match mod_path() {
Ok(p) => p,
Err(e) => return e.into_compile_error(),
@@ -93,66 +161,85 @@ pub fn ffi_converter_callback_interface_impl(
::uniffi::metadata::codes::TYPE_CALLBACK_INTERFACE,
)
.concat_str(#mod_path)
- .concat_str(#name);
+ .concat_str(#trait_name);
}
- unsafe #lift_ref_impl_spec {
- type LiftType = #box_dyn_trait;
- }
+ #derive_ffi_traits
}
}
-fn gen_method_impl(sig: &FnSignature, internals_ident: &Ident) -> syn::Result<TokenStream> {
+/// Generate a single method for [trait_impl]. This implements a trait method by invoking a
+/// foreign-supplied callback.
+fn gen_method_impl(sig: &FnSignature, vtable_cell: &Ident) -> syn::Result<TokenStream> {
let FnSignature {
ident,
+ is_async,
return_ty,
kind,
receiver,
+ name,
+ span,
..
} = sig;
- let index = match kind {
- // Note: the callback index is 1-based, since 0 is reserved for the free function
- FnKind::TraitMethod { index, .. } => index + 1,
- k => {
- return Err(syn::Error::new(
- sig.span,
- format!(
- "Internal UniFFI error: Unexpected function kind for callback interface {k:?}"
- ),
- ));
- }
- };
+
+ if !matches!(kind, FnKind::TraitMethod { .. }) {
+ return Err(syn::Error::new(
+ *span,
+ format!(
+ "Internal UniFFI error: Unexpected function kind for callback interface {name}: {kind:?}",
+ ),
+ ));
+ }
let self_param = match receiver {
+ Some(ReceiverArg::Ref) => quote! { &self },
+ Some(ReceiverArg::Arc) => quote! { self: Arc<Self> },
None => {
return Err(syn::Error::new(
- sig.span,
+ *span,
"callback interface methods must take &self as their first argument",
));
}
- Some(ReceiverArg::Ref) => quote! { &self },
- Some(ReceiverArg::Arc) => quote! { self: Arc<Self> },
};
+
let params = sig.params();
- let buf_ident = Ident::new("uniffi_args_buf", Span::call_site());
- let write_exprs = sig.write_exprs(&buf_ident);
+ let lower_exprs = sig.args.iter().map(|a| {
+ let lower_impl = a.lower_impl();
+ let ident = &a.ident;
+ quote! { #lower_impl::lower(#ident) }
+ });
- Ok(quote! {
- fn #ident(#self_param, #(#params),*) -> #return_ty {
- #[allow(unused_mut)]
- let mut #buf_ident = ::std::vec::Vec::new();
- #(#write_exprs;)*
- let uniffi_args_rbuf = uniffi::RustBuffer::from_vec(#buf_ident);
+ let lift_return = sig.lift_return_impl();
- #internals_ident.invoke_callback::<#return_ty, crate::UniFfiTag>(self.handle, #index, uniffi_args_rbuf)
- }
- })
+ if !is_async {
+ Ok(quote! {
+ fn #ident(#self_param, #(#params),*) -> #return_ty {
+ let vtable = #vtable_cell.get();
+ let mut uniffi_call_status = ::uniffi::RustCallStatus::new();
+ let mut uniffi_return_value: #lift_return::ReturnType = ::uniffi::FfiDefault::ffi_default();
+ (vtable.#ident)(self.handle, #(#lower_exprs,)* &mut uniffi_return_value, &mut uniffi_call_status);
+ #lift_return::lift_foreign_return(uniffi_return_value, uniffi_call_status)
+ }
+ })
+ } else {
+ Ok(quote! {
+ async fn #ident(#self_param, #(#params),*) -> #return_ty {
+ let vtable = #vtable_cell.get();
+ ::uniffi::foreign_async_call::<_, #return_ty, crate::UniFfiTag>(move |uniffi_future_callback, uniffi_future_callback_data| {
+ let mut uniffi_foreign_future: ::uniffi::ForeignFuture = ::uniffi::FfiDefault::ffi_default();
+ (vtable.#ident)(self.handle, #(#lower_exprs,)* uniffi_future_callback, uniffi_future_callback_data, &mut uniffi_foreign_future);
+ uniffi_foreign_future
+ }).await
+ }
+ })
+ }
}
pub(super) fn metadata_items(
self_ident: &Ident,
items: &[ImplItem],
module_path: &str,
+ docstring: String,
) -> syn::Result<Vec<TokenStream>> {
let trait_name = ident_to_string(self_ident);
let callback_interface_items = create_metadata_items(
@@ -162,13 +249,14 @@ pub(super) fn metadata_items(
::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CALLBACK_INTERFACE)
.concat_str(#module_path)
.concat_str(#trait_name)
+ .concat_long_str(#docstring)
},
None,
);
iter::once(Ok(callback_interface_items))
.chain(items.iter().map(|item| match item {
- ImplItem::Method(sig) => sig.metadata_items(),
+ ImplItem::Method(sig) => sig.metadata_items_for_callback_interface(),
_ => unreachable!("traits have no constructors"),
}))
.collect()
diff --git a/third_party/rust/uniffi_macros/src/export/item.rs b/third_party/rust/uniffi_macros/src/export/item.rs
index 98c7d0ebe2..da3c9455c8 100644
--- a/third_party/rust/uniffi_macros/src/export/item.rs
+++ b/third_party/rust/uniffi_macros/src/export/item.rs
@@ -3,24 +3,34 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::fnsig::FnSignature;
+use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::ToTokens;
-use super::attributes::{ExportAttributeArguments, ExportedImplFnAttributes};
+use super::attributes::{
+ ExportFnArgs, ExportImplArgs, ExportStructArgs, ExportTraitArgs, ExportedImplFnArgs,
+ ExportedImplFnAttributes,
+};
+use crate::util::extract_docstring;
use uniffi_meta::UniffiTraitDiscriminants;
pub(super) enum ExportItem {
Function {
sig: FnSignature,
+ args: ExportFnArgs,
},
Impl {
self_ident: Ident,
items: Vec<ImplItem>,
+ args: ExportImplArgs,
},
Trait {
self_ident: Ident,
items: Vec<ImplItem>,
- callback_interface: bool,
+ with_foreign: bool,
+ callback_interface_only: bool,
+ docstring: String,
+ args: ExportTraitArgs,
},
Struct {
self_ident: Ident,
@@ -29,15 +39,17 @@ pub(super) enum ExportItem {
}
impl ExportItem {
- pub fn new(item: syn::Item, args: &ExportAttributeArguments) -> syn::Result<Self> {
+ pub fn new(item: syn::Item, attr_args: TokenStream) -> syn::Result<Self> {
match item {
syn::Item::Fn(item) => {
- let sig = FnSignature::new_function(item.sig)?;
- Ok(Self::Function { sig })
+ let args: ExportFnArgs = syn::parse(attr_args)?;
+ let docstring = extract_docstring(&item.attrs)?;
+ let sig = FnSignature::new_function(item.sig, args.clone(), docstring)?;
+ Ok(Self::Function { sig, args })
}
- syn::Item::Impl(item) => Self::from_impl(item, args.constructor.is_some()),
- syn::Item::Trait(item) => Self::from_trait(item, args.callback_interface.is_some()),
- syn::Item::Struct(item) => Self::from_struct(item, args),
+ syn::Item::Impl(item) => Self::from_impl(item, attr_args),
+ syn::Item::Trait(item) => Self::from_trait(item, attr_args),
+ syn::Item::Struct(item) => Self::from_struct(item, attr_args),
// FIXME: Support const / static?
_ => Err(syn::Error::new(
Span::call_site(),
@@ -47,7 +59,8 @@ impl ExportItem {
}
}
- pub fn from_impl(item: syn::ItemImpl, force_constructor: bool) -> syn::Result<Self> {
+ pub fn from_impl(item: syn::ItemImpl, attr_args: TokenStream) -> syn::Result<Self> {
+ let args: ExportImplArgs = syn::parse(attr_args)?;
if !item.generics.params.is_empty() || item.generics.where_clause.is_some() {
return Err(syn::Error::new_spanned(
&item.generics,
@@ -88,14 +101,22 @@ impl ExportItem {
}
};
+ let docstring = extract_docstring(&impl_fn.attrs)?;
let attrs = ExportedImplFnAttributes::new(&impl_fn.attrs)?;
- let item = if force_constructor || attrs.constructor {
+ let item = if attrs.constructor {
ImplItem::Constructor(FnSignature::new_constructor(
self_ident.clone(),
impl_fn.sig,
+ attrs.args,
+ docstring,
)?)
} else {
- ImplItem::Method(FnSignature::new_method(self_ident.clone(), impl_fn.sig)?)
+ ImplItem::Method(FnSignature::new_method(
+ self_ident.clone(),
+ impl_fn.sig,
+ attrs.args,
+ docstring,
+ )?)
};
Ok(item)
@@ -105,10 +126,15 @@ impl ExportItem {
Ok(Self::Impl {
items,
self_ident: self_ident.to_owned(),
+ args,
})
}
- fn from_trait(item: syn::ItemTrait, callback_interface: bool) -> syn::Result<Self> {
+ fn from_trait(item: syn::ItemTrait, attr_args: TokenStream) -> syn::Result<Self> {
+ let args: ExportTraitArgs = syn::parse(attr_args)?;
+ let with_foreign = args.callback_interface.is_some() || args.with_foreign.is_some();
+ let callback_interface_only = args.callback_interface.is_some();
+
if !item.generics.params.is_empty() || item.generics.where_clause.is_some() {
return Err(syn::Error::new_spanned(
&item.generics,
@@ -117,6 +143,7 @@ impl ExportItem {
}
let self_ident = item.ident.to_owned();
+ let docstring = extract_docstring(&item.attrs)?;
let items = item
.items
.into_iter()
@@ -132,6 +159,7 @@ impl ExportItem {
}
};
+ let docstring = extract_docstring(&tim.attrs)?;
let attrs = ExportedImplFnAttributes::new(&tim.attrs)?;
let item = if attrs.constructor {
return Err(syn::Error::new_spanned(
@@ -142,7 +170,9 @@ impl ExportItem {
ImplItem::Method(FnSignature::new_trait_method(
self_ident.clone(),
tim.sig,
+ ExportedImplFnArgs::default(),
i as u32,
+ docstring,
)?)
};
@@ -153,28 +183,26 @@ impl ExportItem {
Ok(Self::Trait {
items,
self_ident,
- callback_interface,
+ with_foreign,
+ callback_interface_only,
+ docstring,
+ args,
})
}
- fn from_struct(item: syn::ItemStruct, args: &ExportAttributeArguments) -> syn::Result<Self> {
- let mut uniffi_traits = Vec::new();
- if args.trait_debug.is_some() {
- uniffi_traits.push(UniffiTraitDiscriminants::Debug);
- }
- if args.trait_display.is_some() {
- uniffi_traits.push(UniffiTraitDiscriminants::Display);
- }
- if args.trait_hash.is_some() {
- uniffi_traits.push(UniffiTraitDiscriminants::Hash);
- }
- if args.trait_eq.is_some() {
- uniffi_traits.push(UniffiTraitDiscriminants::Eq);
+ fn from_struct(item: syn::ItemStruct, attr_args: TokenStream) -> syn::Result<Self> {
+ let args: ExportStructArgs = syn::parse(attr_args)?;
+ let uniffi_traits: Vec<UniffiTraitDiscriminants> = args.traits.into_iter().collect();
+ if uniffi_traits.is_empty() {
+ Err(syn::Error::new(Span::call_site(),
+ "uniffi::export on a struct must supply a builtin trait name. Did you mean `#[derive(uniffi::Object)]`?"
+ ))
+ } else {
+ Ok(Self::Struct {
+ self_ident: item.ident,
+ uniffi_traits,
+ })
}
- Ok(Self::Struct {
- self_ident: item.ident,
- uniffi_traits,
- })
}
}
diff --git a/third_party/rust/uniffi_macros/src/export/scaffolding.rs b/third_party/rust/uniffi_macros/src/export/scaffolding.rs
index f120ccc880..fa7b61deca 100644
--- a/third_party/rust/uniffi_macros/src/export/scaffolding.rs
+++ b/third_party/rust/uniffi_macros/src/export/scaffolding.rs
@@ -6,12 +6,12 @@ use proc_macro2::{Ident, TokenStream};
use quote::quote;
use std::iter;
-use super::attributes::{AsyncRuntime, ExportAttributeArguments};
-use crate::fnsig::{FnKind, FnSignature, NamedArg};
+use super::attributes::AsyncRuntime;
+use crate::fnsig::{FnKind, FnSignature};
pub(super) fn gen_fn_scaffolding(
sig: FnSignature,
- arguments: &ExportAttributeArguments,
+ ar: &Option<AsyncRuntime>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
if sig.receiver.is_some() {
@@ -21,7 +21,7 @@ pub(super) fn gen_fn_scaffolding(
));
}
if !sig.is_async {
- if let Some(async_runtime) = &arguments.async_runtime {
+ if let Some(async_runtime) = ar {
return Err(syn::Error::new_spanned(
async_runtime,
"this attribute is only allowed on async functions",
@@ -32,7 +32,7 @@ pub(super) fn gen_fn_scaffolding(
sig.metadata_items()
.unwrap_or_else(syn::Error::into_compile_error)
});
- let scaffolding_func = gen_ffi_function(&sig, arguments, udl_mode)?;
+ let scaffolding_func = gen_ffi_function(&sig, ar, udl_mode)?;
Ok(quote! {
#scaffolding_func
#metadata_items
@@ -41,7 +41,7 @@ pub(super) fn gen_fn_scaffolding(
pub(super) fn gen_constructor_scaffolding(
sig: FnSignature,
- arguments: &ExportAttributeArguments,
+ ar: &Option<AsyncRuntime>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
if sig.receiver.is_some() {
@@ -50,14 +50,11 @@ pub(super) fn gen_constructor_scaffolding(
"constructors must not have a self parameter",
));
}
- if sig.is_async {
- return Err(syn::Error::new(sig.span, "constructors can't be async"));
- }
let metadata_items = (!udl_mode).then(|| {
sig.metadata_items()
.unwrap_or_else(syn::Error::into_compile_error)
});
- let scaffolding_func = gen_ffi_function(&sig, arguments, udl_mode)?;
+ let scaffolding_func = gen_ffi_function(&sig, ar, udl_mode)?;
Ok(quote! {
#scaffolding_func
#metadata_items
@@ -66,7 +63,7 @@ pub(super) fn gen_constructor_scaffolding(
pub(super) fn gen_method_scaffolding(
sig: FnSignature,
- arguments: &ExportAttributeArguments,
+ ar: &Option<AsyncRuntime>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
let scaffolding_func = if sig.receiver.is_none() {
@@ -75,7 +72,7 @@ pub(super) fn gen_method_scaffolding(
"associated functions are not currently supported",
));
} else {
- gen_ffi_function(&sig, arguments, udl_mode)?
+ gen_ffi_function(&sig, ar, udl_mode)?
};
let metadata_items = (!udl_mode).then(|| {
@@ -90,31 +87,37 @@ pub(super) fn gen_method_scaffolding(
// Pieces of code for the scaffolding function
struct ScaffoldingBits {
- /// Parameters for the scaffolding function
- params: Vec<TokenStream>,
+ /// Parameter names for the scaffolding function
+ param_names: Vec<TokenStream>,
+ /// Parameter types for the scaffolding function
+ param_types: Vec<TokenStream>,
/// Lift closure. See `FnSignature::lift_closure` for an explanation of this.
lift_closure: TokenStream,
/// Expression to call the Rust function after a successful lift.
rust_fn_call: TokenStream,
+ /// Convert the result of `rust_fn_call`, stored in a variable named `uniffi_result` into its final value.
+ /// This is used to do things like error conversion / Arc wrapping
+ convert_result: TokenStream,
}
impl ScaffoldingBits {
fn new_for_function(sig: &FnSignature, udl_mode: bool) -> Self {
let ident = &sig.ident;
- let params: Vec<_> = sig.args.iter().map(NamedArg::scaffolding_param).collect();
let call_params = sig.rust_call_params(false);
let rust_fn_call = quote! { #ident(#call_params) };
// UDL mode adds an extra conversion (#1749)
- let rust_fn_call = if udl_mode && sig.looks_like_result {
- quote! { #rust_fn_call.map_err(::std::convert::Into::into) }
+ let convert_result = if udl_mode && sig.looks_like_result {
+ quote! { uniffi_result.map_err(::std::convert::Into::into) }
} else {
- rust_fn_call
+ quote! { uniffi_result }
};
Self {
- params,
+ param_names: sig.scaffolding_param_names().collect(),
+ param_types: sig.scaffolding_param_types().collect(),
lift_closure: sig.lift_closure(None),
rust_fn_call,
+ convert_result,
}
}
@@ -125,20 +128,32 @@ impl ScaffoldingBits {
udl_mode: bool,
) -> Self {
let ident = &sig.ident;
- let ffi_converter = if is_trait {
+ let lift_impl = if is_trait {
quote! {
- <::std::sync::Arc<dyn #self_ident> as ::uniffi::FfiConverter<crate::UniFfiTag>>
+ <::std::sync::Arc<dyn #self_ident> as ::uniffi::Lift<crate::UniFfiTag>>
}
} else {
quote! {
- <::std::sync::Arc<#self_ident> as ::uniffi::FfiConverter<crate::UniFfiTag>>
+ <::std::sync::Arc<#self_ident> as ::uniffi::Lift<crate::UniFfiTag>>
}
};
- let params: Vec<_> = iter::once(quote! { uniffi_self_lowered: #ffi_converter::FfiType })
- .chain(sig.scaffolding_params())
- .collect();
+ let try_lift_self = if is_trait {
+ // For trait interfaces we need to special case this. Trait interfaces normally lift
+ // foreign trait impl pointers. However, for a method call, we want to lift a Rust
+ // pointer.
+ quote! {
+ {
+ let boxed_foreign_arc = unsafe { Box::from_raw(uniffi_self_lowered as *mut ::std::sync::Arc<dyn #self_ident>) };
+ // Take a clone for our own use.
+ Ok(*boxed_foreign_arc)
+ }
+ }
+ } else {
+ quote! { #lift_impl::try_lift(uniffi_self_lowered) }
+ };
+
let lift_closure = sig.lift_closure(Some(quote! {
- match #ffi_converter::try_lift(uniffi_self_lowered) {
+ match #try_lift_self {
Ok(v) => v,
Err(e) => return Err(("self", e))
}
@@ -146,38 +161,45 @@ impl ScaffoldingBits {
let call_params = sig.rust_call_params(true);
let rust_fn_call = quote! { uniffi_args.0.#ident(#call_params) };
// UDL mode adds an extra conversion (#1749)
- let rust_fn_call = if udl_mode && sig.looks_like_result {
- quote! { #rust_fn_call.map_err(::std::convert::Into::into) }
+ let convert_result = if udl_mode && sig.looks_like_result {
+ quote! { uniffi_result .map_err(::std::convert::Into::into) }
} else {
- rust_fn_call
+ quote! { uniffi_result }
};
Self {
- params,
+ param_names: iter::once(quote! { uniffi_self_lowered })
+ .chain(sig.scaffolding_param_names())
+ .collect(),
+ param_types: iter::once(quote! { #lift_impl::FfiType })
+ .chain(sig.scaffolding_param_types())
+ .collect(),
lift_closure,
rust_fn_call,
+ convert_result,
}
}
fn new_for_constructor(sig: &FnSignature, self_ident: &Ident, udl_mode: bool) -> Self {
let ident = &sig.ident;
- let params: Vec<_> = sig.args.iter().map(NamedArg::scaffolding_param).collect();
let call_params = sig.rust_call_params(false);
let rust_fn_call = quote! { #self_ident::#ident(#call_params) };
// UDL mode adds extra conversions (#1749)
- let rust_fn_call = match (udl_mode, sig.looks_like_result) {
+ let convert_result = match (udl_mode, sig.looks_like_result) {
// For UDL
- (true, false) => quote! { ::std::sync::Arc::new(#rust_fn_call) },
+ (true, false) => quote! { ::std::sync::Arc::new(uniffi_result) },
(true, true) => {
- quote! { #rust_fn_call.map(::std::sync::Arc::new).map_err(::std::convert::Into::into) }
+ quote! { uniffi_result.map(::std::sync::Arc::new).map_err(::std::convert::Into::into) }
}
- (false, _) => rust_fn_call,
+ (false, _) => quote! { uniffi_result },
};
Self {
- params,
+ param_names: sig.scaffolding_param_names().collect(),
+ param_types: sig.scaffolding_param_types().collect(),
lift_closure: sig.lift_closure(None),
rust_fn_call,
+ convert_result,
}
}
}
@@ -188,13 +210,15 @@ impl ScaffoldingBits {
/// `rust_fn` is the Rust function to call.
pub(super) fn gen_ffi_function(
sig: &FnSignature,
- arguments: &ExportAttributeArguments,
+ ar: &Option<AsyncRuntime>,
udl_mode: bool,
) -> syn::Result<TokenStream> {
let ScaffoldingBits {
- params,
+ param_names,
+ param_types,
lift_closure,
rust_fn_call,
+ convert_result,
} = match &sig.kind {
FnKind::Function => ScaffoldingBits::new_for_function(sig, udl_mode),
FnKind::Method { self_ident } => {
@@ -216,14 +240,15 @@ pub(super) fn gen_ffi_function(
let ffi_ident = sig.scaffolding_fn_ident()?;
let name = &sig.name;
- let return_impl = &sig.return_impl();
+ let return_ty = &sig.return_ty;
+ let return_impl = &sig.lower_return_impl();
Ok(if !sig.is_async {
quote! {
#[doc(hidden)]
#[no_mangle]
#vis extern "C" fn #ffi_ident(
- #(#params,)*
+ #(#param_names: #param_types,)*
call_status: &mut ::uniffi::RustCallStatus,
) -> #return_impl::ReturnType {
::uniffi::deps::log::debug!(#name);
@@ -231,7 +256,10 @@ pub(super) fn gen_ffi_function(
::uniffi::rust_call(call_status, || {
#return_impl::lower_return(
match uniffi_lift_args() {
- Ok(uniffi_args) => #rust_fn_call,
+ Ok(uniffi_args) => {
+ let uniffi_result = #rust_fn_call;
+ #convert_result
+ }
Err((arg_name, anyhow_error)) => {
#return_impl::handle_failed_lift(arg_name, anyhow_error)
},
@@ -242,25 +270,28 @@ pub(super) fn gen_ffi_function(
}
} else {
let mut future_expr = rust_fn_call;
- if matches!(arguments.async_runtime, Some(AsyncRuntime::Tokio(_))) {
+ if matches!(ar, Some(AsyncRuntime::Tokio(_))) {
future_expr = quote! { ::uniffi::deps::async_compat::Compat::new(#future_expr) }
}
quote! {
#[doc(hidden)]
#[no_mangle]
- pub extern "C" fn #ffi_ident(#(#params,)*) -> ::uniffi::RustFutureHandle {
+ pub extern "C" fn #ffi_ident(#(#param_names: #param_types,)*) -> ::uniffi::Handle {
::uniffi::deps::log::debug!(#name);
let uniffi_lift_args = #lift_closure;
match uniffi_lift_args() {
Ok(uniffi_args) => {
- ::uniffi::rust_future_new(
- async move { #future_expr.await },
+ ::uniffi::rust_future_new::<_, #return_ty, _>(
+ async move {
+ let uniffi_result = #future_expr.await;
+ #convert_result
+ },
crate::UniFfiTag
)
},
Err((arg_name, anyhow_error)) => {
- ::uniffi::rust_future_new(
+ ::uniffi::rust_future_new::<_, #return_ty, _>(
async move {
#return_impl::handle_failed_lift(arg_name, anyhow_error)
},
diff --git a/third_party/rust/uniffi_macros/src/export/trait_interface.rs b/third_party/rust/uniffi_macros/src/export/trait_interface.rs
new file mode 100644
index 0000000000..83587ae386
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/trait_interface.rs
@@ -0,0 +1,183 @@
+/* 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/. */
+
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::{quote, quote_spanned};
+
+use uniffi_meta::ObjectImpl;
+
+use crate::{
+ export::{
+ attributes::ExportTraitArgs, callback_interface, gen_method_scaffolding, item::ImplItem,
+ },
+ object::interface_meta_static_var,
+ util::{ident_to_string, tagged_impl_header},
+};
+
+pub(super) fn gen_trait_scaffolding(
+ mod_path: &str,
+ args: ExportTraitArgs,
+ self_ident: Ident,
+ items: Vec<ImplItem>,
+ udl_mode: bool,
+ with_foreign: bool,
+ docstring: String,
+) -> syn::Result<TokenStream> {
+ if let Some(rt) = args.async_runtime {
+ return Err(syn::Error::new_spanned(rt, "not supported for traits"));
+ }
+ let trait_name = ident_to_string(&self_ident);
+ let trait_impl = with_foreign.then(|| {
+ callback_interface::trait_impl(mod_path, &self_ident, &items)
+ .unwrap_or_else(|e| e.into_compile_error())
+ });
+
+ let clone_fn_ident = Ident::new(
+ &uniffi_meta::clone_fn_symbol_name(mod_path, &trait_name),
+ Span::call_site(),
+ );
+ let free_fn_ident = Ident::new(
+ &uniffi_meta::free_fn_symbol_name(mod_path, &trait_name),
+ Span::call_site(),
+ );
+
+ let helper_fn_tokens = quote! {
+ #[doc(hidden)]
+ #[no_mangle]
+ /// Clone a pointer to this object type
+ ///
+ /// Safety: Only pass pointers returned by a UniFFI call. Do not pass pointers that were
+ /// passed to the free function.
+ pub unsafe extern "C" fn #clone_fn_ident(
+ ptr: *const ::std::ffi::c_void,
+ call_status: &mut ::uniffi::RustCallStatus
+ ) -> *const ::std::ffi::c_void {
+ uniffi::rust_call(call_status, || {
+ let ptr = ptr as *mut std::sync::Arc<dyn #self_ident>;
+ let arc = unsafe { ::std::sync::Arc::clone(&*ptr) };
+ Ok(::std::boxed::Box::into_raw(::std::boxed::Box::new(arc)) as *const ::std::ffi::c_void)
+ })
+ }
+
+ #[doc(hidden)]
+ #[no_mangle]
+ /// Free a pointer to this object type
+ ///
+ /// Safety: Only pass pointers returned by a UniFFI call. Do not pass pointers that were
+ /// passed to the free function.
+ ///
+ /// Note: clippy doesn't complain about this being unsafe, but it definitely is since it
+ /// calls `Box::from_raw`.
+ pub unsafe extern "C" fn #free_fn_ident(
+ ptr: *const ::std::ffi::c_void,
+ call_status: &mut ::uniffi::RustCallStatus
+ ) {
+ uniffi::rust_call(call_status, || {
+ assert!(!ptr.is_null());
+ drop(unsafe { ::std::boxed::Box::from_raw(ptr as *mut std::sync::Arc<dyn #self_ident>) });
+ Ok(())
+ });
+ }
+ };
+
+ let impl_tokens: TokenStream = items
+ .into_iter()
+ .map(|item| match item {
+ ImplItem::Method(sig) => gen_method_scaffolding(sig, &None, udl_mode),
+ _ => unreachable!("traits have no constructors"),
+ })
+ .collect::<syn::Result<_>>()?;
+
+ let meta_static_var = (!udl_mode).then(|| {
+ let imp = if with_foreign {
+ ObjectImpl::CallbackTrait
+ } else {
+ ObjectImpl::Trait
+ };
+ interface_meta_static_var(&self_ident, imp, mod_path, docstring)
+ .unwrap_or_else(syn::Error::into_compile_error)
+ });
+ let ffi_converter_tokens = ffi_converter(mod_path, &self_ident, udl_mode, with_foreign);
+
+ Ok(quote_spanned! { self_ident.span() =>
+ #meta_static_var
+ #helper_fn_tokens
+ #trait_impl
+ #impl_tokens
+ #ffi_converter_tokens
+ })
+}
+
+pub(crate) fn ffi_converter(
+ mod_path: &str,
+ trait_ident: &Ident,
+ udl_mode: bool,
+ with_foreign: bool,
+) -> TokenStream {
+ let impl_spec = tagged_impl_header("FfiConverterArc", &quote! { dyn #trait_ident }, udl_mode);
+ let lift_ref_impl_spec = tagged_impl_header("LiftRef", &quote! { dyn #trait_ident }, udl_mode);
+ let trait_name = ident_to_string(trait_ident);
+ let try_lift = if with_foreign {
+ let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name);
+ quote! {
+ fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc<Self>> {
+ Ok(::std::sync::Arc::new(<#trait_impl_ident>::new(v as u64)))
+ }
+ }
+ } else {
+ quote! {
+ fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc<Self>> {
+ unsafe {
+ Ok(*::std::boxed::Box::from_raw(v as *mut ::std::sync::Arc<Self>))
+ }
+ }
+ }
+ };
+ let metadata_code = if with_foreign {
+ quote! { ::uniffi::metadata::codes::TYPE_CALLBACK_TRAIT_INTERFACE }
+ } else {
+ quote! { ::uniffi::metadata::codes::TYPE_TRAIT_INTERFACE }
+ };
+
+ quote! {
+ // All traits must be `Sync + Send`. The generated scaffolding will fail to compile
+ // if they are not, but unfortunately it fails with an unactionably obscure error message.
+ // By asserting the requirement explicitly, we help Rust produce a more scrutable error message
+ // and thus help the user debug why the requirement isn't being met.
+ uniffi::deps::static_assertions::assert_impl_all!(dyn #trait_ident: ::core::marker::Sync, ::core::marker::Send);
+
+ unsafe #impl_spec {
+ type FfiType = *const ::std::os::raw::c_void;
+
+ fn lower(obj: ::std::sync::Arc<Self>) -> Self::FfiType {
+ ::std::boxed::Box::into_raw(::std::boxed::Box::new(obj)) as *const ::std::os::raw::c_void
+ }
+
+ #try_lift
+
+ fn write(obj: ::std::sync::Arc<Self>, buf: &mut Vec<u8>) {
+ ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8);
+ ::uniffi::deps::bytes::BufMut::put_u64(
+ buf,
+ <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(obj) as u64,
+ );
+ }
+
+ fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc<Self>> {
+ ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8);
+ ::uniffi::check_remaining(buf, 8)?;
+ <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::try_lift(
+ ::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType)
+ }
+
+ const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(#metadata_code)
+ .concat_str(#mod_path)
+ .concat_str(#trait_name);
+ }
+
+ unsafe #lift_ref_impl_spec {
+ type LiftType = ::std::sync::Arc<dyn #trait_ident>;
+ }
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/export/utrait.rs b/third_party/rust/uniffi_macros/src/export/utrait.rs
index 3db09ea2b7..9007ae2c56 100644
--- a/third_party/rust/uniffi_macros/src/export/utrait.rs
+++ b/third_party/rust/uniffi_macros/src/export/utrait.rs
@@ -6,8 +6,10 @@ use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::ext::IdentExt;
-use super::{attributes::ExportAttributeArguments, gen_ffi_function};
+use super::gen_ffi_function;
+use crate::export::ExportedImplFnArgs;
use crate::fnsig::FnSignature;
+use crate::util::extract_docstring;
use uniffi_meta::UniffiTraitDiscriminants;
pub(crate) fn expand_uniffi_trait_export(
@@ -157,12 +159,25 @@ fn process_uniffi_trait_method(
unreachable!()
};
+ let docstring = extract_docstring(&item.attrs)?;
+
let ffi_func = gen_ffi_function(
- &FnSignature::new_method(self_ident.clone(), item.sig.clone())?,
- &ExportAttributeArguments::default(),
+ &FnSignature::new_method(
+ self_ident.clone(),
+ item.sig.clone(),
+ ExportedImplFnArgs::default(),
+ docstring.clone(),
+ )?,
+ &None,
udl_mode,
)?;
// metadata for the method, which will be packed inside metadata for the trait.
- let method_meta = FnSignature::new_method(self_ident.clone(), item.sig)?.metadata_expr()?;
+ let method_meta = FnSignature::new_method(
+ self_ident.clone(),
+ item.sig,
+ ExportedImplFnArgs::default(),
+ docstring,
+ )?
+ .metadata_expr()?;
Ok((ffi_func, method_meta))
}
diff --git a/third_party/rust/uniffi_macros/src/fnsig.rs b/third_party/rust/uniffi_macros/src/fnsig.rs
index 69b6529d1e..9c59125207 100644
--- a/third_party/rust/uniffi_macros/src/fnsig.rs
+++ b/third_party/rust/uniffi_macros/src/fnsig.rs
@@ -2,8 +2,10 @@
* 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/. */
-use crate::util::{
- create_metadata_items, ident_to_string, mod_path, try_metadata_value_from_usize,
+use crate::{
+ default::{default_value_metadata_calls, DefaultValue},
+ export::{DefaultMap, ExportFnArgs, ExportedImplFnArgs},
+ util::{create_metadata_items, ident_to_string, mod_path, try_metadata_value_from_usize},
};
use proc_macro2::{Span, TokenStream};
use quote::quote;
@@ -13,7 +15,9 @@ pub(crate) struct FnSignature {
pub kind: FnKind,
pub span: Span,
pub mod_path: String,
+ // The identifier of the Rust function.
pub ident: Ident,
+ // The foreign name for this function, usually == ident.
pub name: String,
pub is_async: bool,
pub receiver: Option<ReceiverArg>,
@@ -23,30 +27,71 @@ pub(crate) struct FnSignature {
// Only use this in UDL mode.
// In general, it's not reliable because it fails for type aliases.
pub looks_like_result: bool,
+ pub docstring: String,
}
impl FnSignature {
- pub(crate) fn new_function(sig: syn::Signature) -> syn::Result<Self> {
- Self::new(FnKind::Function, sig)
+ pub(crate) fn new_function(
+ sig: syn::Signature,
+ args: ExportFnArgs,
+ docstring: String,
+ ) -> syn::Result<Self> {
+ Self::new(FnKind::Function, sig, args.name, args.defaults, docstring)
}
- pub(crate) fn new_method(self_ident: Ident, sig: syn::Signature) -> syn::Result<Self> {
- Self::new(FnKind::Method { self_ident }, sig)
+ pub(crate) fn new_method(
+ self_ident: Ident,
+ sig: syn::Signature,
+ args: ExportedImplFnArgs,
+ docstring: String,
+ ) -> syn::Result<Self> {
+ Self::new(
+ FnKind::Method { self_ident },
+ sig,
+ args.name,
+ args.defaults,
+ docstring,
+ )
}
- pub(crate) fn new_constructor(self_ident: Ident, sig: syn::Signature) -> syn::Result<Self> {
- Self::new(FnKind::Constructor { self_ident }, sig)
+ pub(crate) fn new_constructor(
+ self_ident: Ident,
+ sig: syn::Signature,
+ args: ExportedImplFnArgs,
+ docstring: String,
+ ) -> syn::Result<Self> {
+ Self::new(
+ FnKind::Constructor { self_ident },
+ sig,
+ args.name,
+ args.defaults,
+ docstring,
+ )
}
pub(crate) fn new_trait_method(
self_ident: Ident,
sig: syn::Signature,
+ args: ExportedImplFnArgs,
index: u32,
+ docstring: String,
) -> syn::Result<Self> {
- Self::new(FnKind::TraitMethod { self_ident, index }, sig)
+ Self::new(
+ FnKind::TraitMethod { self_ident, index },
+ sig,
+ args.name,
+ args.defaults,
+ docstring,
+ )
}
- pub(crate) fn new(kind: FnKind, sig: syn::Signature) -> syn::Result<Self> {
+ pub(crate) fn new(
+ kind: FnKind,
+ sig: syn::Signature,
+ name: Option<String>,
+ mut defaults: DefaultMap,
+ docstring: String,
+ ) -> syn::Result<Self> {
let span = sig.span();
let ident = sig.ident;
let looks_like_result = looks_like_result(&sig.output);
@@ -56,14 +101,11 @@ impl FnSignature {
};
let is_async = sig.asyncness.is_some();
- if is_async && matches!(kind, FnKind::Constructor { .. }) {
- return Err(syn::Error::new(
- span,
- "Async constructors are not supported",
- ));
- }
-
- let mut input_iter = sig.inputs.into_iter().map(Arg::try_from).peekable();
+ let mut input_iter = sig
+ .inputs
+ .into_iter()
+ .map(|a| Arg::new(a, &mut defaults))
+ .peekable();
let receiver = input_iter
.next_if(|a| matches!(a, Ok(a) if a.is_receiver()))
@@ -84,29 +126,43 @@ impl FnSignature {
})
})
.collect::<syn::Result<Vec<_>>>()?;
- let mod_path = mod_path()?;
+
+ if let Some(ident) = defaults.idents().first() {
+ return Err(syn::Error::new(
+ ident.span(),
+ format!("Unknown default argument: {}", ident),
+ ));
+ }
Ok(Self {
kind,
span,
- mod_path,
- name: ident_to_string(&ident),
+ mod_path: mod_path()?,
+ name: name.unwrap_or_else(|| ident_to_string(&ident)),
ident,
is_async,
receiver,
args,
return_ty: output,
looks_like_result,
+ docstring,
})
}
- pub fn return_impl(&self) -> TokenStream {
+ pub fn lower_return_impl(&self) -> TokenStream {
let return_ty = &self.return_ty;
quote! {
<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>
}
}
+ pub fn lift_return_impl(&self) -> TokenStream {
+ let return_ty = &self.return_ty;
+ quote! {
+ <#return_ty as ::uniffi::LiftReturn<crate::UniFfiTag>>
+ }
+ }
+
/// Generate a closure that tries to lift all arguments into a tuple.
///
/// The closure moves all scaffolding arguments into itself and returns:
@@ -151,14 +207,6 @@ impl FnSignature {
quote! { #(#args),* }
}
- /// Write expressions for each of our arguments
- pub fn write_exprs<'a>(
- &'a self,
- buf_ident: &'a Ident,
- ) -> impl Iterator<Item = TokenStream> + 'a {
- self.args.iter().map(|a| a.write_expr(buf_ident))
- }
-
/// Parameters expressions for each of our arguments
pub fn params(&self) -> impl Iterator<Item = TokenStream> + '_ {
self.args.iter().map(NamedArg::param)
@@ -182,8 +230,18 @@ impl FnSignature {
}
/// Scaffolding parameters expressions for each of our arguments
- pub fn scaffolding_params(&self) -> impl Iterator<Item = TokenStream> + '_ {
- self.args.iter().map(NamedArg::scaffolding_param)
+ pub fn scaffolding_param_names(&self) -> impl Iterator<Item = TokenStream> + '_ {
+ self.args.iter().map(|a| {
+ let ident = &a.ident;
+ quote! { #ident }
+ })
+ }
+
+ pub fn scaffolding_param_types(&self) -> impl Iterator<Item = TokenStream> + '_ {
+ self.args.iter().map(|a| {
+ let lift_impl = a.lift_impl();
+ quote! { #lift_impl::FfiType }
+ })
}
/// Generate metadata items for this function
@@ -193,6 +251,7 @@ impl FnSignature {
return_ty,
is_async,
mod_path,
+ docstring,
..
} = &self;
let args_len = try_metadata_value_from_usize(
@@ -201,7 +260,11 @@ impl FnSignature {
self.args.len(),
"UniFFI limits functions to 256 arguments",
)?;
- let arg_metadata_calls = self.args.iter().map(NamedArg::arg_metadata);
+ let arg_metadata_calls = self
+ .args
+ .iter()
+ .map(NamedArg::arg_metadata)
+ .collect::<syn::Result<Vec<_>>>()?;
match &self.kind {
FnKind::Function => Ok(quote! {
@@ -212,6 +275,7 @@ impl FnSignature {
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META)
+ .concat_long_str(#docstring)
}),
FnKind::Method { self_ident } => {
@@ -225,6 +289,7 @@ impl FnSignature {
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META)
+ .concat_long_str(#docstring)
})
}
@@ -240,6 +305,7 @@ impl FnSignature {
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META)
+ .concat_long_str(#docstring)
})
}
@@ -250,9 +316,11 @@ impl FnSignature {
.concat_str(#mod_path)
.concat_str(#object_name)
.concat_str(#name)
+ .concat_bool(#is_async)
.concat_value(#args_len)
#(#arg_metadata_calls)*
.concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META)
+ .concat_long_str(#docstring)
})
}
}
@@ -300,6 +368,69 @@ impl FnSignature {
}
}
+ /// Generate metadata items for callback interfaces
+ ///
+ /// Unfortunately, most of this is duplicate code from [Self::metadata_items] and
+ /// [Self::metadata_expr]. However, one issue with that code is that it needs to assume if the
+ /// arguments are being lifted vs lowered in order to get TYPE_ID_META. That code uses
+ /// `<Type as Lift>::TYPE_ID_META` for arguments and `<Type as LowerReturn>::TYPE_ID_META` for
+ /// return types, which works for accidental/historical reasons.
+ ///
+ /// The one exception is callback interfaces (#1947), which are handled by this method.
+ ///
+ /// TODO: fix the metadata system so that this is not needed.
+ pub(crate) fn metadata_items_for_callback_interface(&self) -> syn::Result<TokenStream> {
+ let Self {
+ name,
+ return_ty,
+ is_async,
+ mod_path,
+ docstring,
+ ..
+ } = &self;
+ match &self.kind {
+ FnKind::TraitMethod {
+ self_ident, index, ..
+ } => {
+ let object_name = ident_to_string(self_ident);
+ let args_len = try_metadata_value_from_usize(
+ // Use param_lifts to calculate this instead of sig.inputs to avoid counting any self
+ // params
+ self.args.len(),
+ "UniFFI limits functions to 256 arguments",
+ )?;
+ let arg_metadata_calls = self
+ .args
+ .iter()
+ .map(NamedArg::arg_metadata)
+ .collect::<syn::Result<Vec<_>>>()?;
+ let metadata_expr = quote! {
+ ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TRAIT_METHOD)
+ .concat_str(#mod_path)
+ .concat_str(#object_name)
+ .concat_u32(#index)
+ .concat_str(#name)
+ .concat_bool(#is_async)
+ .concat_value(#args_len)
+ #(#arg_metadata_calls)*
+ .concat(<#return_ty as ::uniffi::LiftReturn<crate::UniFfiTag>>::TYPE_ID_META)
+ .concat_long_str(#docstring)
+ };
+ Ok(create_metadata_items(
+ "method",
+ &format!("{object_name}_{name}"),
+ metadata_expr,
+ Some(self.checksum_symbol_name()),
+ ))
+ }
+
+ // This should never happen and indicates an error in the internal code
+ _ => panic!(
+ "metadata_items_for_callback_interface can only be called with `TraitMethod` sigs"
+ ),
+ }
+ }
+
pub(crate) fn checksum_symbol_name(&self) -> String {
let name = &self.name;
match &self.kind {
@@ -331,19 +462,11 @@ pub(crate) enum ArgKind {
}
impl Arg {
- pub(crate) fn is_receiver(&self) -> bool {
- matches!(self.kind, ArgKind::Receiver(_))
- }
-}
-
-impl TryFrom<FnArg> for Arg {
- type Error = syn::Error;
-
- fn try_from(syn_arg: FnArg) -> syn::Result<Self> {
+ fn new(syn_arg: FnArg, defaults: &mut DefaultMap) -> syn::Result<Self> {
let span = syn_arg.span();
let kind = match syn_arg {
FnArg::Typed(p) => match *p.pat {
- Pat::Ident(i) => Ok(ArgKind::Named(NamedArg::new(i.ident, &p.ty))),
+ Pat::Ident(i) => Ok(ArgKind::Named(NamedArg::new(i.ident, &p.ty, defaults)?)),
_ => Err(syn::Error::new_spanned(p, "Argument name missing")),
},
FnArg::Receiver(receiver) => Ok(ArgKind::Receiver(ReceiverArg::from(receiver))),
@@ -351,6 +474,10 @@ impl TryFrom<FnArg> for Arg {
Ok(Self { span, kind })
}
+
+ pub(crate) fn is_receiver(&self) -> bool {
+ matches!(self.kind, ArgKind::Receiver(_))
+ }
}
pub(crate) enum ReceiverArg {
@@ -379,27 +506,30 @@ pub(crate) struct NamedArg {
pub(crate) name: String,
pub(crate) ty: TokenStream,
pub(crate) ref_type: Option<Type>,
+ pub(crate) default: Option<DefaultValue>,
}
impl NamedArg {
- pub(crate) fn new(ident: Ident, ty: &Type) -> Self {
- match ty {
+ pub(crate) fn new(ident: Ident, ty: &Type, defaults: &mut DefaultMap) -> syn::Result<Self> {
+ Ok(match ty {
Type::Reference(r) => {
let inner = &r.elem;
Self {
name: ident_to_string(&ident),
- ident,
ty: quote! { <#inner as ::uniffi::LiftRef<crate::UniFfiTag>>::LiftType },
ref_type: Some(*inner.clone()),
+ default: defaults.remove(&ident),
+ ident,
}
}
_ => Self {
name: ident_to_string(&ident),
- ident,
ty: quote! { #ty },
ref_type: None,
+ default: defaults.remove(&ident),
+ ident,
},
- }
+ })
}
pub(crate) fn lift_impl(&self) -> TokenStream {
@@ -419,27 +549,15 @@ impl NamedArg {
quote! { #ident: #ty }
}
- /// Generate the scaffolding parameter for this Arg
- pub(crate) fn scaffolding_param(&self) -> TokenStream {
- let ident = &self.ident;
- let lift_impl = self.lift_impl();
- quote! { #ident: #lift_impl::FfiType }
- }
-
- /// Generate the expression to write the scaffolding parameter for this arg
- pub(crate) fn write_expr(&self, buf_ident: &Ident) -> TokenStream {
- let ident = &self.ident;
- let lower_impl = self.lower_impl();
- quote! { #lower_impl::write(#ident, &mut #buf_ident) }
- }
-
- pub(crate) fn arg_metadata(&self) -> TokenStream {
+ pub(crate) fn arg_metadata(&self) -> syn::Result<TokenStream> {
let name = &self.name;
let lift_impl = self.lift_impl();
- quote! {
+ let default_calls = default_value_metadata_calls(&self.default)?;
+ Ok(quote! {
.concat_str(#name)
.concat(#lift_impl::TYPE_ID_META)
- }
+ #default_calls
+ })
}
}
diff --git a/third_party/rust/uniffi_macros/src/lib.rs b/third_party/rust/uniffi_macros/src/lib.rs
index 4cffddfa0e..929400c885 100644
--- a/third_party/rust/uniffi_macros/src/lib.rs
+++ b/third_party/rust/uniffi_macros/src/lib.rs
@@ -5,10 +5,8 @@
#![warn(rust_2018_idioms, unused_qualifications)]
//! Macros for `uniffi`.
-//!
-//! Currently this is just for easily generating integration tests, but maybe
-//! we'll put some other code-annotation helper macros in here at some point.
+#[cfg(feature = "trybuild")]
use camino::Utf8Path;
use proc_macro::TokenStream;
use quote::quote;
@@ -18,6 +16,7 @@ use syn::{
};
mod custom;
+mod default;
mod enum_;
mod error;
mod export;
@@ -33,20 +32,6 @@ use self::{
record::expand_record,
};
-struct IdentPair {
- lhs: Ident,
- rhs: Ident,
-}
-
-impl Parse for IdentPair {
- fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
- let lhs = input.parse()?;
- input.parse::<Token![,]>()?;
- let rhs = input.parse()?;
- Ok(Self { lhs, rhs })
- }
-}
-
struct CustomTypeInfo {
ident: Ident,
builtin: Path,
@@ -107,9 +92,8 @@ fn do_export(attr_args: TokenStream, input: TokenStream, udl_mode: bool) -> Toke
let copied_input = (!udl_mode).then(|| proc_macro2::TokenStream::from(input.clone()));
let gen_output = || {
- let args = syn::parse(attr_args)?;
let item = syn::parse(input)?;
- expand_export(item, args, udl_mode)
+ expand_export(item, attr_args, udl_mode)
};
let output = gen_output().unwrap_or_else(syn::Error::into_compile_error);
@@ -129,7 +113,7 @@ pub fn derive_record(input: TokenStream) -> TokenStream {
#[proc_macro_derive(Enum)]
pub fn derive_enum(input: TokenStream) -> TokenStream {
- expand_enum(parse_macro_input!(input), false)
+ expand_enum(parse_macro_input!(input), None, false)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
@@ -225,10 +209,14 @@ pub fn derive_record_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenSt
#[doc(hidden)]
#[proc_macro_attribute]
-pub fn derive_enum_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream {
- expand_enum(syn::parse_macro_input!(input), true)
- .unwrap_or_else(syn::Error::into_compile_error)
- .into()
+pub fn derive_enum_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream {
+ expand_enum(
+ syn::parse_macro_input!(input),
+ Some(syn::parse_macro_input!(attrs)),
+ true,
+ )
+ .unwrap_or_else(syn::Error::into_compile_error)
+ .into()
}
#[doc(hidden)]
@@ -257,22 +245,6 @@ pub fn export_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream {
do_export(attrs, input, true)
}
-/// Generate various support elements, including the FfiConverter implementation,
-/// for a trait interface for the scaffolding code
-#[doc(hidden)]
-#[proc_macro]
-pub fn expand_trait_interface_support(tokens: TokenStream) -> TokenStream {
- export::ffi_converter_trait_impl(&syn::parse_macro_input!(tokens), true).into()
-}
-
-/// Generate the FfiConverter implementation for an trait interface for the scaffolding code
-#[doc(hidden)]
-#[proc_macro]
-pub fn scaffolding_ffi_converter_callback_interface(tokens: TokenStream) -> TokenStream {
- let input: IdentPair = syn::parse_macro_input!(tokens);
- export::ffi_converter_callback_interface_impl(&input.lhs, &input.rhs, true).into()
-}
-
/// A helper macro to include generated component scaffolding.
///
/// This is a simple convenience macro to include the UniFFI component
@@ -373,6 +345,7 @@ pub fn use_udl_object(tokens: TokenStream) -> TokenStream {
/// uniffi_macros::generate_and_include_scaffolding!("path/to/my/interface.udl");
/// ```
#[proc_macro]
+#[cfg(feature = "trybuild")]
pub fn generate_and_include_scaffolding(udl_file: TokenStream) -> TokenStream {
let udl_file = syn::parse_macro_input!(udl_file as LitStr);
let udl_file_string = udl_file.value();
@@ -396,15 +369,25 @@ pub fn generate_and_include_scaffolding(udl_file: TokenStream) -> TokenStream {
}.into()
}
-/// A dummy macro that does nothing.
+/// An attribute for constructors.
+///
+/// Constructors are in `impl` blocks which have a `#[uniffi::export]` attribute,
///
/// This exists so `#[uniffi::export]` can emit its input verbatim without
-/// causing unexpected errors, plus some extra code in case everything is okay.
+/// causing unexpected errors in the entire exported block.
+/// This happens very often when the proc-macro is run on an incomplete
+/// input by rust-analyzer while the developer is typing.
///
-/// It is important for `#[uniffi::export]` to not raise unexpected errors if it
-/// fails to parse the input as this happens very often when the proc-macro is
-/// run on an incomplete input by rust-analyzer while the developer is typing.
+/// So much better to do nothing here then let the impl block find the attribute.
#[proc_macro_attribute]
pub fn constructor(_attrs: TokenStream, input: TokenStream) -> TokenStream {
input
}
+
+/// An attribute for methods.
+///
+/// Everything above applies here too.
+#[proc_macro_attribute]
+pub fn method(_attrs: TokenStream, input: TokenStream) -> TokenStream {
+ input
+}
diff --git a/third_party/rust/uniffi_macros/src/object.rs b/third_party/rust/uniffi_macros/src/object.rs
index 573a1eaadd..6bcc07a14e 100644
--- a/third_party/rust/uniffi_macros/src/object.rs
+++ b/third_party/rust/uniffi_macros/src/object.rs
@@ -1,17 +1,27 @@
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::DeriveInput;
-use uniffi_meta::free_fn_symbol_name;
-use crate::util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header};
+use crate::util::{
+ create_metadata_items, extract_docstring, ident_to_string, mod_path, tagged_impl_header,
+};
+use uniffi_meta::ObjectImpl;
pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> {
let module_path = mod_path()?;
let ident = &input.ident;
+ let docstring = extract_docstring(&input.attrs)?;
let name = ident_to_string(ident);
- let free_fn_ident = Ident::new(&free_fn_symbol_name(&module_path, &name), Span::call_site());
+ let clone_fn_ident = Ident::new(
+ &uniffi_meta::clone_fn_symbol_name(&module_path, &name),
+ Span::call_site(),
+ );
+ let free_fn_ident = Ident::new(
+ &uniffi_meta::free_fn_symbol_name(&module_path, &name),
+ Span::call_site(),
+ );
let meta_static_var = (!udl_mode).then(|| {
- interface_meta_static_var(ident, false, &module_path)
+ interface_meta_static_var(ident, ObjectImpl::Struct, &module_path, docstring)
.unwrap_or_else(syn::Error::into_compile_error)
});
let interface_impl = interface_impl(ident, udl_mode);
@@ -19,7 +29,19 @@ pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStr
Ok(quote! {
#[doc(hidden)]
#[no_mangle]
- pub extern "C" fn #free_fn_ident(
+ pub unsafe extern "C" fn #clone_fn_ident(
+ ptr: *const ::std::ffi::c_void,
+ call_status: &mut ::uniffi::RustCallStatus
+ ) -> *const ::std::ffi::c_void {
+ uniffi::rust_call(call_status, || {
+ unsafe { ::std::sync::Arc::increment_strong_count(ptr) };
+ Ok(ptr)
+ })
+ }
+
+ #[doc(hidden)]
+ #[no_mangle]
+ pub unsafe extern "C" fn #free_fn_ident(
ptr: *const ::std::ffi::c_void,
call_status: &mut ::uniffi::RustCallStatus
) {
@@ -41,6 +63,7 @@ pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStr
pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream {
let name = ident_to_string(ident);
let impl_spec = tagged_impl_header("FfiConverterArc", ident, udl_mode);
+ let lower_return_impl_spec = tagged_impl_header("LowerReturn", ident, udl_mode);
let lift_ref_impl_spec = tagged_impl_header("LiftRef", ident, udl_mode);
let mod_path = match mod_path() {
Ok(p) => p,
@@ -52,7 +75,7 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream {
// if they are not, but unfortunately it fails with an unactionably obscure error message.
// By asserting the requirement explicitly, we help Rust produce a more scrutable error message
// and thus help the user debug why the requirement isn't being met.
- uniffi::deps::static_assertions::assert_impl_all!(#ident: Sync, Send);
+ uniffi::deps::static_assertions::assert_impl_all!(#ident: ::core::marker::Sync, ::core::marker::Send);
#[doc(hidden)]
#[automatically_derived]
@@ -78,17 +101,10 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream {
::std::sync::Arc::into_raw(obj) as Self::FfiType
}
- /// When lifting, we receive a "borrow" of the `Arc` that is owned by
- /// the foreign-language code, and make a clone of it for our own use.
- ///
- /// Safety: the provided value must be a pointer previously obtained by calling
- /// the `lower()` or `write()` method of this impl.
+ /// When lifting, we receive an owned `Arc` that the foreign language code cloned.
fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc<Self>> {
let v = v as *const #ident;
- // We musn't drop the `Arc` that is owned by the foreign-language code.
- let foreign_arc = ::std::mem::ManuallyDrop::new(unsafe { ::std::sync::Arc::<Self>::from_raw(v) });
- // Take a clone for our own use.
- Ok(::std::sync::Arc::clone(&*foreign_arc))
+ Ok(unsafe { ::std::sync::Arc::<Self>::from_raw(v) })
}
/// When writing as a field of a complex structure, make a clone and transfer ownership
@@ -117,8 +133,17 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream {
const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_INTERFACE)
.concat_str(#mod_path)
- .concat_str(#name)
- .concat_bool(false);
+ .concat_str(#name);
+ }
+
+ unsafe #lower_return_impl_spec {
+ type ReturnType = <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::FfiType;
+
+ fn lower_return(obj: Self) -> ::std::result::Result<Self::ReturnType, ::uniffi::RustBuffer> {
+ Ok(<Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(::std::sync::Arc::new(obj)))
+ }
+
+ const TYPE_ID_META: ::uniffi::MetadataBuffer = <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::TYPE_ID_META;
}
unsafe #lift_ref_impl_spec {
@@ -129,18 +154,25 @@ pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream {
pub(crate) fn interface_meta_static_var(
ident: &Ident,
- is_trait: bool,
+ imp: ObjectImpl,
module_path: &str,
+ docstring: String,
) -> syn::Result<TokenStream> {
let name = ident_to_string(ident);
+ let code = match imp {
+ ObjectImpl::Struct => quote! { ::uniffi::metadata::codes::INTERFACE },
+ ObjectImpl::Trait => quote! { ::uniffi::metadata::codes::TRAIT_INTERFACE },
+ ObjectImpl::CallbackTrait => quote! { ::uniffi::metadata::codes::CALLBACK_TRAIT_INTERFACE },
+ };
+
Ok(create_metadata_items(
"interface",
&name,
quote! {
- ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::INTERFACE)
- .concat_str(#module_path)
- .concat_str(#name)
- .concat_bool(#is_trait)
+ ::uniffi::MetadataBuffer::from_code(#code)
+ .concat_str(#module_path)
+ .concat_str(#name)
+ .concat_long_str(#docstring)
},
None,
))
diff --git a/third_party/rust/uniffi_macros/src/record.rs b/third_party/rust/uniffi_macros/src/record.rs
index abf2743ec6..41f5d016ac 100644
--- a/third_party/rust/uniffi_macros/src/record.rs
+++ b/third_party/rust/uniffi_macros/src/record.rs
@@ -1,17 +1,20 @@
use proc_macro2::{Ident, Span, TokenStream};
-use quote::{quote, ToTokens};
-use syn::{
- parse::{Parse, ParseStream},
- Data, DataStruct, DeriveInput, Field, Lit, Token,
-};
-
-use crate::util::{
- create_metadata_items, derive_all_ffi_traits, either_attribute_arg, ident_to_string, kw,
- mod_path, tagged_impl_header, try_metadata_value_from_usize, try_read_field, AttributeSliceExt,
- UniffiAttributeArgs,
+use quote::quote;
+use syn::{parse::ParseStream, Data, DataStruct, DeriveInput, Field, Token};
+
+use crate::{
+ default::{default_value_metadata_calls, DefaultValue},
+ util::{
+ create_metadata_items, derive_all_ffi_traits, either_attribute_arg, extract_docstring,
+ ident_to_string, kw, mod_path, tagged_impl_header, try_metadata_value_from_usize,
+ try_read_field, AttributeSliceExt, UniffiAttributeArgs,
+ },
};
pub fn expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> {
+ if let Some(e) = input.attrs.uniffi_attr_args_not_allowed_here() {
+ return Err(e);
+ }
let record = match input.data {
Data::Struct(s) => s,
_ => {
@@ -23,10 +26,12 @@ pub fn expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStr
};
let ident = &input.ident;
+ let docstring = extract_docstring(&input.attrs)?;
let ffi_converter = record_ffi_converter_impl(ident, &record, udl_mode)
.unwrap_or_else(syn::Error::into_compile_error);
let meta_static_var = (!udl_mode).then(|| {
- record_meta_static_var(ident, &record).unwrap_or_else(syn::Error::into_compile_error)
+ record_meta_static_var(ident, docstring, &record)
+ .unwrap_or_else(syn::Error::into_compile_error)
});
Ok(quote! {
@@ -78,35 +83,9 @@ fn write_field(f: &Field) -> TokenStream {
}
}
-pub enum FieldDefault {
- Literal(Lit),
- Null(kw::None),
-}
-
-impl ToTokens for FieldDefault {
- fn to_tokens(&self, tokens: &mut TokenStream) {
- match self {
- FieldDefault::Literal(lit) => lit.to_tokens(tokens),
- FieldDefault::Null(kw) => kw.to_tokens(tokens),
- }
- }
-}
-
-impl Parse for FieldDefault {
- fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
- let lookahead = input.lookahead1();
- if lookahead.peek(kw::None) {
- let none_kw: kw::None = input.parse()?;
- Ok(Self::Null(none_kw))
- } else {
- Ok(Self::Literal(input.parse()?))
- }
- }
-}
-
#[derive(Default)]
pub struct FieldAttributeArguments {
- pub(crate) default: Option<FieldDefault>,
+ pub(crate) default: Option<DefaultValue>,
}
impl UniffiAttributeArgs for FieldAttributeArguments {
@@ -128,6 +107,7 @@ impl UniffiAttributeArgs for FieldAttributeArguments {
pub(crate) fn record_meta_static_var(
ident: &Ident,
+ docstring: String,
record: &DataStruct,
) -> syn::Result<TokenStream> {
let name = ident_to_string(ident);
@@ -144,17 +124,9 @@ pub(crate) fn record_meta_static_var(
.parse_uniffi_attr_args::<FieldAttributeArguments>()?;
let name = ident_to_string(f.ident.as_ref().unwrap());
+ let docstring = extract_docstring(&f.attrs)?;
let ty = &f.ty;
- let default = match attrs.default {
- Some(default) => {
- let default_value = default_value_concat_calls(default)?;
- quote! {
- .concat_bool(true)
- #default_value
- }
- }
- None => quote! { .concat_bool(false) },
- };
+ let default = default_value_metadata_calls(&attrs.default)?;
// Note: fields need to implement both `Lower` and `Lift` to be used in a record. The
// TYPE_ID_META should be the same for both traits.
@@ -162,6 +134,7 @@ pub(crate) fn record_meta_static_var(
.concat_str(#name)
.concat(<#ty as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META)
#default
+ .concat_long_str(#docstring)
})
})
.collect::<syn::Result<_>>()?;
@@ -175,50 +148,8 @@ pub(crate) fn record_meta_static_var(
.concat_str(#name)
.concat_value(#fields_len)
#concat_fields
+ .concat_long_str(#docstring)
},
None,
))
}
-
-fn default_value_concat_calls(default: FieldDefault) -> syn::Result<TokenStream> {
- match default {
- FieldDefault::Literal(Lit::Int(i)) if !i.suffix().is_empty() => Err(
- syn::Error::new_spanned(i, "integer literals with suffix not supported here"),
- ),
- FieldDefault::Literal(Lit::Float(f)) if !f.suffix().is_empty() => Err(
- syn::Error::new_spanned(f, "float literals with suffix not supported here"),
- ),
-
- FieldDefault::Literal(Lit::Str(s)) => Ok(quote! {
- .concat_value(::uniffi::metadata::codes::LIT_STR)
- .concat_str(#s)
- }),
- FieldDefault::Literal(Lit::Int(i)) => {
- let digits = i.base10_digits();
- Ok(quote! {
- .concat_value(::uniffi::metadata::codes::LIT_INT)
- .concat_str(#digits)
- })
- }
- FieldDefault::Literal(Lit::Float(f)) => {
- let digits = f.base10_digits();
- Ok(quote! {
- .concat_value(::uniffi::metadata::codes::LIT_FLOAT)
- .concat_str(#digits)
- })
- }
- FieldDefault::Literal(Lit::Bool(b)) => Ok(quote! {
- .concat_value(::uniffi::metadata::codes::LIT_BOOL)
- .concat_bool(#b)
- }),
-
- FieldDefault::Literal(_) => Err(syn::Error::new_spanned(
- default,
- "this type of literal is not currently supported as a default",
- )),
-
- FieldDefault::Null(_) => Ok(quote! {
- .concat_value(::uniffi::metadata::codes::LIT_NULL)
- }),
- }
-}
diff --git a/third_party/rust/uniffi_macros/src/setup_scaffolding.rs b/third_party/rust/uniffi_macros/src/setup_scaffolding.rs
index afdb119cc4..c82e9389bb 100644
--- a/third_party/rust/uniffi_macros/src/setup_scaffolding.rs
+++ b/third_party/rust/uniffi_macros/src/setup_scaffolding.rs
@@ -20,15 +20,7 @@ pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> {
let ffi_rustbuffer_free_ident = format_ident!("ffi_{module_path}_rustbuffer_free");
let ffi_rustbuffer_reserve_ident = format_ident!("ffi_{module_path}_rustbuffer_reserve");
let reexport_hack_ident = format_ident!("{module_path}_uniffi_reexport_hack");
- let ffi_foreign_executor_callback_set_ident =
- format_ident!("ffi_{module_path}_foreign_executor_callback_set");
- let ffi_rust_future_continuation_callback_set =
- format_ident!("ffi_{module_path}_rust_future_continuation_callback_set");
let ffi_rust_future_scaffolding_fns = rust_future_scaffolding_fns(&module_path);
- let continuation_cell = format_ident!(
- "RUST_FUTURE_CONTINUATION_CALLBACK_CELL_{}",
- module_path.to_uppercase()
- );
Ok(quote! {
// Unit struct to parameterize the FfiConverter trait.
@@ -68,7 +60,7 @@ pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> {
#[allow(clippy::missing_safety_doc, missing_docs)]
#[doc(hidden)]
#[no_mangle]
- pub extern "C" fn #ffi_rustbuffer_alloc_ident(size: i32, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
+ pub extern "C" fn #ffi_rustbuffer_alloc_ident(size: u64, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
uniffi::ffi::uniffi_rustbuffer_alloc(size, call_status)
}
@@ -89,31 +81,10 @@ pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> {
#[allow(clippy::missing_safety_doc, missing_docs)]
#[doc(hidden)]
#[no_mangle]
- pub unsafe extern "C" fn #ffi_rustbuffer_reserve_ident(buf: uniffi::RustBuffer, additional: i32, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
+ pub unsafe extern "C" fn #ffi_rustbuffer_reserve_ident(buf: uniffi::RustBuffer, additional: u64, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer {
uniffi::ffi::uniffi_rustbuffer_reserve(buf, additional, call_status)
}
- static #continuation_cell: ::uniffi::deps::once_cell::sync::OnceCell<::uniffi::RustFutureContinuationCallback> = ::uniffi::deps::once_cell::sync::OnceCell::new();
-
- #[allow(clippy::missing_safety_doc, missing_docs)]
- #[doc(hidden)]
- #[no_mangle]
- pub extern "C" fn #ffi_foreign_executor_callback_set_ident(callback: uniffi::ffi::ForeignExecutorCallback) {
- uniffi::ffi::foreign_executor_callback_set(callback)
- }
-
- #[allow(clippy::missing_safety_doc, missing_docs)]
- #[doc(hidden)]
- #[no_mangle]
- pub unsafe extern "C" fn #ffi_rust_future_continuation_callback_set(callback: ::uniffi::RustFutureContinuationCallback) {
- if let Err(existing) = #continuation_cell.set(callback) {
- // Don't panic if this to be called multiple times with the same callback.
- if existing != callback {
- panic!("Attempt to set the RustFuture continuation callback twice");
- }
- }
- }
-
#ffi_rust_future_scaffolding_fns
// Code to re-export the UniFFI scaffolding functions.
@@ -158,12 +129,12 @@ pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> {
/// Generates the rust_future_* functions
///
-/// The foreign side uses a type-erased `RustFutureHandle` to interact with futures, which presents
+/// The foreign side uses a type-erased `Handle` to interact with futures, which presents
/// a problem when creating scaffolding functions. What is the `ReturnType` parameter of `RustFutureFfi`?
///
/// Handle this by using some brute-force monomorphization. For each possible ffi type, we
/// generate a set of scaffolding functions. The bindings code is responsible for calling the one
-/// corresponds the scaffolding function that created the `RustFutureHandle`.
+/// corresponds the scaffolding function that created the `Handle`.
///
/// This introduces safety issues, but we do get some type checking. If the bindings code calls
/// the wrong rust_future_complete function, they should get an unexpected return type, which
@@ -190,41 +161,37 @@ fn rust_future_scaffolding_fns(module_path: &str) -> TokenStream {
let ffi_rust_future_cancel = format_ident!("ffi_{module_path}_rust_future_cancel_{fn_suffix}");
let ffi_rust_future_complete = format_ident!("ffi_{module_path}_rust_future_complete_{fn_suffix}");
let ffi_rust_future_free = format_ident!("ffi_{module_path}_rust_future_free_{fn_suffix}");
- let continuation_cell = format_ident!("RUST_FUTURE_CONTINUATION_CALLBACK_CELL_{}", module_path.to_uppercase());
quote! {
#[allow(clippy::missing_safety_doc, missing_docs)]
#[doc(hidden)]
#[no_mangle]
- pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::RustFutureHandle, data: *const ()) {
- let callback = #continuation_cell
- .get()
- .expect("RustFuture continuation callback not set. This is likely a uniffi bug.");
- ::uniffi::ffi::rust_future_poll::<#return_type>(handle, *callback, data);
+ pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::Handle, callback: ::uniffi::RustFutureContinuationCallback, data: u64) {
+ ::uniffi::ffi::rust_future_poll::<#return_type, crate::UniFfiTag>(handle, callback, data);
}
#[allow(clippy::missing_safety_doc, missing_docs)]
#[doc(hidden)]
#[no_mangle]
- pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::RustFutureHandle) {
- ::uniffi::ffi::rust_future_cancel::<#return_type>(handle)
+ pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::Handle) {
+ ::uniffi::ffi::rust_future_cancel::<#return_type, crate::UniFfiTag>(handle)
}
#[allow(clippy::missing_safety_doc, missing_docs)]
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn #ffi_rust_future_complete(
- handle: ::uniffi::RustFutureHandle,
+ handle: ::uniffi::Handle,
out_status: &mut ::uniffi::RustCallStatus
) -> #return_type {
- ::uniffi::ffi::rust_future_complete::<#return_type>(handle, out_status)
+ ::uniffi::ffi::rust_future_complete::<#return_type, crate::UniFfiTag>(handle, out_status)
}
#[allow(clippy::missing_safety_doc, missing_docs)]
#[doc(hidden)]
#[no_mangle]
- pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::RustFutureHandle) {
- ::uniffi::ffi::rust_future_free::<#return_type>(handle)
+ pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::Handle) {
+ ::uniffi::ffi::rust_future_free::<#return_type, crate::UniFfiTag>(handle)
}
}
})
diff --git a/third_party/rust/uniffi_macros/src/util.rs b/third_party/rust/uniffi_macros/src/util.rs
index 9f213ea1d7..97faad9c4d 100644
--- a/third_party/rust/uniffi_macros/src/util.rs
+++ b/third_party/rust/uniffi_macros/src/util.rs
@@ -8,7 +8,7 @@ use std::path::{Path as StdPath, PathBuf};
use syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
- Attribute, Token,
+ Attribute, Expr, Lit, Token,
};
pub fn manifest_path() -> Result<PathBuf, String> {
@@ -79,8 +79,13 @@ pub fn try_read_field(f: &syn::Field) -> TokenStream {
let ident = &f.ident;
let ty = &f.ty;
- quote! {
- #ident: <#ty as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?,
+ match ident {
+ Some(ident) => quote! {
+ #ident: <#ty as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?,
+ },
+ None => quote! {
+ <#ty as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?,
+ },
}
}
@@ -151,13 +156,7 @@ pub fn parse_comma_separated<T: UniffiAttributeArgs>(input: ParseStream<'_>) ->
}
#[derive(Default)]
-pub struct ArgumentNotAllowedHere;
-
-impl Parse for ArgumentNotAllowedHere {
- fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
- parse_comma_separated(input)
- }
-}
+struct ArgumentNotAllowedHere;
impl UniffiAttributeArgs for ArgumentNotAllowedHere {
fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> {
@@ -224,7 +223,11 @@ pub(crate) fn derive_all_ffi_traits(ty: &Ident, udl_mode: bool) -> TokenStream {
}
}
-pub(crate) fn derive_ffi_traits(ty: &Ident, udl_mode: bool, trait_names: &[&str]) -> TokenStream {
+pub(crate) fn derive_ffi_traits(
+ ty: impl ToTokens,
+ udl_mode: bool,
+ trait_names: &[&str],
+) -> TokenStream {
let trait_idents = trait_names
.iter()
.map(|name| Ident::new(name, Span::call_site()));
@@ -247,11 +250,14 @@ pub(crate) fn derive_ffi_traits(ty: &Ident, udl_mode: bool, trait_names: &[&str]
pub mod kw {
syn::custom_keyword!(async_runtime);
syn::custom_keyword!(callback_interface);
- syn::custom_keyword!(constructor);
+ syn::custom_keyword!(with_foreign);
syn::custom_keyword!(default);
syn::custom_keyword!(flat_error);
syn::custom_keyword!(None);
+ syn::custom_keyword!(Some);
syn::custom_keyword!(with_try_read);
+ syn::custom_keyword!(name);
+ syn::custom_keyword!(non_exhaustive);
syn::custom_keyword!(Debug);
syn::custom_keyword!(Display);
syn::custom_keyword!(Eq);
@@ -276,3 +282,20 @@ impl Parse for ExternalTypeItem {
})
}
}
+
+pub(crate) fn extract_docstring(attrs: &[Attribute]) -> syn::Result<String> {
+ return attrs
+ .iter()
+ .filter(|attr| attr.path().is_ident("doc"))
+ .map(|attr| {
+ let name_value = attr.meta.require_name_value()?;
+ if let Expr::Lit(expr) = &name_value.value {
+ if let Lit::Str(lit_str) = &expr.lit {
+ return Ok(lit_str.value().trim().to_owned());
+ }
+ }
+ Err(syn::Error::new_spanned(attr, "Cannot parse doc attribute"))
+ })
+ .collect::<syn::Result<Vec<_>>>()
+ .map(|lines| lines.join("\n"));
+}
diff --git a/third_party/rust/uniffi_meta/.cargo-checksum.json b/third_party/rust/uniffi_meta/.cargo-checksum.json
index cb02cde83f..31b45ce807 100644
--- a/third_party/rust/uniffi_meta/.cargo-checksum.json
+++ b/third_party/rust/uniffi_meta/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"cb9f8aad563572bd4f12ee234ede6773f189a79ba5bd3bfd7622d3c0ec49d6a3","src/ffi_names.rs":"422bbe9d49d5476de752a9f9b2330f59b37a79e67f19a828caceb64d1bdabff8","src/group.rs":"ae996e6b9f83d459af04eb392e36487d0fe19c7328a395823186cce76a0955ff","src/lib.rs":"a442e2271a0eb538ec1d4fc7573a3acc7e5f366e2b2ac8d0e659fd998fd7d995","src/metadata.rs":"4ae425a8eab7b8c19a6b96c914f2c02c5bee00358888fd55b936fd1fd175a93c","src/reader.rs":"57fb771584491b8e90b01c68f9d53bac7cfa3135888e11e24e14b59312185ff9","src/types.rs":"8c155ed1301e11a365863989e29c2271149048092fb7052ec145f58c948482d5"},"package":"71dc8573a7b1ac4b71643d6da34888273ebfc03440c525121f1b3634ad3417a2"} \ No newline at end of file
+{"files":{"Cargo.toml":"5620cf9840477b158641547703ba353e3ad8427ec7b20b9dd5e5f5fe4df7d6d2","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","src/ffi_names.rs":"ca38b700a0a103c9faaf456ed91b67adf46d4e750aee9e9cd01ad97fb1840494","src/group.rs":"d0a43f3c528aba9403649715981ad3a8849d7a370f4ef9e2d618b88f60a3102f","src/lib.rs":"3f00d5214e2785e4b3045bc48899f6f6b1dce32ab3da6be3ebce716ee9d24c5f","src/metadata.rs":"3f236b337a1fd5082ea9cc4fee6800193a903ee88b81f1c3202843402f122a14","src/reader.rs":"579e2b87d8dd9d703b8811294abfb992621c0a46765800e4db2fad2906db2208","src/types.rs":"c2c5188da8cdf5af7f8496d4660bcfaa971b81ed73b64486c05b47256048544f"},"package":"f7224422c4cfd181c7ca9fca2154abca4d21db962f926f270f996edd38b0c4b8"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_meta/Cargo.toml b/third_party/rust/uniffi_meta/Cargo.toml
index 34999eee18..04d8170011 100644
--- a/third_party/rust/uniffi_meta/Cargo.toml
+++ b/third_party/rust/uniffi_meta/Cargo.toml
@@ -12,9 +12,10 @@
[package]
edition = "2021"
name = "uniffi_meta"
-version = "0.25.3"
+version = "0.27.1"
description = "uniffi_meta"
homepage = "https://mozilla.github.io/uniffi-rs"
+readme = "README.md"
keywords = [
"ffi",
"bindgen",
@@ -32,4 +33,4 @@ version = "1.3"
version = "0.3"
[dependencies.uniffi_checksum_derive]
-version = "0.25.3"
+version = "0.27.1"
diff --git a/third_party/rust/uniffi_meta/README.md b/third_party/rust/uniffi_meta/README.md
new file mode 100644
index 0000000000..64ac3486a3
--- /dev/null
+++ b/third_party/rust/uniffi_meta/README.md
@@ -0,0 +1,81 @@
+# UniFFI - a multi-language bindings generator for Rust
+
+UniFFI is a toolkit for building cross-platform software components in Rust.
+
+For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/)
+or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components).
+
+By writing your core business logic in Rust and describing its interface in an "object model",
+you can use UniFFI to help you:
+
+* Compile your Rust code into a shared library for use on different target platforms.
+* Generate bindings to load and use the library from different target languages.
+
+You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html).
+
+UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers;
+written once in Rust, auto-generated bindings allow that functionality to be called
+from both Kotlin (for Android apps) and Swift (for iOS apps).
+It also has a growing community of users shipping various cool things to many users.
+
+UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**.
+Additional foreign language bindings can be developed externally and we welcome contributions to list them here.
+See [Third-party foreign language bindings](#third-party-foreign-language-bindings).
+
+## User Guide
+
+You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/).
+
+We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on.
+We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time.
+
+### Etymology and Pronunciation
+
+ˈjuːnɪfaɪ. Pronounced to rhyme with "unify".
+
+A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages.
+
+uni - [Latin ūni-, from ūnus, one]
+FFI - [Abbreviation, Foreign Function Interface]
+
+## Alternative tools
+
+Other tools we know of which try and solve a similarly shaped problem are:
+
+* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of
+ the different approach taken by that tool](docs/diplomat-and-macros.md)
+* [Interoptopus](https://github.com/ralfbiedert/interoptopus/)
+
+(Please open a PR if you think other tools should be listed!)
+
+## Third-party foreign language bindings
+
+* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native.
+* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go)
+* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs)
+* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart)
+
+### External resources
+
+There are a few third-party resources that make it easier to work with UniFFI:
+
+* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/).
+* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts.
+* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful.
+* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure.
+
+(Please open a PR if you think other resources should be listed!)
+
+## Contributing
+
+If this tool sounds interesting to you, please help us develop it! You can:
+
+* View the [contributor guidelines](./docs/contributing.md).
+* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub.
+* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org)
+ room on Matrix.
+
+## Code of Conduct
+
+This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md).
diff --git a/third_party/rust/uniffi_meta/src/ffi_names.rs b/third_party/rust/uniffi_meta/src/ffi_names.rs
index 44a5bc3e63..5c931a09e3 100644
--- a/third_party/rust/uniffi_meta/src/ffi_names.rs
+++ b/third_party/rust/uniffi_meta/src/ffi_names.rs
@@ -33,6 +33,12 @@ pub fn method_symbol_name(namespace: &str, object_name: &str, name: &str) -> Str
format!("uniffi_{namespace}_fn_method_{object_name}_{name}")
}
+/// FFI symbol name for the `clone` function for an object.
+pub fn clone_fn_symbol_name(namespace: &str, object_name: &str) -> String {
+ let object_name = object_name.to_ascii_lowercase();
+ format!("uniffi_{namespace}_fn_clone_{object_name}")
+}
+
/// FFI symbol name for the `free` function for an object.
pub fn free_fn_symbol_name(namespace: &str, object_name: &str) -> String {
let object_name = object_name.to_ascii_lowercase();
@@ -40,9 +46,12 @@ pub fn free_fn_symbol_name(namespace: &str, object_name: &str) -> String {
}
/// FFI symbol name for the `init_callback` function for a callback interface
-pub fn init_callback_fn_symbol_name(namespace: &str, callback_interface_name: &str) -> String {
+pub fn init_callback_vtable_fn_symbol_name(
+ namespace: &str,
+ callback_interface_name: &str,
+) -> String {
let callback_interface_name = callback_interface_name.to_ascii_lowercase();
- format!("uniffi_{namespace}_fn_init_callback_{callback_interface_name}")
+ format!("uniffi_{namespace}_fn_init_callback_vtable_{callback_interface_name}")
}
/// FFI checksum symbol name for a top-level function
diff --git a/third_party/rust/uniffi_meta/src/group.rs b/third_party/rust/uniffi_meta/src/group.rs
index f0be2e5a98..a41776bf8a 100644
--- a/third_party/rust/uniffi_meta/src/group.rs
+++ b/third_party/rust/uniffi_meta/src/group.rs
@@ -18,6 +18,7 @@ pub fn create_metadata_groups(items: &[Metadata]) -> MetadataGroupMap {
Metadata::Namespace(namespace) => {
let group = MetadataGroup {
namespace: namespace.clone(),
+ namespace_docstring: None,
items: BTreeSet::new(),
};
Some((namespace.crate_name.clone(), group))
@@ -29,6 +30,7 @@ pub fn create_metadata_groups(items: &[Metadata]) -> MetadataGroupMap {
};
let group = MetadataGroup {
namespace,
+ namespace_docstring: None,
items: BTreeSet::new(),
};
Some((udl.module_path.clone(), group))
@@ -63,6 +65,7 @@ pub fn group_metadata(group_map: &mut MetadataGroupMap, items: Vec<Metadata>) ->
#[derive(Debug)]
pub struct MetadataGroup {
pub namespace: NamespaceMetadata,
+ pub namespace_docstring: Option<String>,
pub items: BTreeSet<Metadata>,
}
@@ -127,12 +130,6 @@ impl<'a> ExternalTypeConverter<'a> {
..meta
}),
Metadata::Enum(meta) => Metadata::Enum(self.convert_enum(meta)),
- Metadata::Error(meta) => Metadata::Error(match meta {
- ErrorMetadata::Enum { enum_, is_flat } => ErrorMetadata::Enum {
- enum_: self.convert_enum(enum_),
- is_flat,
- },
- }),
_ => item,
}
}
diff --git a/third_party/rust/uniffi_meta/src/lib.rs b/third_party/rust/uniffi_meta/src/lib.rs
index e486d84d89..90f7b2d3cd 100644
--- a/third_party/rust/uniffi_meta/src/lib.rs
+++ b/third_party/rust/uniffi_meta/src/lib.rs
@@ -23,7 +23,7 @@ mod metadata;
// `docs/uniffi-versioning.md` for details.
//
// Once we get to 1.0, then we'll need to update the scheme to something like 100 + major_version
-pub const UNIFFI_CONTRACT_VERSION: u32 = 24;
+pub const UNIFFI_CONTRACT_VERSION: u32 = 26;
/// Similar to std::hash::Hash.
///
@@ -143,6 +143,7 @@ pub struct FnMetadata {
pub return_type: Option<Type>,
pub throws: Option<Type>,
pub checksum: Option<u16>,
+ pub docstring: Option<String>,
}
impl FnMetadata {
@@ -160,9 +161,11 @@ pub struct ConstructorMetadata {
pub module_path: String,
pub self_name: String,
pub name: String,
+ pub is_async: bool,
pub inputs: Vec<FnParamMetadata>,
pub throws: Option<Type>,
pub checksum: Option<u16>,
+ pub docstring: Option<String>,
}
impl ConstructorMetadata {
@@ -190,6 +193,7 @@ pub struct MethodMetadata {
pub throws: Option<Type>,
pub takes_self_by_arc: bool, // unused except by rust udl bindgen.
pub checksum: Option<u16>,
+ pub docstring: Option<String>,
}
impl MethodMetadata {
@@ -216,6 +220,7 @@ pub struct TraitMethodMetadata {
pub throws: Option<Type>,
pub takes_self_by_arc: bool, // unused except by rust udl bindgen.
pub checksum: Option<u16>,
+ pub docstring: Option<String>,
}
impl TraitMethodMetadata {
@@ -266,7 +271,17 @@ pub enum LiteralMetadata {
Enum(String, Type),
EmptySequence,
EmptyMap,
- Null,
+ None,
+ Some { inner: Box<LiteralMetadata> },
+}
+
+impl LiteralMetadata {
+ pub fn new_uint(v: u64) -> Self {
+ LiteralMetadata::UInt(v, Radix::Decimal, Type::UInt64)
+ }
+ pub fn new_int(v: i64) -> Self {
+ LiteralMetadata::Int(v, Radix::Decimal, Type::Int64)
+ }
}
// Represent the radix of integer literal values.
@@ -283,6 +298,7 @@ pub struct RecordMetadata {
pub module_path: String,
pub name: String,
pub fields: Vec<FieldMetadata>,
+ pub docstring: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
@@ -290,19 +306,26 @@ pub struct FieldMetadata {
pub name: String,
pub ty: Type,
pub default: Option<LiteralMetadata>,
+ pub docstring: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct EnumMetadata {
pub module_path: String,
pub name: String,
+ pub forced_flatness: Option<bool>,
pub variants: Vec<VariantMetadata>,
+ pub discr_type: Option<Type>,
+ pub non_exhaustive: bool,
+ pub docstring: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct VariantMetadata {
pub name: String,
+ pub discr: Option<LiteralMetadata>,
pub fields: Vec<FieldMetadata>,
+ pub docstring: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
@@ -310,15 +333,25 @@ pub struct ObjectMetadata {
pub module_path: String,
pub name: String,
pub imp: types::ObjectImpl,
+ pub docstring: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct CallbackInterfaceMetadata {
pub module_path: String,
pub name: String,
+ pub docstring: Option<String>,
}
impl ObjectMetadata {
+ /// FFI symbol name for the `clone` function for this object.
+ ///
+ /// This function is used to increment the reference count before lowering an object to pass
+ /// back to Rust.
+ pub fn clone_ffi_symbol_name(&self) -> String {
+ clone_fn_symbol_name(&self.module_path, &self.name)
+ }
+
/// FFI symbol name for the `free` function for this object.
///
/// This function is used to free the memory used by this object.
@@ -368,6 +401,7 @@ impl UniffiTraitMetadata {
}
#[repr(u8)]
+#[derive(Eq, PartialEq, Hash)]
pub enum UniffiTraitDiscriminants {
Debug,
Display,
@@ -388,25 +422,6 @@ impl UniffiTraitDiscriminants {
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub enum ErrorMetadata {
- Enum { enum_: EnumMetadata, is_flat: bool },
-}
-
-impl ErrorMetadata {
- pub fn name(&self) -> &String {
- match self {
- Self::Enum { enum_, .. } => &enum_.name,
- }
- }
-
- pub fn module_path(&self) -> &String {
- match self {
- Self::Enum { enum_, .. } => &enum_.module_path,
- }
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct CustomTypeMetadata {
pub module_path: String,
pub name: String,
@@ -433,7 +448,6 @@ pub enum Metadata {
CallbackInterface(CallbackInterfaceMetadata),
Record(RecordMetadata),
Enum(EnumMetadata),
- Error(ErrorMetadata),
Constructor(ConstructorMetadata),
Method(MethodMetadata),
TraitMethod(TraitMethodMetadata),
@@ -458,7 +472,6 @@ impl Metadata {
Metadata::Object(meta) => &meta.module_path,
Metadata::CallbackInterface(meta) => &meta.module_path,
Metadata::TraitMethod(meta) => &meta.module_path,
- Metadata::Error(meta) => meta.module_path(),
Metadata::CustomType(meta) => &meta.module_path,
Metadata::UniffiTrait(meta) => meta.module_path(),
}
@@ -507,12 +520,6 @@ impl From<EnumMetadata> for Metadata {
}
}
-impl From<ErrorMetadata> for Metadata {
- fn from(e: ErrorMetadata) -> Self {
- Self::Error(e)
- }
-}
-
impl From<ObjectMetadata> for Metadata {
fn from(v: ObjectMetadata) -> Self {
Self::Object(v)
diff --git a/third_party/rust/uniffi_meta/src/metadata.rs b/third_party/rust/uniffi_meta/src/metadata.rs
index 6e490a4866..9cfb77a244 100644
--- a/third_party/rust/uniffi_meta/src/metadata.rs
+++ b/third_party/rust/uniffi_meta/src/metadata.rs
@@ -7,7 +7,7 @@
// `uniffi_core`.
// This is the easy way out of that issue and is a temporary hacky solution.
-/// Metadata constants, make sure to keep this in sync with copy in `uniffi_meta::reader`
+/// Metadata constants, make sure to keep this in sync with copy in `uniffi_core::metadata`
pub mod codes {
// Top-level metadata item codes
pub const FUNC: u8 = 0;
@@ -15,13 +15,14 @@ pub mod codes {
pub const RECORD: u8 = 2;
pub const ENUM: u8 = 3;
pub const INTERFACE: u8 = 4;
- pub const ERROR: u8 = 5;
pub const NAMESPACE: u8 = 6;
pub const CONSTRUCTOR: u8 = 7;
pub const UDL_FILE: u8 = 8;
pub const CALLBACK_INTERFACE: u8 = 9;
pub const TRAIT_METHOD: u8 = 10;
pub const UNIFFI_TRAIT: u8 = 11;
+ pub const TRAIT_INTERFACE: u8 = 12;
+ pub const CALLBACK_TRAIT_INTERFACE: u8 = 13;
//pub const UNKNOWN: u8 = 255;
// Type codes
@@ -49,8 +50,8 @@ pub mod codes {
pub const TYPE_CALLBACK_INTERFACE: u8 = 21;
pub const TYPE_CUSTOM: u8 = 22;
pub const TYPE_RESULT: u8 = 23;
- //pub const TYPE_FUTURE: u8 = 24;
- pub const TYPE_FOREIGN_EXECUTOR: u8 = 25;
+ pub const TYPE_TRAIT_INTERFACE: u8 = 24;
+ pub const TYPE_CALLBACK_TRAIT_INTERFACE: u8 = 25;
pub const TYPE_UNIT: u8 = 255;
// Literal codes
@@ -58,7 +59,9 @@ pub mod codes {
pub const LIT_INT: u8 = 1;
pub const LIT_FLOAT: u8 = 2;
pub const LIT_BOOL: u8 = 3;
- pub const LIT_NULL: u8 = 4;
+ pub const LIT_NONE: u8 = 4;
+ pub const LIT_SOME: u8 = 5;
+ pub const LIT_EMPTY_SEQ: u8 = 6;
}
// Create a checksum for a MetadataBuffer
diff --git a/third_party/rust/uniffi_meta/src/reader.rs b/third_party/rust/uniffi_meta/src/reader.rs
index bf6525f2b5..6fec6cb3a3 100644
--- a/third_party/rust/uniffi_meta/src/reader.rs
+++ b/third_party/rust/uniffi_meta/src/reader.rs
@@ -52,9 +52,10 @@ impl<'a> MetadataReader<'a> {
codes::CONSTRUCTOR => self.read_constructor()?.into(),
codes::METHOD => self.read_method()?.into(),
codes::RECORD => self.read_record()?.into(),
- codes::ENUM => self.read_enum(false)?.into(),
- codes::ERROR => self.read_error()?.into(),
- codes::INTERFACE => self.read_object()?.into(),
+ codes::ENUM => self.read_enum()?.into(),
+ codes::INTERFACE => self.read_object(ObjectImpl::Struct)?.into(),
+ codes::TRAIT_INTERFACE => self.read_object(ObjectImpl::Trait)?.into(),
+ codes::CALLBACK_TRAIT_INTERFACE => self.read_object(ObjectImpl::CallbackTrait)?.into(),
codes::CALLBACK_INTERFACE => self.read_callback_interface()?.into(),
codes::TRAIT_METHOD => self.read_trait_method()?.into(),
codes::UNIFFI_TRAIT => self.read_uniffi_trait()?.into(),
@@ -80,6 +81,17 @@ impl<'a> MetadataReader<'a> {
}
}
+ fn read_u16(&mut self) -> Result<u16> {
+ if self.buf.len() >= 2 {
+ // read the value as little-endian
+ let value = u16::from_le_bytes([self.buf[0], self.buf[1]]);
+ self.buf = &self.buf[2..];
+ Ok(value)
+ } else {
+ bail!("Not enough data left in buffer to read a u16 value");
+ }
+ }
+
fn read_u32(&mut self) -> Result<u32> {
if self.buf.len() >= 4 {
// read the value as little-endian
@@ -105,6 +117,17 @@ impl<'a> MetadataReader<'a> {
String::from_utf8(slice.into()).context("Invalid string data")
}
+ fn read_long_string(&mut self) -> Result<String> {
+ let size = self.read_u16()? as usize;
+ let slice;
+ (slice, self.buf) = self.buf.split_at(size);
+ String::from_utf8(slice.into()).context("Invalid string data")
+ }
+
+ fn read_optional_long_string(&mut self) -> Result<Option<String>> {
+ Ok(Some(self.read_long_string()?).filter(|str| !str.is_empty()))
+ }
+
fn read_type(&mut self) -> Result<Type> {
let value = self.read_u8()?;
Ok(match value {
@@ -122,7 +145,6 @@ impl<'a> MetadataReader<'a> {
codes::TYPE_STRING => Type::String,
codes::TYPE_DURATION => Type::Duration,
codes::TYPE_SYSTEM_TIME => Type::Timestamp,
- codes::TYPE_FOREIGN_EXECUTOR => Type::ForeignExecutor,
codes::TYPE_RECORD => Type::Record {
module_path: self.read_string()?,
name: self.read_string()?,
@@ -134,7 +156,17 @@ impl<'a> MetadataReader<'a> {
codes::TYPE_INTERFACE => Type::Object {
module_path: self.read_string()?,
name: self.read_string()?,
- imp: ObjectImpl::from_is_trait(self.read_bool()?),
+ imp: ObjectImpl::Struct,
+ },
+ codes::TYPE_TRAIT_INTERFACE => Type::Object {
+ module_path: self.read_string()?,
+ name: self.read_string()?,
+ imp: ObjectImpl::Trait,
+ },
+ codes::TYPE_CALLBACK_TRAIT_INTERFACE => Type::Object {
+ module_path: self.read_string()?,
+ name: self.read_string()?,
+ imp: ObjectImpl::CallbackTrait,
},
codes::TYPE_CALLBACK_INTERFACE => Type::CallbackInterface {
module_path: self.read_string()?,
@@ -198,6 +230,7 @@ impl<'a> MetadataReader<'a> {
let is_async = self.read_bool()?;
let inputs = self.read_inputs()?;
let (return_type, throws) = self.read_return_type()?;
+ let docstring = self.read_optional_long_string()?;
Ok(FnMetadata {
module_path,
name,
@@ -205,6 +238,7 @@ impl<'a> MetadataReader<'a> {
inputs,
return_type,
throws,
+ docstring,
checksum: self.calc_checksum(),
})
}
@@ -213,8 +247,10 @@ impl<'a> MetadataReader<'a> {
let module_path = self.read_string()?;
let self_name = self.read_string()?;
let name = self.read_string()?;
+ let is_async = self.read_bool()?;
let inputs = self.read_inputs()?;
let (return_type, throws) = self.read_return_type()?;
+ let docstring = self.read_optional_long_string()?;
return_type
.filter(|t| {
@@ -228,10 +264,12 @@ impl<'a> MetadataReader<'a> {
Ok(ConstructorMetadata {
module_path,
self_name,
+ is_async,
name,
inputs,
throws,
checksum: self.calc_checksum(),
+ docstring,
})
}
@@ -242,6 +280,7 @@ impl<'a> MetadataReader<'a> {
let is_async = self.read_bool()?;
let inputs = self.read_inputs()?;
let (return_type, throws) = self.read_return_type()?;
+ let docstring = self.read_optional_long_string()?;
Ok(MethodMetadata {
module_path,
self_name,
@@ -252,6 +291,7 @@ impl<'a> MetadataReader<'a> {
throws,
takes_self_by_arc: false, // not emitted by macros
checksum: self.calc_checksum(),
+ docstring,
})
}
@@ -260,13 +300,25 @@ impl<'a> MetadataReader<'a> {
module_path: self.read_string()?,
name: self.read_string()?,
fields: self.read_fields()?,
+ docstring: self.read_optional_long_string()?,
})
}
- fn read_enum(&mut self, is_flat_error: bool) -> Result<EnumMetadata> {
+ fn read_enum(&mut self) -> Result<EnumMetadata> {
let module_path = self.read_string()?;
let name = self.read_string()?;
- let variants = if is_flat_error {
+ let forced_flatness = match self.read_u8()? {
+ 0 => None,
+ 1 => Some(false),
+ 2 => Some(true),
+ _ => unreachable!("invalid flatness"),
+ };
+ let discr_type = if self.read_bool()? {
+ Some(self.read_type()?)
+ } else {
+ None
+ };
+ let variants = if forced_flatness == Some(true) {
self.read_flat_variants()?
} else {
self.read_variants()?
@@ -275,21 +327,20 @@ impl<'a> MetadataReader<'a> {
Ok(EnumMetadata {
module_path,
name,
+ forced_flatness,
+ discr_type,
variants,
+ non_exhaustive: self.read_bool()?,
+ docstring: self.read_optional_long_string()?,
})
}
- fn read_error(&mut self) -> Result<ErrorMetadata> {
- let is_flat = self.read_bool()?;
- let enum_ = self.read_enum(is_flat)?;
- Ok(ErrorMetadata::Enum { enum_, is_flat })
- }
-
- fn read_object(&mut self) -> Result<ObjectMetadata> {
+ fn read_object(&mut self, imp: ObjectImpl) -> Result<ObjectMetadata> {
Ok(ObjectMetadata {
module_path: self.read_string()?,
name: self.read_string()?,
- imp: ObjectImpl::from_is_trait(self.read_bool()?),
+ imp,
+ docstring: self.read_optional_long_string()?,
})
}
@@ -322,6 +373,7 @@ impl<'a> MetadataReader<'a> {
Ok(CallbackInterfaceMetadata {
module_path: self.read_string()?,
name: self.read_string()?,
+ docstring: self.read_optional_long_string()?,
})
}
@@ -333,6 +385,7 @@ impl<'a> MetadataReader<'a> {
let is_async = self.read_bool()?;
let inputs = self.read_inputs()?;
let (return_type, throws) = self.read_return_type()?;
+ let docstring = self.read_optional_long_string()?;
Ok(TraitMethodMetadata {
module_path,
trait_name,
@@ -344,6 +397,7 @@ impl<'a> MetadataReader<'a> {
throws,
takes_self_by_arc: false, // not emitted by macros
checksum: self.calc_checksum(),
+ docstring,
})
}
@@ -353,8 +407,13 @@ impl<'a> MetadataReader<'a> {
.map(|_| {
let name = self.read_string()?;
let ty = self.read_type()?;
- let default = self.read_default(&name, &ty)?;
- Ok(FieldMetadata { name, ty, default })
+ let default = self.read_optional_default(&name, &ty)?;
+ Ok(FieldMetadata {
+ name,
+ ty,
+ default,
+ docstring: self.read_optional_long_string()?,
+ })
})
.collect()
}
@@ -365,7 +424,9 @@ impl<'a> MetadataReader<'a> {
.map(|_| {
Ok(VariantMetadata {
name: self.read_string()?,
+ discr: self.read_optional_default("<variant-value>", &Type::UInt64)?,
fields: self.read_fields()?,
+ docstring: self.read_optional_long_string()?,
})
})
.collect()
@@ -377,7 +438,9 @@ impl<'a> MetadataReader<'a> {
.map(|_| {
Ok(VariantMetadata {
name: self.read_string()?,
+ discr: None,
fields: vec![],
+ docstring: self.read_optional_long_string()?,
})
})
.collect()
@@ -387,13 +450,16 @@ impl<'a> MetadataReader<'a> {
let len = self.read_u8()?;
(0..len)
.map(|_| {
+ let name = self.read_string()?;
+ let ty = self.read_type()?;
+ let default = self.read_optional_default(&name, &ty)?;
Ok(FnParamMetadata {
- name: self.read_string()?,
- ty: self.read_type()?,
+ name,
+ ty,
+ default,
// not emitted by macros
by_ref: false,
optional: false,
- default: None,
})
})
.collect()
@@ -405,14 +471,18 @@ impl<'a> MetadataReader<'a> {
Some(checksum_metadata(metadata_buf))
}
- fn read_default(&mut self, name: &str, ty: &Type) -> Result<Option<LiteralMetadata>> {
- let has_default = self.read_bool()?;
- if !has_default {
- return Ok(None);
+ fn read_optional_default(&mut self, name: &str, ty: &Type) -> Result<Option<LiteralMetadata>> {
+ if self.read_bool()? {
+ Ok(Some(self.read_default(name, ty)?))
+ } else {
+ Ok(None)
}
+ }
+ fn read_default(&mut self, name: &str, ty: &Type) -> Result<LiteralMetadata> {
let literal_kind = self.read_u8()?;
- Ok(Some(match literal_kind {
+
+ Ok(match literal_kind {
codes::LIT_STR => {
ensure!(
matches!(ty, Type::String),
@@ -422,12 +492,24 @@ impl<'a> MetadataReader<'a> {
}
codes::LIT_INT => {
let base10_digits = self.read_string()?;
+ // procmacros emit the type for discriminant values based purely on whether the constant
+ // is positive or negative.
+ let ty = if !base10_digits.is_empty()
+ && base10_digits.as_bytes()[0] == b'-'
+ && ty == &Type::UInt64
+ {
+ &Type::Int64
+ } else {
+ ty
+ };
macro_rules! parse_int {
($ty:ident, $variant:ident) => {
LiteralMetadata::$variant(
base10_digits
.parse::<$ty>()
- .with_context(|| format!("parsing default for field {name}"))?
+ .with_context(|| {
+ format!("parsing default for field {name}: {base10_digits}")
+ })?
.into(),
Radix::Decimal,
ty.to_owned(),
@@ -458,8 +540,18 @@ impl<'a> MetadataReader<'a> {
}
},
codes::LIT_BOOL => LiteralMetadata::Boolean(self.read_bool()?),
- codes::LIT_NULL => LiteralMetadata::Null,
+ codes::LIT_NONE => match ty {
+ Type::Optional { .. } => LiteralMetadata::None,
+ _ => bail!("field {name} of type {ty:?} can't have a default value of None"),
+ },
+ codes::LIT_SOME => match ty {
+ Type::Optional { inner_type, .. } => LiteralMetadata::Some {
+ inner: Box::new(self.read_default(name, inner_type)?),
+ },
+ _ => bail!("field {name} of type {ty:?} can't have a default value of None"),
+ },
+ codes::LIT_EMPTY_SEQ => LiteralMetadata::EmptySequence,
_ => bail!("Unexpected literal kind code: {literal_kind:?}"),
- }))
+ })
}
}
diff --git a/third_party/rust/uniffi_meta/src/types.rs b/third_party/rust/uniffi_meta/src/types.rs
index 24f8a6f2a8..51bf156b50 100644
--- a/third_party/rust/uniffi_meta/src/types.rs
+++ b/third_party/rust/uniffi_meta/src/types.rs
@@ -21,8 +21,12 @@ use crate::Checksum;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Checksum, Ord, PartialOrd)]
pub enum ObjectImpl {
+ // A single Rust type
Struct,
+ // A trait that's can be implemented by Rust types
Trait,
+ // A trait + a callback interface -- can be implemented by both Rust and foreign types.
+ CallbackTrait,
}
impl ObjectImpl {
@@ -31,27 +35,26 @@ impl ObjectImpl {
/// Includes `r#`, traits get a leading `dyn`. If we ever supported associated types, then
/// this would also include them.
pub fn rust_name_for(&self, name: &str) -> String {
- if self == &ObjectImpl::Trait {
+ if self.is_trait_interface() {
format!("dyn r#{name}")
} else {
format!("r#{name}")
}
}
- // uniffi_meta and procmacro support tend to carry around `is_trait` bools. This makes that
- // mildly less painful
- pub fn from_is_trait(is_trait: bool) -> Self {
- if is_trait {
- ObjectImpl::Trait
- } else {
- ObjectImpl::Struct
- }
+ pub fn is_trait_interface(&self) -> bool {
+ matches!(self, Self::Trait | Self::CallbackTrait)
+ }
+
+ pub fn has_callback_interface(&self) -> bool {
+ matches!(self, Self::CallbackTrait)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Checksum, Ord, PartialOrd)]
pub enum ExternalKind {
Interface,
+ Trait,
// Either a record or enum
DataClass,
}
@@ -85,7 +88,6 @@ pub enum Type {
// How the object is implemented.
imp: ObjectImpl,
},
- ForeignExecutor,
// Types defined in the component API, each of which has a string name.
Record {
module_path: String,
diff --git a/third_party/rust/uniffi_testing/.cargo-checksum.json b/third_party/rust/uniffi_testing/.cargo-checksum.json
index 0af9b557d8..47b58d8bcf 100644
--- a/third_party/rust/uniffi_testing/.cargo-checksum.json
+++ b/third_party/rust/uniffi_testing/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"300889e9b31b2f73133d69a8c5f8854c72cefd398b4f656c7fc06f4c46475dcc","README.md":"ec6aba24af9a011ef6647422aa22efabdee519cdee3da1a9f9033b07b7cbdb0d","src/lib.rs":"e19f60aed5a137401203b9054ead27894b98490bab3e2f680a23549c8ee8a13a"},"package":"118448debffcb676ddbe8c5305fb933ab7e0123753e659a71dc4a693f8d9f23c"} \ No newline at end of file
+{"files":{"Cargo.toml":"f29ebbc363e01ee31c5a351aa6c19bc99343b4d293d5ea7954bca3f49e77ad54","README.md":"ec6aba24af9a011ef6647422aa22efabdee519cdee3da1a9f9033b07b7cbdb0d","src/lib.rs":"e19f60aed5a137401203b9054ead27894b98490bab3e2f680a23549c8ee8a13a"},"package":"f8ce878d0bdfc288b58797044eaaedf748526c56eef3575380bb4d4b19d69eee"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_testing/Cargo.toml b/third_party/rust/uniffi_testing/Cargo.toml
index a4f6f0bf54..5dacf0cf31 100644
--- a/third_party/rust/uniffi_testing/Cargo.toml
+++ b/third_party/rust/uniffi_testing/Cargo.toml
@@ -12,7 +12,7 @@
[package]
edition = "2021"
name = "uniffi_testing"
-version = "0.25.3"
+version = "0.27.1"
authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
description = "a multi-language bindings generator for rust (testing helpers)"
homepage = "https://mozilla.github.io/uniffi-rs"
diff --git a/third_party/rust/uniffi_udl/.cargo-checksum.json b/third_party/rust/uniffi_udl/.cargo-checksum.json
index 233a1e7795..88ed1cb4b7 100644
--- a/third_party/rust/uniffi_udl/.cargo-checksum.json
+++ b/third_party/rust/uniffi_udl/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"8bb1bcb6089114c92a0d06f884e64769edc493bd26d271eb659d6592d8d2f5fa","src/attributes.rs":"faf1c53c17ff511f587113afdb37de4890087150f32e19c9af4d878daebe7c89","src/collectors.rs":"3d80f169ccb7a64c434a6890948e49a64947062c29487b5bea58721189cb2786","src/converters/callables.rs":"c41d15cf816f5838e74f0c1d7a5806c4f1c1b2d925ca11b2b6658a978b2373fb","src/converters/enum_.rs":"1742441b812949b83ec28c2c2c4684c34fd618195cff47af2934c7ee9c895c59","src/converters/interface.rs":"a3deb1915eb4be80d21116f24bec7163736bdffbad4eb2236fb531386a97c4f8","src/converters/mod.rs":"4035ec70c56917fa7c61cced5f8a8398c99028e13959662acc1f7a48aa71ae3b","src/finder.rs":"c496aca0ac640d4ca5fd57b32131297544e5468c9e792f8b5da1644df6d22d2e","src/lib.rs":"11a5d3c288f5786164471b0870b5ca9190305757461c6a6d20777a96622457f1","src/literal.rs":"30e50d7c1d3f061bb6aaa7ad3a6eb31d75a6ea21159dfe454f7ab9ae4bdb580e","src/resolver.rs":"96212b52c4f4f7637713d0a39f5c4972e6480e4ce98070dcb8647d67c9b8d5e2"},"package":"889edb7109c6078abe0e53e9b4070cf74a6b3468d141bdf5ef1bd4d1dc24a1c3"} \ No newline at end of file
+{"files":{"Cargo.toml":"e4553df91528daadbc52244f436c3ae17d9c6a4629d232643a20ed63c1311625","README.md":"37c1af00ec81a9f1bc206ab3578356e5f9ad4077dc46dd1bb623d81d804948b8","src/attributes.rs":"f1cdee01db837920dbd109d60b39dd4b0ece56f83797fe4ace1e0caf97c02483","src/collectors.rs":"00c9cd8e8f7be6949996f069b449f120f0b0694ff2f1ca92b79201721c18c594","src/converters/callables.rs":"1ad26c2782629e98272edc75c38343f58194ebf9626ae451ba84546a60d45a48","src/converters/enum_.rs":"aa0ca7a7a50521a45e296c6f53be5f1981a41872443946f72c2ca8baebe4d69b","src/converters/interface.rs":"6042d4abd5e236ed6158c5f6d4d9b81bb01fbbcb8b42bf8a5b385b978c68f6f8","src/converters/mod.rs":"c34a30e3b7a2e3c092a7074f4bab6f6c34364177c096af59a7dda13e44ffdc3c","src/finder.rs":"3586ffd3772151eabbc3d6062725933c88978e1b5feb64312bbf42cd43af30fa","src/lib.rs":"56c50ce61ba5e7266fe7fc9fa9d0022cdbfbe9801730753bd4ee66fed040221c","src/literal.rs":"4f28ad49a17246b4dc30a677cfff65b345bfac0924856e19f58e7574a74c2c40","src/resolver.rs":"c4ff362055dc4ed489e94a063ec0fd3becb8787ad35ce65cdec437a1aae518a0"},"package":"8c43c9ed40a8d20a5c3eae2d23031092db6b96dc8e571beb449ba9757484cea0"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_udl/Cargo.toml b/third_party/rust/uniffi_udl/Cargo.toml
index 346cd71c27..fd03f3434a 100644
--- a/third_party/rust/uniffi_udl/Cargo.toml
+++ b/third_party/rust/uniffi_udl/Cargo.toml
@@ -12,10 +12,11 @@
[package]
edition = "2021"
name = "uniffi_udl"
-version = "0.25.3"
+version = "0.27.1"
description = "udl parsing for the uniffi project"
homepage = "https://mozilla.github.io/uniffi-rs"
documentation = "https://mozilla.github.io/uniffi-rs"
+readme = "README.md"
keywords = [
"ffi",
"bindgen",
@@ -26,11 +27,14 @@ repository = "https://github.com/mozilla/uniffi-rs"
[dependencies.anyhow]
version = "1"
+[dependencies.textwrap]
+version = "0.16"
+
[dependencies.uniffi_meta]
-version = "=0.25.3"
+version = "=0.27.1"
[dependencies.uniffi_testing]
-version = "=0.25.3"
+version = "=0.27.1"
[dependencies.weedle2]
-version = "4.0.0"
+version = "5.0.0"
diff --git a/third_party/rust/uniffi_udl/README.md b/third_party/rust/uniffi_udl/README.md
new file mode 100644
index 0000000000..64ac3486a3
--- /dev/null
+++ b/third_party/rust/uniffi_udl/README.md
@@ -0,0 +1,81 @@
+# UniFFI - a multi-language bindings generator for Rust
+
+UniFFI is a toolkit for building cross-platform software components in Rust.
+
+For the impatient, see [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/)
+or [**the UniFFI examples**](https://github.com/mozilla/uniffi-rs/tree/main/examples#example-uniffi-components).
+
+By writing your core business logic in Rust and describing its interface in an "object model",
+you can use UniFFI to help you:
+
+* Compile your Rust code into a shared library for use on different target platforms.
+* Generate bindings to load and use the library from different target languages.
+
+You can describe your object model in an [interface definition file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+or [by using proc-macros](https://mozilla.github.io/uniffi-rs/proc_macro/index.html).
+
+UniFFI is currently used extensively by Mozilla in Firefox mobile and desktop browsers;
+written once in Rust, auto-generated bindings allow that functionality to be called
+from both Kotlin (for Android apps) and Swift (for iOS apps).
+It also has a growing community of users shipping various cool things to many users.
+
+UniFFI comes with support for **Kotlin**, **Swift**, **Python** and **Ruby** with 3rd party bindings available for **C#** and **Golang**.
+Additional foreign language bindings can be developed externally and we welcome contributions to list them here.
+See [Third-party foreign language bindings](#third-party-foreign-language-bindings).
+
+## User Guide
+
+You can read more about using the tool in [**the UniFFI user guide**](https://mozilla.github.io/uniffi-rs/).
+
+We consider it ready for production use, but UniFFI is a long way from a 1.0 release with lots of internal work still going on.
+We try hard to avoid breaking simple consumers, but more advanced things might break as you upgrade over time.
+
+### Etymology and Pronunciation
+
+ˈjuːnɪfaɪ. Pronounced to rhyme with "unify".
+
+A portmanteau word that also puns with "unify", to signify the joining of one codebase accessed from many languages.
+
+uni - [Latin ūni-, from ūnus, one]
+FFI - [Abbreviation, Foreign Function Interface]
+
+## Alternative tools
+
+Other tools we know of which try and solve a similarly shaped problem are:
+
+* [Diplomat](https://github.com/rust-diplomat/diplomat/) - see our [writeup of
+ the different approach taken by that tool](docs/diplomat-and-macros.md)
+* [Interoptopus](https://github.com/ralfbiedert/interoptopus/)
+
+(Please open a PR if you think other tools should be listed!)
+
+## Third-party foreign language bindings
+
+* [Kotlin Multiplatform support](https://gitlab.com/trixnity/uniffi-kotlin-multiplatform-bindings). The repository contains Kotlin Multiplatform bindings generation for UniFFI, letting you target both JVM and Native.
+* [Go bindings](https://github.com/NordSecurity/uniffi-bindgen-go)
+* [C# bindings](https://github.com/NordSecurity/uniffi-bindgen-cs)
+* [Dart bindings](https://github.com/NiallBunting/uniffi-rs-dart)
+
+### External resources
+
+There are a few third-party resources that make it easier to work with UniFFI:
+
+* [Plugin support for `.udl` files](https://github.com/Lonami/uniffi-dl) for the IDEA platform ([*uniffi-dl* in the JetBrains marketplace](https://plugins.jetbrains.com/plugin/20527-uniffi-dl)). It provides syntax highlighting, code folding, code completion, reference resolution and navigation (among others features) for the [UniFFI Definition Language (UDL)](https://mozilla.github.io/uniffi-rs/).
+* [cargo swift](https://github.com/antoniusnaumann/cargo-swift), a cargo plugin to build a Swift Package from Rust code. It provides an init command for setting up a UniFFI crate and a package command for building a Swift package from Rust code - without the need for additional configuration or build scripts.
+* [Cargo NDK Gradle Plugin](https://github.com/willir/cargo-ndk-android-gradle) allows you to build Rust code using [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk), which generally makes Android library builds less painful.
+* [`uniffi-starter`](https://github.com/ianthetechie/uniffi-starter) is a minimal project demonstrates a wide range of UniFFI in a complete project in a compact manner. It includes a full Android library build process, an XCFramework generation script, and example Swift package structure.
+
+(Please open a PR if you think other resources should be listed!)
+
+## Contributing
+
+If this tool sounds interesting to you, please help us develop it! You can:
+
+* View the [contributor guidelines](./docs/contributing.md).
+* File or work on [issues](https://github.com/mozilla/uniffi-rs/issues) here in GitHub.
+* Join discussions in the [#uniffi:mozilla.org](https://matrix.to/#/#uniffi:mozilla.org)
+ room on Matrix.
+
+## Code of Conduct
+
+This project is governed by Mozilla's [Community Participation Guidelines](./CODE_OF_CONDUCT.md).
diff --git a/third_party/rust/uniffi_udl/src/attributes.rs b/third_party/rust/uniffi_udl/src/attributes.rs
index f06b4f29c1..7bb05808c4 100644
--- a/third_party/rust/uniffi_udl/src/attributes.rs
+++ b/third_party/rust/uniffi_udl/src/attributes.rs
@@ -37,10 +37,29 @@ pub(super) enum Attribute {
kind: ExternalKind,
export: bool,
},
+ Rust {
+ kind: RustKind,
+ },
// Custom type on the scaffolding side
Custom,
// The interface described is implemented as a trait.
Trait,
+ // Modifies `Trait` to enable foreign implementations (callback interfaces)
+ WithForeign,
+ Async,
+ NonExhaustive,
+}
+
+// A type defined in Rust via procmacros but which should be available
+// in UDL.
+#[derive(Debug, Copy, Clone, Checksum)]
+pub(super) enum RustKind {
+ Object,
+ CallbackTrait,
+ Trait,
+ Record,
+ Enum,
+ CallbackInterface,
}
impl Attribute {
@@ -67,6 +86,9 @@ impl TryFrom<&weedle::attribute::ExtendedAttribute<'_>> for Attribute {
"Error" => Ok(Attribute::Error),
"Custom" => Ok(Attribute::Custom),
"Trait" => Ok(Attribute::Trait),
+ "WithForeign" => Ok(Attribute::WithForeign),
+ "Async" => Ok(Attribute::Async),
+ "NonExhaustive" => Ok(Attribute::NonExhaustive),
_ => anyhow::bail!("ExtendedAttributeNoArgs not supported: {:?}", (attr.0).0),
},
// Matches assignment-style attributes like ["Throws=Error"]
@@ -95,6 +117,19 @@ impl TryFrom<&weedle::attribute::ExtendedAttribute<'_>> for Attribute {
kind: ExternalKind::Interface,
export: true,
}),
+ "ExternalTrait" => Ok(Attribute::External {
+ crate_name: name_from_id_or_string(&identity.rhs),
+ kind: ExternalKind::Trait,
+ export: false,
+ }),
+ "ExternalTraitExport" => Ok(Attribute::External {
+ crate_name: name_from_id_or_string(&identity.rhs),
+ kind: ExternalKind::Trait,
+ export: true,
+ }),
+ "Rust" => Ok(Attribute::Rust {
+ kind: rust_kind_from_id_or_string(&identity.rhs)?,
+ }),
_ => anyhow::bail!(
"Attribute identity Identifier not supported: {:?}",
identity.lhs_identifier.0
@@ -130,6 +165,26 @@ fn name_from_id_or_string(nm: &weedle::attribute::IdentifierOrString<'_>) -> Str
}
}
+fn rust_kind_from_id_or_string(nm: &weedle::attribute::IdentifierOrString<'_>) -> Result<RustKind> {
+ Ok(match nm {
+ weedle::attribute::IdentifierOrString::String(str_lit) => match str_lit.0 {
+ // support names which match either procmacro or udl
+ "interface" => RustKind::Object,
+ "object" => RustKind::Object,
+ "record" => RustKind::Record,
+ "dictionary" => RustKind::Record,
+ "enum" => RustKind::Enum,
+ "trait" => RustKind::Trait,
+ "callback" => RustKind::CallbackInterface,
+ "trait_with_foreign" => RustKind::CallbackTrait,
+ _ => anyhow::bail!("Unknown `[Rust=]` kind {:?}", str_lit.0),
+ },
+ weedle::attribute::IdentifierOrString::Identifier(_) => {
+ anyhow::bail!("Expected string attribute value, got identifier")
+ }
+ })
+}
+
/// Parse a weedle `ExtendedAttributeList` into a list of `Attribute`s,
/// erroring out on duplicates.
fn parse_attributes<F>(
@@ -161,7 +216,6 @@ where
}
/// Attributes that can be attached to an `enum` definition in the UDL.
-/// There's only one case here: using `[Error]` to mark an enum as an error class.
#[derive(Debug, Clone, Checksum, Default)]
pub(super) struct EnumAttributes(Vec<Attribute>);
@@ -169,6 +223,12 @@ impl EnumAttributes {
pub fn contains_error_attr(&self) -> bool {
self.0.iter().any(|attr| attr.is_error())
}
+
+ pub fn contains_non_exhaustive_attr(&self) -> bool {
+ self.0
+ .iter()
+ .any(|attr| matches!(attr, Attribute::NonExhaustive))
+ }
}
impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for EnumAttributes {
@@ -178,6 +238,10 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for EnumAttributes {
) -> Result<Self, Self::Error> {
let attrs = parse_attributes(weedle_attributes, |attr| match attr {
Attribute::Error => Ok(()),
+ Attribute::NonExhaustive => Ok(()),
+ // Allow `[Enum]`, since we may be parsing an attribute list from an interface with the
+ // `[Enum]` attribute.
+ Attribute::Enum => Ok(()),
_ => bail!(format!("{attr:?} not supported for enums")),
})?;
Ok(Self(attrs))
@@ -196,8 +260,9 @@ impl<T: TryInto<EnumAttributes, Error = anyhow::Error>> TryFrom<Option<T>> for E
/// Represents UDL attributes that might appear on a function.
///
-/// This supports the `[Throws=ErrorName]` attribute for functions that
-/// can produce an error.
+/// This supports:
+/// * `[Throws=ErrorName]` attribute for functions that can produce an error.
+/// * `[Async] for async functions
#[derive(Debug, Clone, Checksum, Default)]
pub(super) struct FunctionAttributes(Vec<Attribute>);
@@ -210,6 +275,10 @@ impl FunctionAttributes {
_ => None,
})
}
+
+ pub(super) fn is_async(&self) -> bool {
+ self.0.iter().any(|attr| matches!(attr, Attribute::Async))
+ }
}
impl FromIterator<Attribute> for FunctionAttributes {
@@ -224,7 +293,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for FunctionAttribut
weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
) -> Result<Self, Self::Error> {
let attrs = parse_attributes(weedle_attributes, |attr| match attr {
- Attribute::Throws(_) => Ok(()),
+ Attribute::Throws(_) | Attribute::Async => Ok(()),
_ => bail!(format!("{attr:?} not supported for functions")),
})?;
Ok(Self(attrs))
@@ -294,12 +363,25 @@ impl InterfaceAttributes {
self.0.iter().any(|attr| attr.is_error())
}
- pub fn object_impl(&self) -> ObjectImpl {
- if self.0.iter().any(|attr| matches!(attr, Attribute::Trait)) {
- ObjectImpl::Trait
- } else {
- ObjectImpl::Struct
- }
+ pub fn contains_trait(&self) -> bool {
+ self.0.iter().any(|attr| matches!(attr, Attribute::Trait))
+ }
+
+ pub fn contains_with_foreign(&self) -> bool {
+ self.0
+ .iter()
+ .any(|attr| matches!(attr, Attribute::WithForeign))
+ }
+
+ pub fn object_impl(&self) -> Result<ObjectImpl> {
+ Ok(
+ match (self.contains_trait(), self.contains_with_foreign()) {
+ (true, true) => ObjectImpl::CallbackTrait,
+ (true, false) => ObjectImpl::Trait,
+ (false, false) => ObjectImpl::Struct,
+ (false, true) => bail!("WithForeign can't be specified without Trait"),
+ },
+ )
}
pub fn get_traits(&self) -> Vec<String> {
self.0
@@ -321,6 +403,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for InterfaceAttribu
Attribute::Enum => Ok(()),
Attribute::Error => Ok(()),
Attribute::Trait => Ok(()),
+ Attribute::WithForeign => Ok(()),
Attribute::Traits(_) => Ok(()),
_ => bail!(format!("{attr:?} not supported for interface definition")),
})?;
@@ -373,6 +456,10 @@ impl ConstructorAttributes {
_ => None,
})
}
+
+ pub(super) fn is_async(&self) -> bool {
+ self.0.iter().any(|attr| matches!(attr, Attribute::Async))
+ }
}
impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for ConstructorAttributes {
@@ -383,6 +470,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for ConstructorAttri
let attrs = parse_attributes(weedle_attributes, |attr| match attr {
Attribute::Throws(_) => Ok(()),
Attribute::Name(_) => Ok(()),
+ Attribute::Async => Ok(()),
_ => bail!(format!("{attr:?} not supported for constructors")),
})?;
Ok(Self(attrs))
@@ -406,6 +494,10 @@ impl MethodAttributes {
})
}
+ pub(super) fn is_async(&self) -> bool {
+ self.0.iter().any(|attr| matches!(attr, Attribute::Async))
+ }
+
pub(super) fn get_self_by_arc(&self) -> bool {
self.0
.iter()
@@ -425,8 +517,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for MethodAttributes
weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
) -> Result<Self, Self::Error> {
let attrs = parse_attributes(weedle_attributes, |attr| match attr {
- Attribute::SelfType(_) => Ok(()),
- Attribute::Throws(_) => Ok(()),
+ Attribute::SelfType(_) | Attribute::Throws(_) | Attribute::Async => Ok(()),
_ => bail!(format!("{attr:?} not supported for methods")),
})?;
Ok(Self(attrs))
@@ -498,10 +589,18 @@ impl TypedefAttributes {
})
}
+ pub(super) fn rust_kind(&self) -> Option<RustKind> {
+ self.0.iter().find_map(|attr| match attr {
+ Attribute::Rust { kind, .. } => Some(*kind),
+ _ => None,
+ })
+ }
+
pub(super) fn external_tagged(&self) -> Option<bool> {
// If it was "exported" via a proc-macro the FfiConverter was not tagged.
self.0.iter().find_map(|attr| match attr {
Attribute::External { export, .. } => Some(!*export),
+ Attribute::Rust { .. } => Some(false),
_ => None,
})
}
@@ -513,7 +612,7 @@ impl TryFrom<&weedle::attribute::ExtendedAttributeList<'_>> for TypedefAttribute
weedle_attributes: &weedle::attribute::ExtendedAttributeList<'_>,
) -> Result<Self, Self::Error> {
let attrs = parse_attributes(weedle_attributes, |attr| match attr {
- Attribute::External { .. } | Attribute::Custom => Ok(()),
+ Attribute::External { .. } | Attribute::Custom | Attribute::Rust { .. } => Ok(()),
_ => bail!(format!("{attr:?} not supported for typedefs")),
})?;
Ok(Self(attrs))
@@ -641,14 +740,22 @@ mod test {
}
#[test]
- fn test_throws_attribute() {
+ fn test_function_attributes() {
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Throws=Error]").unwrap();
let attrs = FunctionAttributes::try_from(&node).unwrap();
assert!(matches!(attrs.get_throws_err(), Some("Error")));
+ assert!(!attrs.is_async());
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap();
let attrs = FunctionAttributes::try_from(&node).unwrap();
assert!(attrs.get_throws_err().is_none());
+ assert!(!attrs.is_async());
+
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Throws=Error, Async]").unwrap();
+ let attrs = FunctionAttributes::try_from(&node).unwrap();
+ assert!(matches!(attrs.get_throws_err(), Some("Error")));
+ assert!(attrs.is_async());
}
#[test]
@@ -673,22 +780,34 @@ mod test {
let attrs = MethodAttributes::try_from(&node).unwrap();
assert!(!attrs.get_self_by_arc());
assert!(matches!(attrs.get_throws_err(), Some("Error")));
+ assert!(!attrs.is_async());
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap();
let attrs = MethodAttributes::try_from(&node).unwrap();
assert!(!attrs.get_self_by_arc());
assert!(attrs.get_throws_err().is_none());
+ assert!(!attrs.is_async());
let (_, node) =
weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc, Throws=Error]").unwrap();
let attrs = MethodAttributes::try_from(&node).unwrap();
assert!(attrs.get_self_by_arc());
assert!(attrs.get_throws_err().is_some());
+ assert!(!attrs.is_async());
+
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc, Throws=Error, Async]")
+ .unwrap();
+ let attrs = MethodAttributes::try_from(&node).unwrap();
+ assert!(attrs.get_self_by_arc());
+ assert!(attrs.get_throws_err().is_some());
+ assert!(attrs.is_async());
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Self=ByArc]").unwrap();
let attrs = MethodAttributes::try_from(&node).unwrap();
assert!(attrs.get_self_by_arc());
assert!(attrs.get_throws_err().is_none());
+ assert!(!attrs.is_async());
}
#[test]
@@ -710,6 +829,11 @@ mod test {
let attrs = ConstructorAttributes::try_from(&node).unwrap();
assert!(matches!(attrs.get_throws_err(), Some("Error")));
assert!(matches!(attrs.get_name(), Some("MyFactory")));
+ assert!(!attrs.is_async());
+
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Async]").unwrap();
+ let attrs = ConstructorAttributes::try_from(&node).unwrap();
+ assert!(attrs.is_async());
}
#[test]
@@ -754,15 +878,24 @@ mod test {
fn test_trait_attribute() {
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Trait]").unwrap();
let attrs = InterfaceAttributes::try_from(&node).unwrap();
- assert_eq!(attrs.object_impl(), ObjectImpl::Trait);
+ assert_eq!(attrs.object_impl().unwrap(), ObjectImpl::Trait);
+
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Trait, WithForeign]").unwrap();
+ let attrs = InterfaceAttributes::try_from(&node).unwrap();
+ assert_eq!(attrs.object_impl().unwrap(), ObjectImpl::CallbackTrait);
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[]").unwrap();
let attrs = InterfaceAttributes::try_from(&node).unwrap();
- assert_eq!(attrs.object_impl(), ObjectImpl::Struct);
+ assert_eq!(attrs.object_impl().unwrap(), ObjectImpl::Struct);
+
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[WithForeign]").unwrap();
+ let attrs = InterfaceAttributes::try_from(&node).unwrap();
+ assert!(attrs.object_impl().is_err())
}
#[test]
- fn test_enum_attribute() {
+ fn test_enum_attribute_on_interface() {
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Enum]").unwrap();
let attrs = InterfaceAttributes::try_from(&node).unwrap();
assert!(matches!(attrs.contains_enum_attr(), true));
@@ -783,6 +916,38 @@ mod test {
);
}
+ // Test parsing attributes for enum definitions
+ #[test]
+ fn test_enum_attributes() {
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Error, NonExhaustive]").unwrap();
+ let attrs = EnumAttributes::try_from(&node).unwrap();
+ assert!(attrs.contains_error_attr());
+ assert!(attrs.contains_non_exhaustive_attr());
+
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Trait]").unwrap();
+ let err = EnumAttributes::try_from(&node).unwrap_err();
+ assert_eq!(err.to_string(), "Trait not supported for enums");
+ }
+
+ // Test parsing attributes for interface definitions with the `[Enum]` attribute
+ #[test]
+ fn test_enum_attributes_from_interface() {
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Enum]").unwrap();
+ assert!(EnumAttributes::try_from(&node).is_ok());
+
+ let (_, node) =
+ weedle::attribute::ExtendedAttributeList::parse("[Enum, Error, NonExhaustive]")
+ .unwrap();
+ let attrs = EnumAttributes::try_from(&node).unwrap();
+ assert!(attrs.contains_error_attr());
+ assert!(attrs.contains_non_exhaustive_attr());
+
+ let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Enum, Trait]").unwrap();
+ let err = EnumAttributes::try_from(&node).unwrap_err();
+ assert_eq!(err.to_string(), "Trait not supported for enums");
+ }
+
#[test]
fn test_other_attributes_not_supported_for_interfaces() {
let (_, node) = weedle::attribute::ExtendedAttributeList::parse("[Trait, ByRef]").unwrap();
diff --git a/third_party/rust/uniffi_udl/src/collectors.rs b/third_party/rust/uniffi_udl/src/collectors.rs
index 6a91ab4a93..de5489f5f9 100644
--- a/third_party/rust/uniffi_udl/src/collectors.rs
+++ b/third_party/rust/uniffi_udl/src/collectors.rs
@@ -5,7 +5,7 @@
//! # Collects metadata from UDL.
use crate::attributes;
-use crate::converters::APIConverter;
+use crate::converters::{convert_docstring, APIConverter};
use crate::finder;
use crate::resolver::TypeResolver;
use anyhow::{bail, Result};
@@ -138,6 +138,7 @@ impl From<InterfaceCollector> for uniffi_meta::MetadataGroup {
crate_name: value.types.module_path(),
name: value.types.namespace,
},
+ namespace_docstring: value.types.namespace_docstring.clone(),
items: value.items,
}
}
@@ -171,15 +172,13 @@ impl APIBuilder for weedle::Definition<'_> {
match self {
weedle::Definition::Namespace(d) => d.process(ci)?,
weedle::Definition::Enum(d) => {
+ let mut e: uniffi_meta::EnumMetadata = d.convert(ci)?;
// We check if the enum represents an error...
let attrs = attributes::EnumAttributes::try_from(d.attributes.as_ref())?;
if attrs.contains_error_attr() {
- let e: uniffi_meta::ErrorMetadata = d.convert(ci)?;
- ci.add_definition(e.into())?;
- } else {
- let e: uniffi_meta::EnumMetadata = d.convert(ci)?;
- ci.add_definition(e.into())?;
+ e.forced_flatness = Some(true);
}
+ ci.add_definition(e.into())?;
}
weedle::Definition::Dictionary(d) => {
let rec = d.convert(ci)?;
@@ -187,12 +186,9 @@ impl APIBuilder for weedle::Definition<'_> {
}
weedle::Definition::Interface(d) => {
let attrs = attributes::InterfaceAttributes::try_from(d.attributes.as_ref())?;
- if attrs.contains_enum_attr() {
+ if attrs.contains_enum_attr() || attrs.contains_error_attr() {
let e: uniffi_meta::EnumMetadata = d.convert(ci)?;
ci.add_definition(e.into())?;
- } else if attrs.contains_error_attr() {
- let e: uniffi_meta::ErrorMetadata = d.convert(ci)?;
- ci.add_definition(e.into())?;
} else {
let obj: uniffi_meta::ObjectMetadata = d.convert(ci)?;
ci.add_definition(obj.into())?;
@@ -218,6 +214,7 @@ impl APIBuilder for weedle::NamespaceDefinition<'_> {
if self.identifier.0 != ci.types.namespace {
bail!("duplicate namespace definition");
}
+ ci.types.namespace_docstring = self.docstring.as_ref().map(|v| convert_docstring(&v.0));
for func in self.members.body.convert(ci)? {
ci.add_definition(func.into())?;
}
@@ -229,6 +226,7 @@ impl APIBuilder for weedle::NamespaceDefinition<'_> {
pub(crate) struct TypeCollector {
/// The unique prefix that we'll use for namespacing when exposing this component's API.
pub namespace: String,
+ pub namespace_docstring: Option<String>,
pub crate_name: String,
diff --git a/third_party/rust/uniffi_udl/src/converters/callables.rs b/third_party/rust/uniffi_udl/src/converters/callables.rs
index 3e15bb8e02..dda3c3a3ce 100644
--- a/third_party/rust/uniffi_udl/src/converters/callables.rs
+++ b/third_party/rust/uniffi_udl/src/converters/callables.rs
@@ -5,6 +5,7 @@
use super::APIConverter;
use crate::attributes::ArgumentAttributes;
use crate::attributes::{ConstructorAttributes, FunctionAttributes, MethodAttributes};
+use crate::converters::convert_docstring;
use crate::literal::convert_default_value;
use crate::InterfaceCollector;
use anyhow::{bail, Result};
@@ -41,6 +42,7 @@ impl APIConverter<FieldMetadata> for weedle::argument::SingleArgument<'_> {
name: self.identifier.0.to_string(),
ty: type_,
default: None,
+ docstring: None,
})
}
}
@@ -89,6 +91,7 @@ impl APIConverter<FnMetadata> for weedle::namespace::OperationNamespaceMember<'_
Some(id) => id.0.to_string(),
};
let attrs = FunctionAttributes::try_from(self.attributes.as_ref())?;
+ let is_async = attrs.is_async();
let throws = match attrs.get_throws_err() {
None => None,
Some(name) => match ci.get_type(name) {
@@ -99,10 +102,11 @@ impl APIConverter<FnMetadata> for weedle::namespace::OperationNamespaceMember<'_
Ok(FnMetadata {
module_path: ci.module_path(),
name,
- is_async: false,
+ is_async,
return_type,
inputs: self.args.body.list.convert(ci)?,
throws,
+ docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
checksum: None,
})
}
@@ -122,10 +126,12 @@ impl APIConverter<ConstructorMetadata> for weedle::interface::ConstructorInterfa
name: String::from(attributes.get_name().unwrap_or("new")),
// We don't know the name of the containing `Object` at this point, fill it in later.
self_name: Default::default(),
+ is_async: attributes.is_async(),
// Also fill in checksum_fn_name later, since it depends on object_name
inputs: self.args.body.list.convert(ci)?,
throws,
checksum: None,
+ docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
})
}
}
@@ -140,6 +146,7 @@ impl APIConverter<MethodMetadata> for weedle::interface::OperationInterfaceMembe
}
let return_type = ci.resolve_return_type_expression(&self.return_type)?;
let attributes = MethodAttributes::try_from(self.attributes.as_ref())?;
+ let is_async = attributes.is_async();
let throws = match attributes.get_throws_err() {
Some(name) => match ci.get_type(name) {
@@ -164,12 +171,13 @@ impl APIConverter<MethodMetadata> for weedle::interface::OperationInterfaceMembe
},
// We don't know the name of the containing `Object` at this point, fill it in later.
self_name: Default::default(),
- is_async: false, // not supported in UDL
+ is_async,
inputs: self.args.body.list.convert(ci)?,
return_type,
throws,
takes_self_by_arc,
checksum: None,
+ docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
})
}
}
@@ -184,6 +192,7 @@ impl APIConverter<TraitMethodMetadata> for weedle::interface::OperationInterface
}
let return_type = ci.resolve_return_type_expression(&self.return_type)?;
let attributes = MethodAttributes::try_from(self.attributes.as_ref())?;
+ let is_async = attributes.is_async();
let throws = match attributes.get_throws_err() {
Some(name) => match ci.get_type(name) {
@@ -208,12 +217,13 @@ impl APIConverter<TraitMethodMetadata> for weedle::interface::OperationInterface
name
}
},
- is_async: false, // not supported in udl
+ is_async,
inputs: self.args.body.list.convert(ci)?,
return_type,
throws,
takes_self_by_arc,
checksum: None,
+ docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
})
}
}
diff --git a/third_party/rust/uniffi_udl/src/converters/enum_.rs b/third_party/rust/uniffi_udl/src/converters/enum_.rs
index a3e68fd23e..1615a1a7ca 100644
--- a/third_party/rust/uniffi_udl/src/converters/enum_.rs
+++ b/third_party/rust/uniffi_udl/src/converters/enum_.rs
@@ -3,19 +3,22 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use super::APIConverter;
-use crate::InterfaceCollector;
+use crate::{attributes::EnumAttributes, converters::convert_docstring, InterfaceCollector};
use anyhow::{bail, Result};
-use uniffi_meta::{EnumMetadata, ErrorMetadata, VariantMetadata};
+use uniffi_meta::{EnumMetadata, VariantMetadata};
-// Note that we have four `APIConverter` impls here - one for the `enum` case,
-// one for the `[Error] enum` case, and and one for the `[Enum] interface` case,
-// and one for the `[Error] interface` case.
+// Note that we have 2 `APIConverter` impls here - one for the `enum` case
+// (including an enum with `[Error]`), and one for the `[Error] interface` cas
+// (which is still an enum, but with different "flatness" characteristics.)
impl APIConverter<EnumMetadata> for weedle::EnumDefinition<'_> {
fn convert(&self, ci: &mut InterfaceCollector) -> Result<EnumMetadata> {
+ let attributes = EnumAttributes::try_from(self.attributes.as_ref())?;
Ok(EnumMetadata {
module_path: ci.module_path(),
name: self.identifier.0.to_string(),
+ forced_flatness: None,
+ discr_type: None,
variants: self
.values
.body
@@ -23,35 +26,15 @@ impl APIConverter<EnumMetadata> for weedle::EnumDefinition<'_> {
.iter()
.map::<Result<_>, _>(|v| {
Ok(VariantMetadata {
- name: v.0.to_string(),
+ name: v.value.0.to_string(),
+ discr: None,
fields: vec![],
+ docstring: v.docstring.as_ref().map(|v| convert_docstring(&v.0)),
})
})
.collect::<Result<Vec<_>>>()?,
- })
- }
-}
-
-impl APIConverter<ErrorMetadata> for weedle::EnumDefinition<'_> {
- fn convert(&self, ci: &mut InterfaceCollector) -> Result<ErrorMetadata> {
- Ok(ErrorMetadata::Enum {
- enum_: EnumMetadata {
- module_path: ci.module_path(),
- name: self.identifier.0.to_string(),
- variants: self
- .values
- .body
- .list
- .iter()
- .map::<Result<_>, _>(|v| {
- Ok(VariantMetadata {
- name: v.0.to_string(),
- fields: vec![],
- })
- })
- .collect::<Result<Vec<_>>>()?,
- },
- is_flat: true,
+ non_exhaustive: attributes.contains_non_exhaustive_attr(),
+ docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
})
}
}
@@ -61,11 +44,11 @@ impl APIConverter<EnumMetadata> for weedle::InterfaceDefinition<'_> {
if self.inheritance.is_some() {
bail!("interface inheritance is not supported for enum interfaces");
}
- // We don't need to check `self.attributes` here; if calling code has dispatched
- // to this impl then we already know there was an `[Enum]` attribute.
+ let attributes = EnumAttributes::try_from(self.attributes.as_ref())?;
Ok(EnumMetadata {
module_path: ci.module_path(),
name: self.identifier.0.to_string(),
+ forced_flatness: Some(false),
variants: self
.members
.body
@@ -78,41 +61,15 @@ impl APIConverter<EnumMetadata> for weedle::InterfaceDefinition<'_> {
),
})
.collect::<Result<Vec<_>>>()?,
+ discr_type: None,
+ non_exhaustive: attributes.contains_non_exhaustive_attr(),
+ docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
// Enums declared using the `[Enum] interface` syntax might have variants with fields.
//flat: false,
})
}
}
-impl APIConverter<ErrorMetadata> for weedle::InterfaceDefinition<'_> {
- fn convert(&self, ci: &mut InterfaceCollector) -> Result<ErrorMetadata> {
- if self.inheritance.is_some() {
- bail!("interface inheritance is not supported for enum interfaces");
- }
- // We don't need to check `self.attributes` here; callers have already checked them
- // to work out which version to dispatch to.
- Ok(ErrorMetadata::Enum {
- enum_: EnumMetadata {
- module_path: ci.module_path(),
- name: self.identifier.0.to_string(),
- variants: self
- .members
- .body
- .iter()
- .map::<Result<VariantMetadata>, _>(|member| match member {
- weedle::interface::InterfaceMember::Operation(t) => Ok(t.convert(ci)?),
- _ => bail!(
- "interface member type {:?} not supported in enum interface",
- member
- ),
- })
- .collect::<Result<Vec<_>>>()?,
- },
- is_flat: false,
- })
- }
-}
-
#[cfg(test)]
mod test {
use super::*;
diff --git a/third_party/rust/uniffi_udl/src/converters/interface.rs b/third_party/rust/uniffi_udl/src/converters/interface.rs
index 58e6a9c8a0..ef9bdd9540 100644
--- a/third_party/rust/uniffi_udl/src/converters/interface.rs
+++ b/third_party/rust/uniffi_udl/src/converters/interface.rs
@@ -4,7 +4,7 @@
use super::APIConverter;
use crate::attributes::InterfaceAttributes;
-use crate::InterfaceCollector;
+use crate::{converters::convert_docstring, InterfaceCollector};
use anyhow::{bail, Result};
use std::collections::HashSet;
use uniffi_meta::{
@@ -23,7 +23,7 @@ impl APIConverter<ObjectMetadata> for weedle::InterfaceDefinition<'_> {
};
let object_name = self.identifier.0;
- let object_impl = attributes.object_impl();
+ let object_impl = attributes.object_impl()?;
// Convert each member into a constructor or method, guarding against duplicate names.
// They get added to the ci and aren't carried in ObjectMetadata.
let mut member_names = HashSet::new();
@@ -70,6 +70,7 @@ impl APIConverter<ObjectMetadata> for weedle::InterfaceDefinition<'_> {
throws: None,
takes_self_by_arc: false,
checksum: None,
+ docstring: None,
})
};
// Trait methods are in the Metadata.
@@ -130,6 +131,7 @@ impl APIConverter<ObjectMetadata> for weedle::InterfaceDefinition<'_> {
module_path: ci.module_path(),
name: object_name.to_string(),
imp: object_impl,
+ docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
})
}
}
diff --git a/third_party/rust/uniffi_udl/src/converters/mod.rs b/third_party/rust/uniffi_udl/src/converters/mod.rs
index 7a2d22ac42..195d9cc0b7 100644
--- a/third_party/rust/uniffi_udl/src/converters/mod.rs
+++ b/third_party/rust/uniffi_udl/src/converters/mod.rs
@@ -29,6 +29,11 @@ pub(crate) trait APIConverter<T> {
fn convert(&self, ci: &mut InterfaceCollector) -> Result<T>;
}
+// Convert UDL docstring into metadata docstring
+pub(crate) fn convert_docstring(docstring: &str) -> String {
+ textwrap::dedent(docstring)
+}
+
/// Convert a list of weedle items into a list of `InterfaceCollector` items,
/// by doing a direct item-by-item mapping.
impl<U, T: APIConverter<U>> APIConverter<Vec<U>> for Vec<T> {
@@ -72,6 +77,7 @@ impl APIConverter<VariantMetadata> for weedle::interface::OperationInterfaceMemb
};
Ok(VariantMetadata {
name,
+ discr: None,
fields: self
.args
.body
@@ -79,6 +85,7 @@ impl APIConverter<VariantMetadata> for weedle::interface::OperationInterfaceMemb
.iter()
.map(|arg| arg.convert(ci))
.collect::<Result<Vec<_>>>()?,
+ docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
})
}
}
@@ -95,6 +102,7 @@ impl APIConverter<RecordMetadata> for weedle::DictionaryDefinition<'_> {
module_path: ci.module_path(),
name: self.identifier.0.to_string(),
fields: self.members.body.convert(ci)?,
+ docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
})
}
}
@@ -113,6 +121,7 @@ impl APIConverter<FieldMetadata> for weedle::dictionary::DictionaryMember<'_> {
name: self.identifier.0.to_string(),
ty: type_,
default,
+ docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
})
}
}
@@ -150,6 +159,7 @@ impl APIConverter<CallbackInterfaceMetadata> for weedle::CallbackInterfaceDefini
Ok(CallbackInterfaceMetadata {
module_path: ci.module_path(),
name: object_name.to_string(),
+ docstring: self.docstring.as_ref().map(|v| convert_docstring(&v.0)),
})
}
}
diff --git a/third_party/rust/uniffi_udl/src/finder.rs b/third_party/rust/uniffi_udl/src/finder.rs
index 0c4c187dc0..259557ad07 100644
--- a/third_party/rust/uniffi_udl/src/finder.rs
+++ b/third_party/rust/uniffi_udl/src/finder.rs
@@ -22,8 +22,8 @@ use std::convert::TryFrom;
use anyhow::{bail, Result};
use super::TypeCollector;
-use crate::attributes::{InterfaceAttributes, TypedefAttributes};
-use uniffi_meta::Type;
+use crate::attributes::{InterfaceAttributes, RustKind, TypedefAttributes};
+use uniffi_meta::{ObjectImpl, Type};
/// Trait to help with an early "type discovery" phase when processing the UDL.
///
@@ -75,7 +75,7 @@ impl TypeFinder for weedle::InterfaceDefinition<'_> {
Type::Object {
name,
module_path: types.module_path(),
- imp: attrs.object_impl(),
+ imp: attrs.object_impl()?,
},
)
}
@@ -111,7 +111,6 @@ impl TypeFinder for weedle::EnumDefinition<'_> {
impl TypeFinder for weedle::TypedefDefinition<'_> {
fn add_type_definitions_to(&self, types: &mut TypeCollector) -> Result<()> {
- let name = self.identifier.0;
let attrs = TypedefAttributes::try_from(self.attributes.as_ref())?;
// If we wanted simple `typedef`s, it would be as easy as:
// > let t = types.resolve_type_expression(&self.type_)?;
@@ -122,29 +121,52 @@ impl TypeFinder for weedle::TypedefDefinition<'_> {
// `FfiConverter` implementation.
let builtin = types.resolve_type_expression(&self.type_)?;
types.add_type_definition(
- name,
+ self.identifier.0,
Type::Custom {
module_path: types.module_path(),
- name: name.to_string(),
+ name: self.identifier.0.to_string(),
builtin: builtin.into(),
},
)
} else {
- let kind = attrs.external_kind().expect("External missing");
- let tagged = attrs.external_tagged().expect("External missing");
+ let module_path = types.module_path();
+ let name = self.identifier.0.to_string();
+ let ty = match attrs.rust_kind() {
+ Some(RustKind::Object) => Type::Object {
+ module_path,
+ name,
+ imp: ObjectImpl::Struct,
+ },
+ Some(RustKind::Trait) => Type::Object {
+ module_path,
+ name,
+ imp: ObjectImpl::Trait,
+ },
+ Some(RustKind::CallbackTrait) => Type::Object {
+ module_path,
+ name,
+ imp: ObjectImpl::CallbackTrait,
+ },
+ Some(RustKind::Record) => Type::Record { module_path, name },
+ Some(RustKind::Enum) => Type::Enum { module_path, name },
+ Some(RustKind::CallbackInterface) => Type::CallbackInterface { module_path, name },
+ // must be external
+ None => {
+ let kind = attrs.external_kind().expect("External missing kind");
+ let tagged = attrs.external_tagged().expect("External missing tagged");
+ Type::External {
+ name,
+ namespace: "".to_string(), // we don't know this yet
+ module_path: attrs.get_crate_name(),
+ kind,
+ tagged,
+ }
+ }
+ };
// A crate which can supply an `FfiConverter`.
// We don't reference `self._type`, so ideally we could insist on it being
// the literal 'extern' but that's tricky
- types.add_type_definition(
- name,
- Type::External {
- name: name.to_string(),
- namespace: "".to_string(), // we don't know this yet
- module_path: attrs.get_crate_name(),
- kind,
- tagged,
- },
- )
+ types.add_type_definition(self.identifier.0, ty)
}
}
}
@@ -152,7 +174,7 @@ impl TypeFinder for weedle::TypedefDefinition<'_> {
impl TypeFinder for weedle::CallbackInterfaceDefinition<'_> {
fn add_type_definitions_to(&self, types: &mut TypeCollector) -> Result<()> {
if self.attributes.is_some() {
- bail!("no typedef attributes are currently supported");
+ bail!("no callback interface attributes are currently supported");
}
let name = self.identifier.0.to_string();
types.add_type_definition(
diff --git a/third_party/rust/uniffi_udl/src/lib.rs b/third_party/rust/uniffi_udl/src/lib.rs
index 5e6e72a7f7..a9dad5d6c5 100644
--- a/third_party/rust/uniffi_udl/src/lib.rs
+++ b/third_party/rust/uniffi_udl/src/lib.rs
@@ -6,7 +6,7 @@
//!
//! This library is dedicated to parsing a string in a webidl syntax, as described by
//! weedle and with our own custom take on the attributes etc, pushing the boundaries
-//! of that syntax to describe a uniffi `MetatadataGroup`.
+//! of that syntax to describe a uniffi `MetadataGroup`.
//!
//! The output of this module is consumed by uniffi_bindgen to generate stuff.
diff --git a/third_party/rust/uniffi_udl/src/literal.rs b/third_party/rust/uniffi_udl/src/literal.rs
index 78f2544254..5fbf022644 100644
--- a/third_party/rust/uniffi_udl/src/literal.rs
+++ b/third_party/rust/uniffi_udl/src/literal.rs
@@ -84,8 +84,10 @@ pub(super) fn convert_default_value(
(weedle::literal::DefaultValue::String(s), Type::Enum { .. }) => {
Literal::Enum(s.0.to_string(), type_.clone())
}
- (weedle::literal::DefaultValue::Null(_), Type::Optional { .. }) => Literal::Null,
- (_, Type::Optional { inner_type, .. }) => convert_default_value(default_value, inner_type)?,
+ (weedle::literal::DefaultValue::Null(_), Type::Optional { .. }) => Literal::None,
+ (_, Type::Optional { inner_type, .. }) => Literal::Some {
+ inner: Box::new(convert_default_value(default_value, inner_type)?),
+ },
// We'll ensure the type safety in the convert_* number methods.
(weedle::literal::DefaultValue::Integer(i), _) => convert_integer(i, type_)?,
@@ -144,7 +146,7 @@ mod test {
inner_type: Box::new(Type::String)
}
)?,
- Literal::Null
+ Literal::None
));
Ok(())
}
diff --git a/third_party/rust/uniffi_udl/src/resolver.rs b/third_party/rust/uniffi_udl/src/resolver.rs
index 14a7a4c6f1..ea98cd7a99 100644
--- a/third_party/rust/uniffi_udl/src/resolver.rs
+++ b/third_party/rust/uniffi_udl/src/resolver.rs
@@ -209,7 +209,6 @@ pub(crate) fn resolve_builtin_type(name: &str) -> Option<Type> {
"f64" => Some(Type::Float64),
"timestamp" => Some(Type::Timestamp),
"duration" => Some(Type::Duration),
- "ForeignExecutor" => Some(Type::ForeignExecutor),
_ => None,
}
}
diff --git a/third_party/rust/wasm-encoder/.cargo-checksum.json b/third_party/rust/wasm-encoder/.cargo-checksum.json
index 4c82ccdb63..b9591f5309 100644
--- a/third_party/rust/wasm-encoder/.cargo-checksum.json
+++ b/third_party/rust/wasm-encoder/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"fc84e9fbaaf9adbac39c8099ddbf6bb252a332f84e569392a7efe9a9712c1829","LICENSE":"268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5","README.md":"ac016c4843a7e1a5737255b39418732783592222dc518020730edf9dd46a1c13","src/component.rs":"700351503077106eadf612f46079418e0b437ed0a6179dc16979768205b2ecbf","src/component/aliases.rs":"be5215154b872ed5664f3bfe2aa1391f36a2575aa9d53971ab61868a1c446e9d","src/component/builder.rs":"ea35c74cb0369181e9c0ce9729ff175d25f05bd63b48dcbbf703a53dfc113c7c","src/component/canonicals.rs":"a4f9c7a2b8ac3783d7337a5579cf71df2f33b315b354965980bd2c483d3aa141","src/component/components.rs":"07b8cae3a400e1955cc39d142569910b4fef2136021053d0ddbd6404d810aa52","src/component/exports.rs":"1af747401f153ccd5c5adfcab55dc4d75e241536cbbdc62624c3a07dd7620056","src/component/imports.rs":"a6cd7f914de7706a1a93a40fb8d0031b4b140786a347da636c70d093401079cd","src/component/instances.rs":"a76c5e7ba0ef73ae4bff5569d177198b7f3a9959dbe9f5c1b4bae7d1a9c839b1","src/component/modules.rs":"9e80907e72360fae4d8057b6b0e7a6b58edd7ba6aba6e63ba17346518e169617","src/component/names.rs":"f3b6691f822d53eb743b397a4e735786501cf000451753ae9a65031ae3249835","src/component/start.rs":"4055553d5c99c99abbc01bb8abb928ecb8b909d148b647a9977fbd444bb464a3","src/component/types.rs":"f44501e9b356e5588767323f1844e768221e00843a33354ff2597a76f32def46","src/core.rs":"a00656f82a623656c59a2d7230b40a5849a5083e117bc57061746f6e3022c7bb","src/core/code.rs":"06cc521073c2cd0e33e0e417f3721b9c63b2c64ba778a927c98fb6737c8cdc34","src/core/custom.rs":"4b9f07b701dc7b6990d92fb43016c28fb971411670bb6a48553b92b1c35eb9a3","src/core/data.rs":"c9d59eab2ab811bd950da52e6766c7df2367986c7a3a0d94d7aeb47d86f203ac","src/core/dump.rs":"8feaa532e3851186277ec1f4906e7fdc82c6399b211b8c928b16e293db1205b0","src/core/elements.rs":"2df1ad85f683b5cf3ce43c8cfef3f08f74a17cec9171537e3f24205dc067b4f7","src/core/exports.rs":"9e1eb3db0e0f73e00b66d82431257923feec8c052ac8b98717c8f36ffcc03537","src/core/functions.rs":"c18b9872ac0c21048a3ce32e5e44e8e702f97a57fa1b3a07bdd98c7f6c820f09","src/core/globals.rs":"0ed5f033489318d91e974090e8bf240720baa1ba621d0b5e0956dbd68e2165c1","src/core/imports.rs":"b4b096a6b1f449c4429efff605ca597549ea31873e18a9e6e55c616b4edc76db","src/core/linking.rs":"2f1053d9c2671e91a2b6e253dd38921bfc5f1b8a1a047b10c843033fe0f492de","src/core/memories.rs":"42e11ff5e8b2634bcd87810aef3fb9057c5d0b23c68ca9862a1e99aab5ecad72","src/core/names.rs":"2624a58835b27b17260fdbd20704856dce04e1826eb2b3b6196f8f1ffcf560ef","src/core/producers.rs":"f4916c1cf61e26170cd10fe350de7dad18005461c362909b9557c16c507517bc","src/core/start.rs":"a01d4a91bcd93048977ccafc6af160357297450395bf598351f5a3e6d322e0de","src/core/tables.rs":"c8286f5624ff429d42879932dedf45975513abfd98071a4084271860225af078","src/core/tags.rs":"66251072d5108dfde8177039cef95deac21606a361cddb04f37c08b8a8969db4","src/core/types.rs":"eb61f4f53a6c283ff6788c9724ffb728d2f525a62230bd13494a797ee23fbfef","src/lib.rs":"e61325ab8de1b4c404f4ee7665eef404fca2d23322a9c8f94efec8426edc166b","src/raw.rs":"a6a72cfe8f88ea6476eccee4acf362030ba2d2e5710215bc4f13cde7de6d71ae"},"package":"d162eb64168969ae90e8668ca0593b0e47667e315aa08e717a9c9574d700d826"} \ No newline at end of file
+{"files":{"Cargo.toml":"e0d334beb383d7a06a870e93a43cee25fb37c9c416b01e056a8c5589e9b5ae0c","LICENSE":"268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5","README.md":"ac016c4843a7e1a5737255b39418732783592222dc518020730edf9dd46a1c13","src/component.rs":"700351503077106eadf612f46079418e0b437ed0a6179dc16979768205b2ecbf","src/component/aliases.rs":"be5215154b872ed5664f3bfe2aa1391f36a2575aa9d53971ab61868a1c446e9d","src/component/builder.rs":"ea35c74cb0369181e9c0ce9729ff175d25f05bd63b48dcbbf703a53dfc113c7c","src/component/canonicals.rs":"a4f9c7a2b8ac3783d7337a5579cf71df2f33b315b354965980bd2c483d3aa141","src/component/components.rs":"07b8cae3a400e1955cc39d142569910b4fef2136021053d0ddbd6404d810aa52","src/component/exports.rs":"1af747401f153ccd5c5adfcab55dc4d75e241536cbbdc62624c3a07dd7620056","src/component/imports.rs":"a6cd7f914de7706a1a93a40fb8d0031b4b140786a347da636c70d093401079cd","src/component/instances.rs":"a76c5e7ba0ef73ae4bff5569d177198b7f3a9959dbe9f5c1b4bae7d1a9c839b1","src/component/modules.rs":"9e80907e72360fae4d8057b6b0e7a6b58edd7ba6aba6e63ba17346518e169617","src/component/names.rs":"ca2f46a6dbc872a9cdb297d58c958e16acaf8893318bfdba30e468841107d886","src/component/start.rs":"4055553d5c99c99abbc01bb8abb928ecb8b909d148b647a9977fbd444bb464a3","src/component/types.rs":"f44501e9b356e5588767323f1844e768221e00843a33354ff2597a76f32def46","src/core.rs":"a00656f82a623656c59a2d7230b40a5849a5083e117bc57061746f6e3022c7bb","src/core/code.rs":"23f96e90b57b1334b304bc66c9c39518d9d8baee1e2a7f5e3045f817019c62da","src/core/custom.rs":"4b9f07b701dc7b6990d92fb43016c28fb971411670bb6a48553b92b1c35eb9a3","src/core/data.rs":"c9d59eab2ab811bd950da52e6766c7df2367986c7a3a0d94d7aeb47d86f203ac","src/core/dump.rs":"8feaa532e3851186277ec1f4906e7fdc82c6399b211b8c928b16e293db1205b0","src/core/elements.rs":"2df1ad85f683b5cf3ce43c8cfef3f08f74a17cec9171537e3f24205dc067b4f7","src/core/exports.rs":"9e1eb3db0e0f73e00b66d82431257923feec8c052ac8b98717c8f36ffcc03537","src/core/functions.rs":"c18b9872ac0c21048a3ce32e5e44e8e702f97a57fa1b3a07bdd98c7f6c820f09","src/core/globals.rs":"0ed5f033489318d91e974090e8bf240720baa1ba621d0b5e0956dbd68e2165c1","src/core/imports.rs":"b4b096a6b1f449c4429efff605ca597549ea31873e18a9e6e55c616b4edc76db","src/core/linking.rs":"2f1053d9c2671e91a2b6e253dd38921bfc5f1b8a1a047b10c843033fe0f492de","src/core/memories.rs":"42e11ff5e8b2634bcd87810aef3fb9057c5d0b23c68ca9862a1e99aab5ecad72","src/core/names.rs":"2624a58835b27b17260fdbd20704856dce04e1826eb2b3b6196f8f1ffcf560ef","src/core/producers.rs":"f4916c1cf61e26170cd10fe350de7dad18005461c362909b9557c16c507517bc","src/core/start.rs":"a01d4a91bcd93048977ccafc6af160357297450395bf598351f5a3e6d322e0de","src/core/tables.rs":"c8286f5624ff429d42879932dedf45975513abfd98071a4084271860225af078","src/core/tags.rs":"66251072d5108dfde8177039cef95deac21606a361cddb04f37c08b8a8969db4","src/core/types.rs":"eb61f4f53a6c283ff6788c9724ffb728d2f525a62230bd13494a797ee23fbfef","src/lib.rs":"e61325ab8de1b4c404f4ee7665eef404fca2d23322a9c8f94efec8426edc166b","src/raw.rs":"a6a72cfe8f88ea6476eccee4acf362030ba2d2e5710215bc4f13cde7de6d71ae"},"package":"b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a"} \ No newline at end of file
diff --git a/third_party/rust/wasm-encoder/Cargo.toml b/third_party/rust/wasm-encoder/Cargo.toml
index 1f9750fd1f..aa4ce5bc7c 100644
--- a/third_party/rust/wasm-encoder/Cargo.toml
+++ b/third_party/rust/wasm-encoder/Cargo.toml
@@ -12,7 +12,7 @@
[package]
edition = "2021"
name = "wasm-encoder"
-version = "0.40.0"
+version = "0.201.0"
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
description = """
A low-level WebAssembly encoder.
@@ -27,7 +27,7 @@ repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wa
version = "0.2.4"
[dependencies.wasmparser]
-version = "0.120.0"
+version = "0.201.0"
optional = true
[dev-dependencies.anyhow]
@@ -35,3 +35,9 @@ version = "1.0.58"
[dev-dependencies.tempfile]
version = "3.2.0"
+
+[lints.clippy]
+all = "allow"
+
+[lints.rust]
+unsafe_code = "deny"
diff --git a/third_party/rust/wasm-encoder/src/component/names.rs b/third_party/rust/wasm-encoder/src/component/names.rs
index 99db10bda0..1cbb1062c6 100644
--- a/third_party/rust/wasm-encoder/src/component/names.rs
+++ b/third_party/rust/wasm-encoder/src/component/names.rs
@@ -1,7 +1,7 @@
use std::borrow::Cow;
use super::*;
-use crate::{encoding_size, CustomSection, Encode, ExportKind, NameMap, SectionId};
+use crate::{encoding_size, ExportKind, NameMap, SectionId};
/// Encoding for the `component-name` custom section which assigns
/// human-readable names to items within a component.
diff --git a/third_party/rust/wasm-encoder/src/core/code.rs b/third_party/rust/wasm-encoder/src/core/code.rs
index cec2d4191e..76bfd7afde 100644
--- a/third_party/rust/wasm-encoder/src/core/code.rs
+++ b/third_party/rust/wasm-encoder/src/core/code.rs
@@ -3188,6 +3188,11 @@ impl ConstExpr {
Self { bytes }
}
+ fn with_insn(mut self, insn: Instruction) -> Self {
+ insn.encode(&mut self.bytes);
+ self
+ }
+
/// Create a constant expression containing a single `global.get` instruction.
pub fn global_get(index: u32) -> Self {
Self::new_insn(Instruction::GlobalGet(index))
@@ -3227,6 +3232,90 @@ impl ConstExpr {
pub fn v128_const(value: i128) -> Self {
Self::new_insn(Instruction::V128Const(value))
}
+
+ /// Add a `global.get` instruction to this constant expression.
+ pub fn with_global_get(self, index: u32) -> Self {
+ self.with_insn(Instruction::GlobalGet(index))
+ }
+
+ /// Add a `ref.null` instruction to this constant expression.
+ pub fn with_ref_null(self, ty: HeapType) -> Self {
+ self.with_insn(Instruction::RefNull(ty))
+ }
+
+ /// Add a `ref.func` instruction to this constant expression.
+ pub fn with_ref_func(self, func: u32) -> Self {
+ self.with_insn(Instruction::RefFunc(func))
+ }
+
+ /// Add an `i32.const` instruction to this constant expression.
+ pub fn with_i32_const(self, value: i32) -> Self {
+ self.with_insn(Instruction::I32Const(value))
+ }
+
+ /// Add an `i64.const` instruction to this constant expression.
+ pub fn with_i64_const(self, value: i64) -> Self {
+ self.with_insn(Instruction::I64Const(value))
+ }
+
+ /// Add a `f32.const` instruction to this constant expression.
+ pub fn with_f32_const(self, value: f32) -> Self {
+ self.with_insn(Instruction::F32Const(value))
+ }
+
+ /// Add a `f64.const` instruction to this constant expression.
+ pub fn with_f64_const(self, value: f64) -> Self {
+ self.with_insn(Instruction::F64Const(value))
+ }
+
+ /// Add a `v128.const` instruction to this constant expression.
+ pub fn with_v128_const(self, value: i128) -> Self {
+ self.with_insn(Instruction::V128Const(value))
+ }
+
+ /// Add an `i32.add` instruction to this constant expression.
+ pub fn with_i32_add(self) -> Self {
+ self.with_insn(Instruction::I32Add)
+ }
+
+ /// Add an `i32.sub` instruction to this constant expression.
+ pub fn with_i32_sub(self) -> Self {
+ self.with_insn(Instruction::I32Sub)
+ }
+
+ /// Add an `i32.mul` instruction to this constant expression.
+ pub fn with_i32_mul(self) -> Self {
+ self.with_insn(Instruction::I32Mul)
+ }
+
+ /// Add an `i64.add` instruction to this constant expression.
+ pub fn with_i64_add(self) -> Self {
+ self.with_insn(Instruction::I64Add)
+ }
+
+ /// Add an `i64.sub` instruction to this constant expression.
+ pub fn with_i64_sub(self) -> Self {
+ self.with_insn(Instruction::I64Sub)
+ }
+
+ /// Add an `i64.mul` instruction to this constant expression.
+ pub fn with_i64_mul(self) -> Self {
+ self.with_insn(Instruction::I64Mul)
+ }
+
+ /// Returns the function, if any, referenced by this global.
+ pub fn get_ref_func(&self) -> Option<u32> {
+ let prefix = *self.bytes.get(0)?;
+ // 0xd2 == `ref.func` opcode, and if that's found then load the leb
+ // corresponding to the function index.
+ if prefix != 0xd2 {
+ return None;
+ }
+ leb128::read::unsigned(&mut &self.bytes[1..])
+ .ok()?
+ .try_into()
+ .ok()
+ }
}
impl Encode for ConstExpr {
diff --git a/third_party/rust/wasm-smith/.cargo-checksum.json b/third_party/rust/wasm-smith/.cargo-checksum.json
index 092ef268b6..d61edb195b 100644
--- a/third_party/rust/wasm-smith/.cargo-checksum.json
+++ b/third_party/rust/wasm-smith/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"b36ff9dd80b356c2fc8e62ec88a8e6433a387d4a44b71bd4b56106da793a1558","LICENSE":"268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5","README.md":"e7b2a96cf39834aa19aed7cad8658e33445cdec7c4957658ba39e3b82cc4254b","benches/corpus.rs":"2df29556be0799f0cb1f32c8d0ae5ba0c4b9815cf4d59a8b71744d926c0693a0","src/component.rs":"f605082d1e1ed2f5a277e9f7c1a0f36842fe5bc10d9ca816fbd301a380946617","src/component/encode.rs":"5985e3f412595e07372a0787ec3db6c3018840df2bcfc638ab8798fd74b22778","src/config.rs":"9e52b815c79e1b65af21b23c8148ca9330201bfff657626352d5055e5be77abb","src/core.rs":"a5e13de2fb7c97eb12f2c1bc439f617b8afe6c509868fcb92db5e2aa9f252e65","src/core/code_builder.rs":"2e9d1eb09711f3e4ec4334d6a3952d9b5aed827778c14a7e01fb9676640ac561","src/core/code_builder/no_traps.rs":"e595dbde06551f5f8b23e03cfac0634beacab08e6243c66d6ffda95079212b24","src/core/encode.rs":"76ac812803e16cebf852f6d1265a0bd9ef16661e87f77a15f3ba997e75b351ce","src/core/terminate.rs":"d24af5206a13aee7d6a6ea900ccdf088c09d053c36026cf1607cc38c972b3ba9","src/lib.rs":"3a5521a9d4f1f38d01206339b1997e2943d6129be7d434457d59d019e2937456","tests/available_imports.rs":"3157294aa496c9e571398bdbd907539350f1015368cf47e45ecf822473df1f4e","tests/component.rs":"44684e990e832590a0b348ce33fadf39ee55dec52fdfa9353570edcbf65325ee","tests/core.rs":"1f581629a235399127b525c3aa56f1fef5da54b7887824d7c40169642cb5adb1"},"package":"51803e2c31e82f93ec1796f781f9855844ff1203f993409eb74d1d17130928a7"} \ No newline at end of file
+{"files":{"Cargo.toml":"b911aa53dc1f5c0cb04a77e15b43c31cee98893c7bcb98b6d73405aeca6b447c","LICENSE":"268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5","README.md":"e7b2a96cf39834aa19aed7cad8658e33445cdec7c4957658ba39e3b82cc4254b","benches/corpus.rs":"2df29556be0799f0cb1f32c8d0ae5ba0c4b9815cf4d59a8b71744d926c0693a0","src/component.rs":"253cf8d8abf785e4b5a5c614561a85b032e7f7bdec366f83d9db0702f73d2122","src/component/encode.rs":"5985e3f412595e07372a0787ec3db6c3018840df2bcfc638ab8798fd74b22778","src/config.rs":"e75d93c0a6a5ba07ff3ec1164a9b58601fadc0a0899149e14a77e8f0ab4595e4","src/core.rs":"35d3854c8caa1f61494b2f8e2b5e150d67cf88582243feeeb198e2aa93247339","src/core/code_builder.rs":"a8b27f652c8ae4b964e05933df313016ab7ffc297ca76005faca3da2792e3afc","src/core/code_builder/no_traps.rs":"2f36c548a32a6a003e6bb22a8fe7c75517b9be00c5a299212836a7e2431a8693","src/core/encode.rs":"77e1395c78ecf1b688a3c515a58be6efe1aa58d450a20a50d69598c54f7162de","src/core/terminate.rs":"c7c5f5618d393e5d9e44ae5e9bbde4b81bfa1ca3d2111d1eea875e2490f0f64b","src/lib.rs":"d393ae9daba211ba68a48dbdee545e0570cb8298e4c62319b8c8a8b9355cd6e3","tests/available_imports.rs":"6d4c1b111f35fefce55b668474ac9ba25edf7fc3e3decb88696b644aec13d603","tests/common/mod.rs":"9d0712a0b3e30030934b04785fb8632576bdcc31921ee8f524e0b2c903dee28d","tests/component.rs":"44684e990e832590a0b348ce33fadf39ee55dec52fdfa9353570edcbf65325ee","tests/core.rs":"408a50da4438be4e5343b116aa80f5ce344ba9508fad413cc5c32cd0aa2164fb","tests/exports.rs":"dcac4e18f277cbdb821f4ad7eda0a900bd73924e0818cf1b408700c9b7bba789"},"package":"61ff53a54a853f174b0df74cdb1553f1451e7bcdc23b26b1379f664ee1913d1a"} \ No newline at end of file
diff --git a/third_party/rust/wasm-smith/Cargo.toml b/third_party/rust/wasm-smith/Cargo.toml
index bebb2807d5..3847409e33 100644
--- a/third_party/rust/wasm-smith/Cargo.toml
+++ b/third_party/rust/wasm-smith/Cargo.toml
@@ -12,7 +12,7 @@
[package]
edition = "2021"
name = "wasm-smith"
-version = "0.15.0"
+version = "0.201.0"
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
exclude = ["/benches/corpus"]
description = "A WebAssembly test case generator"
@@ -33,7 +33,6 @@ harness = false
[dependencies.anyhow]
version = "1.0.58"
-optional = true
[dependencies.arbitrary]
version = "1.1.0"
@@ -62,14 +61,14 @@ version = "1.0.166"
optional = true
[dependencies.wasm-encoder]
-version = "0.40.0"
+version = "0.201.0"
[dependencies.wasmparser]
-version = "0.120.0"
+version = "0.201.0"
optional = true
[dependencies.wat]
-version = "1.0.84"
+version = "1.201.0"
optional = true
[dev-dependencies.criterion]
@@ -80,11 +79,10 @@ version = "0.8.4"
features = ["small_rng"]
[dev-dependencies.wasmparser]
-version = "0.120.0"
+version = "0.201.0"
[features]
_internal_cli = [
- "anyhow",
"clap",
"flagset/serde",
"serde",
@@ -99,3 +97,9 @@ wasmparser = [
[target."cfg(not(target_family = \"wasm\"))".dev-dependencies.libfuzzer-sys]
version = "0.4.0"
+
+[lints.clippy]
+all = "allow"
+
+[lints.rust]
+unsafe_code = "deny"
diff --git a/third_party/rust/wasm-smith/src/component.rs b/third_party/rust/wasm-smith/src/component.rs
index e18373b4f4..7a85b2bf10 100644
--- a/third_party/rust/wasm-smith/src/component.rs
+++ b/third_party/rust/wasm-smith/src/component.rs
@@ -7,12 +7,13 @@
use crate::{arbitrary_loop, Config};
use arbitrary::{Arbitrary, Result, Unstructured};
use std::collections::BTreeMap;
-use std::convert::TryFrom;
use std::{
collections::{HashMap, HashSet},
rc::Rc,
};
-use wasm_encoder::{ComponentTypeRef, ComponentValType, PrimitiveValType, TypeBounds, ValType};
+use wasm_encoder::{
+ ComponentTypeRef, ComponentValType, HeapType, PrimitiveValType, RefType, TypeBounds, ValType,
+};
mod encode;
@@ -540,7 +541,7 @@ impl ComponentBuilder {
}
let ty = match u.int_in_range::<u8>(0..=1)? {
- 0 => CoreType::Func(crate::core::arbitrary_func_type(
+ 0 => CoreType::Func(arbitrary_func_type(
u,
&self.config,
&self.core_valtypes,
@@ -819,7 +820,7 @@ impl ComponentBuilder {
// Type definition.
2 => {
- let ty = crate::core::arbitrary_func_type(
+ let ty = arbitrary_func_type(
u,
&self.config,
&self.core_valtypes,
@@ -955,7 +956,7 @@ impl ComponentBuilder {
}
fn arbitrary_core_table_type(&self, u: &mut Unstructured) -> Result<crate::core::TableType> {
- crate::core::arbitrary_table_type(u, &self.config)
+ crate::core::arbitrary_table_type(u, &self.config, None)
}
fn arbitrary_core_memory_type(&self, u: &mut Unstructured) -> Result<crate::core::MemoryType> {
@@ -2176,3 +2177,45 @@ struct CoreInstanceSection {}
struct CoreTypeSection {
types: Vec<Rc<CoreType>>,
}
+
+fn arbitrary_func_type(
+ u: &mut Unstructured,
+ config: &Config,
+ valtypes: &[ValType],
+ max_results: Option<usize>,
+ type_ref_limit: u32,
+) -> Result<Rc<crate::core::FuncType>> {
+ let mut params = vec![];
+ let mut results = vec![];
+ arbitrary_loop(u, 0, 20, |u| {
+ params.push(arbitrary_valtype(u, config, valtypes, type_ref_limit)?);
+ Ok(true)
+ })?;
+ arbitrary_loop(u, 0, max_results.unwrap_or(20), |u| {
+ results.push(arbitrary_valtype(u, config, valtypes, type_ref_limit)?);
+ Ok(true)
+ })?;
+ Ok(Rc::new(crate::core::FuncType { params, results }))
+}
+
+fn arbitrary_valtype(
+ u: &mut Unstructured,
+ config: &Config,
+ valtypes: &[ValType],
+ type_ref_limit: u32,
+) -> Result<ValType> {
+ if config.gc_enabled && type_ref_limit > 0 && u.ratio(1, 20)? {
+ Ok(ValType::Ref(RefType {
+ // TODO: For now, only create allow nullable reference
+ // types. Eventually we should support non-nullable reference types,
+ // but this means that we will also need to recognize when it is
+ // impossible to create an instance of the reference (eg `(ref
+ // nofunc)` has no instances, and self-referential types that
+ // contain a non-null self-reference are also impossible to create).
+ nullable: true,
+ heap_type: HeapType::Concrete(u.int_in_range(0..=type_ref_limit - 1)?),
+ }))
+ } else {
+ Ok(*u.choose(valtypes)?)
+ }
+}
diff --git a/third_party/rust/wasm-smith/src/config.rs b/third_party/rust/wasm-smith/src/config.rs
index b183530e4c..43dbecceb8 100644
--- a/third_party/rust/wasm-smith/src/config.rs
+++ b/third_party/rust/wasm-smith/src/config.rs
@@ -20,13 +20,13 @@ macro_rules! define_config {
/// Defaults to `None` which means that any arbitrary import can be
/// generated.
///
- /// To only allow specific imports, override this method to return a
- /// WebAssembly module which describes the imports allowed.
+ /// To only allow specific imports, set this field to a WebAssembly
+ /// module which describes the imports allowed.
///
/// Note that [`Self::min_imports`] is ignored when
/// `available_imports` are enabled.
///
- /// The returned value must be a valid binary encoding of a
+ /// The provided value must be a valid binary encoding of a
/// WebAssembly module. `wasm-smith` will panic if the module cannot
/// be parsed.
///
@@ -49,6 +49,51 @@ macro_rules! define_config {
/// ```
pub available_imports: Option<Vec<u8>>,
+ /// If provided, the generated module will have exports with exactly
+ /// the same names and types as those in the provided WebAssembly
+ /// module. The implementation (e.g. function bodies, global
+ /// initializers) of each export in the generated module will be
+ /// random and unrelated to the implementation in the provided
+ /// module. Only globals and functions are supported.
+ ///
+ ///
+ /// Defaults to `None` which means arbitrary exports will be
+ /// generated.
+ ///
+ /// To specify which exports the generated modules should have, set
+ /// this field to a WebAssembly module which describes the desired
+ /// exports. To generate modules with varying exports that meet some
+ /// constraints, consider randomly generating the value for this
+ /// field.
+ ///
+ /// The provided value must be a valid binary encoding of a
+ /// WebAssembly module. `wasm-smith` will panic if the module cannot
+ /// be parsed.
+ ///
+ /// # Module Limits
+ ///
+ /// All types, functions, globals, and exports that are needed to
+ /// provide the required exports will be generated, even if it
+ /// causes the resulting module to exceed the limits defined in
+ /// [`Self::max_type_size`], [`Self::max_types`],
+ /// [`Self::max_funcs`], [`Self::max_globals`], or
+ /// [`Self::max_exports`].
+ ///
+ /// # Example
+ ///
+ /// As for [`Self::available_imports`], the `wat` crate can be used
+ /// to provide an human-readable description of the desired exports:
+ ///
+ /// ```rust
+ /// Some(wat::parse_str(r#"
+ /// (module
+ /// (func (export "foo") (param i32) (result i64) unreachable)
+ /// (global (export "bar") f32 f32.const 0)
+ /// )
+ /// "#));
+ /// ```
+ pub exports: Option<Vec<u8>>,
+
$(
$(#[$field_attr])*
pub $field: $field_ty,
@@ -59,6 +104,7 @@ macro_rules! define_config {
fn default() -> Config {
Config {
available_imports: None,
+ exports: None,
$(
$field: $default,
@@ -82,12 +128,44 @@ macro_rules! define_config {
/// Note that [`Self::min_imports`] is ignored when
/// `available_imports` are enabled.
///
- /// The returned value must be a valid binary encoding of a
+ /// The provided value must be a valid binary encoding of a
/// WebAssembly module. `wasm-smith` will panic if the module cannot
/// be parsed.
#[cfg_attr(feature = "clap", clap(long))]
available_imports: Option<std::path::PathBuf>,
+ /// If provided, the generated module will have exports with exactly
+ /// the same names and types as those in the provided WebAssembly
+ /// module. The implementation (e.g. function bodies, global
+ /// initializers) of each export in the generated module will be
+ /// random and unrelated to the implementation in the provided
+ /// module. Only globals and functions are supported.
+ ///
+ /// Defaults to `None` which means arbitrary exports will be
+ /// generated.
+ ///
+ /// To specify which exports the generated modules should have, set
+ /// this field to a WebAssembly module which describes the desired
+ /// exports. To generate modules with varying exports that meet some
+ /// constraints, consider randomly generating the value for this
+ /// field.
+ ///
+ /// The provided value must be a valid binary encoding of a
+ /// WebAssembly module. `wasm-smith` will panic if the module cannot
+ /// be parsed.
+ ///
+ /// # Module Limits
+ ///
+ /// All types, functions, globals, and exports that are needed to
+ /// provide the required exports will be generated, even if it
+ /// causes the resulting module to exceed the limits defined in
+ /// [`Self::max_type_size`], [`Self::max_types`],
+ /// [`Self::max_funcs`], [`Self::max_globals`], or
+ /// [`Self::max_exports`].
+ ///
+ #[cfg_attr(feature = "clap", clap(long))]
+ exports: Option<std::path::PathBuf>,
+
$(
$(#[$field_attr])*
#[cfg_attr(feature = "clap", clap(long))]
@@ -100,6 +178,7 @@ macro_rules! define_config {
pub fn or(self, other: Self) -> Self {
Self {
available_imports: self.available_imports.or(other.available_imports),
+ exports: self.exports.or(other.exports),
$(
$field: self.$field.or(other.$field),
@@ -121,6 +200,13 @@ macro_rules! define_config {
} else {
None
},
+ exports: if let Some(file) = config
+ .exports
+ .as_ref() {
+ Some(wat::parse_file(file)?)
+ } else {
+ None
+ },
$(
$field: config.$field.unwrap_or(default.$field),
@@ -481,6 +567,18 @@ define_config! {
///
/// Defaults to `false`.
pub threads_enabled: bool = false,
+
+ /// Indicates whether wasm-smith is allowed to generate invalid function
+ /// bodies.
+ ///
+ /// When enabled this option will enable taking raw bytes from the input
+ /// byte stream and using them as a wasm function body. This means that
+ /// the output module is not guaranteed to be valid but can help tickle
+ /// various parts of validation/compilation in some circumstances as
+ /// well.
+ ///
+ /// Defaults to `false`.
+ pub allow_invalid_funcs: bool = false,
}
}
@@ -611,12 +709,14 @@ impl<'a> Arbitrary<'a> for Config {
max_type_size: 1000,
canonicalize_nans: false,
available_imports: None,
+ exports: None,
threads_enabled: false,
export_everything: false,
disallow_traps: false,
tail_call_enabled: false,
gc_enabled: false,
generate_custom_sections: false,
+ allow_invalid_funcs: false,
})
}
}
diff --git a/third_party/rust/wasm-smith/src/core.rs b/third_party/rust/wasm-smith/src/core.rs
index 6a2836ae0e..194d2101ae 100644
--- a/third_party/rust/wasm-smith/src/core.rs
+++ b/third_party/rust/wasm-smith/src/core.rs
@@ -9,7 +9,8 @@ use arbitrary::{Arbitrary, Result, Unstructured};
use code_builder::CodeBuilderAllocations;
use flagset::{flags, FlagSet};
use std::collections::{HashMap, HashSet};
-use std::convert::TryFrom;
+use std::fmt;
+use std::mem;
use std::ops::Range;
use std::rc::Rc;
use std::str::{self, FromStr};
@@ -42,7 +43,6 @@ type Instruction = wasm_encoder::Instruction<'static>;
/// To configure the shape of generated module, create a
/// [`Config`][crate::Config] and then call [`Module::new`][crate::Module::new]
/// with it.
-#[derive(Debug)]
pub struct Module {
config: Config,
duplicate_imports_behavior: DuplicateImportsBehavior,
@@ -95,9 +95,8 @@ pub struct Module {
/// aliased).
num_defined_funcs: usize,
- /// The number of tables defined in this module (not imported or
- /// aliased).
- num_defined_tables: usize,
+ /// Initialization expressions for all defined tables in this module.
+ defined_tables: Vec<Option<ConstExpr>>,
/// The number of memories defined in this module (not imported or
/// aliased).
@@ -105,7 +104,7 @@ pub struct Module {
/// The indexes and initialization expressions of globals defined in this
/// module.
- defined_globals: Vec<(u32, GlobalInitExpr)>,
+ defined_globals: Vec<(u32, ConstExpr)>,
/// All tags available to this module, sorted by their index. The list
/// entry is the type of each tag.
@@ -140,6 +139,13 @@ pub struct Module {
/// Names currently exported from this module.
export_names: HashSet<String>,
+
+ /// Reusable buffer in `self.arbitrary_const_expr` to amortize the cost of
+ /// allocation.
+ const_expr_choices: Vec<Box<dyn Fn(&mut Unstructured, ValType) -> Result<ConstExpr>>>,
+
+ /// What the maximum type index that can be referenced is.
+ max_type_limit: MaxTypeLimit,
}
impl<'a> Arbitrary<'a> for Module {
@@ -148,12 +154,27 @@ impl<'a> Arbitrary<'a> for Module {
}
}
+impl fmt::Debug for Module {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Module")
+ .field("config", &self.config)
+ .field(&"...", &"...")
+ .finish()
+ }
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DuplicateImportsBehavior {
Allowed,
Disallowed,
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum MaxTypeLimit {
+ ModuleTypes,
+ Num(u32),
+}
+
impl Module {
/// Returns a reference to the internal configuration.
pub fn config(&self) -> &Config {
@@ -172,7 +193,7 @@ impl Module {
duplicate_imports_behavior: DuplicateImportsBehavior,
) -> Result<Self> {
let mut module = Module::empty(config, duplicate_imports_behavior);
- module.build(u, false)?;
+ module.build(u)?;
Ok(module)
}
@@ -194,7 +215,7 @@ impl Module {
num_imports: 0,
num_defined_tags: 0,
num_defined_funcs: 0,
- num_defined_tables: 0,
+ defined_tables: Vec::new(),
num_defined_memories: 0,
defined_globals: Vec::new(),
tags: Vec::new(),
@@ -209,34 +230,12 @@ impl Module {
data: Vec::new(),
type_size: 0,
export_names: HashSet::new(),
+ const_expr_choices: Vec::new(),
+ max_type_limit: MaxTypeLimit::ModuleTypes,
}
}
}
-/// Same as [`Module`], but may be invalid.
-///
-/// This module generates function bodies differnetly than `Module` to try to
-/// better explore wasm decoders and such.
-#[derive(Debug)]
-pub struct MaybeInvalidModule {
- module: Module,
-}
-
-impl MaybeInvalidModule {
- /// Encode this Wasm module into bytes.
- pub fn to_bytes(&self) -> Vec<u8> {
- self.module.to_bytes()
- }
-}
-
-impl<'a> Arbitrary<'a> for MaybeInvalidModule {
- fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
- let mut module = Module::empty(Config::default(), DuplicateImportsBehavior::Allowed);
- module.build(u, true)?;
- Ok(MaybeInvalidModule { module })
- }
-}
-
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct RecGroup {
pub(crate) types: Vec<SubType>,
@@ -357,7 +356,7 @@ enum ElementKind {
#[derive(Debug)]
enum Elements {
Functions(Vec<u32>),
- Expressions(Vec<Option<u32>>),
+ Expressions(Vec<ConstExpr>),
}
#[derive(Debug)]
@@ -391,14 +390,8 @@ pub(crate) enum Offset {
Global(u32),
}
-#[derive(Debug)]
-pub(crate) enum GlobalInitExpr {
- FuncRef(u32),
- ConstExpr(ConstExpr),
-}
-
impl Module {
- fn build(&mut self, u: &mut Unstructured, allow_invalid: bool) -> Result<()> {
+ fn build(&mut self, u: &mut Unstructured) -> Result<()> {
self.valtypes = configured_valtypes(&self.config);
// We attempt to figure out our available imports *before* creating the types section here,
@@ -413,7 +406,6 @@ impl Module {
self.arbitrary_imports(u)?;
}
- self.should_encode_types = !self.types.is_empty() || u.arbitrary()?;
self.should_encode_imports = !self.imports.is_empty() || u.arbitrary()?;
self.arbitrary_tags(u)?;
@@ -421,11 +413,14 @@ impl Module {
self.arbitrary_tables(u)?;
self.arbitrary_memories(u)?;
self.arbitrary_globals(u)?;
- self.arbitrary_exports(u)?;
+ if !self.required_exports(u)? {
+ self.arbitrary_exports(u)?;
+ };
+ self.should_encode_types = !self.types.is_empty() || u.arbitrary()?;
self.arbitrary_start(u)?;
self.arbitrary_elems(u)?;
self.arbitrary_data(u)?;
- self.arbitrary_code(u, allow_invalid)?;
+ self.arbitrary_code(u)?;
Ok(())
}
@@ -562,6 +557,8 @@ impl Module {
fn arbitrary_rec_group(&mut self, u: &mut Unstructured) -> Result<()> {
let rec_group_start = self.types.len();
+ assert!(matches!(self.max_type_limit, MaxTypeLimit::ModuleTypes));
+
if self.config.gc_enabled {
// With small probability, clone an existing rec group.
if self.clonable_rec_groups().next().is_some() && u.ratio(1, u8::MAX)? {
@@ -572,16 +569,20 @@ impl Module {
let max_rec_group_size = self.config.max_types - self.types.len();
let rec_group_size = u.int_in_range(0..=max_rec_group_size)?;
let type_ref_limit = u32::try_from(self.types.len() + rec_group_size).unwrap();
+ self.max_type_limit = MaxTypeLimit::Num(type_ref_limit);
for _ in 0..rec_group_size {
- let ty = self.arbitrary_sub_type(u, type_ref_limit)?;
+ let ty = self.arbitrary_sub_type(u)?;
self.add_type(ty);
}
} else {
let type_ref_limit = u32::try_from(self.types.len()).unwrap();
- let ty = self.arbitrary_sub_type(u, type_ref_limit)?;
+ self.max_type_limit = MaxTypeLimit::Num(type_ref_limit);
+ let ty = self.arbitrary_sub_type(u)?;
self.add_type(ty);
}
+ self.max_type_limit = MaxTypeLimit::ModuleTypes;
+
self.rec_groups.push(rec_group_start..self.types.len());
Ok(())
}
@@ -615,40 +616,27 @@ impl Module {
Ok(())
}
- fn arbitrary_sub_type(
- &mut self,
- u: &mut Unstructured,
- // NB: Types can be referenced up to (but not including)
- // `type_ref_limit`. It is an exclusive bound to avoid an option: to
- // disallow any reference to a concrete type (e.g. `(ref $type)`) you
- // can simply pass `0` here.
- type_ref_limit: u32,
- ) -> Result<SubType> {
+ fn arbitrary_sub_type(&mut self, u: &mut Unstructured) -> Result<SubType> {
if !self.config.gc_enabled {
- debug_assert_eq!(type_ref_limit, u32::try_from(self.types.len()).unwrap());
return Ok(SubType {
is_final: true,
supertype: None,
- composite_type: CompositeType::Func(self.arbitrary_func_type(u, type_ref_limit)?),
+ composite_type: CompositeType::Func(self.arbitrary_func_type(u)?),
});
}
if !self.can_subtype.is_empty() && u.ratio(1, 32_u8)? {
- self.arbitrary_sub_type_of_super_type(u, type_ref_limit)
+ self.arbitrary_sub_type_of_super_type(u)
} else {
Ok(SubType {
is_final: u.arbitrary()?,
supertype: None,
- composite_type: self.arbitrary_composite_type(u, type_ref_limit)?,
+ composite_type: self.arbitrary_composite_type(u)?,
})
}
}
- fn arbitrary_sub_type_of_super_type(
- &mut self,
- u: &mut Unstructured,
- type_ref_limit: u32,
- ) -> Result<SubType> {
+ fn arbitrary_sub_type_of_super_type(&mut self, u: &mut Unstructured) -> Result<SubType> {
let supertype = *u.choose(&self.can_subtype)?;
let mut composite_type = self.types[usize::try_from(supertype).unwrap()]
.composite_type
@@ -661,7 +649,7 @@ impl Module {
*f = self.arbitrary_matching_func_type(u, f)?;
}
CompositeType::Struct(s) => {
- *s = self.arbitrary_matching_struct_type(u, s, type_ref_limit)?;
+ *s = self.arbitrary_matching_struct_type(u, s)?;
}
}
Ok(SubType {
@@ -675,7 +663,6 @@ impl Module {
&mut self,
u: &mut Unstructured,
ty: &StructType,
- type_ref_limit: u32,
) -> Result<StructType> {
let len_extra_fields = u.int_in_range(0..=5)?;
let mut fields = Vec::with_capacity(ty.fields.len() + len_extra_fields);
@@ -683,7 +670,7 @@ impl Module {
fields.push(self.arbitrary_matching_field_type(u, *field)?);
}
for _ in 0..len_extra_fields {
- fields.push(self.arbitrary_field_type(u, type_ref_limit)?);
+ fields.push(self.arbitrary_field_type(u)?);
}
Ok(StructType {
fields: fields.into_boxed_slice(),
@@ -915,71 +902,52 @@ impl Module {
Ok(*u.choose(&choices)?)
}
- fn arbitrary_composite_type(
- &mut self,
- u: &mut Unstructured,
- type_ref_limit: u32,
- ) -> Result<CompositeType> {
+ fn arbitrary_composite_type(&mut self, u: &mut Unstructured) -> Result<CompositeType> {
if !self.config.gc_enabled {
- return Ok(CompositeType::Func(
- self.arbitrary_func_type(u, type_ref_limit)?,
- ));
+ return Ok(CompositeType::Func(self.arbitrary_func_type(u)?));
}
match u.int_in_range(0..=2)? {
0 => Ok(CompositeType::Array(ArrayType(
- self.arbitrary_field_type(u, type_ref_limit)?,
+ self.arbitrary_field_type(u)?,
))),
- 1 => Ok(CompositeType::Func(
- self.arbitrary_func_type(u, type_ref_limit)?,
- )),
- 2 => Ok(CompositeType::Struct(
- self.arbitrary_struct_type(u, type_ref_limit)?,
- )),
+ 1 => Ok(CompositeType::Func(self.arbitrary_func_type(u)?)),
+ 2 => Ok(CompositeType::Struct(self.arbitrary_struct_type(u)?)),
_ => unreachable!(),
}
}
- fn arbitrary_struct_type(
- &mut self,
- u: &mut Unstructured,
- type_ref_limit: u32,
- ) -> Result<StructType> {
+ fn arbitrary_struct_type(&mut self, u: &mut Unstructured) -> Result<StructType> {
let len = u.int_in_range(0..=20)?;
let mut fields = Vec::with_capacity(len);
for _ in 0..len {
- fields.push(self.arbitrary_field_type(u, type_ref_limit)?);
+ fields.push(self.arbitrary_field_type(u)?);
}
Ok(StructType {
fields: fields.into_boxed_slice(),
})
}
- fn arbitrary_field_type(
- &mut self,
- u: &mut Unstructured,
- type_ref_limit: u32,
- ) -> Result<FieldType> {
+ fn arbitrary_field_type(&mut self, u: &mut Unstructured) -> Result<FieldType> {
Ok(FieldType {
- element_type: self.arbitrary_storage_type(u, type_ref_limit)?,
+ element_type: self.arbitrary_storage_type(u)?,
mutable: u.arbitrary()?,
})
}
- fn arbitrary_storage_type(
- &mut self,
- u: &mut Unstructured,
- type_ref_limit: u32,
- ) -> Result<StorageType> {
+ fn arbitrary_storage_type(&mut self, u: &mut Unstructured) -> Result<StorageType> {
match u.int_in_range(0..=2)? {
0 => Ok(StorageType::I8),
1 => Ok(StorageType::I16),
- 2 => Ok(StorageType::Val(self.arbitrary_valtype(u, type_ref_limit)?)),
+ 2 => Ok(StorageType::Val(self.arbitrary_valtype(u)?)),
_ => unreachable!(),
}
}
fn arbitrary_ref_type(&self, u: &mut Unstructured) -> Result<RefType> {
+ if !self.config.reference_types_enabled {
+ return Ok(RefType::FUNCREF);
+ }
Ok(RefType {
nullable: true,
heap_type: self.arbitrary_heap_type(u)?,
@@ -989,9 +957,13 @@ impl Module {
fn arbitrary_heap_type(&self, u: &mut Unstructured) -> Result<HeapType> {
assert!(self.config.reference_types_enabled);
- if self.config.gc_enabled && !self.types.is_empty() && u.arbitrary()? {
- let type_ref_limit = u32::try_from(self.types.len()).unwrap();
- let idx = u.int_in_range(0..=type_ref_limit)?;
+ let concrete_type_limit = match self.max_type_limit {
+ MaxTypeLimit::Num(n) => n,
+ MaxTypeLimit::ModuleTypes => u32::try_from(self.types.len()).unwrap(),
+ };
+
+ if self.config.gc_enabled && concrete_type_limit > 0 && u.arbitrary()? {
+ let idx = u.int_in_range(0..=concrete_type_limit - 1)?;
return Ok(HeapType::Concrete(idx));
}
@@ -1018,22 +990,24 @@ impl Module {
u.choose(&choices).copied()
}
- fn arbitrary_func_type(
- &mut self,
- u: &mut Unstructured,
- type_ref_limit: u32,
- ) -> Result<Rc<FuncType>> {
- arbitrary_func_type(
- u,
- &self.config,
- &self.valtypes,
- if !self.config.multi_value_enabled {
- Some(1)
- } else {
- None
- },
- type_ref_limit,
- )
+ fn arbitrary_func_type(&mut self, u: &mut Unstructured) -> Result<Rc<FuncType>> {
+ let mut params = vec![];
+ let mut results = vec![];
+ let max_params = 20;
+ arbitrary_loop(u, 0, max_params, |u| {
+ params.push(self.arbitrary_valtype(u)?);
+ Ok(true)
+ })?;
+ let max_results = if self.config.multi_value_enabled {
+ max_params
+ } else {
+ 1
+ };
+ arbitrary_loop(u, 0, max_results, |u| {
+ results.push(self.arbitrary_valtype(u)?);
+ Ok(true)
+ })?;
+ Ok(Rc::new(FuncType { params, results }))
}
fn can_add_local_or_import_tag(&self) -> bool {
@@ -1097,7 +1071,7 @@ impl Module {
}
if self.can_add_local_or_import_table() {
choices.push(|u, m| {
- let ty = arbitrary_table_type(u, m.config())?;
+ let ty = arbitrary_table_type(u, m.config(), Some(m))?;
Ok(EntityType::Table(ty))
});
}
@@ -1411,13 +1385,42 @@ impl Module {
.filter(move |i| self.func_type(*i).results.is_empty())
}
- fn arbitrary_valtype(&self, u: &mut Unstructured, type_ref_limit: u32) -> Result<ValType> {
- arbitrary_valtype(u, &self.config, &self.valtypes, type_ref_limit)
+ fn arbitrary_valtype(&self, u: &mut Unstructured) -> Result<ValType> {
+ #[derive(Arbitrary)]
+ enum ValTypeClass {
+ I32,
+ I64,
+ F32,
+ F64,
+ V128,
+ Ref,
+ }
+
+ match u.arbitrary::<ValTypeClass>()? {
+ ValTypeClass::I32 => Ok(ValType::I32),
+ ValTypeClass::I64 => Ok(ValType::I64),
+ ValTypeClass::F32 => Ok(ValType::F32),
+ ValTypeClass::F64 => Ok(ValType::F64),
+ ValTypeClass::V128 => {
+ if self.config.simd_enabled {
+ Ok(ValType::V128)
+ } else {
+ Ok(ValType::I32)
+ }
+ }
+ ValTypeClass::Ref => {
+ if self.config.reference_types_enabled {
+ Ok(ValType::Ref(self.arbitrary_ref_type(u)?))
+ } else {
+ Ok(ValType::I32)
+ }
+ }
+ }
}
fn arbitrary_global_type(&self, u: &mut Unstructured) -> Result<GlobalType> {
Ok(GlobalType {
- val_type: self.arbitrary_valtype(u, u32::try_from(self.types.len()).unwrap())?,
+ val_type: self.arbitrary_valtype(u)?,
mutable: u.arbitrary()?,
})
}
@@ -1470,14 +1473,38 @@ impl Module {
if !self.can_add_local_or_import_table() {
return Ok(false);
}
- self.num_defined_tables += 1;
- let ty = arbitrary_table_type(u, self.config())?;
+ let ty = arbitrary_table_type(u, self.config(), Some(self))?;
+ let init = self.arbitrary_table_init(u, ty.element_type)?;
+ self.defined_tables.push(init);
self.tables.push(ty);
Ok(true)
},
)
}
+ /// Generates an arbitrary table initialization expression for a table whose
+ /// element type is `ty`.
+ ///
+ /// Table initialization expressions were added by the GC proposal to
+ /// initialize non-nullable tables.
+ fn arbitrary_table_init(
+ &mut self,
+ u: &mut Unstructured,
+ ty: RefType,
+ ) -> Result<Option<ConstExpr>> {
+ if !self.config.gc_enabled {
+ assert!(ty.nullable);
+ return Ok(None);
+ }
+ // Even with the GC proposal an initialization expression is not
+ // required if the element type is nullable.
+ if ty.nullable && u.arbitrary()? {
+ return Ok(None);
+ }
+ let expr = self.arbitrary_const_expr(ValType::Ref(ty), u)?;
+ Ok(Some(expr))
+ }
+
fn arbitrary_memories(&mut self, u: &mut Unstructured) -> Result<()> {
arbitrary_loop(
u,
@@ -1494,53 +1521,260 @@ impl Module {
)
}
- fn arbitrary_globals(&mut self, u: &mut Unstructured) -> Result<()> {
- let mut choices: Vec<Box<dyn Fn(&mut Unstructured, ValType) -> Result<GlobalInitExpr>>> =
- vec![];
- let num_imported_globals = self.globals.len();
+ /// Add a new global of the given type and return its global index.
+ fn add_arbitrary_global_of_type(
+ &mut self,
+ ty: GlobalType,
+ u: &mut Unstructured,
+ ) -> Result<u32> {
+ let expr = self.arbitrary_const_expr(ty.val_type, u)?;
+ let global_idx = self.globals.len() as u32;
+ self.globals.push(ty);
+ self.defined_globals.push((global_idx, expr));
+ Ok(global_idx)
+ }
+
+ /// Generates an arbitrary constant expression of the type `ty`.
+ fn arbitrary_const_expr(&mut self, ty: ValType, u: &mut Unstructured) -> Result<ConstExpr> {
+ let mut choices = mem::take(&mut self.const_expr_choices);
+ choices.clear();
+ let num_funcs = self.funcs.len() as u32;
+
+ // MVP wasm can `global.get` any immutable imported global in a
+ // constant expression, and the GC proposal enables this for all
+ // globals, so make all matching globals a candidate.
+ for i in self.globals_for_const_expr(ty) {
+ choices.push(Box::new(move |_, _| Ok(ConstExpr::global_get(i))));
+ }
+
+ // Another option for all types is to have an actual value of each type.
+ // Change `ty` to any valid subtype of `ty` and then generate a matching
+ // type of that value.
+ let ty = self.arbitrary_matching_val_type(u, ty)?;
+ match ty {
+ ValType::I32 => choices.push(Box::new(|u, _| Ok(ConstExpr::i32_const(u.arbitrary()?)))),
+ ValType::I64 => choices.push(Box::new(|u, _| Ok(ConstExpr::i64_const(u.arbitrary()?)))),
+ ValType::F32 => choices.push(Box::new(|u, _| Ok(ConstExpr::f32_const(u.arbitrary()?)))),
+ ValType::F64 => choices.push(Box::new(|u, _| Ok(ConstExpr::f64_const(u.arbitrary()?)))),
+ ValType::V128 => {
+ choices.push(Box::new(|u, _| Ok(ConstExpr::v128_const(u.arbitrary()?))))
+ }
+ ValType::Ref(ty) => {
+ if ty.nullable {
+ choices.push(Box::new(move |_, _| Ok(ConstExpr::ref_null(ty.heap_type))));
+ }
+
+ match ty.heap_type {
+ HeapType::Func if num_funcs > 0 => {
+ choices.push(Box::new(move |u, _| {
+ let func = u.int_in_range(0..=num_funcs - 1)?;
+ Ok(ConstExpr::ref_func(func))
+ }));
+ }
+
+ HeapType::Concrete(ty) => {
+ for (i, fty) in self.funcs.iter().map(|(t, _)| *t).enumerate() {
+ if ty != fty {
+ continue;
+ }
+ choices.push(Box::new(move |_, _| Ok(ConstExpr::ref_func(i as u32))));
+ }
+ }
+
+ // TODO: fill out more GC types e.g `array.new` and
+ // `struct.new`
+ _ => {}
+ }
+ }
+ }
+
+ let f = u.choose(&choices)?;
+ let ret = f(u, ty);
+ self.const_expr_choices = choices;
+ ret
+ }
+
+ fn arbitrary_globals(&mut self, u: &mut Unstructured) -> Result<()> {
arbitrary_loop(u, self.config.min_globals, self.config.max_globals, |u| {
if !self.can_add_local_or_import_global() {
return Ok(false);
}
let ty = self.arbitrary_global_type(u)?;
+ self.add_arbitrary_global_of_type(ty, u)?;
- choices.clear();
- let num_funcs = self.funcs.len() as u32;
- choices.push(Box::new(move |u, ty| {
- Ok(GlobalInitExpr::ConstExpr(match ty {
- ValType::I32 => ConstExpr::i32_const(u.arbitrary()?),
- ValType::I64 => ConstExpr::i64_const(u.arbitrary()?),
- ValType::F32 => ConstExpr::f32_const(u.arbitrary()?),
- ValType::F64 => ConstExpr::f64_const(u.arbitrary()?),
- ValType::V128 => ConstExpr::v128_const(u.arbitrary()?),
- ValType::Ref(ty) => {
- assert!(ty.nullable);
- if ty.heap_type == HeapType::Func && num_funcs > 0 && u.arbitrary()? {
- let func = u.int_in_range(0..=num_funcs - 1)?;
- return Ok(GlobalInitExpr::FuncRef(func));
+ Ok(true)
+ })
+ }
+
+ fn required_exports(&mut self, u: &mut Unstructured) -> Result<bool> {
+ let example_module = if let Some(wasm) = self.config.exports.clone() {
+ wasm
+ } else {
+ return Ok(false);
+ };
+
+ #[cfg(feature = "wasmparser")]
+ {
+ self._required_exports(u, &example_module)?;
+ Ok(true)
+ }
+ #[cfg(not(feature = "wasmparser"))]
+ {
+ let _ = (example_module, u);
+ panic!("support for `exports` was disabled at compile time");
+ }
+ }
+
+ #[cfg(feature = "wasmparser")]
+ fn _required_exports(&mut self, u: &mut Unstructured, example_module: &[u8]) -> Result<()> {
+ fn convert_heap_type(ty: &wasmparser::HeapType) -> HeapType {
+ match ty {
+ wasmparser::HeapType::Concrete(_) => {
+ panic!("Unable to handle concrete types in exports")
+ }
+ wasmparser::HeapType::Func => HeapType::Func,
+ wasmparser::HeapType::Extern => HeapType::Extern,
+ wasmparser::HeapType::Any => HeapType::Any,
+ wasmparser::HeapType::None => HeapType::None,
+ wasmparser::HeapType::NoExtern => HeapType::NoExtern,
+ wasmparser::HeapType::NoFunc => HeapType::NoFunc,
+ wasmparser::HeapType::Eq => HeapType::Eq,
+ wasmparser::HeapType::Struct => HeapType::Struct,
+ wasmparser::HeapType::Array => HeapType::Array,
+ wasmparser::HeapType::I31 => HeapType::I31,
+ wasmparser::HeapType::Exn => HeapType::Exn,
+ }
+ }
+
+ fn convert_val_type(ty: &wasmparser::ValType) -> ValType {
+ match ty {
+ wasmparser::ValType::I32 => ValType::I32,
+ wasmparser::ValType::I64 => ValType::I64,
+ wasmparser::ValType::F32 => ValType::F32,
+ wasmparser::ValType::F64 => ValType::F64,
+ wasmparser::ValType::V128 => ValType::V128,
+ wasmparser::ValType::Ref(r) => ValType::Ref(RefType {
+ nullable: r.is_nullable(),
+ heap_type: convert_heap_type(&r.heap_type()),
+ }),
+ }
+ }
+
+ fn convert_export_kind(kind: &wasmparser::ExternalKind) -> ExportKind {
+ match kind {
+ wasmparser::ExternalKind::Func => ExportKind::Func,
+ wasmparser::ExternalKind::Table => ExportKind::Table,
+ wasmparser::ExternalKind::Memory => ExportKind::Memory,
+ wasmparser::ExternalKind::Global => ExportKind::Global,
+ wasmparser::ExternalKind::Tag => ExportKind::Tag,
+ }
+ }
+
+ let mut required_exports: Vec<wasmparser::Export> = vec![];
+ let mut validator = wasmparser::Validator::new();
+ let exports_types = validator
+ .validate_all(&example_module)
+ .expect("Failed to validate `exports` Wasm");
+ for payload in wasmparser::Parser::new(0).parse_all(&example_module) {
+ match payload.expect("Failed to read `exports` Wasm") {
+ wasmparser::Payload::ExportSection(export_reader) => {
+ required_exports = export_reader
+ .into_iter()
+ .collect::<Result<_, _>>()
+ .expect("Failed to read `exports` export section");
+ }
+ _ => {}
+ }
+ }
+
+ // For each export, add necessary prerequisites to the module.
+ for export in required_exports {
+ let new_index = match exports_types
+ .entity_type_from_export(&export)
+ .unwrap_or_else(|| {
+ panic!(
+ "Unable to get type from export {:?} in `exports` Wasm",
+ export,
+ )
+ }) {
+ // For functions, add the type and a function with that type.
+ wasmparser::types::EntityType::Func(id) => {
+ let subtype = exports_types.get(id).unwrap_or_else(|| {
+ panic!(
+ "Unable to get subtype for function {:?} in `exports` Wasm",
+ id
+ )
+ });
+ match &subtype.composite_type {
+ wasmparser::CompositeType::Func(func_type) => {
+ assert!(
+ subtype.is_final,
+ "Subtype {:?} from `exports` Wasm is not final",
+ subtype
+ );
+ assert!(
+ subtype.supertype_idx.is_none(),
+ "Subtype {:?} from `exports` Wasm has non-empty supertype",
+ subtype
+ );
+ let new_type = Rc::new(FuncType {
+ params: func_type
+ .params()
+ .into_iter()
+ .map(convert_val_type)
+ .collect(),
+ results: func_type
+ .results()
+ .into_iter()
+ .map(convert_val_type)
+ .collect(),
+ });
+ self.rec_groups.push(self.types.len()..self.types.len() + 1);
+ let type_index = self.add_type(SubType {
+ is_final: true,
+ supertype: None,
+ composite_type: CompositeType::Func(Rc::clone(&new_type)),
+ });
+ let func_index = self.funcs.len() as u32;
+ self.funcs.push((type_index, new_type));
+ self.num_defined_funcs += 1;
+ func_index
}
- ConstExpr::ref_null(ty.heap_type)
+ _ => panic!(
+ "Unable to handle type {:?} from `exports` Wasm",
+ subtype.composite_type
+ ),
}
- }))
- }));
-
- for (i, g) in self.globals[..num_imported_globals].iter().enumerate() {
- if !g.mutable && g.val_type == ty.val_type {
- choices.push(Box::new(move |_, _| {
- Ok(GlobalInitExpr::ConstExpr(ConstExpr::global_get(i as u32)))
- }));
}
- }
+ // For globals, add a new global.
+ wasmparser::types::EntityType::Global(global_type) => self
+ .add_arbitrary_global_of_type(
+ GlobalType {
+ val_type: convert_val_type(&global_type.content_type),
+ mutable: global_type.mutable,
+ },
+ u,
+ )?,
+ wasmparser::types::EntityType::Table(_)
+ | wasmparser::types::EntityType::Memory(_)
+ | wasmparser::types::EntityType::Tag(_) => {
+ panic!(
+ "Config `exports` has an export of type {:?} which cannot yet be handled.",
+ export.kind
+ )
+ }
+ };
+ self.exports.push((
+ export.name.to_string(),
+ convert_export_kind(&export.kind),
+ new_index,
+ ));
+ self.export_names.insert(export.name.to_string());
+ }
- let f = u.choose(&choices)?;
- let expr = f(u, ty.val_type)?;
- let global_idx = self.globals.len() as u32;
- self.globals.push(ty);
- self.defined_globals.push((global_idx, expr));
- Ok(true)
- })
+ Ok(())
}
fn arbitrary_exports(&mut self, u: &mut Unstructured) -> Result<()> {
@@ -1644,24 +1878,17 @@ impl Module {
}
fn arbitrary_elems(&mut self, u: &mut Unstructured) -> Result<()> {
- let func_max = self.funcs.len() as u32;
-
// Create a helper closure to choose an arbitrary offset.
let mut offset_global_choices = vec![];
if !self.config.disallow_traps {
- for (i, g) in self.globals[..self.globals.len() - self.defined_globals.len()]
- .iter()
- .enumerate()
- {
- if !g.mutable && g.val_type == ValType::I32 {
- offset_global_choices.push(i as u32);
- }
+ for i in self.globals_for_const_expr(ValType::I32) {
+ offset_global_choices.push(i);
}
}
+ let disallow_traps = self.config.disallow_traps;
let arbitrary_active_elem = |u: &mut Unstructured,
min_mem_size: u32,
table: Option<u32>,
- disallow_traps: bool,
table_ty: &TableType| {
let (offset, max_size_hint) = if !offset_global_choices.is_empty() && u.arbitrary()? {
let g = u.choose(&offset_global_choices)?;
@@ -1686,11 +1913,20 @@ impl Module {
Ok((ElementKind::Active { table, offset }, max_size_hint))
};
+ // Generate a list of candidates for "kinds" of elements segments. For
+ // example we can have an active segment for any existing table or
+ // passive/declared segments if the right wasm features are enabled.
type GenElemSegment<'a> =
dyn Fn(&mut Unstructured) -> Result<(ElementKind, Option<u32>)> + 'a;
- let mut funcrefs: Vec<Box<GenElemSegment>> = Vec::new();
- let mut externrefs: Vec<Box<GenElemSegment>> = Vec::new();
- let disallow_traps = self.config.disallow_traps;
+ let mut choices: Vec<Box<GenElemSegment>> = Vec::new();
+
+ // Bulk memory enables passive/declared segments, and note that the
+ // types used are selected later.
+ if self.config.bulk_memory_enabled {
+ choices.push(Box::new(|_| Ok((ElementKind::Passive, None))));
+ choices.push(Box::new(|_| Ok((ElementKind::Declared, None))));
+ }
+
for (i, ty) in self.tables.iter().enumerate() {
// If this table starts with no capacity then any non-empty element
// segment placed onto it will immediately trap, which isn't too
@@ -1700,93 +1936,100 @@ impl Module {
continue;
}
- let dst = if ty.element_type == RefType::FUNCREF {
- &mut funcrefs
- } else {
- &mut externrefs
- };
let minimum = ty.minimum;
// If the first table is a funcref table then it's a candidate for
// the MVP encoding of element segments.
+ let ty = *ty;
if i == 0 && ty.element_type == RefType::FUNCREF {
- dst.push(Box::new(move |u| {
- arbitrary_active_elem(u, minimum, None, disallow_traps, ty)
+ choices.push(Box::new(move |u| {
+ arbitrary_active_elem(u, minimum, None, &ty)
}));
}
if self.config.bulk_memory_enabled {
let idx = Some(i as u32);
- dst.push(Box::new(move |u| {
- arbitrary_active_elem(u, minimum, idx, disallow_traps, ty)
+ choices.push(Box::new(move |u| {
+ arbitrary_active_elem(u, minimum, idx, &ty)
}));
}
}
- // Bulk memory enables passive/declared segments for funcrefs, and
- // reference types additionally enables the segments for externrefs.
- if self.config.bulk_memory_enabled {
- funcrefs.push(Box::new(|_| Ok((ElementKind::Passive, None))));
- funcrefs.push(Box::new(|_| Ok((ElementKind::Declared, None))));
- if self.config.reference_types_enabled {
- externrefs.push(Box::new(|_| Ok((ElementKind::Passive, None))));
- externrefs.push(Box::new(|_| Ok((ElementKind::Declared, None))));
- }
- }
-
- let mut choices = Vec::new();
- if !funcrefs.is_empty() {
- choices.push((&funcrefs, RefType::FUNCREF));
- }
- if !externrefs.is_empty() {
- choices.push((&externrefs, RefType::EXTERNREF));
- }
-
if choices.is_empty() {
return Ok(());
}
+
arbitrary_loop(
u,
self.config.min_element_segments,
self.config.max_element_segments,
|u| {
- // Choose whether to generate a segment whose elements are initialized via
- // expressions, or one whose elements are initialized via function indices.
- let (kind_candidates, ty) = *u.choose(&choices)?;
-
- // Select a kind for this segment now that we know the number of
- // items the segment will hold.
- let (kind, max_size_hint) = u.choose(kind_candidates)?(u)?;
+ // Pick a kind of element segment to generate which will also
+ // give us a hint of the maximum size, if any.
+ let (kind, max_size_hint) = u.choose(&choices)?(u)?;
let max = max_size_hint
.map(|i| usize::try_from(i).unwrap())
.unwrap_or_else(|| self.config.max_elements);
- // Pick whether we're going to use expression elements or
- // indices. Note that externrefs must use expressions,
- // and functions without reference types must use indices.
- let items = if ty == RefType::EXTERNREF
- || (self.config.reference_types_enabled && u.arbitrary()?)
+ // Infer, from the kind of segment, the type of the element
+ // segment. Passive/declared segments can be declared with any
+ // reference type, but active segments must match their table.
+ let ty = match kind {
+ ElementKind::Passive | ElementKind::Declared => self.arbitrary_ref_type(u)?,
+ ElementKind::Active { table, .. } => {
+ let idx = table.unwrap_or(0);
+ self.arbitrary_matching_ref_type(u, self.tables[idx as usize].element_type)?
+ }
+ };
+
+ // The `Elements::Functions` encoding is only possible when the
+ // element type is a `funcref` because the binary format can't
+ // allow encoding any other type in that form.
+ let can_use_function_list = ty == RefType::FUNCREF;
+ if !self.config.reference_types_enabled {
+ assert!(can_use_function_list);
+ }
+
+ // If a function list is possible then build up a list of
+ // functions that can be selected from.
+ let mut func_candidates = Vec::new();
+ if can_use_function_list {
+ match ty.heap_type {
+ HeapType::Func => {
+ func_candidates.extend(0..self.funcs.len() as u32);
+ }
+ HeapType::Concrete(ty) => {
+ for (i, (fty, _)) in self.funcs.iter().enumerate() {
+ if *fty == ty {
+ func_candidates.push(i as u32);
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+
+ // And finally actually generate the arbitrary elements of this
+ // element segment. Function indices are used if they're either
+ // forced or allowed, and otherwise expressions are used
+ // instead.
+ let items = if !self.config.reference_types_enabled
+ || (can_use_function_list && u.arbitrary()?)
{
let mut init = vec![];
- arbitrary_loop(u, self.config.min_elements, max, |u| {
- init.push(
- if ty == RefType::EXTERNREF || func_max == 0 || u.arbitrary()? {
- None
- } else {
- Some(u.int_in_range(0..=func_max - 1)?)
- },
- );
- Ok(true)
- })?;
- Elements::Expressions(init)
- } else {
- let mut init = vec![];
- if func_max > 0 {
+ if func_candidates.len() > 0 {
arbitrary_loop(u, self.config.min_elements, max, |u| {
- let func_idx = u.int_in_range(0..=func_max - 1)?;
+ let func_idx = *u.choose(&func_candidates)?;
init.push(func_idx);
Ok(true)
})?;
}
Elements::Functions(init)
+ } else {
+ let mut init = vec![];
+ arbitrary_loop(u, self.config.min_elements, max, |u| {
+ init.push(self.arbitrary_const_expr(ValType::Ref(ty), u)?);
+ Ok(true)
+ })?;
+ Elements::Expressions(init)
};
self.elems.push(ElementSegment { kind, ty, items });
@@ -1795,11 +2038,11 @@ impl Module {
)
}
- fn arbitrary_code(&mut self, u: &mut Unstructured, allow_invalid: bool) -> Result<()> {
+ fn arbitrary_code(&mut self, u: &mut Unstructured) -> Result<()> {
self.code.reserve(self.num_defined_funcs);
- let mut allocs = CodeBuilderAllocations::new(self);
+ let mut allocs = CodeBuilderAllocations::new(self, self.config.exports.is_some());
for (_, ty) in self.funcs[self.funcs.len() - self.num_defined_funcs..].iter() {
- let body = self.arbitrary_func_body(u, ty, &mut allocs, allow_invalid)?;
+ let body = self.arbitrary_func_body(u, ty, &mut allocs)?;
self.code.push(body);
}
allocs.finish(u, self)?;
@@ -1811,11 +2054,10 @@ impl Module {
u: &mut Unstructured,
ty: &FuncType,
allocs: &mut CodeBuilderAllocations,
- allow_invalid: bool,
) -> Result<Code> {
let mut locals = self.arbitrary_locals(u)?;
let builder = allocs.builder(ty, &mut locals);
- let instructions = if allow_invalid && u.arbitrary().unwrap_or(false) {
+ let instructions = if self.config.allow_invalid_funcs && u.arbitrary().unwrap_or(false) {
Instructions::Arbitrary(arbitrary_vec_u8(u)?)
} else {
Instructions::Generated(builder.arbitrary(u, self)?)
@@ -1830,7 +2072,7 @@ impl Module {
fn arbitrary_locals(&self, u: &mut Unstructured) -> Result<Vec<ValType>> {
let mut ret = Vec::new();
arbitrary_loop(u, 0, 100, |u| {
- ret.push(self.arbitrary_valtype(u, u32::try_from(self.types.len()).unwrap())?);
+ ret.push(self.arbitrary_valtype(u)?);
Ok(true)
})?;
Ok(ret)
@@ -1865,18 +2107,11 @@ impl Module {
))
}));
if !self.config.disallow_traps {
- for (i, g) in self.globals[..self.globals.len() - self.defined_globals.len()]
- .iter()
- .enumerate()
- {
- if g.mutable {
- continue;
- }
- if g.val_type == ValType::I32 {
- choices32.push(Box::new(move |_, _, _| Ok(Offset::Global(i as u32))));
- } else if g.val_type == ValType::I64 {
- choices64.push(Box::new(move |_, _, _| Ok(Offset::Global(i as u32))));
- }
+ for i in self.globals_for_const_expr(ValType::I32) {
+ choices32.push(Box::new(move |_, _, _| Ok(Offset::Global(i))));
+ }
+ for i in self.globals_for_const_expr(ValType::I64) {
+ choices64.push(Box::new(move |_, _, _| Ok(Offset::Global(i))));
}
}
@@ -1962,6 +2197,33 @@ impl Module {
}
}
}
+
+ /// Returns an iterator of all globals which can be used in constant
+ /// expressions for a value of type `ty` specified.
+ fn globals_for_const_expr(&self, ty: ValType) -> impl Iterator<Item = u32> + '_ {
+ // Before the GC proposal only imported globals could be referenced, but
+ // the GC proposal relaxed this feature to allow any global.
+ let num_imported_globals = self.globals.len() - self.defined_globals.len();
+ let max_global = if self.config.gc_enabled {
+ self.globals.len()
+ } else {
+ num_imported_globals
+ };
+
+ self.globals[..max_global]
+ .iter()
+ .enumerate()
+ .filter_map(move |(i, g)| {
+ // Mutable globals cannot participate in constant expressions,
+ // but otherwise so long as the global is a subtype of the
+ // desired type it's a candidate.
+ if !g.mutable && self.val_type_is_sub_type(g.val_type, ty) {
+ Some(i as u32)
+ } else {
+ None
+ }
+ })
+ }
}
pub(crate) fn arbitrary_limits32(
@@ -2044,49 +2306,11 @@ pub(crate) fn configured_valtypes(config: &Config) -> Vec<ValType> {
valtypes
}
-pub(crate) fn arbitrary_func_type(
- u: &mut Unstructured,
- config: &Config,
- valtypes: &[ValType],
- max_results: Option<usize>,
- type_ref_limit: u32,
-) -> Result<Rc<FuncType>> {
- let mut params = vec![];
- let mut results = vec![];
- arbitrary_loop(u, 0, 20, |u| {
- params.push(arbitrary_valtype(u, config, valtypes, type_ref_limit)?);
- Ok(true)
- })?;
- arbitrary_loop(u, 0, max_results.unwrap_or(20), |u| {
- results.push(arbitrary_valtype(u, config, valtypes, type_ref_limit)?);
- Ok(true)
- })?;
- Ok(Rc::new(FuncType { params, results }))
-}
-
-fn arbitrary_valtype(
+pub(crate) fn arbitrary_table_type(
u: &mut Unstructured,
config: &Config,
- valtypes: &[ValType],
- type_ref_limit: u32,
-) -> Result<ValType> {
- if config.gc_enabled && type_ref_limit > 0 && u.ratio(1, 20)? {
- Ok(ValType::Ref(RefType {
- // TODO: For now, only create allow nullable reference
- // types. Eventually we should support non-nullable reference types,
- // but this means that we will also need to recognize when it is
- // impossible to create an instance of the reference (eg `(ref
- // nofunc)` has no instances, and self-referential types that
- // contain a non-null self-reference are also impossible to create).
- nullable: true,
- heap_type: HeapType::Concrete(u.int_in_range(0..=type_ref_limit - 1)?),
- }))
- } else {
- Ok(*u.choose(valtypes)?)
- }
-}
-
-pub(crate) fn arbitrary_table_type(u: &mut Unstructured, config: &Config) -> Result<TableType> {
+ module: Option<&Module>,
+) -> Result<TableType> {
// We don't want to generate tables that are too large on average, so
// keep the "inbounds" limit here a bit smaller.
let max_inbounds = 10_000;
@@ -2102,12 +2326,12 @@ pub(crate) fn arbitrary_table_type(u: &mut Unstructured, config: &Config) -> Res
if config.disallow_traps {
assert!(minimum > 0);
}
+ let element_type = match module {
+ Some(module) => module.arbitrary_ref_type(u)?,
+ None => RefType::FUNCREF,
+ };
Ok(TableType {
- element_type: if config.reference_types_enabled {
- *u.choose(&[RefType::FUNCREF, RefType::EXTERNREF])?
- } else {
- RefType::FUNCREF
- },
+ element_type,
minimum,
maximum,
})
diff --git a/third_party/rust/wasm-smith/src/core/code_builder.rs b/third_party/rust/wasm-smith/src/core/code_builder.rs
index 1bc09008ad..a55c5aafda 100644
--- a/third_party/rust/wasm-smith/src/core/code_builder.rs
+++ b/third_party/rust/wasm-smith/src/core/code_builder.rs
@@ -1,11 +1,10 @@
use super::{
- CompositeType, Elements, FuncType, GlobalInitExpr, Instruction, InstructionKind::*,
- InstructionKinds, Module, ValType,
+ CompositeType, Elements, FuncType, Instruction, InstructionKind::*, InstructionKinds, Module,
+ ValType,
};
use crate::{unique_string, MemoryOffsetChoices};
use arbitrary::{Result, Unstructured};
use std::collections::{BTreeMap, BTreeSet};
-use std::convert::TryFrom;
use std::rc::Rc;
use wasm_encoder::{
ArrayType, BlockType, Catch, ConstExpr, ExportKind, FieldType, GlobalType, HeapType, MemArg,
@@ -648,6 +647,10 @@ pub(crate) struct CodeBuilderAllocations {
global_dropped_f32: Option<u32>,
global_dropped_f64: Option<u32>,
global_dropped_v128: Option<u32>,
+
+ // Indicates that additional exports cannot be generated. This will be true
+ // if the `Config` specifies exactly which exports should be present.
+ disallow_exporting: bool,
}
pub(crate) struct CodeBuilder<'a> {
@@ -704,7 +707,7 @@ enum Float {
}
impl CodeBuilderAllocations {
- pub(crate) fn new(module: &Module) -> Self {
+ pub(crate) fn new(module: &Module, disallow_exporting: bool) -> Self {
let mut mutable_globals = BTreeMap::new();
for (i, global) in module.globals.iter().enumerate() {
if global.mutable {
@@ -741,14 +744,14 @@ impl CodeBuilderAllocations {
let mut referenced_functions = BTreeSet::new();
for (_, expr) in module.defined_globals.iter() {
- if let GlobalInitExpr::FuncRef(i) = *expr {
+ if let Some(i) = expr.get_ref_func() {
referenced_functions.insert(i);
}
}
for g in module.elems.iter() {
match &g.items {
Elements::Expressions(e) => {
- let iter = e.iter().filter_map(|i| *i);
+ let iter = e.iter().filter_map(|e| e.get_ref_func());
referenced_functions.extend(iter);
}
Elements::Functions(e) => {
@@ -769,6 +772,42 @@ impl CodeBuilderAllocations {
}
}
+ let mut global_dropped_i32 = None;
+ let mut global_dropped_i64 = None;
+ let mut global_dropped_f32 = None;
+ let mut global_dropped_f64 = None;
+ let mut global_dropped_v128 = None;
+
+ // If we can't export additional globals, try to use existing exported
+ // mutable globals for dropped values.
+ if disallow_exporting {
+ for (_, kind, index) in module.exports.iter() {
+ if *kind == ExportKind::Global {
+ let ty = module.globals[*index as usize];
+ if ty.mutable {
+ match ty.val_type {
+ ValType::I32 => {
+ if global_dropped_i32.is_none() {
+ global_dropped_i32 = Some(*index)
+ } else {
+ global_dropped_f32 = Some(*index)
+ }
+ }
+ ValType::I64 => {
+ if global_dropped_i64.is_none() {
+ global_dropped_i64 = Some(*index)
+ } else {
+ global_dropped_f64 = Some(*index)
+ }
+ }
+ ValType::V128 => global_dropped_v128 = Some(*index),
+ _ => {}
+ }
+ }
+ }
+ }
+ }
+
CodeBuilderAllocations {
controls: Vec::with_capacity(4),
operands: Vec::with_capacity(16),
@@ -782,13 +821,14 @@ impl CodeBuilderAllocations {
memory32,
memory64,
- global_dropped_i32: None,
- global_dropped_i64: None,
- global_dropped_f32: None,
- global_dropped_f64: None,
- global_dropped_v128: None,
+ global_dropped_i32,
+ global_dropped_i64,
+ global_dropped_f32,
+ global_dropped_f64,
+ global_dropped_v128,
globals_cnt: module.globals.len() as u32,
new_globals: Vec::new(),
+ disallow_exporting,
}
}
@@ -822,18 +862,18 @@ impl CodeBuilderAllocations {
pub fn finish(self, u: &mut Unstructured<'_>, module: &mut Module) -> arbitrary::Result<()> {
// Any globals injected as part of dropping operands on the stack get
// injected into the module here. Each global is then exported, most of
- // the time, to ensure it's part of the "image" of this module available
- // for differential execution for example.
+ // the time (if additional exports are allowed), to ensure it's part of
+ // the "image" of this module available for differential execution for
+ // example.
for (ty, init) in self.new_globals {
let global_idx = module.globals.len() as u32;
module.globals.push(GlobalType {
val_type: ty,
mutable: true,
});
- let init = GlobalInitExpr::ConstExpr(init);
module.defined_globals.push((global_idx, init));
- if u.ratio(1, 100).unwrap_or(false) {
+ if self.disallow_exporting || u.ratio(1, 100).unwrap_or(false) {
continue;
}
@@ -922,6 +962,33 @@ impl CodeBuilder<'_> {
self.allocs.operands.push(ty);
}
+ fn pop_label_types(&mut self, module: &Module, target: u32) {
+ let target = usize::try_from(target).unwrap();
+ let control = &self.allocs.controls[self.allocs.controls.len() - 1 - target];
+ debug_assert!(self.label_types_on_stack(module, control));
+ self.allocs
+ .operands
+ .truncate(self.allocs.operands.len() - control.label_types().len());
+ }
+
+ fn push_label_types(&mut self, target: u32) {
+ let target = usize::try_from(target).unwrap();
+ let control = &self.allocs.controls[self.allocs.controls.len() - 1 - target];
+ self.allocs
+ .operands
+ .extend(control.label_types().iter().copied().map(Some));
+ }
+
+ /// Pop the target label types, and then push them again.
+ ///
+ /// This is not a no-op due to subtyping: if we have a `T <: U` on the
+ /// stack, and the target label's type is `[U]`, then this will erase the
+ /// information about `T` and subsequent operations may only operate on `U`.
+ fn pop_push_label_types(&mut self, module: &Module, target: u32) {
+ self.pop_label_types(module, target);
+ self.push_label_types(target)
+ }
+
fn label_types_on_stack(&self, module: &Module, to_check: &Control) -> bool {
self.types_on_stack(module, to_check.label_types())
}
@@ -1087,12 +1154,7 @@ impl CodeBuilder<'_> {
fn arbitrary_block_type(&self, u: &mut Unstructured, module: &Module) -> Result<BlockType> {
let mut options: Vec<Box<dyn Fn(&mut Unstructured) -> Result<BlockType>>> = vec![
Box::new(|_| Ok(BlockType::Empty)),
- Box::new(|u| {
- Ok(BlockType::Result(module.arbitrary_valtype(
- u,
- u32::try_from(module.types.len()).unwrap(),
- )?))
- }),
+ Box::new(|u| Ok(BlockType::Result(module.arbitrary_valtype(u)?))),
];
if module.config.multi_value_enabled {
for (i, ty) in module.func_types() {
@@ -1710,10 +1772,9 @@ fn br(
.filter(|(_, l)| builder.label_types_on_stack(module, l))
.nth(i)
.unwrap();
- let control = &builder.allocs.controls[builder.allocs.controls.len() - 1 - target];
- let tys = control.label_types().to_vec();
- builder.pop_operands(module, &tys);
- instructions.push(Instruction::Br(target as u32));
+ let target = u32::try_from(target).unwrap();
+ builder.pop_label_types(module, target);
+ instructions.push(Instruction::Br(target));
Ok(())
}
@@ -1757,7 +1818,9 @@ fn br_if(
.filter(|(_, l)| builder.label_types_on_stack(module, l))
.nth(i)
.unwrap();
- instructions.push(Instruction::BrIf(target as u32));
+ let target = u32::try_from(target).unwrap();
+ builder.pop_push_label_types(module, target);
+ instructions.push(Instruction::BrIf(target));
Ok(())
}
@@ -2203,13 +2266,15 @@ fn br_on_null(
.filter(|(_, l)| builder.label_types_on_stack(module, l))
.nth(i)
.unwrap();
+ let target = u32::try_from(target).unwrap();
+ builder.pop_push_label_types(module, target);
builder.push_operands(&[ValType::Ref(RefType {
nullable: false,
heap_type,
})]);
- instructions.push(Instruction::BrOnNull(u32::try_from(target).unwrap()));
+ instructions.push(Instruction::BrOnNull(target));
Ok(())
}
@@ -2270,9 +2335,11 @@ fn br_on_non_null(
.filter(|(_, l)| is_valid_br_on_non_null_control(module, l, builder))
.nth(i)
.unwrap();
+ let target = u32::try_from(target).unwrap();
+ builder.pop_push_label_types(module, target);
builder.pop_ref_type();
- instructions.push(Instruction::BrOnNonNull(u32::try_from(target).unwrap()));
+ instructions.push(Instruction::BrOnNonNull(target));
Ok(())
}
@@ -2354,6 +2421,7 @@ fn br_on_cast(
.unwrap();
let relative_depth = u32::try_from(relative_depth).unwrap();
+ let num_label_types = control.label_types().len();
let to_ref_type = match control.label_types().last() {
Some(ValType::Ref(r)) => *r,
_ => unreachable!(),
@@ -2363,6 +2431,15 @@ fn br_on_cast(
let from_ref_type = from_ref_type.unwrap_or(to_ref_type);
let from_ref_type = module.arbitrary_super_type_of_ref_type(u, from_ref_type)?;
+ // Do `pop_push_label_types` but without its debug assert that the types are
+ // on the stack, since we know that we have a `from_ref_type` but the label
+ // requires a `to_ref_type`.
+ for _ in 0..num_label_types {
+ builder.pop_operand();
+ }
+ builder.push_label_types(relative_depth);
+
+ // Replace the label's `to_ref_type` with the type difference.
builder.pop_operand();
builder.push_operands(&[ValType::Ref(ref_type_difference(
from_ref_type,
@@ -2433,7 +2510,7 @@ fn br_on_cast_fail(
debug_assert!(n > 0);
let i = u.int_in_range(0..=n - 1)?;
- let (target, control) = builder
+ let (relative_depth, control) = builder
.allocs
.controls
.iter()
@@ -2442,6 +2519,7 @@ fn br_on_cast_fail(
.filter(|(_, l)| is_valid_br_on_cast_fail_control(module, builder, l, from_ref_type))
.nth(i)
.unwrap();
+ let relative_depth = u32::try_from(relative_depth).unwrap();
let from_ref_type =
from_ref_type.unwrap_or_else(|| match control.label_types().last().unwrap() {
@@ -2450,13 +2528,16 @@ fn br_on_cast_fail(
});
let to_ref_type = module.arbitrary_matching_ref_type(u, from_ref_type)?;
+ // Pop-push the label types and then replace its last reference type with
+ // our `to_ref_type`.
+ builder.pop_push_label_types(module, relative_depth);
builder.pop_operand();
builder.push_operand(Some(ValType::Ref(to_ref_type)));
instructions.push(Instruction::BrOnCastFail {
from_ref_type,
to_ref_type,
- relative_depth: u32::try_from(target).unwrap(),
+ relative_depth,
});
Ok(())
}
diff --git a/third_party/rust/wasm-smith/src/core/code_builder/no_traps.rs b/third_party/rust/wasm-smith/src/core/code_builder/no_traps.rs
index a1232b6922..4f8b31ca9f 100644
--- a/third_party/rust/wasm-smith/src/core/code_builder/no_traps.rs
+++ b/third_party/rust/wasm-smith/src/core/code_builder/no_traps.rs
@@ -1,5 +1,5 @@
use crate::core::*;
-use wasm_encoder::{BlockType, Instruction, ValType};
+use wasm_encoder::Instruction;
use super::CodeBuilder;
diff --git a/third_party/rust/wasm-smith/src/core/encode.rs b/third_party/rust/wasm-smith/src/core/encode.rs
index 7c781dc731..5a661509a6 100644
--- a/third_party/rust/wasm-smith/src/core/encode.rs
+++ b/third_party/rust/wasm-smith/src/core/encode.rs
@@ -1,5 +1,4 @@
use super::*;
-use std::convert::TryFrom;
impl Module {
/// Encode this Wasm module into bytes.
@@ -122,12 +121,22 @@ impl Module {
}
fn encode_tables(&self, module: &mut wasm_encoder::Module) {
- if self.num_defined_tables == 0 {
+ if self.defined_tables.is_empty() {
return;
}
let mut tables = wasm_encoder::TableSection::new();
- for t in self.tables[self.tables.len() - self.num_defined_tables..].iter() {
- tables.table(*t);
+ for (t, init) in self.tables[self.tables.len() - self.defined_tables.len()..]
+ .iter()
+ .zip(&self.defined_tables)
+ {
+ match init {
+ Some(init) => {
+ tables.table_with_init(*t, init);
+ }
+ None => {
+ tables.table(*t);
+ }
+ }
}
module.section(&tables);
}
@@ -150,10 +159,7 @@ impl Module {
let mut globals = wasm_encoder::GlobalSection::new();
for (idx, expr) in &self.defined_globals {
let ty = &self.globals[*idx as usize];
- match expr {
- GlobalInitExpr::ConstExpr(expr) => globals.global(*ty, expr),
- GlobalInitExpr::FuncRef(func) => globals.global(*ty, &ConstExpr::ref_func(*func)),
- };
+ globals.global(*ty, expr);
}
module.section(&globals);
}
@@ -180,24 +186,13 @@ impl Module {
return;
}
let mut elems = wasm_encoder::ElementSection::new();
- let mut exps = vec![];
for el in &self.elems {
let elements = match &el.items {
- Elements::Expressions(es) => {
- exps.clear();
- exps.extend(es.iter().map(|e| {
- // TODO(nagisa): generate global.get of imported ref globals too.
- match e {
- Some(i) => match el.ty {
- RefType::FUNCREF => wasm_encoder::ConstExpr::ref_func(*i),
- _ => unreachable!(),
- },
- None => wasm_encoder::ConstExpr::ref_null(el.ty.heap_type),
- }
- }));
- wasm_encoder::Elements::Expressions(el.ty, &exps)
+ Elements::Expressions(es) => wasm_encoder::Elements::Expressions(el.ty, es),
+ Elements::Functions(fs) => {
+ assert_eq!(el.ty, RefType::FUNCREF);
+ wasm_encoder::Elements::Functions(fs)
}
- Elements::Functions(fs) => wasm_encoder::Elements::Functions(fs),
};
match &el.kind {
ElementKind::Active { table, offset } => {
diff --git a/third_party/rust/wasm-smith/src/core/terminate.rs b/third_party/rust/wasm-smith/src/core/terminate.rs
index adcfeed54f..7983c35be6 100644
--- a/third_party/rust/wasm-smith/src/core/terminate.rs
+++ b/third_party/rust/wasm-smith/src/core/terminate.rs
@@ -1,6 +1,5 @@
use super::*;
-use std::mem;
-use wasm_encoder::BlockType;
+use anyhow::{bail, Result};
impl Module {
/// Ensure that all of this Wasm module's functions will terminate when
@@ -12,16 +11,21 @@ impl Module {
///
/// The index of the fuel global is returned, so that you may control how
/// much fuel the module is given.
- pub fn ensure_termination(&mut self, default_fuel: u32) -> u32 {
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if any function body was generated with
+ /// possibly-invalid bytes rather than being generated by wasm-smith. In
+ /// such a situation this pass does not parse the input bytes and inject
+ /// instructions, instead it returns an error.
+ pub fn ensure_termination(&mut self, default_fuel: u32) -> Result<u32> {
let fuel_global = self.globals.len() as u32;
self.globals.push(GlobalType {
val_type: ValType::I32,
mutable: true,
});
- self.defined_globals.push((
- fuel_global,
- GlobalInitExpr::ConstExpr(ConstExpr::i32_const(default_fuel as i32)),
- ));
+ self.defined_globals
+ .push((fuel_global, ConstExpr::i32_const(default_fuel as i32)));
for code in &mut self.code {
let check_fuel = |insts: &mut Vec<Instruction>| {
@@ -41,10 +45,12 @@ impl Module {
let instrs = match &mut code.instructions {
Instructions::Generated(list) => list,
- // only present on modules contained within
- // `MaybeInvalidModule`, which doesn't expose its internal
- // `Module`.
- Instructions::Arbitrary(_) => unreachable!(),
+ Instructions::Arbitrary(_) => {
+ bail!(
+ "failed to ensure that a function generated due to it \
+ containing arbitrary instructions"
+ )
+ }
};
let mut new_insts = Vec::with_capacity(instrs.len() * 2);
@@ -65,6 +71,6 @@ impl Module {
*instrs = new_insts;
}
- fuel_global
+ Ok(fuel_global)
}
}
diff --git a/third_party/rust/wasm-smith/src/lib.rs b/third_party/rust/wasm-smith/src/lib.rs
index 8d17290473..b985791aae 100644
--- a/third_party/rust/wasm-smith/src/lib.rs
+++ b/third_party/rust/wasm-smith/src/lib.rs
@@ -58,7 +58,7 @@ mod component;
mod config;
mod core;
-pub use crate::core::{InstructionKind, InstructionKinds, MaybeInvalidModule, Module};
+pub use crate::core::{InstructionKind, InstructionKinds, Module};
use arbitrary::{Result, Unstructured};
pub use component::Component;
pub use config::{Config, MemoryOffsetChoices};
@@ -108,10 +108,7 @@ pub(crate) fn limited_str<'a>(max_size: usize, u: &mut Unstructured<'a>) -> Resu
Err(e) => {
let i = e.valid_up_to();
let valid = u.bytes(i).unwrap();
- let s = unsafe {
- debug_assert!(str::from_utf8(valid).is_ok());
- str::from_utf8_unchecked(valid)
- };
+ let s = str::from_utf8(valid).unwrap();
Ok(s)
}
}
diff --git a/third_party/rust/wasm-smith/tests/available_imports.rs b/third_party/rust/wasm-smith/tests/available_imports.rs
index 2ccc4c1cea..27e4852da9 100644
--- a/third_party/rust/wasm-smith/tests/available_imports.rs
+++ b/third_party/rust/wasm-smith/tests/available_imports.rs
@@ -4,8 +4,11 @@ use arbitrary::{Arbitrary, Unstructured};
use rand::{rngs::SmallRng, RngCore, SeedableRng};
use std::collections::HashMap;
use wasm_smith::{Config, Module};
+use wasmparser::Validator;
use wasmparser::{Parser, TypeRef, ValType};
-use wasmparser::{Validator, WasmFeatures};
+
+mod common;
+use common::{parser_features_from_config, validate};
#[test]
fn smoke_test_imports_config() {
@@ -159,44 +162,3 @@ fn import_config(
);
(config, available)
}
-
-fn parser_features_from_config(config: &Config) -> WasmFeatures {
- WasmFeatures {
- mutable_global: true,
- saturating_float_to_int: config.saturating_float_to_int_enabled,
- sign_extension: config.sign_extension_ops_enabled,
- reference_types: config.reference_types_enabled,
- multi_value: config.multi_value_enabled,
- bulk_memory: config.bulk_memory_enabled,
- simd: config.simd_enabled,
- relaxed_simd: config.relaxed_simd_enabled,
- multi_memory: config.max_memories > 1,
- exceptions: config.exceptions_enabled,
- memory64: config.memory64_enabled,
- tail_call: config.tail_call_enabled,
- function_references: config.gc_enabled,
- gc: config.gc_enabled,
-
- threads: false,
- floats: true,
- extended_const: false,
- component_model: false,
- memory_control: false,
- component_model_values: false,
- component_model_nested_names: false,
- }
-}
-
-fn validate(validator: &mut Validator, bytes: &[u8]) {
- let err = match validator.validate_all(bytes) {
- Ok(_) => return,
- Err(e) => e,
- };
- eprintln!("Writing Wasm to `test.wasm`");
- drop(std::fs::write("test.wasm", &bytes));
- if let Ok(text) = wasmprinter::print_bytes(bytes) {
- eprintln!("Writing WAT to `test.wat`");
- drop(std::fs::write("test.wat", &text));
- }
- panic!("wasm failed to validate: {}", err);
-}
diff --git a/third_party/rust/wasm-smith/tests/common/mod.rs b/third_party/rust/wasm-smith/tests/common/mod.rs
new file mode 100644
index 0000000000..cc24eed278
--- /dev/null
+++ b/third_party/rust/wasm-smith/tests/common/mod.rs
@@ -0,0 +1,43 @@
+use wasm_smith::Config;
+use wasmparser::{types::Types, Validator, WasmFeatures};
+
+pub fn parser_features_from_config(config: &Config) -> WasmFeatures {
+ WasmFeatures {
+ mutable_global: true,
+ saturating_float_to_int: config.saturating_float_to_int_enabled,
+ sign_extension: config.sign_extension_ops_enabled,
+ reference_types: config.reference_types_enabled,
+ multi_value: config.multi_value_enabled,
+ bulk_memory: config.bulk_memory_enabled,
+ simd: config.simd_enabled,
+ relaxed_simd: config.relaxed_simd_enabled,
+ multi_memory: config.max_memories > 1,
+ exceptions: config.exceptions_enabled,
+ memory64: config.memory64_enabled,
+ tail_call: config.tail_call_enabled,
+ function_references: config.gc_enabled,
+ gc: config.gc_enabled,
+
+ threads: false,
+ floats: true,
+ extended_const: false,
+ component_model: false,
+ memory_control: false,
+ component_model_values: false,
+ component_model_nested_names: false,
+ }
+}
+
+pub fn validate(validator: &mut Validator, bytes: &[u8]) -> Types {
+ let err = match validator.validate_all(bytes) {
+ Ok(types) => return types,
+ Err(e) => e,
+ };
+ eprintln!("Writing Wasm to `test.wasm`");
+ drop(std::fs::write("test.wasm", &bytes));
+ if let Ok(text) = wasmprinter::print_bytes(bytes) {
+ eprintln!("Writing WAT to `test.wat`");
+ drop(std::fs::write("test.wat", &text));
+ }
+ panic!("wasm failed to validate: {}", err);
+}
diff --git a/third_party/rust/wasm-smith/tests/core.rs b/third_party/rust/wasm-smith/tests/core.rs
index 5816f8e38b..7286952dc6 100644
--- a/third_party/rust/wasm-smith/tests/core.rs
+++ b/third_party/rust/wasm-smith/tests/core.rs
@@ -27,7 +27,7 @@ fn smoke_test_ensure_termination() {
rng.fill_bytes(&mut buf);
let u = Unstructured::new(&buf);
if let Ok(mut module) = Module::arbitrary_take_rest(u) {
- module.ensure_termination(10);
+ module.ensure_termination(10).unwrap();
let wasm_bytes = module.to_bytes();
let mut validator = Validator::new_with_features(wasm_features());
@@ -128,6 +128,7 @@ fn smoke_test_wasm_gc() {
let mut u = Unstructured::new(&buf);
let config = Config {
gc_enabled: true,
+ reference_types_enabled: true,
..Config::default()
};
if let Ok(module) = Module::new(config, &mut u) {
diff --git a/third_party/rust/wasm-smith/tests/exports.rs b/third_party/rust/wasm-smith/tests/exports.rs
new file mode 100644
index 0000000000..ff1dac0cbe
--- /dev/null
+++ b/third_party/rust/wasm-smith/tests/exports.rs
@@ -0,0 +1,147 @@
+use arbitrary::{Arbitrary, Unstructured};
+use rand::{rngs::SmallRng, RngCore, SeedableRng};
+use wasm_smith::{Config, Module};
+use wasmparser::{
+ types::EntityType, CompositeType, FuncType, GlobalType, Parser, Validator, WasmFeatures,
+};
+
+mod common;
+use common::{parser_features_from_config, validate};
+
+#[derive(Debug, PartialEq)]
+enum ExportType {
+ Func(FuncType),
+ Global(GlobalType),
+}
+
+#[test]
+fn smoke_test_single_export() {
+ let test = r#"
+ (module
+ (func (export "foo") (param i32) (result i64)
+ unreachable
+ )
+ )
+ "#;
+ smoke_test_exports(test, 11)
+}
+
+#[test]
+fn smoke_test_multiple_exports() {
+ let test = r#"
+ (module
+ (func (export "a") (param i32) (result i64)
+ unreachable
+ )
+ (func (export "b")
+ unreachable
+ )
+ (func (export "c")
+ unreachable
+ )
+ )
+ "#;
+ smoke_test_exports(test, 12)
+}
+
+#[test]
+fn smoke_test_exported_global() {
+ let test = r#"
+ (module
+ (func (export "a") (param i32 i32 f32 f64) (result f32)
+ unreachable
+ )
+ (global (export "glob") f64 f64.const 0)
+ )
+ "#;
+ smoke_test_exports(test, 20)
+}
+
+#[test]
+fn smoke_test_export_with_imports() {
+ let test = r#"
+ (module
+ (import "" "foo" (func (param i32)))
+ (import "" "bar" (global (mut f32)))
+ (func (param i64) unreachable)
+ (global i32 (i32.const 0))
+ (export "a" (func 0))
+ (export "b" (global 0))
+ (export "c" (func 1))
+ (export "d" (global 1))
+ )
+ "#;
+ smoke_test_exports(test, 21)
+}
+
+#[test]
+fn smoke_test_with_mutable_global_exports() {
+ let test = r#"
+ (module
+ (global (export "1i32") (mut i32) (i32.const 0))
+ (global (export "2i32") (mut i32) (i32.const 0))
+ (global (export "1i64") (mut i64) (i64.const 0))
+ (global (export "2i64") (mut i64) (i64.const 0))
+ (global (export "3i32") (mut i32) (i32.const 0))
+ (global (export "3i64") (mut i64) (i64.const 0))
+ (global (export "4i32") i32 (i32.const 0))
+ (global (export "4i64") i64 (i64.const 0))
+ )"#;
+ smoke_test_exports(test, 22)
+}
+
+fn get_func_and_global_exports(features: WasmFeatures, module: &[u8]) -> Vec<(String, ExportType)> {
+ let mut validator = Validator::new_with_features(features);
+ let types = validate(&mut validator, module);
+ let mut exports = vec![];
+
+ for payload in Parser::new(0).parse_all(module) {
+ let payload = payload.unwrap();
+ if let wasmparser::Payload::ExportSection(rdr) = payload {
+ for export in rdr {
+ let export = export.unwrap();
+ match types.entity_type_from_export(&export).unwrap() {
+ EntityType::Func(core_id) => {
+ let sub_type = types.get(core_id).expect("Failed to lookup core id");
+ assert!(sub_type.is_final);
+ assert!(sub_type.supertype_idx.is_none());
+ let CompositeType::Func(func_type) = &sub_type.composite_type else {
+ panic!("Expected Func CompositeType, but found {:?}", sub_type);
+ };
+ exports
+ .push((export.name.to_string(), ExportType::Func(func_type.clone())));
+ }
+ EntityType::Global(global_type) => {
+ exports.push((export.name.to_string(), ExportType::Global(global_type)))
+ }
+ other => {
+ panic!("Unexpected entity type {:?}", other)
+ }
+ }
+ }
+ }
+ }
+ exports
+}
+
+fn smoke_test_exports(exports_test_case: &str, seed: u64) {
+ let mut rng = SmallRng::seed_from_u64(seed);
+ let mut buf = vec![0; 512];
+ let wasm = wat::parse_str(exports_test_case).unwrap();
+ let expected_exports = get_func_and_global_exports(WasmFeatures::default(), &wasm);
+
+ for _ in 0..1024 {
+ rng.fill_bytes(&mut buf);
+ let mut u = Unstructured::new(&buf);
+
+ let mut config = Config::arbitrary(&mut u).expect("arbitrary config");
+ config.exports = Some(wasm.clone());
+
+ let features = parser_features_from_config(&config);
+ let module = Module::new(config, &mut u).unwrap();
+ let wasm_bytes = module.to_bytes();
+
+ let generated_exports = get_func_and_global_exports(features, &wasm_bytes);
+ assert_eq!(expected_exports, generated_exports);
+ }
+}
diff --git a/third_party/rust/wast/.cargo-checksum.json b/third_party/rust/wast/.cargo-checksum.json
index bc41b948df..3852650553 100644
--- a/third_party/rust/wast/.cargo-checksum.json
+++ b/third_party/rust/wast/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"ed28c8d2aac09b85e36d2ab074a9a0ef823e2652c6eee337160a27d9a3e1cc77","LICENSE":"268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5","README.md":"5a0d2b894a3ac74ee2be74715a2f22c40a08520cb4ac59183f4e7356f34ac566","src/component.rs":"23a62f4f2774ccfaf60f68e9d9416e68ba203eea782ce0c39cf553ad293f1df4","src/component/alias.rs":"5ec26333e179dc3778dead489f1273815fe9c1c808ba6a7e60eff54072fad795","src/component/binary.rs":"02b07602dc755fd5cec95b6348dd919c75f368c16a81402072201cf911be5f0d","src/component/component.rs":"b23f62918bd302fd96636438c2a877fa91a5ea0c70d2c62f16528d16b1ffff9d","src/component/custom.rs":"f5b23c34b73a716a986fd999fc8d8c9e24c341e83292088fe83325cd82dab4f5","src/component/expand.rs":"71b2e23f50957b4a15d758df7f8651fdbaf5cf8f44fbfeb134b412318dbd8921","src/component/export.rs":"f51e824c839d8bb0884eca509622f376c8cce3335be324b2b25033af6216fd2a","src/component/func.rs":"4f69de6c38cc6fe77b638ed7d8000c8a170d7053a11a6585dcd5b4877a06804c","src/component/import.rs":"ffe6e4ab8f2cec68b1022c753135d675ab27ecd1315bd38517472ceaffd0610c","src/component/instance.rs":"e550a7ee9af092ae084dd41e2c0ae756b7dca8da4b91d672d90265a6a15dff83","src/component/item_ref.rs":"e9c426ccc0210dc0c37bb0448468f5f4d9e52656b72d4ff0f2dc65c89957fe60","src/component/module.rs":"d27a28199d1dea1c64294a514329a524432319288515b0a0e3091fa7d3a33f74","src/component/resolve.rs":"4bc58161f3e38499cf0d7fddcbaec875579e8c48e0f347f1c820db21b8aa2d86","src/component/types.rs":"c854a2df9613145c38720d41cda571d0d01408fc09789bd22740874628cbff99","src/component/wast.rs":"36f9440618be7755db0a2994e5ff14187392c25543cdde2069e4c69111a7f490","src/core.rs":"24b71d1ab2ad874c4e37b4dd709b6532989b60e6bc503194434f95029cc1cda7","src/core/binary.rs":"7dfa60e406206ba349ca5a78ca659e451064e184f4137b4e76fc6db527ddce06","src/core/custom.rs":"edd6044b75d79ec873c28d803fb8dc9a53724f1bba474bcdef2bc77196e0a4d2","src/core/export.rs":"1322a120d9e1dd6f3aa1485ee0bbc4294961028ae8a7584a24170af5823b73b1","src/core/expr.rs":"c3a3dd3c9c8c84007069201df8dfa75de8cc1c9681e4ce60afffa20402a3c785","src/core/func.rs":"f87239645e45b7e40ecf7f8f2b707a7cfc0620cd2632cfdaca3cb155a06da732","src/core/global.rs":"dec0abecadd2fde9298acb729259deba1ef3c4733ae5afcb4fe8cd4a633f5349","src/core/import.rs":"602a13aed2fd5fa63e2562246586546199861df57f304c2906561ab77810cadd","src/core/memory.rs":"2dc551899837d351975c6453425085ebaf4076c1f135cb1654ed8fdabb3fb695","src/core/module.rs":"0bfe00432ea192d2392d7f3f21d173781c39c353081f868632bad89df0b71405","src/core/resolve/deinline_import_export.rs":"b04b953baa68290aabcfe776ef97ef4080c42614f04671ef84ae53a626fb3d4f","src/core/resolve/mod.rs":"9b2680b00a1cafad2dbbfdac86aa1c135ea67203bbc4dee939302b27f4aff6d0","src/core/resolve/names.rs":"afa1df4d75555dd7235a117a3c7217d1ec774b062e3a7c98d7470447561c97af","src/core/resolve/types.rs":"ff34eed438539cd88ea6c2c51e49ed42eee0d8be566d40c799254bf08125cfa8","src/core/table.rs":"1be354d0040a1b29903b717baa7b87c1e17ac3bcfa16b1fe1ef386067e687a29","src/core/tag.rs":"8a3d4fcdb86dedd68cdaf25abd930ef52a461cf2a0e9393bb9cb6f425afaaf2e","src/core/types.rs":"608f59af8c41f83c0d6db41c03f187fd30d3f85a021540a5de522464b011d52a","src/core/wast.rs":"a7307f93fb20274f0c99952deeefb51c28d4afe017702ee75d96324285346442","src/encode.rs":"0b165176db54fb9136202c54180adabda843a88e5436b96c19be9d41623912a3","src/error.rs":"4526260299c7983696a49ffe30a80139beb0d081fa8f42decc5e3283b361b1cb","src/gensym.rs":"b5e02e34443085f01deb5b657e541be7dc5d6dc952304e434a0b08320ea6c185","src/lexer.rs":"bc65c4d71c6f16a55670585a31ee615c9fcfc40a4bfa165e020ed03a9a27930d","src/lib.rs":"87a394a3c8d70135ee4f79a270a733170267abdff69492f1addd3d27c8e0e937","src/names.rs":"81d49fecbff3b2abbbc323595271f32d912f03cd55a5685b7216d7cead32c420","src/parser.rs":"2a726a17de957f1bdb8abf9670946bb988c6727e1e96ba5b759b89211b78f6a7","src/token.rs":"d18c2f304ffbc017a3b0a7764daeb879a8c653c330cdb6b1eae431ae6c6dd14e","src/wast.rs":"af6e208c2a37028461a6ad9101967fbfa5c7fc343fe5eb6ecad54f948c74e15d","src/wat.rs":"b353622be42b9a1dc789b55a5a93d2de7c4b48f2d61c6a156e4a51701dc08f2d","tests/annotations.rs":"06294077550600f93a5a8c0d7e3ac38b47f00bb8933f9dc390ff31868e873afb","tests/comments.rs":"694e8a3467e9c837f723a43c729be0c6f6dfe3441ad9692759b1d55fd63055a2","tests/parse-fail.rs":"0f1d5dffd1e6145105571222096703c89c4f4a46e25c848faa730f731155ea1c","tests/parse-fail/bad-core-func-alias.wat":"b71372064c3fce9d4a616418605040fe5e1356030a709b798b4769d3619cbbfb","tests/parse-fail/bad-core-func-alias.wat.err":"bb63274c26d3a21209bad794767f48372834bdc10cfbebf568a0c65d52803c90","tests/parse-fail/bad-func-alias.wat":"237c07149e1e74afe3b991a1fee6acb63167c1ca8931341614c435000339b887","tests/parse-fail/bad-func-alias.wat.err":"4a4bfc691b06d20fdf71e1dbac04649a52c76787048415599978987d761308fa","tests/parse-fail/bad-index.wat":"d21489daeec3a35327dcc9e2ba2d0acdd05f4aeaff2272cca608fda4d2338497","tests/parse-fail/bad-index.wat.err":"dc11070de0c9160573006ea4e5fa3c4d28e71bc39b24b1938cf6ff3b03ea7154","tests/parse-fail/bad-name.wat":"e5ff5d410007779a0de6609ea4cc693f0e603d36a106b8f5098c1980dd9f8124","tests/parse-fail/bad-name.wat.err":"fb5638476c1b85d9d1919e3dbcb0f16f82d088a4a22d4a0c186d7b8ba6e1902b","tests/parse-fail/bad-name2.wat":"5a6a4d0c19e5f2e48d7cebf361aca9b9000b7ef0c652997b5bd0ffaadbd2ca8a","tests/parse-fail/bad-name2.wat.err":"129707cce45f1e3cfb3e2ca5c702182e16ca5eeb2dbb2edd0710b004a8e194a5","tests/parse-fail/bad-name3.wat":"c19133d738cc84e9174301f27d4050c216bda81c7e9918d03ac792b088f24a05","tests/parse-fail/bad-name3.wat.err":"84ea63d40a619a0782ec6e94fce63921188ab87b1c3875eacae0a371144ed83a","tests/parse-fail/block1.wat":"91e74b5c3b43be692e7a6ae74fbfa674c4b6197299eb61338c4eccf282b18f17","tests/parse-fail/block1.wat.err":"40a083ae496b41dee7002cc6a664c5db0c5e4d904ae03b815773a769c4493fca","tests/parse-fail/block2.wat":"a8c07b4c09d51f10a8ffdf19806586022552398701cd90eb6d09816d45df06e5","tests/parse-fail/block2.wat.err":"33c842ec5dd0f2fdd3a9ce8187dd98b45ceee48c12810802af809d05b9cd25e9","tests/parse-fail/block3.wat":"29739abfbabd7c55f00ddfbbb9ebd818b4a114ef2336d50514f0842f7e075905","tests/parse-fail/block3.wat.err":"fc667ae2e71a260f62a3c7393bc97272e7c0ff38b17594f4370847b8a5019060","tests/parse-fail/confusing-block-comment0.wat":"8f27c9d0d212bbb1862ea89ffd7cbeafde5dfd755d695c1ba696cd520aba1a1d","tests/parse-fail/confusing-block-comment0.wat.err":"b53cbaef7bcec3862c64e09c084b92cd61bd29b954125482b2d083db250cd9e2","tests/parse-fail/confusing-block-comment1.wat":"b1a0447c9a8eaab8938d15cd33bd4adbb8bb69c2d710209b604023991a4347cb","tests/parse-fail/confusing-block-comment1.wat.err":"2fc3b3e4f98416326e1e5ec034026301069b6a98fa24451bc7573e16b8cb3811","tests/parse-fail/confusing-block-comment2.wat":"e3f49c7a388fba81081beb25d87bbd7db0acce5dd8e3eaa04574905ed7ec420c","tests/parse-fail/confusing-block-comment2.wat.err":"2183231d6acd0b5a117f9aea747c3d5c12e758450a6cd74027bb954a3134cf19","tests/parse-fail/confusing-block-comment3.wat":"d83f89c582501eb8833e772b8462c8974984a2f7fbb80b1452dc399fac74e5ed","tests/parse-fail/confusing-block-comment3.wat.err":"8b2096a4833627905c63f49cdabe44be24336646578dcfbdc67e9bfb35cbc601","tests/parse-fail/confusing-block-comment4.wat":"b7c6c68844d918e9ef6dd5ab9c40c7de7b38f04f94fadad630eda4e596f3e0f8","tests/parse-fail/confusing-block-comment4.wat.err":"2f790cc511edfcd89a12c9207901be16039fc1a06a584d73095e77a52f861cd9","tests/parse-fail/confusing-block-comment5.wat":"a159808032638cc914fa80ac4354a68b0af4f435a09cbe3e2d577582e183eb0a","tests/parse-fail/confusing-block-comment5.wat.err":"6fe0d99894307442f83fe93beaa5da706e06c9bdaf8e39d7cbae4c4fffafcb94","tests/parse-fail/confusing-block-comment6.wat":"abe48bcba2587dca98bc80ddde4e813f94fbc8a3538704a0775ea85bca0f8466","tests/parse-fail/confusing-block-comment6.wat.err":"3c97b9bf1112bbb7335d7fe4be5befb6f91eea7bec7dd3e6b543792231003c56","tests/parse-fail/confusing-block-comment7.wat":"e125c416ea5fa0ac35a58295a83a6f345438e2d7ddc6a39bd76c8e89885b3f0e","tests/parse-fail/confusing-block-comment7.wat.err":"5c34528ff2019cd3f0b3df34fd42523c0b66120706321da2c88ec05793478d2e","tests/parse-fail/confusing-block-comment8.wat":"200cc4c0e5af21a25529d7a81633a03642cff807255d6cd72eb45cdccc605cec","tests/parse-fail/confusing-block-comment8.wat.err":"9b81237d150a784b71791eee88fb6264a8bd6412862660f7392945203809e517","tests/parse-fail/confusing-line-comment0.wat":"bcec4c5a1e52b3e392e07c6711c979aa8d7db8baaf2bcdf270ba16d1aa528d26","tests/parse-fail/confusing-line-comment0.wat.err":"41ec5a075dc6b73afe1aec6b3198c5c4ae3a1a900e1610115879058ce034d6f6","tests/parse-fail/confusing-line-comment1.wat":"a2afbcab00ec957dfd9e9bf21fa4238852247b27f0b054f4a00f6b172dddf853","tests/parse-fail/confusing-line-comment1.wat.err":"f19a645e6fb5cbd7a0dd2308732741edcf83dbae0ef62549972029856a9e7fc6","tests/parse-fail/confusing-line-comment2.wat":"7f2a68229d02aac56ec4dfccf139bf2d617a0e89430357b30444dc4239d8aa89","tests/parse-fail/confusing-line-comment2.wat.err":"08add3d33e10e1ab6b4f3ae431f5db61d6f6c0a2b7d6828482a1e51b3a2d3851","tests/parse-fail/confusing-line-comment3.wat":"61173ae54782f6de86685f9555ffb94bbe2cf20b234daf660abb69ba3326f1ff","tests/parse-fail/confusing-line-comment3.wat.err":"4a5333dc02efa3c1eeab9cafa7c707f78abe92defdb01a71d6fe20944e4785f0","tests/parse-fail/confusing-line-comment4.wat":"9ecbbbe82c750e6475af1bfb46fe7a06115e4446a437d19fc08ca3d002f2a1c9","tests/parse-fail/confusing-line-comment4.wat.err":"ddb8aee8006265253b09c313cf5eb5c2dc4da66f502b4f6d3e2e1de77b35aec9","tests/parse-fail/confusing-line-comment5.wat":"8a4c8d342111bc9d37c16dbdf67c52027e1a42632abc9f359b3e4f07a85748b5","tests/parse-fail/confusing-line-comment5.wat.err":"34e368719fc0eab2f1a43c9f8e6f1b31aa9be9f971085d72374e49bde39cbfe5","tests/parse-fail/confusing-line-comment6.wat":"15f0dcdec23736ce92db84b3a7cdfe8689c97f2a7d0b9b0bfb0dcd2675163ed1","tests/parse-fail/confusing-line-comment6.wat.err":"0570be2ede803f071925d249f3858d3a417b5a6d678c9da40fc851d788d12983","tests/parse-fail/confusing-line-comment7.wat":"c7ee59301a701dd52d56cad02df78b0ad3584460bc18efa42ee137fe0c35aef6","tests/parse-fail/confusing-line-comment7.wat.err":"feebbeee8c85d8b3b85cec89435ae18f3ade9f754ca180d747a41406b64ca07a","tests/parse-fail/confusing-line-comment8.wat":"17632a8142154624de88b3cf93516147ed3419d785200bcd7049499eca8e8f04","tests/parse-fail/confusing-line-comment8.wat.err":"9c209285f2295cd2bc999aa7a9534a654932493308ab1f102839ed15a4d04d17","tests/parse-fail/confusing-string0.wat":"497b679b32baddcd6a158f4cadd3d9a9dea3457bac2a8c2c3d4e09b7c2d80842","tests/parse-fail/confusing-string0.wat.err":"cb3d737f2319346675a038716694354cd3b272453daa8a96e32e9861a9277f7b","tests/parse-fail/confusing-string1.wat":"46654cbed1ea6aab5019aef3d20098a391e40dacafa1ad5e83bf4ec384109fce","tests/parse-fail/confusing-string1.wat.err":"de7e7da516dc6c244bd0e4f012577b69f0cacbcc10f727fadb4b50bb04e0e2b4","tests/parse-fail/confusing-string2.wat":"11938f217c14387c05312735130f00c91d9df2d3ff9df7f13395e0f2b81dad54","tests/parse-fail/confusing-string2.wat.err":"e7bd08b146a855d681fefaf9e0576a9c333a2d10044f8e268b916b22a54227c9","tests/parse-fail/confusing-string3.wat":"e0ca4903fcafb9a54a91cf99e5eac95d25c6d2eb67b076f88191ad396f839cb6","tests/parse-fail/confusing-string3.wat.err":"b88d5db9e445c798eb24f95b7661b9c0368934d27ee8208477cd1c99351b939a","tests/parse-fail/confusing-string4.wat":"3ee2aee7f77604d051519c6f1795634469c12e98ae347a98f0c8445eecf1ff3d","tests/parse-fail/confusing-string4.wat.err":"1edc65bb09d8d3eed6ff69e7d9a7a4b5941dc823fa3436fa375657510255f6f4","tests/parse-fail/confusing-string5.wat":"024e50943128840d53f17e31a9b9332ce4f0ee70a847a043015f435b1c3c6e76","tests/parse-fail/confusing-string5.wat.err":"a0f13ec40d596ea2d8b0c4292b0d28775a5116ab7e11d7de88b295d25428c661","tests/parse-fail/confusing-string6.wat":"79cf157e29319800d2652c5a7f3dc90e07ebe2145c9904a70fc12027cdee84b7","tests/parse-fail/confusing-string6.wat.err":"860555e7aa13e3de3639cc2a530d6a42b974b629c4659593e972cbb0f306abae","tests/parse-fail/confusing-string7.wat":"7d8e403766dfb4e569754160d31ed0f9a27f908ed6cff96be43ab3d37f5975d5","tests/parse-fail/confusing-string7.wat.err":"658b6a02ba6d769254485f35c20984e7135d914b4266929963d723f26a40be4a","tests/parse-fail/confusing-string8.wat":"5a9b222e578655d57ee6e9f19bc1ea8e29aa52d652975fac685213444ed6458f","tests/parse-fail/confusing-string8.wat.err":"9a4e1a510330c800a1df7966998ebc3cde931eda20b249e5360f5e9a905dce11","tests/parse-fail/inline1.wat":"4e9767d67207aace2ac5e6f63a30e7510e4aa245ba35420539509e2254470272","tests/parse-fail/inline1.wat.err":"0143017a9825e518baa6009bae2c8d63520051dedd3437705bbe36b038a57f41","tests/parse-fail/newline-in-string.wat":"5c01cf709544ade0a6cdfcc39a3836a3bc018b633dc42a6cd872b6defc763ea7","tests/parse-fail/newline-in-string.wat.err":"1504209cc37a78b2aee778f23eacf78606daf964cf7bff251f5700efcd27ffd7","tests/parse-fail/string1.wat":"620d46d585ce94b382b5fde628c1399f3e562014b7a44af46e92f7bd045ca86e","tests/parse-fail/string1.wat.err":"fc53f3a1c4a65d8f25e5af51dec7699f45cecba114ca9c7871781bc70f664320","tests/parse-fail/string10.wat":"f7409dd45e153a1b11cb23e38f4ed87da12bedde38f8f0ccfe91037b0a4d97bd","tests/parse-fail/string10.wat.err":"ce677db5e37e0ed81ca357ed6b5edb21d85c27303ee194855bea7a88457efb6a","tests/parse-fail/string11.wat":"f6e0400b8c6a2014efa1ac676c567e140d8f86b5f4d5129773e6d67af537b615","tests/parse-fail/string11.wat.err":"4c6a550d29eda38a4e1bf7a589596f11655dc779479d7b8d466cfc53f815a742","tests/parse-fail/string12.wat":"23e30070eef22271651cce096a801fc4f79f3c37343c88bb8d2fc99b32d3b8b9","tests/parse-fail/string12.wat.err":"b5ec59f2996b88b2ee157e22d1774dc3e36fc08ed5bfc621aea830d30f66f586","tests/parse-fail/string13.wat":"81a305b981159ee10e140749ea3220c9edaaff53605e63c21995de47382b5faf","tests/parse-fail/string13.wat.err":"959f26c6b54e0d367b51d11d1addd8a53b5b8ff3caf70ebdd46bbea8ccfa2418","tests/parse-fail/string14.wat":"c45c2cc9f7afbfbd4be8e513106d22f7e5e817091448576c6bdf0701b81d95dd","tests/parse-fail/string14.wat.err":"50b5bccba905ddbe275938edb7ed0b09a5ca53dcdad36a7ff736ce9bc8e7a338","tests/parse-fail/string15.wat":"b5e0d5ade40de53b2d767a132e28376bb8c7a6f6238c4d8c248ae717c41d7f1f","tests/parse-fail/string15.wat.err":"0e9fc502cc90f96d1f592a3f63369fd2a3574bc4a2345a70365dbb76804e870f","tests/parse-fail/string16.wat":"38c3688cee80a9d089d239aa06eb1d27c5364ad2bd270aca57d05997c20aa682","tests/parse-fail/string16.wat.err":"4274b3bbe4df4cf0373619b1fcd082d0c802990817d2aca26ed885168c80e489","tests/parse-fail/string2.wat":"1172964aed31537b8c466d1f045f3e756926e7b221f80b2aff4a9a6721ea0beb","tests/parse-fail/string2.wat.err":"4618d3b20a78a077337eb5d6cae14ac39d9853762f011fbd23cff8921618dbde","tests/parse-fail/string3.wat":"07e0fbcd6270c1db100917c151ee4ac3f935e4ee1b27bce3c453b22b4b74f4d6","tests/parse-fail/string3.wat.err":"08ffc6158a9e030b2e211d53bdb8aeacfd879815c7b284d6a83b030566e35928","tests/parse-fail/string4.wat":"c970da2051b0613bdd1de4664f10424e14f2ebabe604175d4fb9b763b37af577","tests/parse-fail/string4.wat.err":"406706594d305c560fabd66417ad4fc276939990b5e701bd9d13fc223d207219","tests/parse-fail/string5.wat":"386cf314bb05acdaaabdf4da1caf140167271a26bd08bf34c3a7427d4bc4431f","tests/parse-fail/string5.wat.err":"1e56b44a23a37b2b2ad05aa9dd7e1e18191b5cc22151f93bbcf9d618779a57bd","tests/parse-fail/string6.wat":"8f1fe2825ff96f2acee9130a7721f86fcc93c221baa9411bf1fb6f0870d38ccb","tests/parse-fail/string6.wat.err":"d55dfd84d94e893f167ae73b7a080aefb2bfb05cc8a1ec201c4d3066fb8549b4","tests/parse-fail/string7.wat":"b12f8c75313d7f834489d3c353422f90bc945b37139586446eda82e334a97cde","tests/parse-fail/string7.wat.err":"4cee0ca61992c249dd0faaf2529a073cf8deeb36111a3f69b43695e5682560a2","tests/parse-fail/string8.wat":"4c2e0e1f883bb4e8cba9313497ed792130e5848e62bde7716102788d7467be10","tests/parse-fail/string8.wat.err":"840c6def7c60dd7c2b7261549cab435ba78c9b3a937adf6d5d9595ff8af01c91","tests/parse-fail/string9.wat":"2b7670caed2b0688d535de6e4e416f35fa717cfbe096a6cc764a669085c8f52f","tests/parse-fail/string9.wat.err":"37b5a9c3af9631500f31f9e5e3efa821b8d96063c57d60fd01df6be6a5c323e1","tests/parse-fail/unbalanced.wat":"f664fbef53a0308f864ba496d38044eb90482636e32586512939d4930729f3fe","tests/parse-fail/unbalanced.wat.err":"aba579f7b836856e69afe05da8328aabe0643d94e369898e686aa7bb0b07e9c9","tests/recursive.rs":"ad8a2b07bf955121a7c9e326ed35f9b2bc56b440c8cc0bbde24d423a79945c1a"},"package":"f5d415036fe747a32b30c76c8bd6c73f69b7705fb7ebca5f16e852eef0c95802"} \ No newline at end of file
+{"files":{"Cargo.toml":"53d3c5b19092b2144bdfe0d4cd843c2b42ce0fddfca703fd09fec319232468dc","LICENSE":"268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5","README.md":"5a0d2b894a3ac74ee2be74715a2f22c40a08520cb4ac59183f4e7356f34ac566","src/component.rs":"23a62f4f2774ccfaf60f68e9d9416e68ba203eea782ce0c39cf553ad293f1df4","src/component/alias.rs":"5ec26333e179dc3778dead489f1273815fe9c1c808ba6a7e60eff54072fad795","src/component/binary.rs":"02b07602dc755fd5cec95b6348dd919c75f368c16a81402072201cf911be5f0d","src/component/component.rs":"0c49ff1c1c4b8fe6d330eb41bce8ad176c7208c4178090b7325e9994e83c1f20","src/component/custom.rs":"f5b23c34b73a716a986fd999fc8d8c9e24c341e83292088fe83325cd82dab4f5","src/component/expand.rs":"71b2e23f50957b4a15d758df7f8651fdbaf5cf8f44fbfeb134b412318dbd8921","src/component/export.rs":"f51e824c839d8bb0884eca509622f376c8cce3335be324b2b25033af6216fd2a","src/component/func.rs":"4f69de6c38cc6fe77b638ed7d8000c8a170d7053a11a6585dcd5b4877a06804c","src/component/import.rs":"ffe6e4ab8f2cec68b1022c753135d675ab27ecd1315bd38517472ceaffd0610c","src/component/instance.rs":"e550a7ee9af092ae084dd41e2c0ae756b7dca8da4b91d672d90265a6a15dff83","src/component/item_ref.rs":"e9c426ccc0210dc0c37bb0448468f5f4d9e52656b72d4ff0f2dc65c89957fe60","src/component/module.rs":"d27a28199d1dea1c64294a514329a524432319288515b0a0e3091fa7d3a33f74","src/component/resolve.rs":"4bc58161f3e38499cf0d7fddcbaec875579e8c48e0f347f1c820db21b8aa2d86","src/component/types.rs":"c854a2df9613145c38720d41cda571d0d01408fc09789bd22740874628cbff99","src/component/wast.rs":"36f9440618be7755db0a2994e5ff14187392c25543cdde2069e4c69111a7f490","src/core.rs":"24b71d1ab2ad874c4e37b4dd709b6532989b60e6bc503194434f95029cc1cda7","src/core/binary.rs":"59462baaf8ccb7eb44a39721b71c3627c5ff2cf5e4f24c89a158c5372732f026","src/core/custom.rs":"edd6044b75d79ec873c28d803fb8dc9a53724f1bba474bcdef2bc77196e0a4d2","src/core/export.rs":"1322a120d9e1dd6f3aa1485ee0bbc4294961028ae8a7584a24170af5823b73b1","src/core/expr.rs":"c3b66daa6d2ad3f47d1d3c6a69bd1652d289e248ab3244e448f2fba7ea291b55","src/core/func.rs":"f87239645e45b7e40ecf7f8f2b707a7cfc0620cd2632cfdaca3cb155a06da732","src/core/global.rs":"dec0abecadd2fde9298acb729259deba1ef3c4733ae5afcb4fe8cd4a633f5349","src/core/import.rs":"602a13aed2fd5fa63e2562246586546199861df57f304c2906561ab77810cadd","src/core/memory.rs":"de66cc6d6a9238428ed39ea5dccfa5ff065b4f3ec96d6cfc8405cbb16742791d","src/core/module.rs":"2b608a3cfee4df1ceeefaa046863dc964172bd6f52f6678d96078091fae7657a","src/core/resolve/deinline_import_export.rs":"41226cb8c654e7ed5a22bdb774b3901c73bed52c23a4ed6f5d0302950e02c30b","src/core/resolve/mod.rs":"9b2680b00a1cafad2dbbfdac86aa1c135ea67203bbc4dee939302b27f4aff6d0","src/core/resolve/names.rs":"afa1df4d75555dd7235a117a3c7217d1ec774b062e3a7c98d7470447561c97af","src/core/resolve/types.rs":"ff34eed438539cd88ea6c2c51e49ed42eee0d8be566d40c799254bf08125cfa8","src/core/table.rs":"6b611622d7d4f83cbe8e1a82139937c0294996d5f8208ade0886d680d8ef0b5d","src/core/tag.rs":"8a3d4fcdb86dedd68cdaf25abd930ef52a461cf2a0e9393bb9cb6f425afaaf2e","src/core/types.rs":"608f59af8c41f83c0d6db41c03f187fd30d3f85a021540a5de522464b011d52a","src/core/wast.rs":"a7307f93fb20274f0c99952deeefb51c28d4afe017702ee75d96324285346442","src/encode.rs":"0b165176db54fb9136202c54180adabda843a88e5436b96c19be9d41623912a3","src/error.rs":"4526260299c7983696a49ffe30a80139beb0d081fa8f42decc5e3283b361b1cb","src/gensym.rs":"b5e02e34443085f01deb5b657e541be7dc5d6dc952304e434a0b08320ea6c185","src/lexer.rs":"bc65c4d71c6f16a55670585a31ee615c9fcfc40a4bfa165e020ed03a9a27930d","src/lib.rs":"3b1e7af5e618b9d3ac680e7f8e9b0114fedf502496fb0e62dc95d3dbc1633772","src/names.rs":"81d49fecbff3b2abbbc323595271f32d912f03cd55a5685b7216d7cead32c420","src/parser.rs":"fe6a3727c68c8075c38422de545eb189fd8f5747ca681f6704982ed5c8ea4476","src/token.rs":"d18c2f304ffbc017a3b0a7764daeb879a8c653c330cdb6b1eae431ae6c6dd14e","src/wast.rs":"af6e208c2a37028461a6ad9101967fbfa5c7fc343fe5eb6ecad54f948c74e15d","src/wat.rs":"6fd57cf40795fabe977bdae50913f0e75c3dad3a0c0ace95df2de4f92be8aaf5","tests/annotations.rs":"06294077550600f93a5a8c0d7e3ac38b47f00bb8933f9dc390ff31868e873afb","tests/comments.rs":"694e8a3467e9c837f723a43c729be0c6f6dfe3441ad9692759b1d55fd63055a2","tests/parse-fail.rs":"0f1d5dffd1e6145105571222096703c89c4f4a46e25c848faa730f731155ea1c","tests/parse-fail/bad-core-func-alias.wat":"b71372064c3fce9d4a616418605040fe5e1356030a709b798b4769d3619cbbfb","tests/parse-fail/bad-core-func-alias.wat.err":"bb63274c26d3a21209bad794767f48372834bdc10cfbebf568a0c65d52803c90","tests/parse-fail/bad-func-alias.wat":"237c07149e1e74afe3b991a1fee6acb63167c1ca8931341614c435000339b887","tests/parse-fail/bad-func-alias.wat.err":"4a4bfc691b06d20fdf71e1dbac04649a52c76787048415599978987d761308fa","tests/parse-fail/bad-index.wat":"d21489daeec3a35327dcc9e2ba2d0acdd05f4aeaff2272cca608fda4d2338497","tests/parse-fail/bad-index.wat.err":"dc11070de0c9160573006ea4e5fa3c4d28e71bc39b24b1938cf6ff3b03ea7154","tests/parse-fail/bad-name.wat":"e5ff5d410007779a0de6609ea4cc693f0e603d36a106b8f5098c1980dd9f8124","tests/parse-fail/bad-name.wat.err":"fb5638476c1b85d9d1919e3dbcb0f16f82d088a4a22d4a0c186d7b8ba6e1902b","tests/parse-fail/bad-name2.wat":"5a6a4d0c19e5f2e48d7cebf361aca9b9000b7ef0c652997b5bd0ffaadbd2ca8a","tests/parse-fail/bad-name2.wat.err":"129707cce45f1e3cfb3e2ca5c702182e16ca5eeb2dbb2edd0710b004a8e194a5","tests/parse-fail/bad-name3.wat":"c19133d738cc84e9174301f27d4050c216bda81c7e9918d03ac792b088f24a05","tests/parse-fail/bad-name3.wat.err":"84ea63d40a619a0782ec6e94fce63921188ab87b1c3875eacae0a371144ed83a","tests/parse-fail/block1.wat":"91e74b5c3b43be692e7a6ae74fbfa674c4b6197299eb61338c4eccf282b18f17","tests/parse-fail/block1.wat.err":"40a083ae496b41dee7002cc6a664c5db0c5e4d904ae03b815773a769c4493fca","tests/parse-fail/block2.wat":"a8c07b4c09d51f10a8ffdf19806586022552398701cd90eb6d09816d45df06e5","tests/parse-fail/block2.wat.err":"33c842ec5dd0f2fdd3a9ce8187dd98b45ceee48c12810802af809d05b9cd25e9","tests/parse-fail/block3.wat":"29739abfbabd7c55f00ddfbbb9ebd818b4a114ef2336d50514f0842f7e075905","tests/parse-fail/block3.wat.err":"fc667ae2e71a260f62a3c7393bc97272e7c0ff38b17594f4370847b8a5019060","tests/parse-fail/confusing-block-comment0.wat":"8f27c9d0d212bbb1862ea89ffd7cbeafde5dfd755d695c1ba696cd520aba1a1d","tests/parse-fail/confusing-block-comment0.wat.err":"b53cbaef7bcec3862c64e09c084b92cd61bd29b954125482b2d083db250cd9e2","tests/parse-fail/confusing-block-comment1.wat":"b1a0447c9a8eaab8938d15cd33bd4adbb8bb69c2d710209b604023991a4347cb","tests/parse-fail/confusing-block-comment1.wat.err":"2fc3b3e4f98416326e1e5ec034026301069b6a98fa24451bc7573e16b8cb3811","tests/parse-fail/confusing-block-comment2.wat":"e3f49c7a388fba81081beb25d87bbd7db0acce5dd8e3eaa04574905ed7ec420c","tests/parse-fail/confusing-block-comment2.wat.err":"2183231d6acd0b5a117f9aea747c3d5c12e758450a6cd74027bb954a3134cf19","tests/parse-fail/confusing-block-comment3.wat":"d83f89c582501eb8833e772b8462c8974984a2f7fbb80b1452dc399fac74e5ed","tests/parse-fail/confusing-block-comment3.wat.err":"8b2096a4833627905c63f49cdabe44be24336646578dcfbdc67e9bfb35cbc601","tests/parse-fail/confusing-block-comment4.wat":"b7c6c68844d918e9ef6dd5ab9c40c7de7b38f04f94fadad630eda4e596f3e0f8","tests/parse-fail/confusing-block-comment4.wat.err":"2f790cc511edfcd89a12c9207901be16039fc1a06a584d73095e77a52f861cd9","tests/parse-fail/confusing-block-comment5.wat":"a159808032638cc914fa80ac4354a68b0af4f435a09cbe3e2d577582e183eb0a","tests/parse-fail/confusing-block-comment5.wat.err":"6fe0d99894307442f83fe93beaa5da706e06c9bdaf8e39d7cbae4c4fffafcb94","tests/parse-fail/confusing-block-comment6.wat":"abe48bcba2587dca98bc80ddde4e813f94fbc8a3538704a0775ea85bca0f8466","tests/parse-fail/confusing-block-comment6.wat.err":"3c97b9bf1112bbb7335d7fe4be5befb6f91eea7bec7dd3e6b543792231003c56","tests/parse-fail/confusing-block-comment7.wat":"e125c416ea5fa0ac35a58295a83a6f345438e2d7ddc6a39bd76c8e89885b3f0e","tests/parse-fail/confusing-block-comment7.wat.err":"5c34528ff2019cd3f0b3df34fd42523c0b66120706321da2c88ec05793478d2e","tests/parse-fail/confusing-block-comment8.wat":"200cc4c0e5af21a25529d7a81633a03642cff807255d6cd72eb45cdccc605cec","tests/parse-fail/confusing-block-comment8.wat.err":"9b81237d150a784b71791eee88fb6264a8bd6412862660f7392945203809e517","tests/parse-fail/confusing-line-comment0.wat":"bcec4c5a1e52b3e392e07c6711c979aa8d7db8baaf2bcdf270ba16d1aa528d26","tests/parse-fail/confusing-line-comment0.wat.err":"41ec5a075dc6b73afe1aec6b3198c5c4ae3a1a900e1610115879058ce034d6f6","tests/parse-fail/confusing-line-comment1.wat":"a2afbcab00ec957dfd9e9bf21fa4238852247b27f0b054f4a00f6b172dddf853","tests/parse-fail/confusing-line-comment1.wat.err":"f19a645e6fb5cbd7a0dd2308732741edcf83dbae0ef62549972029856a9e7fc6","tests/parse-fail/confusing-line-comment2.wat":"7f2a68229d02aac56ec4dfccf139bf2d617a0e89430357b30444dc4239d8aa89","tests/parse-fail/confusing-line-comment2.wat.err":"08add3d33e10e1ab6b4f3ae431f5db61d6f6c0a2b7d6828482a1e51b3a2d3851","tests/parse-fail/confusing-line-comment3.wat":"61173ae54782f6de86685f9555ffb94bbe2cf20b234daf660abb69ba3326f1ff","tests/parse-fail/confusing-line-comment3.wat.err":"4a5333dc02efa3c1eeab9cafa7c707f78abe92defdb01a71d6fe20944e4785f0","tests/parse-fail/confusing-line-comment4.wat":"9ecbbbe82c750e6475af1bfb46fe7a06115e4446a437d19fc08ca3d002f2a1c9","tests/parse-fail/confusing-line-comment4.wat.err":"ddb8aee8006265253b09c313cf5eb5c2dc4da66f502b4f6d3e2e1de77b35aec9","tests/parse-fail/confusing-line-comment5.wat":"8a4c8d342111bc9d37c16dbdf67c52027e1a42632abc9f359b3e4f07a85748b5","tests/parse-fail/confusing-line-comment5.wat.err":"34e368719fc0eab2f1a43c9f8e6f1b31aa9be9f971085d72374e49bde39cbfe5","tests/parse-fail/confusing-line-comment6.wat":"15f0dcdec23736ce92db84b3a7cdfe8689c97f2a7d0b9b0bfb0dcd2675163ed1","tests/parse-fail/confusing-line-comment6.wat.err":"0570be2ede803f071925d249f3858d3a417b5a6d678c9da40fc851d788d12983","tests/parse-fail/confusing-line-comment7.wat":"c7ee59301a701dd52d56cad02df78b0ad3584460bc18efa42ee137fe0c35aef6","tests/parse-fail/confusing-line-comment7.wat.err":"feebbeee8c85d8b3b85cec89435ae18f3ade9f754ca180d747a41406b64ca07a","tests/parse-fail/confusing-line-comment8.wat":"17632a8142154624de88b3cf93516147ed3419d785200bcd7049499eca8e8f04","tests/parse-fail/confusing-line-comment8.wat.err":"9c209285f2295cd2bc999aa7a9534a654932493308ab1f102839ed15a4d04d17","tests/parse-fail/confusing-string0.wat":"497b679b32baddcd6a158f4cadd3d9a9dea3457bac2a8c2c3d4e09b7c2d80842","tests/parse-fail/confusing-string0.wat.err":"cb3d737f2319346675a038716694354cd3b272453daa8a96e32e9861a9277f7b","tests/parse-fail/confusing-string1.wat":"46654cbed1ea6aab5019aef3d20098a391e40dacafa1ad5e83bf4ec384109fce","tests/parse-fail/confusing-string1.wat.err":"de7e7da516dc6c244bd0e4f012577b69f0cacbcc10f727fadb4b50bb04e0e2b4","tests/parse-fail/confusing-string2.wat":"11938f217c14387c05312735130f00c91d9df2d3ff9df7f13395e0f2b81dad54","tests/parse-fail/confusing-string2.wat.err":"e7bd08b146a855d681fefaf9e0576a9c333a2d10044f8e268b916b22a54227c9","tests/parse-fail/confusing-string3.wat":"e0ca4903fcafb9a54a91cf99e5eac95d25c6d2eb67b076f88191ad396f839cb6","tests/parse-fail/confusing-string3.wat.err":"b88d5db9e445c798eb24f95b7661b9c0368934d27ee8208477cd1c99351b939a","tests/parse-fail/confusing-string4.wat":"3ee2aee7f77604d051519c6f1795634469c12e98ae347a98f0c8445eecf1ff3d","tests/parse-fail/confusing-string4.wat.err":"1edc65bb09d8d3eed6ff69e7d9a7a4b5941dc823fa3436fa375657510255f6f4","tests/parse-fail/confusing-string5.wat":"024e50943128840d53f17e31a9b9332ce4f0ee70a847a043015f435b1c3c6e76","tests/parse-fail/confusing-string5.wat.err":"a0f13ec40d596ea2d8b0c4292b0d28775a5116ab7e11d7de88b295d25428c661","tests/parse-fail/confusing-string6.wat":"79cf157e29319800d2652c5a7f3dc90e07ebe2145c9904a70fc12027cdee84b7","tests/parse-fail/confusing-string6.wat.err":"860555e7aa13e3de3639cc2a530d6a42b974b629c4659593e972cbb0f306abae","tests/parse-fail/confusing-string7.wat":"7d8e403766dfb4e569754160d31ed0f9a27f908ed6cff96be43ab3d37f5975d5","tests/parse-fail/confusing-string7.wat.err":"658b6a02ba6d769254485f35c20984e7135d914b4266929963d723f26a40be4a","tests/parse-fail/confusing-string8.wat":"5a9b222e578655d57ee6e9f19bc1ea8e29aa52d652975fac685213444ed6458f","tests/parse-fail/confusing-string8.wat.err":"9a4e1a510330c800a1df7966998ebc3cde931eda20b249e5360f5e9a905dce11","tests/parse-fail/inline1.wat":"4e9767d67207aace2ac5e6f63a30e7510e4aa245ba35420539509e2254470272","tests/parse-fail/inline1.wat.err":"0143017a9825e518baa6009bae2c8d63520051dedd3437705bbe36b038a57f41","tests/parse-fail/newline-in-string.wat":"5c01cf709544ade0a6cdfcc39a3836a3bc018b633dc42a6cd872b6defc763ea7","tests/parse-fail/newline-in-string.wat.err":"1504209cc37a78b2aee778f23eacf78606daf964cf7bff251f5700efcd27ffd7","tests/parse-fail/string1.wat":"620d46d585ce94b382b5fde628c1399f3e562014b7a44af46e92f7bd045ca86e","tests/parse-fail/string1.wat.err":"fc53f3a1c4a65d8f25e5af51dec7699f45cecba114ca9c7871781bc70f664320","tests/parse-fail/string10.wat":"f7409dd45e153a1b11cb23e38f4ed87da12bedde38f8f0ccfe91037b0a4d97bd","tests/parse-fail/string10.wat.err":"ce677db5e37e0ed81ca357ed6b5edb21d85c27303ee194855bea7a88457efb6a","tests/parse-fail/string11.wat":"f6e0400b8c6a2014efa1ac676c567e140d8f86b5f4d5129773e6d67af537b615","tests/parse-fail/string11.wat.err":"4c6a550d29eda38a4e1bf7a589596f11655dc779479d7b8d466cfc53f815a742","tests/parse-fail/string12.wat":"23e30070eef22271651cce096a801fc4f79f3c37343c88bb8d2fc99b32d3b8b9","tests/parse-fail/string12.wat.err":"b5ec59f2996b88b2ee157e22d1774dc3e36fc08ed5bfc621aea830d30f66f586","tests/parse-fail/string13.wat":"81a305b981159ee10e140749ea3220c9edaaff53605e63c21995de47382b5faf","tests/parse-fail/string13.wat.err":"959f26c6b54e0d367b51d11d1addd8a53b5b8ff3caf70ebdd46bbea8ccfa2418","tests/parse-fail/string14.wat":"c45c2cc9f7afbfbd4be8e513106d22f7e5e817091448576c6bdf0701b81d95dd","tests/parse-fail/string14.wat.err":"50b5bccba905ddbe275938edb7ed0b09a5ca53dcdad36a7ff736ce9bc8e7a338","tests/parse-fail/string15.wat":"b5e0d5ade40de53b2d767a132e28376bb8c7a6f6238c4d8c248ae717c41d7f1f","tests/parse-fail/string15.wat.err":"0e9fc502cc90f96d1f592a3f63369fd2a3574bc4a2345a70365dbb76804e870f","tests/parse-fail/string16.wat":"38c3688cee80a9d089d239aa06eb1d27c5364ad2bd270aca57d05997c20aa682","tests/parse-fail/string16.wat.err":"4274b3bbe4df4cf0373619b1fcd082d0c802990817d2aca26ed885168c80e489","tests/parse-fail/string2.wat":"1172964aed31537b8c466d1f045f3e756926e7b221f80b2aff4a9a6721ea0beb","tests/parse-fail/string2.wat.err":"4618d3b20a78a077337eb5d6cae14ac39d9853762f011fbd23cff8921618dbde","tests/parse-fail/string3.wat":"07e0fbcd6270c1db100917c151ee4ac3f935e4ee1b27bce3c453b22b4b74f4d6","tests/parse-fail/string3.wat.err":"08ffc6158a9e030b2e211d53bdb8aeacfd879815c7b284d6a83b030566e35928","tests/parse-fail/string4.wat":"c970da2051b0613bdd1de4664f10424e14f2ebabe604175d4fb9b763b37af577","tests/parse-fail/string4.wat.err":"406706594d305c560fabd66417ad4fc276939990b5e701bd9d13fc223d207219","tests/parse-fail/string5.wat":"386cf314bb05acdaaabdf4da1caf140167271a26bd08bf34c3a7427d4bc4431f","tests/parse-fail/string5.wat.err":"1e56b44a23a37b2b2ad05aa9dd7e1e18191b5cc22151f93bbcf9d618779a57bd","tests/parse-fail/string6.wat":"8f1fe2825ff96f2acee9130a7721f86fcc93c221baa9411bf1fb6f0870d38ccb","tests/parse-fail/string6.wat.err":"d55dfd84d94e893f167ae73b7a080aefb2bfb05cc8a1ec201c4d3066fb8549b4","tests/parse-fail/string7.wat":"b12f8c75313d7f834489d3c353422f90bc945b37139586446eda82e334a97cde","tests/parse-fail/string7.wat.err":"4cee0ca61992c249dd0faaf2529a073cf8deeb36111a3f69b43695e5682560a2","tests/parse-fail/string8.wat":"4c2e0e1f883bb4e8cba9313497ed792130e5848e62bde7716102788d7467be10","tests/parse-fail/string8.wat.err":"840c6def7c60dd7c2b7261549cab435ba78c9b3a937adf6d5d9595ff8af01c91","tests/parse-fail/string9.wat":"2b7670caed2b0688d535de6e4e416f35fa717cfbe096a6cc764a669085c8f52f","tests/parse-fail/string9.wat.err":"37b5a9c3af9631500f31f9e5e3efa821b8d96063c57d60fd01df6be6a5c323e1","tests/parse-fail/unbalanced.wat":"f664fbef53a0308f864ba496d38044eb90482636e32586512939d4930729f3fe","tests/parse-fail/unbalanced.wat.err":"aba579f7b836856e69afe05da8328aabe0643d94e369898e686aa7bb0b07e9c9","tests/recursive.rs":"ad8a2b07bf955121a7c9e326ed35f9b2bc56b440c8cc0bbde24d423a79945c1a"},"package":"1ef6e1ef34d7da3e2b374fd2b1a9c0227aff6cad596e1b24df9b58d0f6222faa"} \ No newline at end of file
diff --git a/third_party/rust/wast/Cargo.toml b/third_party/rust/wast/Cargo.toml
index e2ac927aa5..7f2d159a8b 100644
--- a/third_party/rust/wast/Cargo.toml
+++ b/third_party/rust/wast/Cargo.toml
@@ -12,7 +12,7 @@
[package]
edition = "2021"
name = "wast"
-version = "70.0.1"
+version = "201.0.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
description = """
Customizable Rust parsers for the WebAssembly Text formats WAT and WAST
@@ -27,6 +27,9 @@ repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wa
name = "parse-fail"
harness = false
+[dependencies.bumpalo]
+version = "3.14.0"
+
[dependencies.leb128]
version = "0.2.4"
@@ -37,7 +40,7 @@ version = "2.4.1"
version = "0.1.9"
[dependencies.wasm-encoder]
-version = "0.40.0"
+version = "0.201.0"
[dev-dependencies.anyhow]
version = "1.0.58"
@@ -48,3 +51,9 @@ version = "1.3"
[features]
default = ["wasm-module"]
wasm-module = []
+
+[lints.clippy]
+all = "allow"
+
+[lints.rust]
+unsafe_code = "deny"
diff --git a/third_party/rust/wast/src/component/component.rs b/third_party/rust/wast/src/component/component.rs
index f954935d87..cc9171b505 100644
--- a/third_party/rust/wast/src/component/component.rs
+++ b/third_party/rust/wast/src/component/component.rs
@@ -111,6 +111,7 @@ impl<'a> Parse<'a> for Component<'a> {
let _r = parser.register_annotation("custom");
let _r = parser.register_annotation("producers");
let _r = parser.register_annotation("name");
+ let _r = parser.register_annotation("metadata.code.branch_hint");
let span = parser.parse::<kw::component>()?.0;
let id = parser.parse()?;
diff --git a/third_party/rust/wast/src/core/binary.rs b/third_party/rust/wast/src/core/binary.rs
index 68facd6431..da94da0241 100644
--- a/third_party/rust/wast/src/core/binary.rs
+++ b/third_party/rust/wast/src/core/binary.rs
@@ -69,7 +69,7 @@ pub fn encode(
if needs_data_count(&funcs) {
e.section(12, &data.len());
}
- e.section_list(10, Code, &funcs);
+ e.code_section(&funcs, &imports);
e.section_list(11, Data, &data);
let names = find_names(module_id, module_name, fields);
@@ -121,6 +121,47 @@ impl Encoder<'_> {
}
self.custom_sections(CustomPlace::After(anchor));
}
+
+ /// Encodes the code section of a wasm module module while additionally
+ /// handling the branch hinting proposal.
+ ///
+ /// The branch hinting proposal requires to encode the offsets of the
+ /// instructions relative from the beginning of the function. Here we encode
+ /// each instruction and we save its offset. If needed, we use this
+ /// information to build the branch hint section and insert it before the
+ /// code section.
+ fn code_section<'a>(&'a mut self, list: &[&'a Func<'_>], imports: &[&Import<'_>]) {
+ self.custom_sections(CustomPlace::Before(CustomPlaceAnchor::Code));
+
+ if !list.is_empty() {
+ let mut branch_hints = Vec::new();
+ let mut code_section = Vec::new();
+
+ list.len().encode(&mut code_section);
+ let mut func_index = imports
+ .iter()
+ .filter(|i| matches!(i.item.kind, ItemKind::Func(..)))
+ .count() as u32;
+ for func in list.iter() {
+ let hints = func.encode(&mut code_section);
+ if !hints.is_empty() {
+ branch_hints.push(FunctionBranchHints { func_index, hints });
+ }
+ func_index += 1;
+ }
+
+ // Branch hints section has to be inserted before the Code section
+ // Insert the section only if we have some hints
+ if !branch_hints.is_empty() {
+ self.section(0, &("metadata.code.branch_hint", branch_hints));
+ }
+
+ // Finally, insert the Code section from the tmp buffer
+ self.wasm.push(10);
+ code_section.encode(&mut self.wasm);
+ }
+ self.custom_sections(CustomPlace::After(CustomPlaceAnchor::Code));
+ }
}
impl Encode for FunctionType<'_> {
@@ -475,7 +516,7 @@ impl Encode for Table<'_> {
e.push(0x40);
e.push(0x00);
ty.encode(e);
- init_expr.encode(e);
+ init_expr.encode(e, 0);
}
_ => panic!("TableKind should be normal during encoding"),
}
@@ -497,7 +538,9 @@ impl Encode for Global<'_> {
assert!(self.exports.names.is_empty());
self.ty.encode(e);
match &self.kind {
- GlobalKind::Inline(expr) => expr.encode(e),
+ GlobalKind::Inline(expr) => {
+ let _hints = expr.encode(e, 0);
+ }
_ => panic!("GlobalKind should be inline during encoding"),
}
}
@@ -534,7 +577,7 @@ impl Encode for Elem<'_> {
ElemPayload::Indices(_),
) => {
e.push(0x00);
- offset.encode(e);
+ offset.encode(e, 0);
}
(ElemKind::Passive, ElemPayload::Indices(_)) => {
e.push(0x01); // flags
@@ -543,7 +586,7 @@ impl Encode for Elem<'_> {
(ElemKind::Active { table, offset }, ElemPayload::Indices(_)) => {
e.push(0x02); // flags
table.encode(e);
- offset.encode(e);
+ offset.encode(e, 0);
e.push(0x00); // extern_kind
}
(ElemKind::Declared, ElemPayload::Indices(_)) => {
@@ -565,7 +608,7 @@ impl Encode for Elem<'_> {
},
) => {
e.push(0x04);
- offset.encode(e);
+ offset.encode(e, 0);
}
(ElemKind::Passive, ElemPayload::Exprs { ty, .. }) => {
e.push(0x05);
@@ -574,7 +617,7 @@ impl Encode for Elem<'_> {
(ElemKind::Active { table, offset }, ElemPayload::Exprs { ty, .. }) => {
e.push(0x06);
table.encode(e);
- offset.encode(e);
+ offset.encode(e, 0);
ty.encode(e);
}
(ElemKind::Declared, ElemPayload::Exprs { ty, .. }) => {
@@ -594,7 +637,7 @@ impl Encode for ElemPayload<'_> {
ElemPayload::Exprs { exprs, ty: _ } => {
exprs.len().encode(e);
for expr in exprs {
- expr.encode(e);
+ expr.encode(e, 0);
}
}
}
@@ -610,12 +653,12 @@ impl Encode for Data<'_> {
offset,
} => {
e.push(0x00);
- offset.encode(e);
+ offset.encode(e, 0);
}
DataKind::Active { memory, offset } => {
e.push(0x02);
memory.encode(e);
- offset.encode(e);
+ offset.encode(e, 0);
}
}
self.data.iter().map(|l| l.len()).sum::<usize>().encode(e);
@@ -625,20 +668,25 @@ impl Encode for Data<'_> {
}
}
-impl Encode for Func<'_> {
- fn encode(&self, e: &mut Vec<u8>) {
+impl Func<'_> {
+ /// Encodes the function into `e` while returning all branch hints with
+ /// known relative offsets after encoding.
+ fn encode(&self, e: &mut Vec<u8>) -> Vec<BranchHint> {
assert!(self.exports.names.is_empty());
- let mut tmp = Vec::new();
let (expr, locals) = match &self.kind {
FuncKind::Inline { expression, locals } => (expression, locals),
_ => panic!("should only have inline functions in emission"),
};
+ // Encode the function into a temporary vector because functions are
+ // prefixed with their length. The temporary vector, when encoded,
+ // encodes its length first then the body.
+ let mut tmp = Vec::new();
locals.encode(&mut tmp);
- expr.encode(&mut tmp);
+ let branch_hints = expr.encode(&mut tmp, 0);
+ tmp.encode(e);
- tmp.len().encode(e);
- e.extend_from_slice(&tmp);
+ branch_hints
}
}
@@ -658,12 +706,25 @@ impl Encode for Box<[Local<'_>]> {
}
}
-impl Encode for Expression<'_> {
- fn encode(&self, e: &mut Vec<u8>) {
- for instr in self.instrs.iter() {
+// Encode the expression and store the offset from the beginning
+// for each instruction.
+impl Expression<'_> {
+ fn encode(&self, e: &mut Vec<u8>, relative_start: usize) -> Vec<BranchHint> {
+ let mut hints = Vec::with_capacity(self.branch_hints.len());
+ let mut next_hint = self.branch_hints.iter().peekable();
+
+ for (i, instr) in self.instrs.iter().enumerate() {
+ if let Some(hint) = next_hint.next_if(|h| h.instr_index == i) {
+ hints.push(BranchHint {
+ branch_func_offset: u32::try_from(e.len() - relative_start).unwrap(),
+ branch_hint_value: hint.value,
+ });
+ }
instr.encode(e);
}
e.push(0x0b);
+
+ hints
}
}
@@ -1146,6 +1207,31 @@ impl Encode for Dylink0Subsection<'_> {
}
}
+struct FunctionBranchHints {
+ func_index: u32,
+ hints: Vec<BranchHint>,
+}
+
+struct BranchHint {
+ branch_func_offset: u32,
+ branch_hint_value: u32,
+}
+
+impl Encode for FunctionBranchHints {
+ fn encode(&self, e: &mut Vec<u8>) {
+ self.func_index.encode(e);
+ self.hints.encode(e);
+ }
+}
+
+impl Encode for BranchHint {
+ fn encode(&self, e: &mut Vec<u8>) {
+ self.branch_func_offset.encode(e);
+ 1u32.encode(e);
+ self.branch_hint_value.encode(e);
+ }
+}
+
impl Encode for Tag<'_> {
fn encode(&self, e: &mut Vec<u8>) {
self.ty.encode(e);
diff --git a/third_party/rust/wast/src/core/expr.rs b/third_party/rust/wast/src/core/expr.rs
index 489ac205af..b45950b896 100644
--- a/third_party/rust/wast/src/core/expr.rs
+++ b/third_party/rust/wast/src/core/expr.rs
@@ -1,3 +1,4 @@
+use crate::annotation;
use crate::core::*;
use crate::encode::Encode;
use crate::kw;
@@ -14,6 +15,20 @@ use std::mem;
#[allow(missing_docs)]
pub struct Expression<'a> {
pub instrs: Box<[Instruction<'a>]>,
+ pub branch_hints: Vec<BranchHint>,
+}
+
+/// A `@metadata.code.branch_hint` in the code, associated with a If or BrIf
+/// This instruction is a placeholder and won't produce anything. Its purpose
+/// is to store the offset of the following instruction and check that
+/// it's followed by `br_if` or `if`.
+#[derive(Debug)]
+pub struct BranchHint {
+ /// Index of instructions in `instrs` field of `Expression` that this hint
+ /// appplies to.
+ pub instr_index: usize,
+ /// The value of this branch hint
+ pub value: u32,
}
impl<'a> Parse<'a> for Expression<'a> {
@@ -22,6 +37,7 @@ impl<'a> Parse<'a> for Expression<'a> {
exprs.parse(parser)?;
Ok(Expression {
instrs: exprs.instrs.into(),
+ branch_hints: exprs.branch_hints,
})
}
}
@@ -47,6 +63,7 @@ impl<'a> Expression<'a> {
exprs.parse_folded_instruction(parser)?;
Ok(Expression {
instrs: exprs.instrs.into(),
+ branch_hints: exprs.branch_hints,
})
}
}
@@ -66,6 +83,11 @@ struct ExpressionParser<'a> {
/// Descriptor of all our nested s-expr blocks. This only happens when
/// instructions themselves are nested.
stack: Vec<Level<'a>>,
+
+ /// Related to the branch hints proposal.
+ /// Will be used later to collect the offsets in the final binary.
+ /// <(index of branch instructions, BranchHintAnnotation)>
+ branch_hints: Vec<BranchHint>,
}
enum Paren {
@@ -89,6 +111,9 @@ enum Level<'a> {
/// which don't correspond to terminating instructions, we're just in a
/// nested block.
IfArm,
+
+ /// This means we are finishing the parsing of a branch hint annotation.
+ BranchHint,
}
/// Possible states of "what is currently being parsed?" in an `if` expression.
@@ -145,6 +170,14 @@ impl<'a> ExpressionParser<'a> {
if self.handle_if_lparen(parser)? {
continue;
}
+
+ // Handle the case of a branch hint annotation
+ if parser.peek::<annotation::metadata_code_branch_hint>()? {
+ self.parse_branch_hint(parser)?;
+ self.stack.push(Level::BranchHint);
+ continue;
+ }
+
match parser.parse()? {
// If block/loop show up then we just need to be sure to
// push an `end` instruction whenever the `)` token is
@@ -177,6 +210,7 @@ impl<'a> ExpressionParser<'a> {
Paren::Right => match self.stack.pop().unwrap() {
Level::EndWith(i) => self.instrs.push(i),
Level::IfArm => {}
+ Level::BranchHint => {}
// If an `if` statement hasn't parsed the clause or `then`
// block, then that's an error because there weren't enough
@@ -191,7 +225,6 @@ impl<'a> ExpressionParser<'a> {
},
}
}
-
Ok(())
}
@@ -287,6 +320,24 @@ impl<'a> ExpressionParser<'a> {
If::Else => Err(parser.error("unexpected token: too many payloads inside of `(if)`")),
}
}
+
+ fn parse_branch_hint(&mut self, parser: Parser<'a>) -> Result<()> {
+ parser.parse::<annotation::metadata_code_branch_hint>()?;
+
+ let hint = parser.parse::<String>()?;
+
+ let value = match hint.as_bytes() {
+ [0] => 0,
+ [1] => 1,
+ _ => return Err(parser.error("invalid value for branch hint")),
+ };
+
+ self.branch_hints.push(BranchHint {
+ instr_index: self.instrs.len(),
+ value,
+ });
+ Ok(())
+ }
}
// TODO: document this obscenity
diff --git a/third_party/rust/wast/src/core/memory.rs b/third_party/rust/wast/src/core/memory.rs
index 3bc7345ef2..eb1baa1a95 100644
--- a/third_party/rust/wast/src/core/memory.rs
+++ b/third_party/rust/wast/src/core/memory.rs
@@ -165,6 +165,7 @@ impl<'a> Parse<'a> for Data<'a> {
if parser.is_empty() {
return Ok(Expression {
instrs: [insn].into(),
+ branch_hints: Vec::new(),
});
}
@@ -184,6 +185,7 @@ impl<'a> Parse<'a> for Data<'a> {
instrs.push(insn);
Ok(Expression {
instrs: instrs.into(),
+ branch_hints: Vec::new(),
})
}
})?;
diff --git a/third_party/rust/wast/src/core/module.rs b/third_party/rust/wast/src/core/module.rs
index 569a8884d4..f74ce6b619 100644
--- a/third_party/rust/wast/src/core/module.rs
+++ b/third_party/rust/wast/src/core/module.rs
@@ -114,6 +114,7 @@ impl<'a> Parse<'a> for Module<'a> {
let _r = parser.register_annotation("producers");
let _r = parser.register_annotation("name");
let _r = parser.register_annotation("dylink.0");
+ let _r = parser.register_annotation("metadata.code.branch_hint");
let span = parser.parse::<kw::module>()?.0;
let id = parser.parse()?;
diff --git a/third_party/rust/wast/src/core/resolve/deinline_import_export.rs b/third_party/rust/wast/src/core/resolve/deinline_import_export.rs
index c338407182..98e680b58a 100644
--- a/third_party/rust/wast/src/core/resolve/deinline_import_export.rs
+++ b/third_party/rust/wast/src/core/resolve/deinline_import_export.rs
@@ -85,6 +85,7 @@ pub fn run(fields: &mut Vec<ModuleField>) {
} else {
Instruction::I64Const(0)
}]),
+ branch_hints: Vec::new(),
},
},
data,
@@ -143,6 +144,7 @@ pub fn run(fields: &mut Vec<ModuleField>) {
table: Index::Id(id),
offset: Expression {
instrs: Box::new([Instruction::I32Const(0)]),
+ branch_hints: Vec::new(),
},
},
payload,
diff --git a/third_party/rust/wast/src/core/table.rs b/third_party/rust/wast/src/core/table.rs
index 280244498f..e7f0b0f974 100644
--- a/third_party/rust/wast/src/core/table.rs
+++ b/third_party/rust/wast/src/core/table.rs
@@ -253,6 +253,7 @@ impl<'a> ElemPayload<'a> {
ElemPayload::Exprs { exprs, .. } => {
let expr = Expression {
instrs: [Instruction::RefFunc(func)].into(),
+ branch_hints: Vec::new(),
};
exprs.push(expr);
}
diff --git a/third_party/rust/wast/src/lib.rs b/third_party/rust/wast/src/lib.rs
index 7923a343b5..bb16574177 100644
--- a/third_party/rust/wast/src/lib.rs
+++ b/third_party/rust/wast/src/lib.rs
@@ -538,4 +538,5 @@ pub mod annotation {
annotation!(name);
annotation!(producers);
annotation!(dylink_0 = "dylink.0");
+ annotation!(metadata_code_branch_hint = "metadata.code.branch_hint");
}
diff --git a/third_party/rust/wast/src/parser.rs b/third_party/rust/wast/src/parser.rs
index 0c85923f83..7a20ebe255 100644
--- a/third_party/rust/wast/src/parser.rs
+++ b/third_party/rust/wast/src/parser.rs
@@ -65,6 +65,7 @@
use crate::lexer::{Float, Integer, Lexer, Token, TokenKind};
use crate::token::Span;
use crate::Error;
+use bumpalo::Bump;
use std::borrow::Cow;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
@@ -303,7 +304,7 @@ pub struct ParseBuffer<'a> {
cur: Cell<Position>,
known_annotations: RefCell<HashMap<String, usize>>,
depth: Cell<usize>,
- strings: RefCell<Vec<Box<[u8]>>>,
+ strings: Bump,
}
/// The current position within a `Lexer` that we're at. This simultaneously
@@ -396,14 +397,7 @@ impl ParseBuffer<'_> {
/// This will return a reference to `s`, but one that's safely rooted in the
/// `Parser`.
fn push_str(&self, s: Vec<u8>) -> &[u8] {
- let s = Box::from(s);
- let ret = &*s as *const [u8];
- self.strings.borrow_mut().push(s);
- // This should be safe in that the address of `ret` isn't changing as
- // it's on the heap itself. Additionally the lifetime of this return
- // value is tied to the lifetime of `self` (nothing is deallocated
- // early), so it should be safe to say the two have the same lifetime.
- unsafe { &*ret }
+ self.strings.alloc_slice_copy(&s)
}
/// Lexes the next "significant" token from the `pos` specified.
diff --git a/third_party/rust/wast/src/wat.rs b/third_party/rust/wast/src/wat.rs
index f74121187d..6d9a233359 100644
--- a/third_party/rust/wast/src/wat.rs
+++ b/third_party/rust/wast/src/wat.rs
@@ -43,6 +43,7 @@ impl<'a> Parse<'a> for Wat<'a> {
let _r = parser.register_annotation("custom");
let _r = parser.register_annotation("producers");
let _r = parser.register_annotation("name");
+ let _r = parser.register_annotation("metadata.code.branch_hint");
let wat = if parser.peek2::<kw::module>()? {
Wat::Module(parser.parens(|parser| parser.parse())?)
} else if parser.peek2::<kw::component>()? {
diff --git a/third_party/rust/webext-storage/.cargo-checksum.json b/third_party/rust/webext-storage/.cargo-checksum.json
index 98fa4d6a22..25f2af17bf 100644
--- a/third_party/rust/webext-storage/.cargo-checksum.json
+++ b/third_party/rust/webext-storage/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"a11f7fbc29c375034289e7bdd11da4aadac9cb2d939a4f2e5dc61aaea35cf465","README.md":"1fd617294339930ee1ad5172377648b268cce0216fc3971facbfe7c6839e9ab1","build.rs":"92f7d380f3d8fab1e6d80276915af57192e276321d132a5f800ea4520e9cb469","sql/create_schema.sql":"a17311a407ec10e033886b7125da4c8b84bc6d761f6b28edc9594de430e1d964","sql/create_sync_temp_tables.sql":"860ede362c94feb47d85522553fa2852f9bdb9f9b025d6438dd5dee3d4acd527","sql/tests/create_schema_v1.sql":"77cf0c90eaac3e1aea626537147e1b8ec349b68d6076c92fa7ae402aac613050","src/api.rs":"6fe362e4f437def2ad2249de385cca8f0d1d5d67679240351e9f57523fefe5e7","src/db.rs":"b95024c1d8f36a76a6f3098acea5a82bc49de144a24cdc280ed38e9bcc8e772b","src/error.rs":"6437e9a0edefac2707af85eef13bdbfcd53a84d7aa7859599155d10451d42361","src/ffi.rs":"f66a81393bebe7a4b7e7960cb426df106ff1f02bfebcaa6e335b4b8b56c5c936","src/lib.rs":"ab25e7c6ea67fb905fe6dad866c0d2c462b1e93bcff283db947513aeabbb2d73","src/migration.rs":"8d92f82b2ba38e1039fd054c8c75078a6b896a0d3cdc1a52571456b25a32c9c3","src/schema.rs":"d8dd8f66cad71e3e369722734e0d5d16fd9423d5f6a5abba1854a27e1e814724","src/store.rs":"d208689c46fb97cd2c60a0c610ba1998a7132fb50fffa2eefa1d6b169b7c34f0","src/sync/bridge.rs":"996de05beb2904f84b3cbfc9ef85c4844078fdb4867d9068390d496156bee614","src/sync/incoming.rs":"dd77c64e2ade4f39cba258decab6d3db8ad0b5f513aa018efbd56b9869a021d9","src/sync/mod.rs":"bd1bc5c428dfda6aee7efe53b6e74b8015da5129a303638a21ca8d63516e4061","src/sync/outgoing.rs":"dacb77b956f2546fd60a89367927a199d9b662b17201d0781145f7405b61fdce","src/sync/sync_tests.rs":"f3846ca7e463315ba9788826613b987ddcff7b21672ff257a98769ee94f4191a","src/webext-storage.udl":"0341d431ba837cf64ea210ef6157010c6664a0b5a194e89acb0414938636b391","uniffi.toml":"beeec89c2f877eb89be0090dc304dbc7c74e787385e7459bad78c6165bb66791"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"b20c30e7d9ae0460d04b744d5a9274985ded6e5b9ed0262b914553ca71425821","README.md":"1fd617294339930ee1ad5172377648b268cce0216fc3971facbfe7c6839e9ab1","build.rs":"92f7d380f3d8fab1e6d80276915af57192e276321d132a5f800ea4520e9cb469","sql/create_schema.sql":"a17311a407ec10e033886b7125da4c8b84bc6d761f6b28edc9594de430e1d964","sql/create_sync_temp_tables.sql":"860ede362c94feb47d85522553fa2852f9bdb9f9b025d6438dd5dee3d4acd527","sql/tests/create_schema_v1.sql":"77cf0c90eaac3e1aea626537147e1b8ec349b68d6076c92fa7ae402aac613050","src/api.rs":"6fe362e4f437def2ad2249de385cca8f0d1d5d67679240351e9f57523fefe5e7","src/db.rs":"b95024c1d8f36a76a6f3098acea5a82bc49de144a24cdc280ed38e9bcc8e772b","src/error.rs":"6437e9a0edefac2707af85eef13bdbfcd53a84d7aa7859599155d10451d42361","src/ffi.rs":"f66a81393bebe7a4b7e7960cb426df106ff1f02bfebcaa6e335b4b8b56c5c936","src/lib.rs":"ab25e7c6ea67fb905fe6dad866c0d2c462b1e93bcff283db947513aeabbb2d73","src/migration.rs":"8d92f82b2ba38e1039fd054c8c75078a6b896a0d3cdc1a52571456b25a32c9c3","src/schema.rs":"d8dd8f66cad71e3e369722734e0d5d16fd9423d5f6a5abba1854a27e1e814724","src/store.rs":"d208689c46fb97cd2c60a0c610ba1998a7132fb50fffa2eefa1d6b169b7c34f0","src/sync/bridge.rs":"996de05beb2904f84b3cbfc9ef85c4844078fdb4867d9068390d496156bee614","src/sync/incoming.rs":"dd77c64e2ade4f39cba258decab6d3db8ad0b5f513aa018efbd56b9869a021d9","src/sync/mod.rs":"bd1bc5c428dfda6aee7efe53b6e74b8015da5129a303638a21ca8d63516e4061","src/sync/outgoing.rs":"dacb77b956f2546fd60a89367927a199d9b662b17201d0781145f7405b61fdce","src/sync/sync_tests.rs":"f3846ca7e463315ba9788826613b987ddcff7b21672ff257a98769ee94f4191a","src/webext-storage.udl":"0341d431ba837cf64ea210ef6157010c6664a0b5a194e89acb0414938636b391","uniffi.toml":"beeec89c2f877eb89be0090dc304dbc7c74e787385e7459bad78c6165bb66791"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/webext-storage/Cargo.toml b/third_party/rust/webext-storage/Cargo.toml
index c3f9b170df..8306a25dd2 100644
--- a/third_party/rust/webext-storage/Cargo.toml
+++ b/third_party/rust/webext-storage/Cargo.toml
@@ -28,7 +28,7 @@ serde = "1"
serde_derive = "1"
serde_json = "1"
thiserror = "1.0"
-uniffi = "0.25.2"
+uniffi = "0.27.1"
[dependencies.error-support]
path = "../support/error"
@@ -78,7 +78,7 @@ path = "../support/sql"
path = "../support/rc_crypto/nss/nss_build_common"
[build-dependencies.uniffi]
-version = "0.25.2"
+version = "0.27.1"
features = ["build"]
[features]
diff --git a/third_party/rust/weedle2/.cargo-checksum.json b/third_party/rust/weedle2/.cargo-checksum.json
index 9eb63aa3c1..13a9d357d5 100644
--- a/third_party/rust/weedle2/.cargo-checksum.json
+++ b/third_party/rust/weedle2/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"1c9da38b083da061c42208494a1161d95938a524054f95b39097248da32ebfd0","LICENSE.md":"467e2e44913e850ca4fb6760fa3b78ffefda2a21688a6b1e26a8b706963669eb","README.md":"32092d702b93e9cc8d95fbcad1e30e315882a55496ad90a855ea575dc47aa7d4","release.toml":"fce2a51533478f7e7d663b11ac69676323856780adf8af158ce213c9dbc86c75","src/argument.rs":"aa7a119364de0a1f91b00db6cffb0a7b2ee73e0fb79a764211165ccebed56dc8","src/attribute.rs":"b3577059be9c1262d3360b4b4cf4cc6cb052251e5b9d64906fb40fc5e925da69","src/common.rs":"9d781dacc2582ac375f2c3b6b22bcb01add4a63db525d18ebb3af6fe633a311e","src/dictionary.rs":"be209d70b0db33acf753ca63998a63dd37a9d4b193583a1490782781360c2d01","src/interface.rs":"bd2f32bfbcab2167f056fabbf92da9852300141ffc80d9542c6e1bbea9d521ac","src/lib.rs":"42466a14426416dc0d3500842c86fe00471c2d73be6df823c3e05ba0e4572d20","src/literal.rs":"16d44ae36f893c3b2d8d807921b31b992b7e3ce7d1729913b4f392f52db44f9e","src/macros.rs":"2f24c285806c863fd9dc65534479d36961d9666ca5e11c9137f2ce2659a712b6","src/mixin.rs":"d65ff3a49615dabdf3f7845723f01ae730a83c8ef5bdd9a945d35149a9ef2858","src/namespace.rs":"fe6b406c2ab8bd904d0dff8a12321c77f4082ab364559e578e53ab415c8541fa","src/term.rs":"049514b2b44f1bcb5c23d225d3bb0a6135892a5660049c694681c5b99d47d8e7","src/types.rs":"0fe236336e71079c88ae4848624af29c87dd16266ba36c30a0d47e7dc4d33587","src/whitespace.rs":"552ebc98857859e8714e06bb25681ceab6052d009e00f55cceda0d4fb3c4f059"},"package":"2e79c5206e1f43a2306fd64bdb95025ee4228960f2e6c5a8b173f3caaf807741"} \ No newline at end of file
+{"files":{"Cargo.toml":"cd75893e67b7ed5e1ee200efddc874a7d235a7275b3d9bbadd728d47a5d5b8c6","LICENSE.md":"467e2e44913e850ca4fb6760fa3b78ffefda2a21688a6b1e26a8b706963669eb","README.md":"b18a5a8af93ebf01f28b3ec637e64fb0bd21d556e356984974d0031f6eeda47a","release.toml":"fce2a51533478f7e7d663b11ac69676323856780adf8af158ce213c9dbc86c75","src/argument.rs":"aa7a119364de0a1f91b00db6cffb0a7b2ee73e0fb79a764211165ccebed56dc8","src/attribute.rs":"b3577059be9c1262d3360b4b4cf4cc6cb052251e5b9d64906fb40fc5e925da69","src/common.rs":"c0ae3236856bbe9917326ac9769e2fe15283cd4d1a0216aafcea3bc03ad724cc","src/dictionary.rs":"d0c9a40e694eb563d7d8307d36a3f9a3be1766846bdfa809d614a6e1e9e149d1","src/interface.rs":"352f7773e9e3870fa61efdb2b737d777a34def1eb8301ec66f7c416b31d5e88b","src/lib.rs":"95c6b27082959165f3899b4a5a520b10fbcab80dcf94ecf030aba74df0748203","src/literal.rs":"16d44ae36f893c3b2d8d807921b31b992b7e3ce7d1729913b4f392f52db44f9e","src/macros.rs":"2f24c285806c863fd9dc65534479d36961d9666ca5e11c9137f2ce2659a712b6","src/mixin.rs":"d65ff3a49615dabdf3f7845723f01ae730a83c8ef5bdd9a945d35149a9ef2858","src/namespace.rs":"417a9a6cb26efc8f84bd84e59c6ae7378502f920a76738b355e8c905e62efed8","src/term.rs":"049514b2b44f1bcb5c23d225d3bb0a6135892a5660049c694681c5b99d47d8e7","src/types.rs":"0fe236336e71079c88ae4848624af29c87dd16266ba36c30a0d47e7dc4d33587","src/whitespace.rs":"9b58cfc0a4b8667a26ff3d4dcd424a3d00bf23abab4dd0d286318cb18bd77a13"},"package":"998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e"} \ No newline at end of file
diff --git a/third_party/rust/weedle2/Cargo.toml b/third_party/rust/weedle2/Cargo.toml
index 072d3f9a2a..66703cf607 100644
--- a/third_party/rust/weedle2/Cargo.toml
+++ b/third_party/rust/weedle2/Cargo.toml
@@ -12,7 +12,7 @@
[package]
edition = "2021"
name = "weedle2"
-version = "4.0.0"
+version = "5.0.0"
authors = [
"Sharad Chand <sharad.d.chand@gmail.com>",
"Jan-Erik Rediger <jrediger@mozilla.com>",
@@ -21,10 +21,9 @@ exclude = ["tests"]
description = "A WebIDL Parser"
homepage = "https://github.com/mozilla/uniffi-rs/tree/main/weedle2"
documentation = "https://docs.rs/weedle2"
-readme = "./README.md"
+readme = "README.md"
license = "MIT"
repository = "https://github.com/mozilla/uniffi-rs"
-resolver = "2"
[lib]
name = "weedle"
diff --git a/third_party/rust/weedle2/README.md b/third_party/rust/weedle2/README.md
index 889dc0da19..88e4cec017 100644
--- a/third_party/rust/weedle2/README.md
+++ b/third_party/rust/weedle2/README.md
@@ -28,7 +28,7 @@ Parses valid WebIDL definitions & produces a data structure starting from
```toml
[dependencies]
-weedle2 = "4.0.0"
+weedle2 = "5.0.0"
```
### `src/main.rs`
diff --git a/third_party/rust/weedle2/src/common.rs b/third_party/rust/weedle2/src/common.rs
index fadf89ba8b..d36f6e5438 100644
--- a/third_party/rust/weedle2/src/common.rs
+++ b/third_party/rust/weedle2/src/common.rs
@@ -33,6 +33,18 @@ impl<'a, T: Parse<'a>, U: Parse<'a>, V: Parse<'a>> Parse<'a> for (T, U, V) {
parser!(nom::sequence::tuple((T::parse, U::parse, V::parse)));
}
+pub(crate) fn docstring(input: &str) -> IResult<&str, String> {
+ nom::multi::many1(nom::sequence::preceded(
+ nom::character::complete::multispace0,
+ nom::sequence::delimited(
+ nom::bytes::complete::tag("///"),
+ nom::bytes::complete::take_until("\n"),
+ nom::bytes::complete::tag("\n"),
+ ),
+ ))(input)
+ .map(|io| (io.0, io.1.join("\n")))
+}
+
ast_types! {
/// Parses `( body )`
#[derive(Copy, Default)]
@@ -103,6 +115,11 @@ ast_types! {
assign: term!(=),
value: DefaultValue<'a>,
}
+
+ /// Represents consecutive comment lines starting with `///`, joined by `\n`.
+ struct Docstring(
+ String = docstring,
+ )
}
#[cfg(test)]
@@ -211,4 +228,32 @@ mod test {
Identifier;
0 == "hello";
});
+
+ test!(should_parse_docstring { "///hello world\n" =>
+ "";
+ Docstring;
+ 0 == "hello world";
+ });
+
+ test!(should_parse_multiline_docstring { "///hello\n///world\n" =>
+ "";
+ Docstring;
+ 0 == "hello\nworld";
+ });
+
+ test!(should_parse_multiline_indented_docstring { "///hello\n ///world\n" =>
+ "";
+ Docstring;
+ 0 == "hello\nworld";
+ });
+
+ test!(should_not_parse_docstring_with_comments { "///hello\n//comment1\n///world\n" =>
+ "//comment1\n///world\n";
+ Docstring;
+ 0 == "hello";
+ });
+
+ test!(err should_not_parse_not_docstring { "" =>
+ Docstring
+ });
}
diff --git a/third_party/rust/weedle2/src/dictionary.rs b/third_party/rust/weedle2/src/dictionary.rs
index 3c9b23cac5..b775d526dc 100644
--- a/third_party/rust/weedle2/src/dictionary.rs
+++ b/third_party/rust/weedle2/src/dictionary.rs
@@ -1,5 +1,5 @@
use crate::attribute::ExtendedAttributeList;
-use crate::common::{Default, Identifier};
+use crate::common::{Default, Docstring, Identifier};
use crate::types::Type;
/// Parses dictionary members
@@ -8,6 +8,7 @@ pub type DictionaryMembers<'a> = Vec<DictionaryMember<'a>>;
ast_types! {
/// Parses dictionary member `[attributes]? required? type identifier ( = default )?;`
struct DictionaryMember<'a> {
+ docstring: Option<Docstring>,
attributes: Option<ExtendedAttributeList<'a>>,
required: Option<term!(required)>,
type_: Type<'a>,
diff --git a/third_party/rust/weedle2/src/interface.rs b/third_party/rust/weedle2/src/interface.rs
index 5e30909c38..ab3c10d3c3 100644
--- a/third_party/rust/weedle2/src/interface.rs
+++ b/third_party/rust/weedle2/src/interface.rs
@@ -1,6 +1,6 @@
use crate::argument::ArgumentList;
use crate::attribute::ExtendedAttributeList;
-use crate::common::{Generics, Identifier, Parenthesized};
+use crate::common::{Docstring, Generics, Identifier, Parenthesized};
use crate::literal::ConstValue;
use crate::types::{AttributedType, ConstType, ReturnType};
@@ -41,6 +41,7 @@ ast_types! {
///
/// (( )) means ( ) chars
Constructor(struct ConstructorInterfaceMember<'a> {
+ docstring: Option<Docstring>,
attributes: Option<ExtendedAttributeList<'a>>,
constructor: term!(constructor),
args: Parenthesized<ArgumentList<'a>>,
@@ -50,6 +51,7 @@ ast_types! {
///
/// (( )) means ( ) chars
Operation(struct OperationInterfaceMember<'a> {
+ docstring: Option<Docstring>,
attributes: Option<ExtendedAttributeList<'a>>,
modifier: Option<StringifierOrStatic>,
special: Option<Special>,
diff --git a/third_party/rust/weedle2/src/lib.rs b/third_party/rust/weedle2/src/lib.rs
index 610a34fa14..71ab7c33b5 100644
--- a/third_party/rust/weedle2/src/lib.rs
+++ b/third_party/rust/weedle2/src/lib.rs
@@ -23,7 +23,7 @@
use self::argument::ArgumentList;
use self::attribute::ExtendedAttributeList;
-use self::common::{Braced, Identifier, Parenthesized, PunctuatedNonEmpty};
+use self::common::{Braced, Docstring, Identifier, Parenthesized, PunctuatedNonEmpty};
use self::dictionary::DictionaryMembers;
use self::interface::{Inheritance, InterfaceMembers};
use self::literal::StringLit;
@@ -109,6 +109,7 @@ ast_types! {
}),
/// Parses `[attributes]? callback interface identifier ( : inheritance )? { members };`
CallbackInterface(struct CallbackInterfaceDefinition<'a> {
+ docstring: Option<Docstring>,
attributes: Option<ExtendedAttributeList<'a>>,
callback: term!(callback),
interface: term!(interface),
@@ -119,6 +120,7 @@ ast_types! {
}),
/// Parses `[attributes]? interface identifier ( : inheritance )? { members };`
Interface(struct InterfaceDefinition<'a> {
+ docstring: Option<Docstring>,
attributes: Option<ExtendedAttributeList<'a>>,
interface: term!(interface),
identifier: Identifier<'a>,
@@ -137,6 +139,7 @@ ast_types! {
}),
/// Parses `[attributes]? namespace identifier { members };`
Namespace(struct NamespaceDefinition<'a> {
+ docstring: Option<Docstring>,
attributes: Option<ExtendedAttributeList<'a>>,
namespace: term!(namespace),
identifier: Identifier<'a>,
@@ -145,6 +148,7 @@ ast_types! {
}),
/// Parses `[attributes]? dictionary identifier ( : inheritance )? { members };`
Dictionary(struct DictionaryDefinition<'a> {
+ docstring: Option<Docstring>,
attributes: Option<ExtendedAttributeList<'a>>,
dictionary: term!(dictionary),
identifier: Identifier<'a>,
@@ -191,6 +195,7 @@ ast_types! {
}),
/// Parses `[attributes]? enum identifier { values };`
Enum(struct EnumDefinition<'a> {
+ docstring: Option<Docstring>,
attributes: Option<ExtendedAttributeList<'a>>,
enum_: term!(enum),
identifier: Identifier<'a>,
@@ -224,8 +229,15 @@ ast_types! {
}
}
+ast_types! {
+ struct EnumVariant<'a> {
+ docstring: Option<Docstring>,
+ value: StringLit<'a>,
+ }
+}
+
/// Parses a non-empty enum value list
-pub type EnumValueList<'a> = PunctuatedNonEmpty<StringLit<'a>, term!(,)>;
+pub type EnumValueList<'a> = PunctuatedNonEmpty<EnumVariant<'a>, term!(,)>;
#[cfg(test)]
mod test {
diff --git a/third_party/rust/weedle2/src/namespace.rs b/third_party/rust/weedle2/src/namespace.rs
index ed28573218..60673cdcec 100644
--- a/third_party/rust/weedle2/src/namespace.rs
+++ b/third_party/rust/weedle2/src/namespace.rs
@@ -1,6 +1,6 @@
use crate::argument::ArgumentList;
use crate::attribute::ExtendedAttributeList;
-use crate::common::{Identifier, Parenthesized};
+use crate::common::{Docstring, Identifier, Parenthesized};
use crate::types::{AttributedType, ReturnType};
/// Parses namespace members declaration
@@ -13,6 +13,7 @@ ast_types! {
///
/// (( )) means ( ) chars
Operation(struct OperationNamespaceMember<'a> {
+ docstring: Option<Docstring>,
attributes: Option<ExtendedAttributeList<'a>>,
return_type: ReturnType<'a>,
identifier: Option<Identifier<'a>>,
@@ -21,6 +22,7 @@ ast_types! {
}),
/// Parses `[attribute]? readonly attributetype type identifier;`
Attribute(struct AttributeNamespaceMember<'a> {
+ docstring: Option<Docstring>,
attributes: Option<ExtendedAttributeList<'a>>,
readonly: term!(readonly),
attribute: term!(attribute),
diff --git a/third_party/rust/weedle2/src/whitespace.rs b/third_party/rust/weedle2/src/whitespace.rs
index 336e4784e1..4be3ca43e8 100644
--- a/third_party/rust/weedle2/src/whitespace.rs
+++ b/third_party/rust/weedle2/src/whitespace.rs
@@ -7,6 +7,7 @@ pub(crate) fn sp(input: &str) -> IResult<&str, &str> {
(),
nom::sequence::tuple((
nom::bytes::complete::tag("//"),
+ nom::combinator::not(nom::bytes::complete::tag("/")),
nom::bytes::complete::take_until("\n"),
nom::bytes::complete::tag("\n"),
)),
diff --git a/third_party/rust/wgpu-core/.cargo-checksum.json b/third_party/rust/wgpu-core/.cargo-checksum.json
index d22e0914d7..f025f2ccaa 100644
--- a/third_party/rust/wgpu-core/.cargo-checksum.json
+++ b/third_party/rust/wgpu-core/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"4880d66b004519ca6e424fc9e2e6ac065536d36334a2e327b90422e97f2a2a35","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","build.rs":"a99478d7f63fb41429e3834f4d0e5cd333f94ba1834c68295f929170e16987de","src/any_surface.rs":"1c032bc1894a222a47f0116b976f1543c1140c0534678502ee1172d4f77fc515","src/binding_model.rs":"bb4aefad17957e770a5f70f00bf5853dc13da1d9f836493c9aa9adbbe7bb8147","src/command/bind.rs":"a37f042484b65d9fdea4cdab3667381623ee9a8943a6d32683d410b92736d306","src/command/bundle.rs":"fea00382acdf204bcb58522953335dd8f0092565693fa65d0c008e2698e39445","src/command/clear.rs":"03cfc0d4c689d56010391440ab279e615ef1d3235eb1f9f9df0323682d275109","src/command/compute.rs":"2b6beed328ed351ad6fe7088cfa1824c1bf4be50deaeab971cdcb09914d791de","src/command/draw.rs":"15f9ad857504d8098279f9c789317feba321c9b6b8f0de20b8ba98f358c99d89","src/command/memory_init.rs":"6ec93b9e2eb21edaa534e60770b4ba95735e9de61e74d827bc492df8e3639449","src/command/mod.rs":"1d347e1746194f7a07d1f75bd3a9d3cbe121fbaa479c25ba6b8c16e9d699e06b","src/command/query.rs":"43b78a163eb0eb5f1427b7a57b6d39a2748c25f880ba024c91e2f71e2a6a817d","src/command/render.rs":"808dc8106811b32877637851e63baeba7c7438748dec67cbb17ea93c58dc61bd","src/command/transfer.rs":"bf1077d1a99a258bad46087ae7234703627e7f4d30b38e6142d016c02deaad3a","src/conv.rs":"7e3ffe33b47a6fd3617aabf9f11cc68f1ccbee2c7343b8dbbcd0e8f3447e1ad8","src/device/any_device.rs":"65f47b58939b60f88f47861e65d5d45209492df8e73e7c1b60b3b459f510c09e","src/device/bgl.rs":"ec8bdd6e9b4cd50c25bed317275863d0c16bb6619f62ed85bf0464948010dfc1","src/device/global.rs":"ff90a9e3b261bedbec37ab1aed0bf23f1e50c5418da72184e2b175057ed18fce","src/device/life.rs":"3cacaaa74df04bb1285a36d70395b35cfa17059f8d6289b41e665ecbc64cb66a","src/device/mod.rs":"fff41f92e1a9f6660e18dc30452d9911ca827701bb8303af2ae06f1c1e1a795f","src/device/queue.rs":"da0aeebfd1d1c6e155dc89cebf75dfdb6ec18062f9512044ed7e0fef0bda2f74","src/device/resource.rs":"74d3180c12602133bee46925d3788ac510d2ad5ea141a2b46f6904f38549053b","src/device/trace.rs":"9deb1b083165e07253b4928ac2f564aba06f9089c3aca1c0a1d438d87d981542","src/error.rs":"e3b6b7a69877437f4e46af7f0e8ca1db1822beae7c8448db41c2bae0f64b2bb4","src/global.rs":"0966475959706650fd036a18d51441a8e14c3ef10107db617f597614ca47e50a","src/hal_api.rs":"1cd9c3fe1c9d8c3a24e3e7f963a2ef26e056a2b26d529b840dbc969090aaf201","src/hash_utils.rs":"e8d484027c7ce81978e4679a5e20af9416ab7d2fa595f1ca95992b29d625b0ca","src/hub.rs":"352a1b75d4535f24b06d16134421db98f910e6e719f50f863a204df6768e3369","src/id.rs":"9f67dbef5d7a416eb740281ecf8a94673f624da16f21ec33c425c11d9ed01e90","src/identity.rs":"12b820eb4b8bd7b226e15eec97d0f100a695f6b9be7acd79ad2421f2d0fe1985","src/init_tracker/buffer.rs":"61eb9cfaa312135b7a937ff6a3117f531b5b7323fae6553a41d6de9bc106d7e0","src/init_tracker/mod.rs":"a0f64730cc025113b656b4690f9dcb0ec18b8770bc7ef24c7b4ad8bebae03d24","src/init_tracker/texture.rs":"030fd594bf9948fad391390d85c5e1fec7eaf67b6e812c60f2dd59bc4fda8fd5","src/instance.rs":"b6de2a371ef3b43d3217102fe87e423dd1eb12da86b65f54b902d9eaa38b6b9f","src/lib.rs":"4ad9979442cf88557fb3b9f8d3b26c7b929a710c60cabcd1f51788917c95aecb","src/pipeline.rs":"89d88de4b8b8e1dd2bc834d101a1bdf34816ebcaa616dc795f604e9183a21cd0","src/pool.rs":"778ea1c23fcfaaa5001606e686f712f606826039d60dd5a3cd26e7de91ac057a","src/present.rs":"f69580ee0baf181162f9dd82b159596c738558d8abb60db93047effbe1436b2f","src/registry.rs":"913e651dc585ff12fe7659443c38d635a2904881e56cb7159c5ca72d45ae5800","src/resource.rs":"59731bc9a207d87b07b6db9c897e20d64be27c144bb8eb8ab2505807163acfc4","src/snatch.rs":"29a1135ee09c06883eac4df6f45b7220c2ba8f89f34232ea1d270d6e7b05c7a8","src/storage.rs":"f0c41461b8f9cdc862dbd3de04c8e720ee416c7c57310696f6f4fd22183fcc85","src/track/buffer.rs":"83a0cbb8026dbd651d32ea5a47f332f691afed1c5e6f14e78a4fe8aa25e2ad12","src/track/metadata.rs":"655985fdfdd1c7fe8220af98abadf33de7e8920b485e3dd27c28688c3dd2e47d","src/track/mod.rs":"52470a48de6b5dce55385e23ba7a3cbf512cc10cdf431a35aa42190e2fc4306d","src/track/range.rs":"2a15794e79b0470d5ba6b3267173a42f34312878e1cb288f198d2854a7888e53","src/track/stateless.rs":"305e0a493fb1cd0a325274c0757e99c19f9d14deaa8ca11ada41c1399a4ae5c4","src/track/texture.rs":"ba3e3814b341b5242548b55d77bef1d1d9e7d52d63784be98c51e342da7fefff","src/validation.rs":"026168ac4f23bc6a58a90c78fd3eb73485b3c1aad630ef43755604d1babade79"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"3f5fa464854359b0150d4fe82cf5b0c17874dbb3c3c708b2a9cc24ebc1a61349","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","build.rs":"a99478d7f63fb41429e3834f4d0e5cd333f94ba1834c68295f929170e16987de","src/any_surface.rs":"1c032bc1894a222a47f0116b976f1543c1140c0534678502ee1172d4f77fc515","src/binding_model.rs":"bb4aefad17957e770a5f70f00bf5853dc13da1d9f836493c9aa9adbbe7bb8147","src/command/bind.rs":"a37f042484b65d9fdea4cdab3667381623ee9a8943a6d32683d410b92736d306","src/command/bundle.rs":"3435ea21daba6f5a26f9a4158b342d0b9a1839da39b82fa8c23d84ee144b9da0","src/command/clear.rs":"2eb9c323c3ae3644563cab1bb1d26e4a17a54f5a120698c7f09ef9f9e9077780","src/command/compute.rs":"1ad1d8b83774422bd73c7c67c153205e451fd71602b37f34162b59e5f7dbbc75","src/command/draw.rs":"15f9ad857504d8098279f9c789317feba321c9b6b8f0de20b8ba98f358c99d89","src/command/memory_init.rs":"71550dabbf7cc3c3ff6aa4ccd31af080bb5e1cb1e21422daea63fee30294476f","src/command/mod.rs":"1d347e1746194f7a07d1f75bd3a9d3cbe121fbaa479c25ba6b8c16e9d699e06b","src/command/query.rs":"43b78a163eb0eb5f1427b7a57b6d39a2748c25f880ba024c91e2f71e2a6a817d","src/command/render.rs":"875545efa83e56face9a857c5d8c814bc46f4b0538a152933265533176ad1e84","src/command/transfer.rs":"63042151145825c5cac6459a5a254a82142c86d299895f72bef2682f71de7ad1","src/conv.rs":"7e3ffe33b47a6fd3617aabf9f11cc68f1ccbee2c7343b8dbbcd0e8f3447e1ad8","src/device/any_device.rs":"65f47b58939b60f88f47861e65d5d45209492df8e73e7c1b60b3b459f510c09e","src/device/bgl.rs":"ec8bdd6e9b4cd50c25bed317275863d0c16bb6619f62ed85bf0464948010dfc1","src/device/global.rs":"6a08dcc25059f2194999c4f5e3a57a1a96b17031873b1a871095dc58115470fb","src/device/life.rs":"efba75c8632e0bd9c655dbff1f8ff027900f388b6eeb357a9c02ddad24474b23","src/device/mod.rs":"e578d03253a9af314e1e81168d45ea8cc0a8f56df79e440dc1ade945d75277ec","src/device/queue.rs":"4405a6b8ea29093239f1556e5352f7c777f4f6b783fd5dea458fde22556741ca","src/device/resource.rs":"65f84150c467aa50615a097d15f6b4dba3514b0adf2de1e9b9837e5084619a80","src/device/trace.rs":"9deb1b083165e07253b4928ac2f564aba06f9089c3aca1c0a1d438d87d981542","src/error.rs":"e3b6b7a69877437f4e46af7f0e8ca1db1822beae7c8448db41c2bae0f64b2bb4","src/global.rs":"0966475959706650fd036a18d51441a8e14c3ef10107db617f597614ca47e50a","src/hal_api.rs":"1cd9c3fe1c9d8c3a24e3e7f963a2ef26e056a2b26d529b840dbc969090aaf201","src/hash_utils.rs":"e8d484027c7ce81978e4679a5e20af9416ab7d2fa595f1ca95992b29d625b0ca","src/hub.rs":"352a1b75d4535f24b06d16134421db98f910e6e719f50f863a204df6768e3369","src/id.rs":"9f67dbef5d7a416eb740281ecf8a94673f624da16f21ec33c425c11d9ed01e90","src/identity.rs":"12b820eb4b8bd7b226e15eec97d0f100a695f6b9be7acd79ad2421f2d0fe1985","src/init_tracker/buffer.rs":"61eb9cfaa312135b7a937ff6a3117f531b5b7323fae6553a41d6de9bc106d7e0","src/init_tracker/mod.rs":"a0f64730cc025113b656b4690f9dcb0ec18b8770bc7ef24c7b4ad8bebae03d24","src/init_tracker/texture.rs":"030fd594bf9948fad391390d85c5e1fec7eaf67b6e812c60f2dd59bc4fda8fd5","src/instance.rs":"b6de2a371ef3b43d3217102fe87e423dd1eb12da86b65f54b902d9eaa38b6b9f","src/lib.rs":"4ad9979442cf88557fb3b9f8d3b26c7b929a710c60cabcd1f51788917c95aecb","src/pipeline.rs":"89d88de4b8b8e1dd2bc834d101a1bdf34816ebcaa616dc795f604e9183a21cd0","src/pool.rs":"778ea1c23fcfaaa5001606e686f712f606826039d60dd5a3cd26e7de91ac057a","src/present.rs":"f69580ee0baf181162f9dd82b159596c738558d8abb60db93047effbe1436b2f","src/registry.rs":"913e651dc585ff12fe7659443c38d635a2904881e56cb7159c5ca72d45ae5800","src/resource.rs":"59731bc9a207d87b07b6db9c897e20d64be27c144bb8eb8ab2505807163acfc4","src/snatch.rs":"5cf0e4e4611083e8256092696ecf0caa9e855dd680c18fc7dbb227d071aa2548","src/storage.rs":"f0c41461b8f9cdc862dbd3de04c8e720ee416c7c57310696f6f4fd22183fcc85","src/track/buffer.rs":"d1b4c85dfaf1bcc93013d7f51ba677f97c36245c9de224fb328595aa15109cf6","src/track/metadata.rs":"7016911136bc5b97053f54241b01d344f5d65c4337a292c8aa32bb142a26be0c","src/track/mod.rs":"5365c532a531d224a958bd8217769ef59d67d293b4f95972c10db71f6bff1a80","src/track/range.rs":"2a15794e79b0470d5ba6b3267173a42f34312878e1cb288f198d2854a7888e53","src/track/stateless.rs":"305e0a493fb1cd0a325274c0757e99c19f9d14deaa8ca11ada41c1399a4ae5c4","src/track/texture.rs":"da83403d5990021c5db78f1ffdf69198eadb99cc77a6bc481648337a5403295c","src/validation.rs":"026168ac4f23bc6a58a90c78fd3eb73485b3c1aad630ef43755604d1babade79"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/wgpu-core/Cargo.toml b/third_party/rust/wgpu-core/Cargo.toml
index 3d3b4dc80c..aef86b3bbf 100644
--- a/third_party/rust/wgpu-core/Cargo.toml
+++ b/third_party/rust/wgpu-core/Cargo.toml
@@ -11,7 +11,7 @@
[package]
edition = "2021"
-rust-version = "1.70"
+rust-version = "1.74"
name = "wgpu-core"
version = "0.19.0"
authors = ["gfx-rs developers"]
@@ -127,7 +127,7 @@ vulkan = ["hal/vulkan"]
wgsl = ["naga/wgsl-in"]
[target."cfg(all(target_arch = \"wasm32\", not(target_os = \"emscripten\")))".dependencies.web-sys]
-version = "0.3.67"
+version = "0.3.69"
features = [
"HtmlCanvasElement",
"OffscreenCanvas",
diff --git a/third_party/rust/wgpu-core/src/command/bundle.rs b/third_party/rust/wgpu-core/src/command/bundle.rs
index ab2d18bc59..47beda8ec6 100644
--- a/third_party/rust/wgpu-core/src/command/bundle.rs
+++ b/third_party/rust/wgpu-core/src/command/bundle.rs
@@ -99,6 +99,7 @@ use crate::{
pipeline::{PipelineFlags, RenderPipeline, VertexStep},
resource::{Buffer, Resource, ResourceInfo, ResourceType},
resource_log,
+ snatch::SnatchGuard,
track::RenderBundleScope,
validation::check_buffer_usage,
Label, LabelHelpers,
@@ -165,7 +166,7 @@ fn validate_indexed_draw<A: HalApi>(
) -> Result<(), DrawError> {
let last_index = first_index as u64 + index_count as u64;
let index_limit = index_state.limit();
- if last_index <= index_limit {
+ if last_index > index_limit {
return Err(DrawError::IndexBeyondLimit {
last_index,
index_limit,
@@ -894,7 +895,11 @@ impl<A: HalApi> RenderBundle<A> {
/// Note that the function isn't expected to fail, generally.
/// All the validation has already been done by this point.
/// The only failure condition is if some of the used buffers are destroyed.
- pub(super) unsafe fn execute(&self, raw: &mut A::CommandEncoder) -> Result<(), ExecutionError> {
+ pub(super) unsafe fn execute(
+ &self,
+ raw: &mut A::CommandEncoder,
+ snatch_guard: &SnatchGuard,
+ ) -> Result<(), ExecutionError> {
let mut offsets = self.base.dynamic_offsets.as_slice();
let mut pipeline_layout = None::<Arc<PipelineLayout<A>>>;
if !self.discard_hal_labels {
@@ -903,8 +908,6 @@ impl<A: HalApi> RenderBundle<A> {
}
}
- let snatch_guard = self.device.snatchable_lock.read();
-
use ArcRenderCommand as Cmd;
for command in self.base.commands.iter() {
match command {
@@ -914,7 +917,7 @@ impl<A: HalApi> RenderBundle<A> {
bind_group,
} => {
let raw_bg = bind_group
- .raw(&snatch_guard)
+ .raw(snatch_guard)
.ok_or(ExecutionError::InvalidBindGroup(bind_group.info.id()))?;
unsafe {
raw.set_bind_group(
@@ -938,7 +941,7 @@ impl<A: HalApi> RenderBundle<A> {
size,
} => {
let buffer: &A::Buffer = buffer
- .raw(&snatch_guard)
+ .raw(snatch_guard)
.ok_or(ExecutionError::DestroyedBuffer(buffer.info.id()))?;
let bb = hal::BufferBinding {
buffer,
@@ -954,7 +957,7 @@ impl<A: HalApi> RenderBundle<A> {
size,
} => {
let buffer = buffer
- .raw(&snatch_guard)
+ .raw(snatch_guard)
.ok_or(ExecutionError::DestroyedBuffer(buffer.info.id()))?;
let bb = hal::BufferBinding {
buffer,
@@ -1041,7 +1044,7 @@ impl<A: HalApi> RenderBundle<A> {
indexed: false,
} => {
let buffer = buffer
- .raw(&snatch_guard)
+ .raw(snatch_guard)
.ok_or(ExecutionError::DestroyedBuffer(buffer.info.id()))?;
unsafe { raw.draw_indirect(buffer, *offset, 1) };
}
@@ -1052,7 +1055,7 @@ impl<A: HalApi> RenderBundle<A> {
indexed: true,
} => {
let buffer = buffer
- .raw(&snatch_guard)
+ .raw(snatch_guard)
.ok_or(ExecutionError::DestroyedBuffer(buffer.info.id()))?;
unsafe { raw.draw_indexed_indirect(buffer, *offset, 1) };
}
diff --git a/third_party/rust/wgpu-core/src/command/clear.rs b/third_party/rust/wgpu-core/src/command/clear.rs
index e404fabb14..72c923f82e 100644
--- a/third_party/rust/wgpu-core/src/command/clear.rs
+++ b/third_party/rust/wgpu-core/src/command/clear.rs
@@ -12,6 +12,7 @@ use crate::{
id::{BufferId, CommandEncoderId, DeviceId, TextureId},
init_tracker::{MemoryInitKind, TextureInitRange},
resource::{Resource, Texture, TextureClearMode},
+ snatch::SnatchGuard,
track::{TextureSelector, TextureTracker},
};
@@ -239,6 +240,7 @@ impl Global {
}
let (encoder, tracker) = cmd_buf_data.open_encoder_and_tracker()?;
+ let snatch_guard = device.snatchable_lock.read();
clear_texture(
&dst_texture,
TextureInitRange {
@@ -249,6 +251,7 @@ impl Global {
&mut tracker.textures,
&device.alignments,
device.zero_buffer.as_ref().unwrap(),
+ &snatch_guard,
)
}
}
@@ -260,10 +263,10 @@ pub(crate) fn clear_texture<A: HalApi>(
texture_tracker: &mut TextureTracker<A>,
alignments: &hal::Alignments,
zero_buffer: &A::Buffer,
+ snatch_guard: &SnatchGuard<'_>,
) -> Result<(), ClearError> {
- let snatch_guard = dst_texture.device.snatchable_lock.read();
let dst_raw = dst_texture
- .raw(&snatch_guard)
+ .raw(snatch_guard)
.ok_or_else(|| ClearError::InvalidTexture(dst_texture.as_info().id()))?;
// Issue the right barrier.
diff --git a/third_party/rust/wgpu-core/src/command/compute.rs b/third_party/rust/wgpu-core/src/command/compute.rs
index c2fd3ab397..b38324984c 100644
--- a/third_party/rust/wgpu-core/src/command/compute.rs
+++ b/third_party/rust/wgpu-core/src/command/compute.rs
@@ -272,14 +272,14 @@ where
}
}
-struct State<A: HalApi> {
+struct State<'a, A: HalApi> {
binder: Binder<A>,
pipeline: Option<id::ComputePipelineId>,
- scope: UsageScope<A>,
+ scope: UsageScope<'a, A>,
debug_scope_depth: u32,
}
-impl<A: HalApi> State<A> {
+impl<'a, A: HalApi> State<'a, A> {
fn is_ready(&self) -> Result<(), DispatchError> {
let bind_mask = self.binder.invalid_mask();
if bind_mask != 0 {
@@ -407,7 +407,7 @@ impl Global {
let mut state = State {
binder: Binder::new(),
pipeline: None,
- scope: UsageScope::new(&device.tracker_indices),
+ scope: device.new_usage_scope(),
debug_scope_depth: 0,
};
let mut temp_offsets = Vec::new();
@@ -868,6 +868,7 @@ impl Global {
transit,
&mut tracker.textures,
device,
+ &snatch_guard,
);
CommandBuffer::insert_barriers_from_tracker(
transit,
diff --git a/third_party/rust/wgpu-core/src/command/memory_init.rs b/third_party/rust/wgpu-core/src/command/memory_init.rs
index 3bfc71f4f7..54bdedb792 100644
--- a/third_party/rust/wgpu-core/src/command/memory_init.rs
+++ b/third_party/rust/wgpu-core/src/command/memory_init.rs
@@ -7,6 +7,7 @@ use crate::{
hal_api::HalApi,
init_tracker::*,
resource::{Resource, Texture},
+ snatch::SnatchGuard,
track::{TextureTracker, Tracker},
FastHashMap,
};
@@ -144,6 +145,7 @@ pub(crate) fn fixup_discarded_surfaces<
encoder: &mut A::CommandEncoder,
texture_tracker: &mut TextureTracker<A>,
device: &Device<A>,
+ snatch_guard: &SnatchGuard<'_>,
) {
for init in inits {
clear_texture(
@@ -156,6 +158,7 @@ pub(crate) fn fixup_discarded_surfaces<
texture_tracker,
&device.alignments,
device.zero_buffer.as_ref().unwrap(),
+ snatch_guard,
)
.unwrap();
}
@@ -167,6 +170,7 @@ impl<A: HalApi> BakedCommands<A> {
pub(crate) fn initialize_buffer_memory(
&mut self,
device_tracker: &mut Tracker<A>,
+ snatch_guard: &SnatchGuard<'_>,
) -> Result<(), DestroyedBufferError> {
// Gather init ranges for each buffer so we can collapse them.
// It is not possible to do this at an earlier point since previously
@@ -225,16 +229,15 @@ impl<A: HalApi> BakedCommands<A> {
.unwrap()
.1;
- let snatch_guard = buffer.device.snatchable_lock.read();
let raw_buf = buffer
.raw
- .get(&snatch_guard)
+ .get(snatch_guard)
.ok_or(DestroyedBufferError(buffer_id))?;
unsafe {
self.encoder.transition_buffers(
transition
- .map(|pending| pending.into_hal(&buffer, &snatch_guard))
+ .map(|pending| pending.into_hal(&buffer, snatch_guard))
.into_iter(),
);
}
@@ -271,6 +274,7 @@ impl<A: HalApi> BakedCommands<A> {
&mut self,
device_tracker: &mut Tracker<A>,
device: &Device<A>,
+ snatch_guard: &SnatchGuard<'_>,
) -> Result<(), DestroyedTextureError> {
let mut ranges: Vec<TextureInitRange> = Vec::new();
for texture_use in self.texture_memory_actions.drain_init_actions() {
@@ -310,6 +314,7 @@ impl<A: HalApi> BakedCommands<A> {
&mut device_tracker.textures,
&device.alignments,
device.zero_buffer.as_ref().unwrap(),
+ snatch_guard,
);
// A Texture can be destroyed between the command recording
diff --git a/third_party/rust/wgpu-core/src/command/render.rs b/third_party/rust/wgpu-core/src/command/render.rs
index 9141ddb021..7e859e3cc8 100644
--- a/third_party/rust/wgpu-core/src/command/render.rs
+++ b/third_party/rust/wgpu-core/src/command/render.rs
@@ -739,9 +739,9 @@ impl<A: HalApi> TextureView<A> {
const MAX_TOTAL_ATTACHMENTS: usize = hal::MAX_COLOR_ATTACHMENTS + hal::MAX_COLOR_ATTACHMENTS + 1;
type AttachmentDataVec<T> = ArrayVec<T, MAX_TOTAL_ATTACHMENTS>;
-struct RenderPassInfo<'a, A: HalApi> {
+struct RenderPassInfo<'a, 'd, A: HalApi> {
context: RenderPassContext,
- usage_scope: UsageScope<A>,
+ usage_scope: UsageScope<'d, A>,
/// All render attachments, including depth/stencil
render_attachments: AttachmentDataVec<RenderAttachment<'a, A>>,
is_depth_read_only: bool,
@@ -754,7 +754,7 @@ struct RenderPassInfo<'a, A: HalApi> {
multiview: Option<NonZeroU32>,
}
-impl<'a, A: HalApi> RenderPassInfo<'a, A> {
+impl<'a, 'd, A: HalApi> RenderPassInfo<'a, 'd, A> {
fn add_pass_texture_init_actions<V>(
channel: &PassChannel<V>,
texture_memory_actions: &mut CommandBufferTextureMemoryActions<A>,
@@ -790,7 +790,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
}
fn start(
- device: &Device<A>,
+ device: &'d Device<A>,
label: Option<&str>,
color_attachments: &[Option<RenderPassColorAttachment>],
depth_stencil_attachment: Option<&RenderPassDepthStencilAttachment>,
@@ -1214,7 +1214,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
Ok(Self {
context,
- usage_scope: UsageScope::new(&device.tracker_indices),
+ usage_scope: device.new_usage_scope(),
render_attachments,
is_depth_read_only,
is_stencil_read_only,
@@ -1230,7 +1230,7 @@ impl<'a, A: HalApi> RenderPassInfo<'a, A> {
mut self,
raw: &mut A::CommandEncoder,
snatch_guard: &SnatchGuard,
- ) -> Result<(UsageScope<A>, SurfacesInDiscardState<A>), RenderPassErrorInner> {
+ ) -> Result<(UsageScope<'d, A>, SurfacesInDiscardState<A>), RenderPassErrorInner> {
profiling::scope!("RenderPassInfo::finish");
unsafe {
raw.end_render_pass();
@@ -2374,7 +2374,7 @@ impl Global {
.extend(texture_memory_actions.register_init_action(action));
}
- unsafe { bundle.execute(raw) }
+ unsafe { bundle.execute(raw, &snatch_guard) }
.map_err(|e| match e {
ExecutionError::DestroyedBuffer(id) => {
RenderCommandError::DestroyedBuffer(id)
@@ -2427,6 +2427,7 @@ impl Global {
transit,
&mut tracker.textures,
&cmd_buf.device,
+ &snatch_guard,
);
cmd_buf_data
diff --git a/third_party/rust/wgpu-core/src/command/transfer.rs b/third_party/rust/wgpu-core/src/command/transfer.rs
index 0a952dfc84..8e98a4c9b9 100644
--- a/third_party/rust/wgpu-core/src/command/transfer.rs
+++ b/third_party/rust/wgpu-core/src/command/transfer.rs
@@ -14,6 +14,7 @@ use crate::{
TextureInitTrackerAction,
},
resource::{Resource, Texture, TextureErrorDimension},
+ snatch::SnatchGuard,
track::{TextureSelector, Tracker},
};
@@ -452,6 +453,7 @@ fn handle_texture_init<A: HalApi>(
copy_texture: &ImageCopyTexture,
copy_size: &Extent3d,
texture: &Arc<Texture<A>>,
+ snatch_guard: &SnatchGuard<'_>,
) -> Result<(), ClearError> {
let init_action = TextureInitTrackerAction {
texture: texture.clone(),
@@ -480,6 +482,7 @@ fn handle_texture_init<A: HalApi>(
&mut trackers.textures,
&device.alignments,
device.zero_buffer.as_ref().unwrap(),
+ snatch_guard,
)?;
}
}
@@ -499,6 +502,7 @@ fn handle_src_texture_init<A: HalApi>(
source: &ImageCopyTexture,
copy_size: &Extent3d,
texture: &Arc<Texture<A>>,
+ snatch_guard: &SnatchGuard<'_>,
) -> Result<(), TransferError> {
handle_texture_init(
MemoryInitKind::NeedsInitializedMemory,
@@ -509,6 +513,7 @@ fn handle_src_texture_init<A: HalApi>(
source,
copy_size,
texture,
+ snatch_guard,
)?;
Ok(())
}
@@ -525,6 +530,7 @@ fn handle_dst_texture_init<A: HalApi>(
destination: &ImageCopyTexture,
copy_size: &Extent3d,
texture: &Arc<Texture<A>>,
+ snatch_guard: &SnatchGuard<'_>,
) -> Result<(), TransferError> {
// Attention: If we don't write full texture subresources, we need to a full
// clear first since we don't track subrects. This means that in rare cases
@@ -549,6 +555,7 @@ fn handle_dst_texture_init<A: HalApi>(
destination,
copy_size,
texture,
+ snatch_guard,
)?;
Ok(())
}
@@ -779,6 +786,8 @@ impl Global {
let (dst_range, dst_base) = extract_texture_selector(destination, copy_size, &dst_texture)?;
+ let snatch_guard = device.snatchable_lock.read();
+
// Handle texture init *before* dealing with barrier transitions so we
// have an easier time inserting "immediate-inits" that may be required
// by prior discards in rare cases.
@@ -790,10 +799,9 @@ impl Global {
destination,
copy_size,
&dst_texture,
+ &snatch_guard,
)?;
- let snatch_guard = device.snatchable_lock.read();
-
let (src_buffer, src_pending) = {
let buffer_guard = hub.buffers.read();
let src_buffer = buffer_guard
@@ -935,6 +943,8 @@ impl Global {
let (src_range, src_base) = extract_texture_selector(source, copy_size, &src_texture)?;
+ let snatch_guard = device.snatchable_lock.read();
+
// Handle texture init *before* dealing with barrier transitions so we
// have an easier time inserting "immediate-inits" that may be required
// by prior discards in rare cases.
@@ -946,10 +956,9 @@ impl Global {
source,
copy_size,
&src_texture,
+ &snatch_guard,
)?;
- let snatch_guard = device.snatchable_lock.read();
-
let src_pending = tracker
.textures
.set_single(&src_texture, src_range, hal::TextureUses::COPY_SRC)
@@ -1152,6 +1161,7 @@ impl Global {
source,
copy_size,
&src_texture,
+ &snatch_guard,
)?;
handle_dst_texture_init(
encoder,
@@ -1161,6 +1171,7 @@ impl Global {
destination,
copy_size,
&dst_texture,
+ &snatch_guard,
)?;
let src_pending = cmd_buf_data
diff --git a/third_party/rust/wgpu-core/src/device/global.rs b/third_party/rust/wgpu-core/src/device/global.rs
index 539b92e0f3..0c97e1b504 100644
--- a/third_party/rust/wgpu-core/src/device/global.rs
+++ b/third_party/rust/wgpu-core/src/device/global.rs
@@ -192,7 +192,15 @@ impl Global {
let ptr = if map_size == 0 {
std::ptr::NonNull::dangling()
} else {
- match map_buffer(device.raw(), &buffer, 0, map_size, HostMap::Write) {
+ let snatch_guard = device.snatchable_lock.read();
+ match map_buffer(
+ device.raw(),
+ &buffer,
+ 0,
+ map_size,
+ HostMap::Write,
+ &snatch_guard,
+ ) {
Ok(ptr) => ptr,
Err(e) => {
to_destroy.push(buffer);
@@ -2008,9 +2016,10 @@ impl Global {
}
// Wait for all work to finish before configuring the surface.
+ let snatch_guard = device.snatchable_lock.read();
let fence = device.fence.read();
let fence = fence.as_ref().unwrap();
- match device.maintain(fence, wgt::Maintain::Wait) {
+ match device.maintain(fence, wgt::Maintain::Wait, snatch_guard) {
Ok((closures, _)) => {
user_callbacks = closures;
}
@@ -2120,9 +2129,10 @@ impl Global {
device: &crate::device::Device<A>,
maintain: wgt::Maintain<queue::WrappedSubmissionIndex>,
) -> Result<DevicePoll, WaitIdleError> {
+ let snatch_guard = device.snatchable_lock.read();
let fence = device.fence.read();
let fence = fence.as_ref().unwrap();
- let (closures, queue_empty) = device.maintain(fence, maintain)?;
+ let (closures, queue_empty) = device.maintain(fence, maintain, snatch_guard)?;
// Some deferred destroys are scheduled in maintain so run this right after
// to avoid holding on to them until the next device poll.
@@ -2240,6 +2250,15 @@ impl Global {
}
}
+ // This is a test-only function to force the device into an
+ // invalid state by inserting an error value in its place in
+ // the registry.
+ pub fn device_make_invalid<A: HalApi>(&self, device_id: DeviceId) {
+ let hub = A::hub(self);
+ hub.devices
+ .force_replace_with_error(device_id, "Made invalid.");
+ }
+
pub fn device_drop<A: HalApi>(&self, device_id: DeviceId) {
profiling::scope!("Device::drop");
api_log!("Device::drop {device_id:?}");
@@ -2275,7 +2294,7 @@ impl Global {
) {
let hub = A::hub(self);
- if let Ok(device) = hub.devices.get(device_id) {
+ if let Ok(Some(device)) = hub.devices.try_get(device_id) {
let mut life_tracker = device.lock_life();
if let Some(existing_closure) = life_tracker.device_lost_closure.take() {
// It's important to not hold the lock while calling the closure.
@@ -2284,6 +2303,12 @@ impl Global {
life_tracker = device.lock_life();
}
life_tracker.device_lost_closure = Some(device_lost_closure);
+ } else {
+ // No device? Okay. Just like we have to call any existing closure
+ // before we drop it, we need to call this closure before we exit
+ // this function, because there's no device that is ever going to
+ // call it.
+ device_lost_closure.call(DeviceLostReason::DeviceInvalid, "".to_string());
}
}
diff --git a/third_party/rust/wgpu-core/src/device/life.rs b/third_party/rust/wgpu-core/src/device/life.rs
index 7b06a4a30b..af345015df 100644
--- a/third_party/rust/wgpu-core/src/device/life.rs
+++ b/third_party/rust/wgpu-core/src/device/life.rs
@@ -12,6 +12,7 @@ use crate::{
self, Buffer, DestroyedBuffer, DestroyedTexture, QuerySet, Resource, Sampler,
StagingBuffer, Texture, TextureView,
},
+ snatch::SnatchGuard,
track::{ResourceTracker, Tracker, TrackerIndex},
FastHashMap, SubmissionIndex,
};
@@ -309,12 +310,12 @@ impl<A: HalApi> LifetimeTracker<A> {
}
pub fn post_submit(&mut self) {
- for v in self.future_suspected_buffers.drain(..).take(1) {
+ for v in self.future_suspected_buffers.drain(..) {
self.suspected_resources
.buffers
.insert(v.as_info().tracker_index(), v);
}
- for v in self.future_suspected_textures.drain(..).take(1) {
+ for v in self.future_suspected_textures.drain(..) {
self.suspected_resources
.textures
.insert(v.as_info().tracker_index(), v);
@@ -780,6 +781,7 @@ impl<A: HalApi> LifetimeTracker<A> {
&mut self,
raw: &A::Device,
trackers: &Mutex<Tracker<A>>,
+ snatch_guard: &SnatchGuard,
) -> Vec<super::BufferMapPendingClosure> {
if self.ready_to_map.is_empty() {
return Vec::new();
@@ -816,7 +818,14 @@ impl<A: HalApi> LifetimeTracker<A> {
log::debug!("Buffer {tracker_index:?} map state -> Active");
let host = mapping.op.host;
let size = mapping.range.end - mapping.range.start;
- match super::map_buffer(raw, &buffer, mapping.range.start, size, host) {
+ match super::map_buffer(
+ raw,
+ &buffer,
+ mapping.range.start,
+ size,
+ host,
+ snatch_guard,
+ ) {
Ok(ptr) => {
*buffer.map_state.lock() = resource::BufferMapState::Active {
ptr,
diff --git a/third_party/rust/wgpu-core/src/device/mod.rs b/third_party/rust/wgpu-core/src/device/mod.rs
index 7ecda830a3..e2ab6c2690 100644
--- a/third_party/rust/wgpu-core/src/device/mod.rs
+++ b/third_party/rust/wgpu-core/src/device/mod.rs
@@ -3,9 +3,10 @@ use crate::{
hal_api::HalApi,
hub::Hub,
id::{BindGroupLayoutId, PipelineLayoutId},
- resource::{Buffer, BufferAccessResult},
- resource::{BufferAccessError, BufferMapOperation},
- resource_log, Label, DOWNLEVEL_ERROR_MESSAGE,
+ resource::{Buffer, BufferAccessError, BufferAccessResult, BufferMapOperation},
+ resource_log,
+ snatch::SnatchGuard,
+ Label, DOWNLEVEL_ERROR_MESSAGE,
};
use arrayvec::ArrayVec;
@@ -317,10 +318,10 @@ fn map_buffer<A: HalApi>(
offset: BufferAddress,
size: BufferAddress,
kind: HostMap,
+ snatch_guard: &SnatchGuard,
) -> Result<ptr::NonNull<u8>, BufferAccessError> {
- let snatch_guard = buffer.device.snatchable_lock.read();
let raw_buffer = buffer
- .raw(&snatch_guard)
+ .raw(snatch_guard)
.ok_or(BufferAccessError::Destroyed)?;
let mapping = unsafe {
raw.map_buffer(raw_buffer, offset..offset + size)
diff --git a/third_party/rust/wgpu-core/src/device/queue.rs b/third_party/rust/wgpu-core/src/device/queue.rs
index 6ebb9eb09b..3cb5f695a7 100644
--- a/third_party/rust/wgpu-core/src/device/queue.rs
+++ b/third_party/rust/wgpu-core/src/device/queue.rs
@@ -815,6 +815,7 @@ impl Global {
&mut trackers.textures,
&device.alignments,
device.zero_buffer.as_ref().unwrap(),
+ &device.snatchable_lock.read(),
)
.map_err(QueueWriteError::from)?;
}
@@ -1084,6 +1085,7 @@ impl Global {
&mut trackers.textures,
&device.alignments,
device.zero_buffer.as_ref().unwrap(),
+ &device.snatchable_lock.read(),
)
.map_err(QueueWriteError::from)?;
}
@@ -1147,6 +1149,9 @@ impl Global {
let device = queue.device.as_ref().unwrap();
+ let snatch_guard = device.snatchable_lock.read();
+
+ // Fence lock must be acquired after the snatch lock everywhere to avoid deadlocks.
let mut fence = device.fence.write();
let fence = fence.as_mut().unwrap();
let submit_index = device
@@ -1155,9 +1160,7 @@ impl Global {
+ 1;
let mut active_executions = Vec::new();
- let mut used_surface_textures = track::TextureUsageScope::new();
-
- let snatch_guard = device.snatchable_lock.read();
+ let mut used_surface_textures = track::TextureUsageScope::default();
let mut submit_surface_textures_owned = SmallVec::<[_; 2]>::new();
@@ -1391,10 +1394,10 @@ impl Global {
//Note: locking the trackers has to be done after the storages
let mut trackers = device.trackers.lock();
baked
- .initialize_buffer_memory(&mut *trackers)
+ .initialize_buffer_memory(&mut *trackers, &snatch_guard)
.map_err(|err| QueueSubmitError::DestroyedBuffer(err.0))?;
baked
- .initialize_texture_memory(&mut *trackers, device)
+ .initialize_texture_memory(&mut *trackers, device, &snatch_guard)
.map_err(|err| QueueSubmitError::DestroyedTexture(err.0))?;
//Note: stateless trackers are not merged:
// device already knows these resources exist.
@@ -1435,7 +1438,7 @@ impl Global {
baked.encoder.end_encoding().unwrap()
};
baked.list.push(present);
- used_surface_textures = track::TextureUsageScope::new();
+ used_surface_textures = track::TextureUsageScope::default();
}
// done
@@ -1542,7 +1545,7 @@ impl Global {
// This will schedule destruction of all resources that are no longer needed
// by the user but used in the command stream, among other things.
- let (closures, _) = match device.maintain(fence, wgt::Maintain::Poll) {
+ let (closures, _) = match device.maintain(fence, wgt::Maintain::Poll, snatch_guard) {
Ok(closures) => closures,
Err(WaitIdleError::Device(err)) => return Err(QueueSubmitError::Queue(err)),
Err(WaitIdleError::StuckGpu) => return Err(QueueSubmitError::StuckGpu),
diff --git a/third_party/rust/wgpu-core/src/device/resource.rs b/third_party/rust/wgpu-core/src/device/resource.rs
index 28ba0eafb1..4892aecb75 100644
--- a/third_party/rust/wgpu-core/src/device/resource.rs
+++ b/third_party/rust/wgpu-core/src/device/resource.rs
@@ -28,7 +28,10 @@ use crate::{
resource_log,
snatch::{SnatchGuard, SnatchLock, Snatchable},
storage::Storage,
- track::{BindGroupStates, TextureSelector, Tracker, TrackerIndexAllocators},
+ track::{
+ BindGroupStates, TextureSelector, Tracker, TrackerIndexAllocators, UsageScope,
+ UsageScopePool,
+ },
validation::{
self, check_buffer_usage, check_texture_usage, validate_color_attachment_bytes_per_sample,
},
@@ -97,6 +100,8 @@ pub struct Device<A: HalApi> {
pub(crate) command_allocator: Mutex<Option<CommandAllocator<A>>>,
//Note: The submission index here corresponds to the last submission that is done.
pub(crate) active_submission_index: AtomicU64, //SubmissionIndex,
+ // NOTE: if both are needed, the `snatchable_lock` must be consistently acquired before the
+ // `fence` lock to avoid deadlocks.
pub(crate) fence: RwLock<Option<A::Fence>>,
pub(crate) snatchable_lock: SnatchLock,
@@ -135,6 +140,7 @@ pub struct Device<A: HalApi> {
pub(crate) deferred_destroy: Mutex<Vec<DeferredDestroy<A>>>,
#[cfg(feature = "trace")]
pub(crate) trace: Mutex<Option<trace::Trace>>,
+ pub(crate) usage_scopes: UsageScopePool<A>,
}
pub(crate) enum DeferredDestroy<A: HalApi> {
@@ -296,6 +302,7 @@ impl<A: HalApi> Device<A> {
instance_flags,
pending_writes: Mutex::new(Some(pending_writes)),
deferred_destroy: Mutex::new(Vec::new()),
+ usage_scopes: Default::default(),
})
}
@@ -387,6 +394,7 @@ impl<A: HalApi> Device<A> {
&'this self,
fence: &A::Fence,
maintain: wgt::Maintain<queue::WrappedSubmissionIndex>,
+ snatch_guard: SnatchGuard,
) -> Result<(UserClosures, bool), WaitIdleError> {
profiling::scope!("Device::maintain");
let last_done_index = if maintain.is_wait() {
@@ -440,7 +448,8 @@ impl<A: HalApi> Device<A> {
life_tracker.triage_mapped();
}
- let mapping_closures = life_tracker.handle_mapping(self.raw(), &self.trackers);
+ let mapping_closures =
+ life_tracker.handle_mapping(self.raw(), &self.trackers, &snatch_guard);
let queue_empty = life_tracker.queue_empty();
@@ -467,8 +476,9 @@ impl<A: HalApi> Device<A> {
}
}
- // Don't hold the lock while calling release_gpu_resources.
+ // Don't hold the locks while calling release_gpu_resources.
drop(life_tracker);
+ drop(snatch_guard);
if should_release_gpu_resource {
self.release_gpu_resources();
@@ -3568,6 +3578,10 @@ impl<A: HalApi> Device<A> {
let _ = texture.destroy();
}
}
+
+ pub(crate) fn new_usage_scope(&self) -> UsageScope<'_, A> {
+ UsageScope::new_pooled(&self.usage_scopes, &self.tracker_indices)
+ }
}
impl<A: HalApi> Device<A> {
diff --git a/third_party/rust/wgpu-core/src/snatch.rs b/third_party/rust/wgpu-core/src/snatch.rs
index 2324d33574..d5cd1a3d37 100644
--- a/third_party/rust/wgpu-core/src/snatch.rs
+++ b/third_party/rust/wgpu-core/src/snatch.rs
@@ -1,7 +1,12 @@
#![allow(unused)]
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
-use std::cell::UnsafeCell;
+use std::{
+ backtrace::Backtrace,
+ cell::{Cell, RefCell, UnsafeCell},
+ panic::{self, Location},
+ thread,
+};
/// A guard that provides read access to snatchable data.
pub struct SnatchGuard<'a>(RwLockReadGuard<'a, ()>);
@@ -59,6 +64,10 @@ impl<T> std::fmt::Debug for Snatchable<T> {
unsafe impl<T> Sync for Snatchable<T> {}
+thread_local! {
+ static READ_LOCK_LOCATION: Cell<Option<(&'static Location<'static>, Backtrace)>> = const { Cell::new(None) };
+}
+
/// A Device-global lock for all snatchable data.
pub struct SnatchLock {
lock: RwLock<()>,
@@ -76,7 +85,24 @@ impl SnatchLock {
}
/// Request read access to snatchable resources.
+ #[track_caller]
pub fn read(&self) -> SnatchGuard {
+ if cfg!(debug_assertions) {
+ let caller = Location::caller();
+ let backtrace = Backtrace::capture();
+ if let Some((prev, bt)) = READ_LOCK_LOCATION.take() {
+ let current = thread::current();
+ let name = current.name().unwrap_or("<unnamed>");
+ panic!(
+ "thread '{name}' attempted to acquire a snatch read lock recursively.\n
+ - {prev}\n{bt}\n
+ - {caller}\n{backtrace}"
+ );
+ } else {
+ READ_LOCK_LOCATION.set(Some((caller, backtrace)));
+ }
+ }
+
SnatchGuard(self.lock.read())
}
@@ -89,3 +115,10 @@ impl SnatchLock {
ExclusiveSnatchGuard(self.lock.write())
}
}
+
+impl Drop for SnatchGuard<'_> {
+ fn drop(&mut self) {
+ #[cfg(debug_assertions)]
+ READ_LOCK_LOCATION.take();
+ }
+}
diff --git a/third_party/rust/wgpu-core/src/track/buffer.rs b/third_party/rust/wgpu-core/src/track/buffer.rs
index a30ac2a225..6cf1fdda6f 100644
--- a/third_party/rust/wgpu-core/src/track/buffer.rs
+++ b/third_party/rust/wgpu-core/src/track/buffer.rs
@@ -108,23 +108,27 @@ impl<A: HalApi> BufferBindGroupState<A> {
#[derive(Debug)]
pub(crate) struct BufferUsageScope<A: HalApi> {
state: Vec<BufferUses>,
-
metadata: ResourceMetadata<Buffer<A>>,
}
-impl<A: HalApi> BufferUsageScope<A> {
- pub fn new() -> Self {
+impl<A: HalApi> Default for BufferUsageScope<A> {
+ fn default() -> Self {
Self {
state: Vec::new(),
-
metadata: ResourceMetadata::new(),
}
}
+}
+impl<A: HalApi> BufferUsageScope<A> {
fn tracker_assert_in_bounds(&self, index: usize) {
strict_assert!(index < self.state.len());
self.metadata.tracker_assert_in_bounds(index);
}
+ pub fn clear(&mut self) {
+ self.state.clear();
+ self.metadata.clear();
+ }
/// Sets the size of all the vectors inside the tracker.
///
diff --git a/third_party/rust/wgpu-core/src/track/metadata.rs b/third_party/rust/wgpu-core/src/track/metadata.rs
index 744783a7fa..3e71e0e084 100644
--- a/third_party/rust/wgpu-core/src/track/metadata.rs
+++ b/third_party/rust/wgpu-core/src/track/metadata.rs
@@ -39,6 +39,11 @@ impl<T: Resource> ResourceMetadata<T> {
resize_bitvec(&mut self.owned, size);
}
+ pub(super) fn clear(&mut self) {
+ self.resources.clear();
+ self.owned.clear();
+ }
+
/// Ensures a given index is in bounds for all arrays and does
/// sanity checks of the presence of a refcount.
///
diff --git a/third_party/rust/wgpu-core/src/track/mod.rs b/third_party/rust/wgpu-core/src/track/mod.rs
index 9ca37ebadc..374dfe7493 100644
--- a/third_party/rust/wgpu-core/src/track/mod.rs
+++ b/third_party/rust/wgpu-core/src/track/mod.rs
@@ -480,8 +480,8 @@ impl<A: HalApi> RenderBundleScope<A> {
/// Create the render bundle scope and pull the maximum IDs from the hubs.
pub fn new() -> Self {
Self {
- buffers: RwLock::new(BufferUsageScope::new()),
- textures: RwLock::new(TextureUsageScope::new()),
+ buffers: RwLock::new(BufferUsageScope::default()),
+ textures: RwLock::new(TextureUsageScope::default()),
bind_groups: RwLock::new(StatelessTracker::new()),
render_pipelines: RwLock::new(StatelessTracker::new()),
query_sets: RwLock::new(StatelessTracker::new()),
@@ -512,28 +512,52 @@ impl<A: HalApi> RenderBundleScope<A> {
}
}
+/// A pool for storing the memory used by [`UsageScope`]s. We take and store this memory when the
+/// scope is dropped to avoid reallocating. The memory required only grows and allocation cost is
+/// significant when a large number of resources have been used.
+pub(crate) type UsageScopePool<A> = Mutex<Vec<(BufferUsageScope<A>, TextureUsageScope<A>)>>;
+
/// A usage scope tracker. Only needs to store stateful resources as stateless
/// resources cannot possibly have a usage conflict.
#[derive(Debug)]
-pub(crate) struct UsageScope<A: HalApi> {
+pub(crate) struct UsageScope<'a, A: HalApi> {
+ pub pool: &'a UsageScopePool<A>,
pub buffers: BufferUsageScope<A>,
pub textures: TextureUsageScope<A>,
}
-impl<A: HalApi> UsageScope<A> {
- /// Create the render bundle scope and pull the maximum IDs from the hubs.
- pub fn new(tracker_indices: &TrackerIndexAllocators) -> Self {
- let mut value = Self {
- buffers: BufferUsageScope::new(),
- textures: TextureUsageScope::new(),
- };
+impl<'a, A: HalApi> Drop for UsageScope<'a, A> {
+ fn drop(&mut self) {
+ // clear vecs and push into pool
+ self.buffers.clear();
+ self.textures.clear();
+ self.pool.lock().push((
+ std::mem::take(&mut self.buffers),
+ std::mem::take(&mut self.textures),
+ ));
+ }
+}
- value.buffers.set_size(tracker_indices.buffers.size());
- value.textures.set_size(tracker_indices.textures.size());
+impl<A: HalApi> UsageScope<'static, A> {
+ pub fn new_pooled<'d>(
+ pool: &'d UsageScopePool<A>,
+ tracker_indices: &TrackerIndexAllocators,
+ ) -> UsageScope<'d, A> {
+ let pooled = pool.lock().pop().unwrap_or_default();
+
+ let mut scope = UsageScope::<'d, A> {
+ pool,
+ buffers: pooled.0,
+ textures: pooled.1,
+ };
- value
+ scope.buffers.set_size(tracker_indices.buffers.size());
+ scope.textures.set_size(tracker_indices.textures.size());
+ scope
}
+}
+impl<'a, A: HalApi> UsageScope<'a, A> {
/// Merge the inner contents of a bind group into the usage scope.
///
/// Only stateful things are merged in here, all other resources are owned
diff --git a/third_party/rust/wgpu-core/src/track/texture.rs b/third_party/rust/wgpu-core/src/track/texture.rs
index e7c4707c93..3cf95ff38a 100644
--- a/third_party/rust/wgpu-core/src/track/texture.rs
+++ b/third_party/rust/wgpu-core/src/track/texture.rs
@@ -210,6 +210,7 @@ pub(crate) struct TextureStateSet {
simple: Vec<TextureUses>,
complex: FastHashMap<usize, ComplexTextureState>,
}
+
impl TextureStateSet {
fn new() -> Self {
Self {
@@ -235,15 +236,16 @@ pub(crate) struct TextureUsageScope<A: HalApi> {
metadata: ResourceMetadata<Texture<A>>,
}
-impl<A: HalApi> TextureUsageScope<A> {
- pub fn new() -> Self {
+impl<A: HalApi> Default for TextureUsageScope<A> {
+ fn default() -> Self {
Self {
set: TextureStateSet::new(),
-
metadata: ResourceMetadata::new(),
}
}
+}
+impl<A: HalApi> TextureUsageScope<A> {
fn tracker_assert_in_bounds(&self, index: usize) {
self.metadata.tracker_assert_in_bounds(index);
@@ -258,6 +260,11 @@ impl<A: HalApi> TextureUsageScope<A> {
});
}
+ pub fn clear(&mut self) {
+ self.set.clear();
+ self.metadata.clear();
+ }
+
/// Sets the size of all the vectors inside the tracker.
///
/// Must be called with the highest possible Texture ID before
diff --git a/third_party/rust/wgpu-hal/.cargo-checksum.json b/third_party/rust/wgpu-hal/.cargo-checksum.json
index de9bc38719..2cba88cfdf 100644
--- a/third_party/rust/wgpu-hal/.cargo-checksum.json
+++ b/third_party/rust/wgpu-hal/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"eaa7b2b51fbe98c0721dc52d94c64b48d2d6e351bf36da3e756378a8d8ebc1de","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","README.md":"099ee611a911dc19330a61bffcde13663929a51b25ac528ee33ea796d695491e","build.rs":"c80bdc0152a00471eec6ed0dd0f7d55d0b975498a00ba05e94100c84ad639a49","examples/halmark/main.rs":"4604737f714943383c57feac2b8468ecf15e9e60c54a5303455e9953ec5c79fb","examples/halmark/shader.wgsl":"26c256ec36d6f0e9a1647431ca772766bee4382d64eaa718ba7b488dcfb6bcca","examples/raw-gles.em.html":"70fbe68394a1a4522192de1dcfaf7d399f60d7bdf5de70b708f9bb0417427546","examples/raw-gles.rs":"095113a1ba0851652a77aabfc8fa6ea7edcc2d09e91fd1e5009ead87d5998ea9","examples/ray-traced-triangle/main.rs":"955c2b8700c3b2daf14e9ef963ff499ed185b6f349dbc63caa422b2cf4942a1f","examples/ray-traced-triangle/shader.wgsl":"cc10caf92746724a71f6dd0dbc3a71e57b37c7d1d83278556805a535c0728a9d","src/auxil/dxgi/conv.rs":"760cd4eaa79b530368a30140b96bf73ac4fbdb4025eb95f0bed581638c8bb1cb","src/auxil/dxgi/exception.rs":"f0cfb5a0adcdc3b6db909601fee51ad51368f5da269bcd46e4dbea45a3bec4b1","src/auxil/dxgi/factory.rs":"5f861fbfe2f4cce08722a95283549b8f62b96f24a306d080d9f1730ae53501d8","src/auxil/dxgi/mod.rs":"a202564d9ac97530b16a234b87d180cd345aae705e082a9b1177dcde813645f9","src/auxil/dxgi/result.rs":"79fe5aa17a2b21a7f06b1b604200c3c3e73fca31e8193aab80b5b15e7e9818a0","src/auxil/dxgi/time.rs":"b6f966b250e9424d5d7e4065f2108cba87197c1e30baae6d87083055d1bc5a4b","src/auxil/mod.rs":"720ef2aae258733322a3274fd858f91effb8951dabaf7bbfd8a9a0be2d2dba97","src/auxil/renderdoc.rs":"c2f849f70f576b0c9b0d32dd155b6a6353f74dff59cbeeaa994a12789d047c0f","src/dx12/adapter.rs":"7d647c9a1211e564fb1220c65df26fe2c519e5eddfa89291eaea45be4b60746a","src/dx12/command.rs":"6fe77b8b27c6428128ed0c3bcf7517e511c3c1eec8491a08936a696d5cb30751","src/dx12/conv.rs":"94d35f117ae003b07049f3a0bc6c45a0ffda9fb8053233d39c173cfb1b644403","src/dx12/descriptor.rs":"e06eb08bee4c805fa76b6ab791893b5b563ee60de9c8f8d8e0e21ab97ade5664","src/dx12/device.rs":"f7ca4a30085fdaecc321a01344f9d8cd907b7ba5a1b92f13a3bd9faad1934ed8","src/dx12/instance.rs":"351a4e0d526de8eafc74bf5f01a41da48efa39e0c66704a85da72e1140b159d4","src/dx12/mod.rs":"4b9d5e2414d628ed537f32f46604eeb95912ad9d5ee61cf4ce11c8dd6a88c8ab","src/dx12/shader_compilation.rs":"5087adb8576e2d7751619dfdf8b37c573bb4e494290c594077ca3208cce1e746","src/dx12/suballocation.rs":"6939fc36223a15cc070c744d0418f9ac6fa2829d794af17cdea7c61eb5f8d2c0","src/dx12/types.rs":"9573736baaa0ef607367c3b72144556d24faf677a26bb8df49a4372a1348e06b","src/dx12/view.rs":"792772e9c87840dcd045b7381a03162eb4a501492a95ca586e77e81aed621c67","src/empty.rs":"5c3a5e39d45b4522ff3496fe6ec3b4a7afd906b6095dff1cad113c826aa9ea62","src/gles/adapter.rs":"3175c86212b6c8caa099a3e34750c18251107461314c02f77c984e5b8301051a","src/gles/command.rs":"9f9ef3d97fcb2bc521b85141dee1ca9e8fe06b08d861766c3b3e9a2f3a53b494","src/gles/conv.rs":"5d15d3a33032d32ff99bc338fba0689fa54c76d0714e335fe48523d841df386f","src/gles/device.rs":"7ccd7aa3b878159190092bf279158289d754cc695bd27b9ec7177cd9b86b37c5","src/gles/egl.rs":"ad9b0ddc66877ae4088511283b8c860dd09b0b4d2c1fc51246c6935aa16703eb","src/gles/emscripten.rs":"19bb73a9d140645f3f32cd48b002151711a9b8456e213eab5f3a2be79239e147","src/gles/mod.rs":"b8999f76ad45e07312b291457100f12699ba6a2635c1f1913b0648e9a9394015","src/gles/queue.rs":"3ead252c54c673da6736a0c0c6b63c848791bc78042def3f3ffff8ffce2c6e64","src/gles/shaders/clear.frag":"9133ed8ed97d3641fbb6b5f5ea894a3554c629ccc1b80a5fc9221d7293aa1954","src/gles/shaders/clear.vert":"a543768725f4121ff2e9e1fb5b00644931e9d6f2f946c0ef01968afb5a135abd","src/gles/shaders/srgb_present.frag":"dd9a43c339a2fa4ccf7f6a1854c6f400cabf271a7d5e9230768e9f39d47f3ff5","src/gles/shaders/srgb_present.vert":"6e85d489403d80b81cc94790730bb53b309dfc5eeede8f1ea3412a660f31d357","src/gles/web.rs":"d263695d45736d3c6ec3528c8c33fe6cf3767d3429a13a92d88b4fdc7b6340fb","src/gles/wgl.rs":"06e947912c357c5275090b12b7e31e596ff264fd460e2449b6db4b79284eb74d","src/lib.rs":"c8b8a95f5bfd58eaada0af2cd0abc80f888aeea85969a1363f4061cc9b542ca4","src/metal/adapter.rs":"bb5d0ca1cecbd914cbb29487303be4ed69035469a8bc137784d5bbb6ab36cec7","src/metal/command.rs":"661b38a75d4f4cd1b0d6957f1f09db0743ec3a13bbafba9baa931894ee193f48","src/metal/conv.rs":"0bce6a8d0ccef16783475803d70d35e03ab7938c19374e22c9d253abe1f8b111","src/metal/device.rs":"c5deeecf475e0aa4b2027c656ea19207716f84b56cfa7c9132dca504d1abebfb","src/metal/mod.rs":"f6d12246a6c7e6d998db796a009702f289b5f56bd35f01c0a619f5345fb363c9","src/metal/surface.rs":"f2b9b65d4117db2b16c04469c573358eb65de104d5a72aa02da8483ee243cbd3","src/metal/time.rs":"c32d69f30e846dfcc0e39e01097fb80df63b2bebb6586143bb62494999850246","src/vulkan/adapter.rs":"ed980734c8239bad7f3371e0e778ec63ecea5fe971f04c3dcdd3fe55c359f63b","src/vulkan/command.rs":"e5a88eab59b3864cdf44ba2231270e16045505dc549b8b90251031de452ba826","src/vulkan/conv.rs":"7e6266e3a0b7d0b8d5d51362a0386a84bc047350eeac663b6352a94d5e5c0a87","src/vulkan/device.rs":"9824d597dbb51030bd337e80bb0f1eab6fdb6935fc87dfd8beae2c1f1048fbcf","src/vulkan/instance.rs":"cd4aa3a8ed343076446117bae21fc438fe8761054489ec7d1ed7c31512c2e5ec","src/vulkan/mod.rs":"0c6bfb321b693930bcae3e61d06ff7b71965a64761ce39d757fc609d4b46a03e"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"332863a1b354637fc62ff5729e7c2f5089fa65db05d330ec268a7aab04bb3d42","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","README.md":"099ee611a911dc19330a61bffcde13663929a51b25ac528ee33ea796d695491e","build.rs":"c80bdc0152a00471eec6ed0dd0f7d55d0b975498a00ba05e94100c84ad639a49","examples/halmark/main.rs":"4604737f714943383c57feac2b8468ecf15e9e60c54a5303455e9953ec5c79fb","examples/halmark/shader.wgsl":"26c256ec36d6f0e9a1647431ca772766bee4382d64eaa718ba7b488dcfb6bcca","examples/raw-gles.em.html":"70fbe68394a1a4522192de1dcfaf7d399f60d7bdf5de70b708f9bb0417427546","examples/raw-gles.rs":"095113a1ba0851652a77aabfc8fa6ea7edcc2d09e91fd1e5009ead87d5998ea9","examples/ray-traced-triangle/main.rs":"955c2b8700c3b2daf14e9ef963ff499ed185b6f349dbc63caa422b2cf4942a1f","examples/ray-traced-triangle/shader.wgsl":"cc10caf92746724a71f6dd0dbc3a71e57b37c7d1d83278556805a535c0728a9d","src/auxil/dxgi/conv.rs":"760cd4eaa79b530368a30140b96bf73ac4fbdb4025eb95f0bed581638c8bb1cb","src/auxil/dxgi/exception.rs":"f0cfb5a0adcdc3b6db909601fee51ad51368f5da269bcd46e4dbea45a3bec4b1","src/auxil/dxgi/factory.rs":"5f861fbfe2f4cce08722a95283549b8f62b96f24a306d080d9f1730ae53501d8","src/auxil/dxgi/mod.rs":"a202564d9ac97530b16a234b87d180cd345aae705e082a9b1177dcde813645f9","src/auxil/dxgi/result.rs":"79fe5aa17a2b21a7f06b1b604200c3c3e73fca31e8193aab80b5b15e7e9818a0","src/auxil/dxgi/time.rs":"b6f966b250e9424d5d7e4065f2108cba87197c1e30baae6d87083055d1bc5a4b","src/auxil/mod.rs":"720ef2aae258733322a3274fd858f91effb8951dabaf7bbfd8a9a0be2d2dba97","src/auxil/renderdoc.rs":"c2f849f70f576b0c9b0d32dd155b6a6353f74dff59cbeeaa994a12789d047c0f","src/dx12/adapter.rs":"2a90a4222702e50fabfc64339ac2aa667e1ee2d2bf801c3e2e59a91e261c7a04","src/dx12/command.rs":"e0675560784214a18e078062cbd0965c21a35c99eecf0e697d1badb9c692db35","src/dx12/conv.rs":"94d35f117ae003b07049f3a0bc6c45a0ffda9fb8053233d39c173cfb1b644403","src/dx12/descriptor.rs":"e06eb08bee4c805fa76b6ab791893b5b563ee60de9c8f8d8e0e21ab97ade5664","src/dx12/device.rs":"6a38dabd4db9d7ca79bfcfbeb3483320ad88f2db6e93a7210051bbf81acb1bbd","src/dx12/instance.rs":"030a86ac810d5ebf151328bbc06b9b5a89788721aa963cb4e0f3ff943b2c8633","src/dx12/mod.rs":"87e7012a113554c976abdbecd10b2fe6aab3ba695c88dc77d6beb3880d96ba54","src/dx12/shader_compilation.rs":"5087adb8576e2d7751619dfdf8b37c573bb4e494290c594077ca3208cce1e746","src/dx12/suballocation.rs":"6939fc36223a15cc070c744d0418f9ac6fa2829d794af17cdea7c61eb5f8d2c0","src/dx12/types.rs":"9573736baaa0ef607367c3b72144556d24faf677a26bb8df49a4372a1348e06b","src/dx12/view.rs":"792772e9c87840dcd045b7381a03162eb4a501492a95ca586e77e81aed621c67","src/empty.rs":"0849e0b7210d33145b3fb1368ed08b13fbc2144b1ccc0a1af913896e916bbe46","src/gles/adapter.rs":"22fe404177d4abb077f35a5be54295f81eacb021d448ad01aa96450ade8b089a","src/gles/command.rs":"788505168ae1af312781c86d31d5bc289fcc4e2b3cb047fb41deb62542db1a95","src/gles/conv.rs":"5d15d3a33032d32ff99bc338fba0689fa54c76d0714e335fe48523d841df386f","src/gles/device.rs":"e126042653b0651a12684504e19c05db3fe333bde3ac408063675eef6c27754a","src/gles/egl.rs":"72feefbcd399e686b5c210a67151ef040e45e123ad371539274d1d52b3dd162d","src/gles/emscripten.rs":"19bb73a9d140645f3f32cd48b002151711a9b8456e213eab5f3a2be79239e147","src/gles/mod.rs":"b8999f76ad45e07312b291457100f12699ba6a2635c1f1913b0648e9a9394015","src/gles/queue.rs":"7f62e9c1cb670f66ac8bb0d62cd531b9c11b2a29f8ed28736c40e21071fbe25d","src/gles/shaders/clear.frag":"9133ed8ed97d3641fbb6b5f5ea894a3554c629ccc1b80a5fc9221d7293aa1954","src/gles/shaders/clear.vert":"a543768725f4121ff2e9e1fb5b00644931e9d6f2f946c0ef01968afb5a135abd","src/gles/shaders/srgb_present.frag":"dd9a43c339a2fa4ccf7f6a1854c6f400cabf271a7d5e9230768e9f39d47f3ff5","src/gles/shaders/srgb_present.vert":"6e85d489403d80b81cc94790730bb53b309dfc5eeede8f1ea3412a660f31d357","src/gles/web.rs":"000ed39eae4a6442ca16bdca68e1e61459e6815a5db8eeec53e0ee0f5f14ae66","src/gles/wgl.rs":"78bfe5bcdacfde8d8df0d6f3987048f8ad8a2c2c93df6d607cf5a16795d3bb8e","src/lib.rs":"b80b7d14f524f25875b1d7e7ee67cd6057bbe9944ea92b59d5d4d08d7b17580d","src/metal/adapter.rs":"e473a1857817030ee5c5358e8387daff66d1aca1f5133224cc844ea4b17d3e49","src/metal/command.rs":"61640f2cb7269f44487df2d911771fbe1f78484d44a5363a6ddfce7d77143b02","src/metal/conv.rs":"0bce6a8d0ccef16783475803d70d35e03ab7938c19374e22c9d253abe1f8b111","src/metal/device.rs":"1343ad60a423b81649a6eaad69de743a2247234a75ec48c0698973af67592e48","src/metal/mod.rs":"3e3f69ac864cb6a96c5b9df551a934bbc4bf08503430b9507561eef973daf57f","src/metal/surface.rs":"397dab6726eead96f0be9ecb6686874e691079da94c072921a6422e9b1284fb5","src/metal/time.rs":"c32d69f30e846dfcc0e39e01097fb80df63b2bebb6586143bb62494999850246","src/vulkan/adapter.rs":"ac4525114d37d6ffa31f2c240b2bb9e1d97d19cd6d400598c2a1153d9144bd0f","src/vulkan/command.rs":"82060e8040eea27b717ec676525139a9c7238074ad5ce6902f5174ac5c7aa048","src/vulkan/conv.rs":"7e6266e3a0b7d0b8d5d51362a0386a84bc047350eeac663b6352a94d5e5c0a87","src/vulkan/device.rs":"24abe83a2cbe8fa3377d0fb8d64fcaac3a3e6b0521ec28143fa8b7dee0c1afee","src/vulkan/instance.rs":"60742c3c1feee63eca0953165dae57c86b0d58e496ad374743ff83ae50a4e8f1","src/vulkan/mod.rs":"dfd8e079b7b783de3dfdfbf2349d7283c1b9108242d1ffbb4e072376901c7bd1"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/wgpu-hal/Cargo.toml b/third_party/rust/wgpu-hal/Cargo.toml
index 47195f996d..f601949231 100644
--- a/third_party/rust/wgpu-hal/Cargo.toml
+++ b/third_party/rust/wgpu-hal/Cargo.toml
@@ -11,7 +11,7 @@
[package]
edition = "2021"
-rust-version = "1.70"
+rust-version = "1.74"
name = "wgpu-hal"
version = "0.19.0"
authors = ["gfx-rs developers"]
@@ -78,7 +78,7 @@ package = "wgpu-types"
[dev-dependencies]
cfg-if = "1"
-env_logger = "0.10"
+env_logger = "0.11"
glam = "0.25.0"
[dev-dependencies.naga]
@@ -87,7 +87,7 @@ path = "../naga"
features = ["wgsl-in"]
[dev-dependencies.winit]
-version = "0.29.10"
+version = "0.29.14"
features = ["android-native-activity"]
[build-dependencies]
@@ -142,11 +142,11 @@ vulkan = [
windows_rs = ["gpu-allocator"]
[target."cfg(all(target_arch = \"wasm32\", not(target_os = \"emscripten\")))".dependencies]
-js-sys = "0.3.67"
+js-sys = "0.3.69"
wasm-bindgen = "0.2.87"
[target."cfg(all(target_arch = \"wasm32\", not(target_os = \"emscripten\")))".dependencies.web-sys]
-version = "0.3.67"
+version = "0.3.69"
features = [
"Window",
"HtmlCanvasElement",
@@ -185,7 +185,7 @@ version = ">=0.7, <0.9"
optional = true
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.renderdoc-sys]
-version = "1.0.0"
+version = "1.1.0"
optional = true
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.smallvec]
diff --git a/third_party/rust/wgpu-hal/src/dx12/adapter.rs b/third_party/rust/wgpu-hal/src/dx12/adapter.rs
index 960e1790a9..b417a88a6f 100644
--- a/third_party/rust/wgpu-hal/src/dx12/adapter.rs
+++ b/third_party/rust/wgpu-hal/src/dx12/adapter.rs
@@ -432,7 +432,9 @@ impl super::Adapter {
}
}
-impl crate::Adapter<super::Api> for super::Adapter {
+impl crate::Adapter for super::Adapter {
+ type A = super::Api;
+
unsafe fn open(
&self,
_features: wgt::Features,
diff --git a/third_party/rust/wgpu-hal/src/dx12/command.rs b/third_party/rust/wgpu-hal/src/dx12/command.rs
index 9d96d29cae..3c535b2234 100644
--- a/third_party/rust/wgpu-hal/src/dx12/command.rs
+++ b/third_party/rust/wgpu-hal/src/dx12/command.rs
@@ -249,7 +249,9 @@ impl super::CommandEncoder {
}
}
-impl crate::CommandEncoder<super::Api> for super::CommandEncoder {
+impl crate::CommandEncoder for super::CommandEncoder {
+ type A = super::Api;
+
unsafe fn begin_encoding(&mut self, label: crate::Label) -> Result<(), crate::DeviceError> {
let list = loop {
if let Some(list) = self.free_lists.pop() {
diff --git a/third_party/rust/wgpu-hal/src/dx12/device.rs b/third_party/rust/wgpu-hal/src/dx12/device.rs
index 3603b033b8..23bd409dc4 100644
--- a/third_party/rust/wgpu-hal/src/dx12/device.rs
+++ b/third_party/rust/wgpu-hal/src/dx12/device.rs
@@ -323,7 +323,9 @@ impl super::Device {
}
}
-impl crate::Device<super::Api> for super::Device {
+impl crate::Device for super::Device {
+ type A = super::Api;
+
unsafe fn exit(mut self, _queue: super::Queue) {
self.rtv_pool.lock().free_handle(self.null_rtv_handle);
self.mem_allocator = None;
@@ -1098,7 +1100,16 @@ impl crate::Device<super::Api> for super::Device {
}
let mut dynamic_buffers = Vec::new();
- for (layout, entry) in desc.layout.entries.iter().zip(desc.entries.iter()) {
+ let layout_and_entry_iter = desc.entries.iter().map(|entry| {
+ let layout = desc
+ .layout
+ .entries
+ .iter()
+ .find(|layout_entry| layout_entry.binding == entry.binding)
+ .expect("internal error: no layout entry found with binding slot");
+ (layout, entry)
+ });
+ for (layout, entry) in layout_and_entry_iter {
match layout.ty {
wgt::BindingType::Buffer {
has_dynamic_offset: true,
diff --git a/third_party/rust/wgpu-hal/src/dx12/instance.rs b/third_party/rust/wgpu-hal/src/dx12/instance.rs
index 020809328e..1dba7101df 100644
--- a/third_party/rust/wgpu-hal/src/dx12/instance.rs
+++ b/third_party/rust/wgpu-hal/src/dx12/instance.rs
@@ -13,7 +13,9 @@ impl Drop for super::Instance {
}
}
-impl crate::Instance<super::Api> for super::Instance {
+impl crate::Instance for super::Instance {
+ type A = super::Api;
+
unsafe fn init(desc: &crate::InstanceDescriptor) -> Result<Self, crate::InstanceError> {
profiling::scope!("Init DX12 Backend");
let lib_main = d3d12::D3D12Lib::new().map_err(|e| {
diff --git a/third_party/rust/wgpu-hal/src/dx12/mod.rs b/third_party/rust/wgpu-hal/src/dx12/mod.rs
index 13b43f8aca..4f958943ca 100644
--- a/third_party/rust/wgpu-hal/src/dx12/mod.rs
+++ b/third_party/rust/wgpu-hal/src/dx12/mod.rs
@@ -639,7 +639,9 @@ impl SwapChain {
}
}
-impl crate::Surface<Api> for Surface {
+impl crate::Surface for Surface {
+ type A = Api;
+
unsafe fn configure(
&self,
device: &Device,
@@ -884,7 +886,9 @@ impl crate::Surface<Api> for Surface {
}
}
-impl crate::Queue<Api> for Queue {
+impl crate::Queue for Queue {
+ type A = Api;
+
unsafe fn submit(
&self,
command_buffers: &[&CommandBuffer],
diff --git a/third_party/rust/wgpu-hal/src/empty.rs b/third_party/rust/wgpu-hal/src/empty.rs
index d58e779b96..ad00da1b7f 100644
--- a/third_party/rust/wgpu-hal/src/empty.rs
+++ b/third_party/rust/wgpu-hal/src/empty.rs
@@ -39,7 +39,9 @@ impl crate::Api for Api {
type ComputePipeline = Resource;
}
-impl crate::Instance<Api> for Context {
+impl crate::Instance for Context {
+ type A = Api;
+
unsafe fn init(desc: &crate::InstanceDescriptor) -> Result<Self, crate::InstanceError> {
Ok(Context)
}
@@ -56,7 +58,9 @@ impl crate::Instance<Api> for Context {
}
}
-impl crate::Surface<Api> for Context {
+impl crate::Surface for Context {
+ type A = Api;
+
unsafe fn configure(
&self,
device: &Context,
@@ -76,7 +80,9 @@ impl crate::Surface<Api> for Context {
unsafe fn discard_texture(&self, texture: Resource) {}
}
-impl crate::Adapter<Api> for Context {
+impl crate::Adapter for Context {
+ type A = Api;
+
unsafe fn open(
&self,
features: wgt::Features,
@@ -100,7 +106,9 @@ impl crate::Adapter<Api> for Context {
}
}
-impl crate::Queue<Api> for Context {
+impl crate::Queue for Context {
+ type A = Api;
+
unsafe fn submit(
&self,
command_buffers: &[&Resource],
@@ -122,7 +130,9 @@ impl crate::Queue<Api> for Context {
}
}
-impl crate::Device<Api> for Context {
+impl crate::Device for Context {
+ type A = Api;
+
unsafe fn exit(self, queue: Context) {}
unsafe fn create_buffer(&self, desc: &crate::BufferDescriptor) -> DeviceResult<Resource> {
Ok(Resource)
@@ -259,7 +269,9 @@ impl crate::Device<Api> for Context {
unsafe fn destroy_acceleration_structure(&self, _acceleration_structure: Resource) {}
}
-impl crate::CommandEncoder<Api> for Encoder {
+impl crate::CommandEncoder for Encoder {
+ type A = Api;
+
unsafe fn begin_encoding(&mut self, label: crate::Label) -> DeviceResult<()> {
Ok(())
}
diff --git a/third_party/rust/wgpu-hal/src/gles/adapter.rs b/third_party/rust/wgpu-hal/src/gles/adapter.rs
index c09725e85f..b9d044337c 100644
--- a/third_party/rust/wgpu-hal/src/gles/adapter.rs
+++ b/third_party/rust/wgpu-hal/src/gles/adapter.rs
@@ -922,7 +922,9 @@ impl super::Adapter {
}
}
-impl crate::Adapter<super::Api> for super::Adapter {
+impl crate::Adapter for super::Adapter {
+ type A = super::Api;
+
unsafe fn open(
&self,
features: wgt::Features,
diff --git a/third_party/rust/wgpu-hal/src/gles/command.rs b/third_party/rust/wgpu-hal/src/gles/command.rs
index 4385e2a31e..258dee76e5 100644
--- a/third_party/rust/wgpu-hal/src/gles/command.rs
+++ b/third_party/rust/wgpu-hal/src/gles/command.rs
@@ -250,7 +250,9 @@ impl super::CommandEncoder {
}
}
-impl crate::CommandEncoder<super::Api> for super::CommandEncoder {
+impl crate::CommandEncoder for super::CommandEncoder {
+ type A = super::Api;
+
unsafe fn begin_encoding(&mut self, label: crate::Label) -> Result<(), crate::DeviceError> {
self.state = State::default();
self.cmd_buffer.label = label.map(str::to_string);
diff --git a/third_party/rust/wgpu-hal/src/gles/device.rs b/third_party/rust/wgpu-hal/src/gles/device.rs
index 2678488cf8..50c07f3ff0 100644
--- a/third_party/rust/wgpu-hal/src/gles/device.rs
+++ b/third_party/rust/wgpu-hal/src/gles/device.rs
@@ -483,7 +483,9 @@ impl super::Device {
}
}
-impl crate::Device<super::Api> for super::Device {
+impl crate::Device for super::Device {
+ type A = super::Api;
+
unsafe fn exit(self, queue: super::Queue) {
let gl = &self.shared.context.lock();
unsafe { gl.delete_vertex_array(self.main_vao) };
@@ -1123,8 +1125,10 @@ impl crate::Device<super::Api> for super::Device {
!0;
bg_layout
.entries
- .last()
- .map_or(0, |b| b.binding as usize + 1)
+ .iter()
+ .map(|b| b.binding)
+ .max()
+ .map_or(0, |idx| idx as usize + 1)
]
.into_boxed_slice();
@@ -1177,7 +1181,16 @@ impl crate::Device<super::Api> for super::Device {
) -> Result<super::BindGroup, crate::DeviceError> {
let mut contents = Vec::new();
- for (entry, layout) in desc.entries.iter().zip(desc.layout.entries.iter()) {
+ let layout_and_entry_iter = desc.entries.iter().map(|entry| {
+ let layout = desc
+ .layout
+ .entries
+ .iter()
+ .find(|layout_entry| layout_entry.binding == entry.binding)
+ .expect("internal error: no layout entry found with binding slot");
+ (entry, layout)
+ });
+ for (entry, layout) in layout_and_entry_iter {
let binding = match layout.ty {
wgt::BindingType::Buffer { .. } => {
let bb = &desc.buffers[entry.resource_index as usize];
diff --git a/third_party/rust/wgpu-hal/src/gles/egl.rs b/third_party/rust/wgpu-hal/src/gles/egl.rs
index f4bfcf5487..b166f4f102 100644
--- a/third_party/rust/wgpu-hal/src/gles/egl.rs
+++ b/third_party/rust/wgpu-hal/src/gles/egl.rs
@@ -703,7 +703,9 @@ impl Instance {
unsafe impl Send for Instance {}
unsafe impl Sync for Instance {}
-impl crate::Instance<super::Api> for Instance {
+impl crate::Instance for Instance {
+ type A = super::Api;
+
unsafe fn init(desc: &crate::InstanceDescriptor) -> Result<Self, crate::InstanceError> {
profiling::scope!("Init OpenGL (EGL) Backend");
#[cfg(Emscripten)]
@@ -1165,7 +1167,9 @@ impl Surface {
}
}
-impl crate::Surface<super::Api> for Surface {
+impl crate::Surface for Surface {
+ type A = super::Api;
+
unsafe fn configure(
&self,
device: &super::Device,
diff --git a/third_party/rust/wgpu-hal/src/gles/queue.rs b/third_party/rust/wgpu-hal/src/gles/queue.rs
index 5db5af9a16..29dfb79d04 100644
--- a/third_party/rust/wgpu-hal/src/gles/queue.rs
+++ b/third_party/rust/wgpu-hal/src/gles/queue.rs
@@ -1748,7 +1748,9 @@ impl super::Queue {
}
}
-impl crate::Queue<super::Api> for super::Queue {
+impl crate::Queue for super::Queue {
+ type A = super::Api;
+
unsafe fn submit(
&self,
command_buffers: &[&super::CommandBuffer],
diff --git a/third_party/rust/wgpu-hal/src/gles/web.rs b/third_party/rust/wgpu-hal/src/gles/web.rs
index 797d6f91d7..ab2ccef8b6 100644
--- a/third_party/rust/wgpu-hal/src/gles/web.rs
+++ b/third_party/rust/wgpu-hal/src/gles/web.rs
@@ -116,7 +116,9 @@ unsafe impl Sync for Instance {}
#[cfg(send_sync)]
unsafe impl Send for Instance {}
-impl crate::Instance<super::Api> for Instance {
+impl crate::Instance for Instance {
+ type A = super::Api;
+
unsafe fn init(_desc: &crate::InstanceDescriptor) -> Result<Self, crate::InstanceError> {
profiling::scope!("Init OpenGL (WebGL) Backend");
Ok(Instance {
@@ -309,7 +311,9 @@ impl Surface {
}
}
-impl crate::Surface<super::Api> for Surface {
+impl crate::Surface for Surface {
+ type A = super::Api;
+
unsafe fn configure(
&self,
device: &super::Device,
diff --git a/third_party/rust/wgpu-hal/src/gles/wgl.rs b/third_party/rust/wgpu-hal/src/gles/wgl.rs
index c9039090b7..2564892969 100644
--- a/third_party/rust/wgpu-hal/src/gles/wgl.rs
+++ b/third_party/rust/wgpu-hal/src/gles/wgl.rs
@@ -422,7 +422,9 @@ fn create_instance_device() -> Result<InstanceDevice, crate::InstanceError> {
Ok(InstanceDevice { dc, _tx: drop_tx })
}
-impl crate::Instance<super::Api> for Instance {
+impl crate::Instance for Instance {
+ type A = super::Api;
+
unsafe fn init(desc: &crate::InstanceDescriptor) -> Result<Self, crate::InstanceError> {
profiling::scope!("Init OpenGL (WGL) Backend");
let opengl_module = unsafe { LoadLibraryA("opengl32.dll\0".as_ptr() as *const _) };
@@ -676,7 +678,9 @@ impl Surface {
}
}
-impl crate::Surface<super::Api> for Surface {
+impl crate::Surface for Surface {
+ type A = super::Api;
+
unsafe fn configure(
&self,
device: &super::Device,
diff --git a/third_party/rust/wgpu-hal/src/lib.rs b/third_party/rust/wgpu-hal/src/lib.rs
index f1794a4a89..79bd54e66e 100644
--- a/third_party/rust/wgpu-hal/src/lib.rs
+++ b/third_party/rust/wgpu-hal/src/lib.rs
@@ -191,13 +191,13 @@ impl InstanceError {
}
pub trait Api: Clone + fmt::Debug + Sized {
- type Instance: Instance<Self>;
- type Surface: Surface<Self>;
- type Adapter: Adapter<Self>;
- type Device: Device<Self>;
+ type Instance: Instance<A = Self>;
+ type Surface: Surface<A = Self>;
+ type Adapter: Adapter<A = Self>;
+ type Device: Device<A = Self>;
- type Queue: Queue<Self>;
- type CommandEncoder: CommandEncoder<Self>;
+ type Queue: Queue<A = Self>;
+ type CommandEncoder: CommandEncoder<A = Self>;
type CommandBuffer: WasmNotSendSync + fmt::Debug;
type Buffer: fmt::Debug + WasmNotSendSync + 'static;
@@ -218,18 +218,22 @@ pub trait Api: Clone + fmt::Debug + Sized {
type AccelerationStructure: fmt::Debug + WasmNotSendSync + 'static;
}
-pub trait Instance<A: Api>: Sized + WasmNotSendSync {
+pub trait Instance: Sized + WasmNotSendSync {
+ type A: Api;
+
unsafe fn init(desc: &InstanceDescriptor) -> Result<Self, InstanceError>;
unsafe fn create_surface(
&self,
display_handle: raw_window_handle::RawDisplayHandle,
window_handle: raw_window_handle::RawWindowHandle,
- ) -> Result<A::Surface, InstanceError>;
- unsafe fn destroy_surface(&self, surface: A::Surface);
- unsafe fn enumerate_adapters(&self) -> Vec<ExposedAdapter<A>>;
+ ) -> Result<<Self::A as Api>::Surface, InstanceError>;
+ unsafe fn destroy_surface(&self, surface: <Self::A as Api>::Surface);
+ unsafe fn enumerate_adapters(&self) -> Vec<ExposedAdapter<Self::A>>;
}
-pub trait Surface<A: Api>: WasmNotSendSync {
+pub trait Surface: WasmNotSendSync {
+ type A: Api;
+
/// Configures the surface to use the given device.
///
/// # Safety
@@ -240,7 +244,7 @@ pub trait Surface<A: Api>: WasmNotSendSync {
/// - All surfaces created using other devices must have been unconfigured before this call.
unsafe fn configure(
&self,
- device: &A::Device,
+ device: &<Self::A as Api>::Device,
config: &SurfaceConfiguration,
) -> Result<(), SurfaceError>;
@@ -252,7 +256,7 @@ pub trait Surface<A: Api>: WasmNotSendSync {
/// - All [`AcquiredSurfaceTexture`]s must have been destroyed.
/// - All [`Api::TextureView`]s derived from the [`AcquiredSurfaceTexture`]s must have been destroyed.
/// - The surface must have been configured on the given device.
- unsafe fn unconfigure(&self, device: &A::Device);
+ unsafe fn unconfigure(&self, device: &<Self::A as Api>::Device);
/// Returns the next texture to be presented by the swapchain for drawing
///
@@ -267,16 +271,18 @@ pub trait Surface<A: Api>: WasmNotSendSync {
unsafe fn acquire_texture(
&self,
timeout: Option<std::time::Duration>,
- ) -> Result<Option<AcquiredSurfaceTexture<A>>, SurfaceError>;
- unsafe fn discard_texture(&self, texture: A::SurfaceTexture);
+ ) -> Result<Option<AcquiredSurfaceTexture<Self::A>>, SurfaceError>;
+ unsafe fn discard_texture(&self, texture: <Self::A as Api>::SurfaceTexture);
}
-pub trait Adapter<A: Api>: WasmNotSendSync {
+pub trait Adapter: WasmNotSendSync {
+ type A: Api;
+
unsafe fn open(
&self,
features: wgt::Features,
limits: &wgt::Limits,
- ) -> Result<OpenDevice<A>, DeviceError>;
+ ) -> Result<OpenDevice<Self::A>, DeviceError>;
/// Return the set of supported capabilities for a texture format.
unsafe fn texture_format_capabilities(
@@ -287,7 +293,10 @@ pub trait Adapter<A: Api>: WasmNotSendSync {
/// Returns the capabilities of working with a specified surface.
///
/// `None` means presentation is not supported for it.
- unsafe fn surface_capabilities(&self, surface: &A::Surface) -> Option<SurfaceCapabilities>;
+ unsafe fn surface_capabilities(
+ &self,
+ surface: &<Self::A as Api>::Surface,
+ ) -> Option<SurfaceCapabilities>;
/// Creates a [`PresentationTimestamp`] using the adapter's WSI.
///
@@ -295,97 +304,111 @@ pub trait Adapter<A: Api>: WasmNotSendSync {
unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp;
}
-pub trait Device<A: Api>: WasmNotSendSync {
+pub trait Device: WasmNotSendSync {
+ type A: Api;
+
/// Exit connection to this logical device.
- unsafe fn exit(self, queue: A::Queue);
+ unsafe fn exit(self, queue: <Self::A as Api>::Queue);
/// Creates a new buffer.
///
/// The initial usage is `BufferUses::empty()`.
- unsafe fn create_buffer(&self, desc: &BufferDescriptor) -> Result<A::Buffer, DeviceError>;
- unsafe fn destroy_buffer(&self, buffer: A::Buffer);
+ unsafe fn create_buffer(
+ &self,
+ desc: &BufferDescriptor,
+ ) -> Result<<Self::A as Api>::Buffer, DeviceError>;
+ unsafe fn destroy_buffer(&self, buffer: <Self::A as Api>::Buffer);
//TODO: clarify if zero-sized mapping is allowed
unsafe fn map_buffer(
&self,
- buffer: &A::Buffer,
+ buffer: &<Self::A as Api>::Buffer,
range: MemoryRange,
) -> Result<BufferMapping, DeviceError>;
- unsafe fn unmap_buffer(&self, buffer: &A::Buffer) -> Result<(), DeviceError>;
- unsafe fn flush_mapped_ranges<I>(&self, buffer: &A::Buffer, ranges: I)
+ unsafe fn unmap_buffer(&self, buffer: &<Self::A as Api>::Buffer) -> Result<(), DeviceError>;
+ unsafe fn flush_mapped_ranges<I>(&self, buffer: &<Self::A as Api>::Buffer, ranges: I)
where
I: Iterator<Item = MemoryRange>;
- unsafe fn invalidate_mapped_ranges<I>(&self, buffer: &A::Buffer, ranges: I)
+ unsafe fn invalidate_mapped_ranges<I>(&self, buffer: &<Self::A as Api>::Buffer, ranges: I)
where
I: Iterator<Item = MemoryRange>;
/// Creates a new texture.
///
/// The initial usage for all subresources is `TextureUses::UNINITIALIZED`.
- unsafe fn create_texture(&self, desc: &TextureDescriptor) -> Result<A::Texture, DeviceError>;
- unsafe fn destroy_texture(&self, texture: A::Texture);
+ unsafe fn create_texture(
+ &self,
+ desc: &TextureDescriptor,
+ ) -> Result<<Self::A as Api>::Texture, DeviceError>;
+ unsafe fn destroy_texture(&self, texture: <Self::A as Api>::Texture);
unsafe fn create_texture_view(
&self,
- texture: &A::Texture,
+ texture: &<Self::A as Api>::Texture,
desc: &TextureViewDescriptor,
- ) -> Result<A::TextureView, DeviceError>;
- unsafe fn destroy_texture_view(&self, view: A::TextureView);
- unsafe fn create_sampler(&self, desc: &SamplerDescriptor) -> Result<A::Sampler, DeviceError>;
- unsafe fn destroy_sampler(&self, sampler: A::Sampler);
+ ) -> Result<<Self::A as Api>::TextureView, DeviceError>;
+ unsafe fn destroy_texture_view(&self, view: <Self::A as Api>::TextureView);
+ unsafe fn create_sampler(
+ &self,
+ desc: &SamplerDescriptor,
+ ) -> Result<<Self::A as Api>::Sampler, DeviceError>;
+ unsafe fn destroy_sampler(&self, sampler: <Self::A as Api>::Sampler);
/// Create a fresh [`CommandEncoder`].
///
/// The new `CommandEncoder` is in the "closed" state.
unsafe fn create_command_encoder(
&self,
- desc: &CommandEncoderDescriptor<A>,
- ) -> Result<A::CommandEncoder, DeviceError>;
- unsafe fn destroy_command_encoder(&self, pool: A::CommandEncoder);
+ desc: &CommandEncoderDescriptor<Self::A>,
+ ) -> Result<<Self::A as Api>::CommandEncoder, DeviceError>;
+ unsafe fn destroy_command_encoder(&self, pool: <Self::A as Api>::CommandEncoder);
/// Creates a bind group layout.
unsafe fn create_bind_group_layout(
&self,
desc: &BindGroupLayoutDescriptor,
- ) -> Result<A::BindGroupLayout, DeviceError>;
- unsafe fn destroy_bind_group_layout(&self, bg_layout: A::BindGroupLayout);
+ ) -> Result<<Self::A as Api>::BindGroupLayout, DeviceError>;
+ unsafe fn destroy_bind_group_layout(&self, bg_layout: <Self::A as Api>::BindGroupLayout);
unsafe fn create_pipeline_layout(
&self,
- desc: &PipelineLayoutDescriptor<A>,
- ) -> Result<A::PipelineLayout, DeviceError>;
- unsafe fn destroy_pipeline_layout(&self, pipeline_layout: A::PipelineLayout);
+ desc: &PipelineLayoutDescriptor<Self::A>,
+ ) -> Result<<Self::A as Api>::PipelineLayout, DeviceError>;
+ unsafe fn destroy_pipeline_layout(&self, pipeline_layout: <Self::A as Api>::PipelineLayout);
unsafe fn create_bind_group(
&self,
- desc: &BindGroupDescriptor<A>,
- ) -> Result<A::BindGroup, DeviceError>;
- unsafe fn destroy_bind_group(&self, group: A::BindGroup);
+ desc: &BindGroupDescriptor<Self::A>,
+ ) -> Result<<Self::A as Api>::BindGroup, DeviceError>;
+ unsafe fn destroy_bind_group(&self, group: <Self::A as Api>::BindGroup);
unsafe fn create_shader_module(
&self,
desc: &ShaderModuleDescriptor,
shader: ShaderInput,
- ) -> Result<A::ShaderModule, ShaderError>;
- unsafe fn destroy_shader_module(&self, module: A::ShaderModule);
+ ) -> Result<<Self::A as Api>::ShaderModule, ShaderError>;
+ unsafe fn destroy_shader_module(&self, module: <Self::A as Api>::ShaderModule);
unsafe fn create_render_pipeline(
&self,
- desc: &RenderPipelineDescriptor<A>,
- ) -> Result<A::RenderPipeline, PipelineError>;
- unsafe fn destroy_render_pipeline(&self, pipeline: A::RenderPipeline);
+ desc: &RenderPipelineDescriptor<Self::A>,
+ ) -> Result<<Self::A as Api>::RenderPipeline, PipelineError>;
+ unsafe fn destroy_render_pipeline(&self, pipeline: <Self::A as Api>::RenderPipeline);
unsafe fn create_compute_pipeline(
&self,
- desc: &ComputePipelineDescriptor<A>,
- ) -> Result<A::ComputePipeline, PipelineError>;
- unsafe fn destroy_compute_pipeline(&self, pipeline: A::ComputePipeline);
+ desc: &ComputePipelineDescriptor<Self::A>,
+ ) -> Result<<Self::A as Api>::ComputePipeline, PipelineError>;
+ unsafe fn destroy_compute_pipeline(&self, pipeline: <Self::A as Api>::ComputePipeline);
unsafe fn create_query_set(
&self,
desc: &wgt::QuerySetDescriptor<Label>,
- ) -> Result<A::QuerySet, DeviceError>;
- unsafe fn destroy_query_set(&self, set: A::QuerySet);
- unsafe fn create_fence(&self) -> Result<A::Fence, DeviceError>;
- unsafe fn destroy_fence(&self, fence: A::Fence);
- unsafe fn get_fence_value(&self, fence: &A::Fence) -> Result<FenceValue, DeviceError>;
+ ) -> Result<<Self::A as Api>::QuerySet, DeviceError>;
+ unsafe fn destroy_query_set(&self, set: <Self::A as Api>::QuerySet);
+ unsafe fn create_fence(&self) -> Result<<Self::A as Api>::Fence, DeviceError>;
+ unsafe fn destroy_fence(&self, fence: <Self::A as Api>::Fence);
+ unsafe fn get_fence_value(
+ &self,
+ fence: &<Self::A as Api>::Fence,
+ ) -> Result<FenceValue, DeviceError>;
/// Calling wait with a lower value than the current fence value will immediately return.
unsafe fn wait(
&self,
- fence: &A::Fence,
+ fence: &<Self::A as Api>::Fence,
value: FenceValue,
timeout_ms: u32,
) -> Result<bool, DeviceError>;
@@ -396,22 +419,24 @@ pub trait Device<A: Api>: WasmNotSendSync {
unsafe fn create_acceleration_structure(
&self,
desc: &AccelerationStructureDescriptor,
- ) -> Result<A::AccelerationStructure, DeviceError>;
+ ) -> Result<<Self::A as Api>::AccelerationStructure, DeviceError>;
unsafe fn get_acceleration_structure_build_sizes(
&self,
- desc: &GetAccelerationStructureBuildSizesDescriptor<A>,
+ desc: &GetAccelerationStructureBuildSizesDescriptor<Self::A>,
) -> AccelerationStructureBuildSizes;
unsafe fn get_acceleration_structure_device_address(
&self,
- acceleration_structure: &A::AccelerationStructure,
+ acceleration_structure: &<Self::A as Api>::AccelerationStructure,
) -> wgt::BufferAddress;
unsafe fn destroy_acceleration_structure(
&self,
- acceleration_structure: A::AccelerationStructure,
+ acceleration_structure: <Self::A as Api>::AccelerationStructure,
);
}
-pub trait Queue<A: Api>: WasmNotSendSync {
+pub trait Queue: WasmNotSendSync {
+ type A: Api;
+
/// Submits the command buffers for execution on GPU.
///
/// Valid usage:
@@ -422,14 +447,14 @@ pub trait Queue<A: Api>: WasmNotSendSync {
/// passed to the surface_textures argument.
unsafe fn submit(
&self,
- command_buffers: &[&A::CommandBuffer],
- surface_textures: &[&A::SurfaceTexture],
- signal_fence: Option<(&mut A::Fence, FenceValue)>,
+ command_buffers: &[&<Self::A as Api>::CommandBuffer],
+ surface_textures: &[&<Self::A as Api>::SurfaceTexture],
+ signal_fence: Option<(&mut <Self::A as Api>::Fence, FenceValue)>,
) -> Result<(), DeviceError>;
unsafe fn present(
&self,
- surface: &A::Surface,
- texture: A::SurfaceTexture,
+ surface: &<Self::A as Api>::Surface,
+ texture: <Self::A as Api>::SurfaceTexture,
) -> Result<(), SurfaceError>;
unsafe fn get_timestamp_period(&self) -> f32;
}
@@ -472,7 +497,9 @@ pub trait Queue<A: Api>: WasmNotSendSync {
/// built it.
///
/// - A `CommandEncoder` must not outlive its `Device`.
-pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
+pub trait CommandEncoder: WasmNotSendSync + fmt::Debug {
+ type A: Api;
+
/// Begin encoding a new command buffer.
///
/// This puts this `CommandEncoder` in the "recording" state.
@@ -510,7 +537,7 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
///
/// [`CommandBuffer`]: Api::CommandBuffer
/// [`begin_encoding`]: CommandEncoder::begin_encoding
- unsafe fn end_encoding(&mut self) -> Result<A::CommandBuffer, DeviceError>;
+ unsafe fn end_encoding(&mut self) -> Result<<Self::A as Api>::CommandBuffer, DeviceError>;
/// Reclaim all resources belonging to this `CommandEncoder`.
///
@@ -525,22 +552,26 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
/// [`CommandBuffer`]: Api::CommandBuffer
unsafe fn reset_all<I>(&mut self, command_buffers: I)
where
- I: Iterator<Item = A::CommandBuffer>;
+ I: Iterator<Item = <Self::A as Api>::CommandBuffer>;
unsafe fn transition_buffers<'a, T>(&mut self, barriers: T)
where
- T: Iterator<Item = BufferBarrier<'a, A>>;
+ T: Iterator<Item = BufferBarrier<'a, Self::A>>;
unsafe fn transition_textures<'a, T>(&mut self, barriers: T)
where
- T: Iterator<Item = TextureBarrier<'a, A>>;
+ T: Iterator<Item = TextureBarrier<'a, Self::A>>;
// copy operations
- unsafe fn clear_buffer(&mut self, buffer: &A::Buffer, range: MemoryRange);
+ unsafe fn clear_buffer(&mut self, buffer: &<Self::A as Api>::Buffer, range: MemoryRange);
- unsafe fn copy_buffer_to_buffer<T>(&mut self, src: &A::Buffer, dst: &A::Buffer, regions: T)
- where
+ unsafe fn copy_buffer_to_buffer<T>(
+ &mut self,
+ src: &<Self::A as Api>::Buffer,
+ dst: &<Self::A as Api>::Buffer,
+ regions: T,
+ ) where
T: Iterator<Item = BufferCopy>;
/// Copy from an external image to an internal texture.
@@ -551,7 +582,7 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
unsafe fn copy_external_image_to_texture<T>(
&mut self,
src: &wgt::ImageCopyExternalImage,
- dst: &A::Texture,
+ dst: &<Self::A as Api>::Texture,
dst_premultiplication: bool,
regions: T,
) where
@@ -563,9 +594,9 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
/// Note: the copy extent is in physical size (rounded to the block size)
unsafe fn copy_texture_to_texture<T>(
&mut self,
- src: &A::Texture,
+ src: &<Self::A as Api>::Texture,
src_usage: TextureUses,
- dst: &A::Texture,
+ dst: &<Self::A as Api>::Texture,
regions: T,
) where
T: Iterator<Item = TextureCopy>;
@@ -574,8 +605,12 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
/// Works with a single array layer.
/// Note: `dst` current usage has to be `TextureUses::COPY_DST`.
/// Note: the copy extent is in physical size (rounded to the block size)
- unsafe fn copy_buffer_to_texture<T>(&mut self, src: &A::Buffer, dst: &A::Texture, regions: T)
- where
+ unsafe fn copy_buffer_to_texture<T>(
+ &mut self,
+ src: &<Self::A as Api>::Buffer,
+ dst: &<Self::A as Api>::Texture,
+ regions: T,
+ ) where
T: Iterator<Item = BufferTextureCopy>;
/// Copy from texture to buffer.
@@ -583,9 +618,9 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
/// Note: the copy extent is in physical size (rounded to the block size)
unsafe fn copy_texture_to_buffer<T>(
&mut self,
- src: &A::Texture,
+ src: &<Self::A as Api>::Texture,
src_usage: TextureUses,
- dst: &A::Buffer,
+ dst: &<Self::A as Api>::Buffer,
regions: T,
) where
T: Iterator<Item = BufferTextureCopy>;
@@ -596,9 +631,9 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
/// of all the preceding groups to be taken from `layout`.
unsafe fn set_bind_group(
&mut self,
- layout: &A::PipelineLayout,
+ layout: &<Self::A as Api>::PipelineLayout,
index: u32,
- group: &A::BindGroup,
+ group: &<Self::A as Api>::BindGroup,
dynamic_offsets: &[wgt::DynamicOffset],
);
@@ -612,7 +647,7 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
/// - The range of push constants written must be valid for the pipeline layout at draw time.
unsafe fn set_push_constants(
&mut self,
- layout: &A::PipelineLayout,
+ layout: &<Self::A as Api>::PipelineLayout,
stages: wgt::ShaderStages,
offset_bytes: u32,
data: &[u32],
@@ -627,18 +662,18 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
/// # Safety:
///
/// - If `set` is an occlusion query set, it must be the same one as used in the [`RenderPassDescriptor::occlusion_query_set`] parameter.
- unsafe fn begin_query(&mut self, set: &A::QuerySet, index: u32);
+ unsafe fn begin_query(&mut self, set: &<Self::A as Api>::QuerySet, index: u32);
/// # Safety:
///
/// - If `set` is an occlusion query set, it must be the same one as used in the [`RenderPassDescriptor::occlusion_query_set`] parameter.
- unsafe fn end_query(&mut self, set: &A::QuerySet, index: u32);
- unsafe fn write_timestamp(&mut self, set: &A::QuerySet, index: u32);
- unsafe fn reset_queries(&mut self, set: &A::QuerySet, range: Range<u32>);
+ unsafe fn end_query(&mut self, set: &<Self::A as Api>::QuerySet, index: u32);
+ unsafe fn write_timestamp(&mut self, set: &<Self::A as Api>::QuerySet, index: u32);
+ unsafe fn reset_queries(&mut self, set: &<Self::A as Api>::QuerySet, range: Range<u32>);
unsafe fn copy_query_results(
&mut self,
- set: &A::QuerySet,
+ set: &<Self::A as Api>::QuerySet,
range: Range<u32>,
- buffer: &A::Buffer,
+ buffer: &<Self::A as Api>::Buffer,
offset: wgt::BufferAddress,
stride: wgt::BufferSize,
);
@@ -646,17 +681,17 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
// render passes
// Begins a render pass, clears all active bindings.
- unsafe fn begin_render_pass(&mut self, desc: &RenderPassDescriptor<A>);
+ unsafe fn begin_render_pass(&mut self, desc: &RenderPassDescriptor<Self::A>);
unsafe fn end_render_pass(&mut self);
- unsafe fn set_render_pipeline(&mut self, pipeline: &A::RenderPipeline);
+ unsafe fn set_render_pipeline(&mut self, pipeline: &<Self::A as Api>::RenderPipeline);
unsafe fn set_index_buffer<'a>(
&mut self,
- binding: BufferBinding<'a, A>,
+ binding: BufferBinding<'a, Self::A>,
format: wgt::IndexFormat,
);
- unsafe fn set_vertex_buffer<'a>(&mut self, index: u32, binding: BufferBinding<'a, A>);
+ unsafe fn set_vertex_buffer<'a>(&mut self, index: u32, binding: BufferBinding<'a, Self::A>);
unsafe fn set_viewport(&mut self, rect: &Rect<f32>, depth_range: Range<f32>);
unsafe fn set_scissor_rect(&mut self, rect: &Rect<u32>);
unsafe fn set_stencil_reference(&mut self, value: u32);
@@ -679,29 +714,29 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
);
unsafe fn draw_indirect(
&mut self,
- buffer: &A::Buffer,
+ buffer: &<Self::A as Api>::Buffer,
offset: wgt::BufferAddress,
draw_count: u32,
);
unsafe fn draw_indexed_indirect(
&mut self,
- buffer: &A::Buffer,
+ buffer: &<Self::A as Api>::Buffer,
offset: wgt::BufferAddress,
draw_count: u32,
);
unsafe fn draw_indirect_count(
&mut self,
- buffer: &A::Buffer,
+ buffer: &<Self::A as Api>::Buffer,
offset: wgt::BufferAddress,
- count_buffer: &A::Buffer,
+ count_buffer: &<Self::A as Api>::Buffer,
count_offset: wgt::BufferAddress,
max_count: u32,
);
unsafe fn draw_indexed_indirect_count(
&mut self,
- buffer: &A::Buffer,
+ buffer: &<Self::A as Api>::Buffer,
offset: wgt::BufferAddress,
- count_buffer: &A::Buffer,
+ count_buffer: &<Self::A as Api>::Buffer,
count_offset: wgt::BufferAddress,
max_count: u32,
);
@@ -709,13 +744,17 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
// compute passes
// Begins a compute pass, clears all active bindings.
- unsafe fn begin_compute_pass(&mut self, desc: &ComputePassDescriptor<A>);
+ unsafe fn begin_compute_pass(&mut self, desc: &ComputePassDescriptor<Self::A>);
unsafe fn end_compute_pass(&mut self);
- unsafe fn set_compute_pipeline(&mut self, pipeline: &A::ComputePipeline);
+ unsafe fn set_compute_pipeline(&mut self, pipeline: &<Self::A as Api>::ComputePipeline);
unsafe fn dispatch(&mut self, count: [u32; 3]);
- unsafe fn dispatch_indirect(&mut self, buffer: &A::Buffer, offset: wgt::BufferAddress);
+ unsafe fn dispatch_indirect(
+ &mut self,
+ buffer: &<Self::A as Api>::Buffer,
+ offset: wgt::BufferAddress,
+ );
/// To get the required sizes for the buffer allocations use `get_acceleration_structure_build_sizes` per descriptor
/// All buffers must be synchronized externally
@@ -729,8 +768,8 @@ pub trait CommandEncoder<A: Api>: WasmNotSendSync + fmt::Debug {
descriptor_count: u32,
descriptors: T,
) where
- A: 'a,
- T: IntoIterator<Item = BuildAccelerationStructureDescriptor<'a, A>>;
+ Self::A: 'a,
+ T: IntoIterator<Item = BuildAccelerationStructureDescriptor<'a, Self::A>>;
unsafe fn place_acceleration_structure_barrier(
&mut self,
diff --git a/third_party/rust/wgpu-hal/src/metal/adapter.rs b/third_party/rust/wgpu-hal/src/metal/adapter.rs
index 9ec777b0f0..6211896838 100644
--- a/third_party/rust/wgpu-hal/src/metal/adapter.rs
+++ b/third_party/rust/wgpu-hal/src/metal/adapter.rs
@@ -18,7 +18,9 @@ impl super::Adapter {
}
}
-impl crate::Adapter<super::Api> for super::Adapter {
+impl crate::Adapter for super::Adapter {
+ type A = super::Api;
+
unsafe fn open(
&self,
features: wgt::Features,
diff --git a/third_party/rust/wgpu-hal/src/metal/command.rs b/third_party/rust/wgpu-hal/src/metal/command.rs
index 6f1a0d9c2f..341712c323 100644
--- a/third_party/rust/wgpu-hal/src/metal/command.rs
+++ b/third_party/rust/wgpu-hal/src/metal/command.rs
@@ -168,7 +168,9 @@ impl super::CommandState {
}
}
-impl crate::CommandEncoder<super::Api> for super::CommandEncoder {
+impl crate::CommandEncoder for super::CommandEncoder {
+ type A = super::Api;
+
unsafe fn begin_encoding(&mut self, label: crate::Label) -> Result<(), crate::DeviceError> {
let queue = &self.raw_queue.lock();
let retain_references = self.shared.settings.retain_command_buffer_references;
diff --git a/third_party/rust/wgpu-hal/src/metal/device.rs b/third_party/rust/wgpu-hal/src/metal/device.rs
index d7fd06c8f3..179429f5d7 100644
--- a/third_party/rust/wgpu-hal/src/metal/device.rs
+++ b/third_party/rust/wgpu-hal/src/metal/device.rs
@@ -273,7 +273,9 @@ impl super::Device {
}
}
-impl crate::Device<super::Api> for super::Device {
+impl crate::Device for super::Device {
+ type A = super::Api;
+
unsafe fn exit(self, _queue: super::Queue) {}
unsafe fn create_buffer(&self, desc: &crate::BufferDescriptor) -> DeviceResult<super::Buffer> {
@@ -706,7 +708,16 @@ impl crate::Device<super::Api> for super::Device {
for (&stage, counter) in super::NAGA_STAGES.iter().zip(bg.counters.iter_mut()) {
let stage_bit = map_naga_stage(stage);
let mut dynamic_offsets_count = 0u32;
- for (entry, layout) in desc.entries.iter().zip(desc.layout.entries.iter()) {
+ let layout_and_entry_iter = desc.entries.iter().map(|entry| {
+ let layout = desc
+ .layout
+ .entries
+ .iter()
+ .find(|layout_entry| layout_entry.binding == entry.binding)
+ .expect("internal error: no layout entry found with binding slot");
+ (entry, layout)
+ });
+ for (entry, layout) in layout_and_entry_iter {
let size = layout.count.map_or(1, |c| c.get());
if let wgt::BindingType::Buffer {
has_dynamic_offset: true,
diff --git a/third_party/rust/wgpu-hal/src/metal/mod.rs b/third_party/rust/wgpu-hal/src/metal/mod.rs
index 62fbf3d49d..6aeafb0f86 100644
--- a/third_party/rust/wgpu-hal/src/metal/mod.rs
+++ b/third_party/rust/wgpu-hal/src/metal/mod.rs
@@ -80,7 +80,9 @@ impl Instance {
}
}
-impl crate::Instance<Api> for Instance {
+impl crate::Instance for Instance {
+ type A = Api;
+
unsafe fn init(_desc: &crate::InstanceDescriptor) -> Result<Self, crate::InstanceError> {
profiling::scope!("Init Metal Backend");
// We do not enable metal validation based on the validation flags as it affects the entire
@@ -365,7 +367,9 @@ impl std::borrow::Borrow<Texture> for SurfaceTexture {
unsafe impl Send for SurfaceTexture {}
unsafe impl Sync for SurfaceTexture {}
-impl crate::Queue<Api> for Queue {
+impl crate::Queue for Queue {
+ type A = Api;
+
unsafe fn submit(
&self,
command_buffers: &[&CommandBuffer],
diff --git a/third_party/rust/wgpu-hal/src/metal/surface.rs b/third_party/rust/wgpu-hal/src/metal/surface.rs
index a97eff0aae..889e319493 100644
--- a/third_party/rust/wgpu-hal/src/metal/surface.rs
+++ b/third_party/rust/wgpu-hal/src/metal/surface.rs
@@ -169,7 +169,9 @@ impl super::Surface {
}
}
-impl crate::Surface<super::Api> for super::Surface {
+impl crate::Surface for super::Surface {
+ type A = super::Api;
+
unsafe fn configure(
&self,
device: &super::Device,
diff --git a/third_party/rust/wgpu-hal/src/vulkan/adapter.rs b/third_party/rust/wgpu-hal/src/vulkan/adapter.rs
index 83b3dfa8e5..2665463792 100644
--- a/third_party/rust/wgpu-hal/src/vulkan/adapter.rs
+++ b/third_party/rust/wgpu-hal/src/vulkan/adapter.rs
@@ -20,25 +20,85 @@ fn indexing_features() -> wgt::Features {
| wgt::Features::PARTIALLY_BOUND_BINDING_ARRAY
}
-/// Aggregate of the `vk::PhysicalDevice*Features` structs used by `gfx`.
+/// Features supported by a [`vk::PhysicalDevice`] and its extensions.
+///
+/// This is used in two phases:
+///
+/// - When enumerating adapters, this represents the features offered by the
+/// adapter. [`Instance::expose_adapter`] calls `vkGetPhysicalDeviceFeatures2`
+/// (or `vkGetPhysicalDeviceFeatures` if that is not available) to collect
+/// this information about the `VkPhysicalDevice` represented by the
+/// `wgpu_hal::ExposedAdapter`.
+///
+/// - When opening a device, this represents the features we would like to
+/// enable. At `wgpu_hal::Device` construction time,
+/// [`PhysicalDeviceFeatures::from_extensions_and_requested_features`]
+/// constructs an value of this type indicating which Vulkan features to
+/// enable, based on the `wgpu_types::Features` requested.
#[derive(Debug, Default)]
pub struct PhysicalDeviceFeatures {
+ /// Basic Vulkan 1.0 features.
core: vk::PhysicalDeviceFeatures,
+
+ /// Features provided by `VK_EXT_descriptor_indexing`, promoted to Vulkan 1.2.
pub(super) descriptor_indexing: Option<vk::PhysicalDeviceDescriptorIndexingFeaturesEXT>,
+
+ /// Features provided by `VK_KHR_imageless_framebuffer`, promoted to Vulkan 1.2.
imageless_framebuffer: Option<vk::PhysicalDeviceImagelessFramebufferFeaturesKHR>,
+
+ /// Features provided by `VK_KHR_timeline_semaphore`, promoted to Vulkan 1.2
timeline_semaphore: Option<vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR>,
+
+ /// Features provided by `VK_EXT_image_robustness`, promoted to Vulkan 1.3
image_robustness: Option<vk::PhysicalDeviceImageRobustnessFeaturesEXT>,
+
+ /// Features provided by `VK_EXT_robustness2`.
robustness2: Option<vk::PhysicalDeviceRobustness2FeaturesEXT>,
+
+ /// Features provided by `VK_KHR_multiview`, promoted to Vulkan 1.1.
multiview: Option<vk::PhysicalDeviceMultiviewFeaturesKHR>,
+
+ /// Features provided by `VK_KHR_sampler_ycbcr_conversion`, promoted to Vulkan 1.1.
sampler_ycbcr_conversion: Option<vk::PhysicalDeviceSamplerYcbcrConversionFeatures>,
+
+ /// Features provided by `VK_EXT_texture_compression_astc_hdr`, promoted to Vulkan 1.3.
astc_hdr: Option<vk::PhysicalDeviceTextureCompressionASTCHDRFeaturesEXT>,
+
+ /// Features provided by `VK_KHR_shader_float16_int8` (promoted to Vulkan
+ /// 1.2) and `VK_KHR_16bit_storage` (promoted to Vulkan 1.1). We use these
+ /// features together, or not at all.
shader_float16: Option<(
vk::PhysicalDeviceShaderFloat16Int8Features,
vk::PhysicalDevice16BitStorageFeatures,
)>,
+
+ /// Features provided by `VK_KHR_acceleration_structure`.
acceleration_structure: Option<vk::PhysicalDeviceAccelerationStructureFeaturesKHR>,
+
+ /// Features provided by `VK_KHR_buffer_device_address`, promoted to Vulkan 1.2.
+ ///
+ /// We only use this feature for
+ /// [`Features::RAY_TRACING_ACCELERATION_STRUCTURE`], which requires
+ /// `VK_KHR_acceleration_structure`, which depends on
+ /// `VK_KHR_buffer_device_address`, so [`Instance::expose_adapter`] only
+ /// bothers to check if `VK_KHR_acceleration_structure` is available,
+ /// leaving this `None`.
+ ///
+ /// However, we do populate this when creating a device if
+ /// [`Features::RAY_TRACING_ACCELERATION_STRUCTURE`] is requested.
buffer_device_address: Option<vk::PhysicalDeviceBufferDeviceAddressFeaturesKHR>,
+
+ /// Features provided by `VK_KHR_ray_query`,
+ ///
+ /// Vulkan requires that the feature be present if the `VK_KHR_ray_query`
+ /// extension is present, so [`Instance::expose_adapter`] doesn't bother retrieving
+ /// this from `vkGetPhysicalDeviceFeatures2`.
+ ///
+ /// However, we do populate this when creating a device if ray tracing is requested.
ray_query: Option<vk::PhysicalDeviceRayQueryFeaturesKHR>,
+
+ /// Features provided by `VK_KHR_zero_initialize_workgroup_memory`, promoted
+ /// to Vulkan 1.3.
zero_initialize_workgroup_memory:
Option<vk::PhysicalDeviceZeroInitializeWorkgroupMemoryFeatures>,
}
@@ -91,9 +151,32 @@ impl PhysicalDeviceFeatures {
info
}
- /// Create a `PhysicalDeviceFeatures` that will be used to create a logical device.
+ /// Create a `PhysicalDeviceFeatures` that can be used to create a logical
+ /// device.
///
- /// `requested_features` should be the same as what was used to generate `enabled_extensions`.
+ /// Return a `PhysicalDeviceFeatures` value capturing all the Vulkan
+ /// features needed for the given [`Features`], [`DownlevelFlags`], and
+ /// [`PrivateCapabilities`]. You can use the returned value's
+ /// [`add_to_device_create_builder`] method to configure a
+ /// [`DeviceCreateInfoBuilder`] to build a logical device providing those
+ /// features.
+ ///
+ /// To ensure that the returned value is able to select all the Vulkan
+ /// features needed to express `requested_features`, `downlevel_flags`, and
+ /// `private_caps`:
+ ///
+ /// - The given `enabled_extensions` set must include all the extensions
+ /// selected by [`Adapter::required_device_extensions`] when passed
+ /// `features`.
+ ///
+ /// - The given `device_api_version` must be the Vulkan API version of the
+ /// physical device we will use to create the logical device.
+ ///
+ /// [`Features`]: wgt::Features
+ /// [`DownlevelFlags`]: wgt::DownlevelFlags
+ /// [`PrivateCapabilities`]: super::PrivateCapabilities
+ /// [`DeviceCreateInfoBuilder`]: vk::DeviceCreateInfoBuilder
+ /// [`Adapter::required_device_extensions`]: super::Adapter::required_device_extensions
fn from_extensions_and_requested_features(
device_api_version: u32,
enabled_extensions: &[&'static CStr],
@@ -354,11 +437,16 @@ impl PhysicalDeviceFeatures {
}
}
+ /// Compute the wgpu [`Features`] and [`DownlevelFlags`] supported by a physical device.
+ ///
+ /// Given `self`, together with the instance and physical device it was
+ /// built from, and a `caps` also built from those, determine which wgpu
+ /// features and downlevel flags the device can support.
fn to_wgpu(
&self,
instance: &ash::Instance,
phd: vk::PhysicalDevice,
- caps: &PhysicalDeviceCapabilities,
+ caps: &PhysicalDeviceProperties,
) -> (wgt::Features, wgt::DownlevelFlags) {
use crate::auxil::db;
use wgt::{DownlevelFlags as Df, Features as F};
@@ -639,15 +727,52 @@ impl PhysicalDeviceFeatures {
}
}
-/// Information gathered about a physical device capabilities.
+/// Vulkan "properties" structures gathered about a physical device.
+///
+/// This structure holds the properties of a [`vk::PhysicalDevice`]:
+/// - the standard Vulkan device properties
+/// - the `VkExtensionProperties` structs for all available extensions, and
+/// - the per-extension properties structures for the available extensions that
+/// `wgpu` cares about.
+///
+/// Generally, if you get it from any of these functions, it's stored
+/// here:
+/// - `vkEnumerateDeviceExtensionProperties`
+/// - `vkGetPhysicalDeviceProperties`
+/// - `vkGetPhysicalDeviceProperties2`
+///
+/// This also includes a copy of the device API version, since we can
+/// use that as a shortcut for searching for an extension, if the
+/// extension has been promoted to core in the current version.
+///
+/// This does not include device features; for those, see
+/// [`PhysicalDeviceFeatures`].
#[derive(Default, Debug)]
-pub struct PhysicalDeviceCapabilities {
+pub struct PhysicalDeviceProperties {
+ /// Extensions supported by the `vk::PhysicalDevice`,
+ /// as returned by `vkEnumerateDeviceExtensionProperties`.
supported_extensions: Vec<vk::ExtensionProperties>,
+
+ /// Properties of the `vk::PhysicalDevice`, as returned by
+ /// `vkGetPhysicalDeviceProperties`.
properties: vk::PhysicalDeviceProperties,
+
+ /// Additional `vk::PhysicalDevice` properties from the
+ /// `VK_KHR_maintenance3` extension, promoted to Vulkan 1.1.
maintenance_3: Option<vk::PhysicalDeviceMaintenance3Properties>,
+
+ /// Additional `vk::PhysicalDevice` properties from the
+ /// `VK_EXT_descriptor_indexing` extension, promoted to Vulkan 1.2.
descriptor_indexing: Option<vk::PhysicalDeviceDescriptorIndexingPropertiesEXT>,
+
+ /// Additional `vk::PhysicalDevice` properties from the
+ /// `VK_KHR_acceleration_structure` extension.
acceleration_structure: Option<vk::PhysicalDeviceAccelerationStructurePropertiesKHR>,
+
+ /// Additional `vk::PhysicalDevice` properties from the
+ /// `VK_KHR_driver_properties` extension, promoted to Vulkan 1.2.
driver: Option<vk::PhysicalDeviceDriverPropertiesKHR>,
+
/// The device API version.
///
/// Which is the version of Vulkan supported for device-level functionality.
@@ -657,10 +782,10 @@ pub struct PhysicalDeviceCapabilities {
}
// This is safe because the structs have `p_next: *mut c_void`, which we null out/never read.
-unsafe impl Send for PhysicalDeviceCapabilities {}
-unsafe impl Sync for PhysicalDeviceCapabilities {}
+unsafe impl Send for PhysicalDeviceProperties {}
+unsafe impl Sync for PhysicalDeviceProperties {}
-impl PhysicalDeviceCapabilities {
+impl PhysicalDeviceProperties {
pub fn properties(&self) -> vk::PhysicalDeviceProperties {
self.properties
}
@@ -899,9 +1024,9 @@ impl super::InstanceShared {
fn inspect(
&self,
phd: vk::PhysicalDevice,
- ) -> (PhysicalDeviceCapabilities, PhysicalDeviceFeatures) {
+ ) -> (PhysicalDeviceProperties, PhysicalDeviceFeatures) {
let capabilities = {
- let mut capabilities = PhysicalDeviceCapabilities::default();
+ let mut capabilities = PhysicalDeviceProperties::default();
capabilities.supported_extensions =
unsafe { self.raw.enumerate_device_extension_properties(phd).unwrap() };
capabilities.properties = unsafe { self.raw.get_physical_device_properties(phd) };
@@ -923,9 +1048,10 @@ impl super::InstanceShared {
let mut builder = vk::PhysicalDeviceProperties2KHR::builder();
if supports_maintenance3 {
- capabilities.maintenance_3 =
- Some(vk::PhysicalDeviceMaintenance3Properties::default());
- builder = builder.push_next(capabilities.maintenance_3.as_mut().unwrap());
+ let next = capabilities
+ .maintenance_3
+ .insert(vk::PhysicalDeviceMaintenance3Properties::default());
+ builder = builder.push_next(next);
}
if supports_descriptor_indexing {
@@ -1001,7 +1127,8 @@ impl super::InstanceShared {
builder = builder.push_next(next);
}
- // `VK_KHR_imageless_framebuffer` is promoted to 1.2, but has no changes, so we can keep using the extension unconditionally.
+ // `VK_KHR_imageless_framebuffer` is promoted to 1.2, but has no
+ // changes, so we can keep using the extension unconditionally.
if capabilities.supports_extension(vk::KhrImagelessFramebufferFn::name()) {
let next = features
.imageless_framebuffer
@@ -1009,7 +1136,8 @@ impl super::InstanceShared {
builder = builder.push_next(next);
}
- // `VK_KHR_timeline_semaphore` is promoted to 1.2, but has no changes, so we can keep using the extension unconditionally.
+ // `VK_KHR_timeline_semaphore` is promoted to 1.2, but has no
+ // changes, so we can keep using the extension unconditionally.
if capabilities.supports_extension(vk::KhrTimelineSemaphoreFn::name()) {
let next = features
.timeline_semaphore
@@ -1295,7 +1423,7 @@ impl super::Adapter {
self.raw
}
- pub fn physical_device_capabilities(&self) -> &PhysicalDeviceCapabilities {
+ pub fn physical_device_capabilities(&self) -> &PhysicalDeviceProperties {
&self.phd_capabilities
}
@@ -1320,7 +1448,20 @@ impl super::Adapter {
supported_extensions
}
- /// `features` must be the same features used to create `enabled_extensions`.
+ /// Create a `PhysicalDeviceFeatures` for opening a logical device with
+ /// `features` from this adapter.
+ ///
+ /// The given `enabled_extensions` set must include all the extensions
+ /// selected by [`required_device_extensions`] when passed `features`.
+ /// Otherwise, the `PhysicalDeviceFeatures` value may not be able to select
+ /// all the Vulkan features needed to represent `features` and this
+ /// adapter's characteristics.
+ ///
+ /// Typically, you'd simply call `required_device_extensions`, and then pass
+ /// its return value and the feature set you gave it directly to this
+ /// function. But it's fine to add more extensions to the list.
+ ///
+ /// [`required_device_extensions`]: Self::required_device_extensions
pub fn physical_device_features(
&self,
enabled_extensions: &[&'static CStr],
@@ -1607,7 +1748,9 @@ impl super::Adapter {
}
}
-impl crate::Adapter<super::Api> for super::Adapter {
+impl crate::Adapter for super::Adapter {
+ type A = super::Api;
+
unsafe fn open(
&self,
features: wgt::Features,
diff --git a/third_party/rust/wgpu-hal/src/vulkan/command.rs b/third_party/rust/wgpu-hal/src/vulkan/command.rs
index 42ea907738..43a2471954 100644
--- a/third_party/rust/wgpu-hal/src/vulkan/command.rs
+++ b/third_party/rust/wgpu-hal/src/vulkan/command.rs
@@ -60,7 +60,9 @@ impl super::CommandEncoder {
}
}
-impl crate::CommandEncoder<super::Api> for super::CommandEncoder {
+impl crate::CommandEncoder for super::CommandEncoder {
+ type A = super::Api;
+
unsafe fn begin_encoding(&mut self, label: crate::Label) -> Result<(), crate::DeviceError> {
if self.free.is_empty() {
let vk_info = vk::CommandBufferAllocateInfo::builder()
diff --git a/third_party/rust/wgpu-hal/src/vulkan/device.rs b/third_party/rust/wgpu-hal/src/vulkan/device.rs
index c00c3d1d43..70028cc700 100644
--- a/third_party/rust/wgpu-hal/src/vulkan/device.rs
+++ b/third_party/rust/wgpu-hal/src/vulkan/device.rs
@@ -830,7 +830,9 @@ impl super::Device {
}
}
-impl crate::Device<super::Api> for super::Device {
+impl crate::Device for super::Device {
+ type A = super::Api;
+
unsafe fn exit(self, queue: super::Queue) {
unsafe { self.mem_allocator.into_inner().cleanup(&*self.shared) };
unsafe { self.desc_allocator.into_inner().cleanup(&*self.shared) };
diff --git a/third_party/rust/wgpu-hal/src/vulkan/instance.rs b/third_party/rust/wgpu-hal/src/vulkan/instance.rs
index 771938b0b0..a0d29a13a3 100644
--- a/third_party/rust/wgpu-hal/src/vulkan/instance.rs
+++ b/third_party/rust/wgpu-hal/src/vulkan/instance.rs
@@ -579,7 +579,9 @@ impl Drop for super::InstanceShared {
}
}
-impl crate::Instance<super::Api> for super::Instance {
+impl crate::Instance for super::Instance {
+ type A = super::Api;
+
unsafe fn init(desc: &crate::InstanceDescriptor) -> Result<Self, crate::InstanceError> {
profiling::scope!("Init Vulkan Backend");
use crate::auxil::cstr_from_bytes_until_nul;
@@ -956,7 +958,9 @@ impl crate::Instance<super::Api> for super::Instance {
}
}
-impl crate::Surface<super::Api> for super::Surface {
+impl crate::Surface for super::Surface {
+ type A = super::Api;
+
unsafe fn configure(
&self,
device: &super::Device,
diff --git a/third_party/rust/wgpu-hal/src/vulkan/mod.rs b/third_party/rust/wgpu-hal/src/vulkan/mod.rs
index 1f922e83da..0cd385045c 100644
--- a/third_party/rust/wgpu-hal/src/vulkan/mod.rs
+++ b/third_party/rust/wgpu-hal/src/vulkan/mod.rs
@@ -189,7 +189,7 @@ pub struct Adapter {
instance: Arc<InstanceShared>,
//queue_families: Vec<vk::QueueFamilyProperties>,
known_memory_flags: vk::MemoryPropertyFlags,
- phd_capabilities: adapter::PhysicalDeviceCapabilities,
+ phd_capabilities: adapter::PhysicalDeviceProperties,
//phd_features: adapter::PhysicalDeviceFeatures,
downlevel_flags: wgt::DownlevelFlags,
private_caps: PrivateCapabilities,
@@ -594,7 +594,9 @@ impl Fence {
}
}
-impl crate::Queue<Api> for Queue {
+impl crate::Queue for Queue {
+ type A = Api;
+
unsafe fn submit(
&self,
command_buffers: &[&CommandBuffer],
diff --git a/third_party/rust/wgpu-types/.cargo-checksum.json b/third_party/rust/wgpu-types/.cargo-checksum.json
index dea747bd18..928cdbab21 100644
--- a/third_party/rust/wgpu-types/.cargo-checksum.json
+++ b/third_party/rust/wgpu-types/.cargo-checksum.json
@@ -1 +1 @@
-{"files":{"Cargo.toml":"c536ec4d70291834fb3e6bcd6f03900bb3a651eda9449e7adf03ef2611be96a9","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","src/assertions.rs":"3fe98027aa73970c8ab7874a3e13dbfd6faa87df2081beb5c83aeec4c60f372f","src/lib.rs":"bafa964caee2fdc6fc2adbb1dea540d5575e29c45946bc51e9912bfbdb13a352","src/math.rs":"4d03039736dd6926feb139bc68734cb59df34ede310427bbf059e5c925e0af3b"},"package":null} \ No newline at end of file
+{"files":{"Cargo.toml":"0bcb9c2d557d01677740fea4690c79544898fe749880a72512dca33db171e590","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","src/assertions.rs":"3fe98027aa73970c8ab7874a3e13dbfd6faa87df2081beb5c83aeec4c60f372f","src/lib.rs":"b9fda00d1b61364cdd7e544aee731ce075ea8403349ed286711d39aa8d414c28","src/math.rs":"4d03039736dd6926feb139bc68734cb59df34ede310427bbf059e5c925e0af3b"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/wgpu-types/Cargo.toml b/third_party/rust/wgpu-types/Cargo.toml
index 792b03dcc8..c6f8b3002d 100644
--- a/third_party/rust/wgpu-types/Cargo.toml
+++ b/third_party/rust/wgpu-types/Cargo.toml
@@ -11,7 +11,7 @@
[package]
edition = "2021"
-rust-version = "1.70"
+rust-version = "1.74"
name = "wgpu-types"
version = "0.19.0"
authors = ["gfx-rs developers"]
@@ -56,10 +56,10 @@ fragile-send-sync-non-atomic-wasm = []
strict_asserts = []
[target."cfg(target_arch = \"wasm32\")".dependencies]
-js-sys = "0.3.67"
+js-sys = "0.3.69"
[target."cfg(target_arch = \"wasm32\")".dependencies.web-sys]
-version = "0.3.67"
+version = "0.3.69"
features = [
"ImageBitmap",
"HtmlVideoElement",
diff --git a/third_party/rust/wgpu-types/src/lib.rs b/third_party/rust/wgpu-types/src/lib.rs
index 347aad76f9..b36801e941 100644
--- a/third_party/rust/wgpu-types/src/lib.rs
+++ b/third_party/rust/wgpu-types/src/lib.rs
@@ -337,7 +337,7 @@ bitflags::bitflags! {
/// For arbitrary timestamp write commands on encoders refer to [`Features::TIMESTAMP_QUERY_INSIDE_ENCODERS`].
/// For arbitrary timestamp write commands on passes refer to [`Features::TIMESTAMP_QUERY_INSIDE_PASSES`].
///
- /// They must be resolved using [`CommandEncoder::resolve_query_sets`] into a buffer,
+ /// They must be resolved using [`CommandEncoder::resolve_query_set`] into a buffer,
/// then the result must be multiplied by the timestamp period [`Queue::get_timestamp_period`]
/// to get the timestamp in nanoseconds. Multiple timestamps can then be diffed to get the
/// time for operations between them to finish.
@@ -480,10 +480,10 @@ bitflags::bitflags! {
// API:
/// Enables use of Pipeline Statistics Queries. These queries tell the count of various operations
- /// performed between the start and stop call. Call [`RenderPassEncoder::begin_pipeline_statistics_query`] to start
- /// a query, then call [`RenderPassEncoder::end_pipeline_statistics_query`] to stop one.
+ /// performed between the start and stop call. Call [`RenderPass::begin_pipeline_statistics_query`] to start
+ /// a query, then call [`RenderPass::end_pipeline_statistics_query`] to stop one.
///
- /// They must be resolved using [`CommandEncoder::resolve_query_sets`] into a buffer.
+ /// They must be resolved using [`CommandEncoder::resolve_query_set`] into a buffer.
/// The rules on how these resolve into buffers are detailed in the documentation for [`PipelineStatisticsTypes`].
///
/// Supported Platforms:
@@ -511,8 +511,8 @@ bitflags::bitflags! {
/// Implies [`Features::TIMESTAMP_QUERY`] & [`Features::TIMESTAMP_QUERY_INSIDE_ENCODERS`] is supported.
///
/// Additionally allows for timestamp queries to be used inside render & compute passes using:
- /// - [`RenderPassEncoder::write_timestamp`]
- /// - [`ComputePassEncoder::write_timestamp`]
+ /// - [`RenderPass::write_timestamp`]
+ /// - [`ComputePass::write_timestamp`]
///
/// Supported platforms:
/// - Vulkan
@@ -682,7 +682,14 @@ bitflags::bitflags! {
/// Allows the user to call [`RenderPass::set_push_constants`], provide a non-empty array
/// to [`PipelineLayoutDescriptor`], and provide a non-zero limit to [`Limits::max_push_constant_size`].
///
- /// A block of push constants can be declared with `layout(push_constant) uniform Name {..}` in shaders.
+ /// A block of push constants can be declared in WGSL with `var<push_constant>`:
+ ///
+ /// ```rust,ignore
+ /// struct PushConstants { example: f32, }
+ /// var<push_constant> c: PushConstants;
+ /// ```
+ ///
+ /// In GLSL, this corresponds to `layout(push_constant) uniform Name {..}`.
///
/// Supported platforms:
/// - DX12
@@ -7183,7 +7190,7 @@ mod send_sync {
///
/// Corresponds to [WebGPU `GPUDeviceLostReason`](https://gpuweb.github.io/gpuweb/#enumdef-gpudevicelostreason).
#[repr(u8)]
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum DeviceLostReason {
/// Triggered by driver
Unknown = 0,
@@ -7203,4 +7210,10 @@ pub enum DeviceLostReason {
/// exactly once before it is dropped, which helps with managing the
/// memory owned by the callback.
ReplacedCallback = 3,
+ /// When setting the callback, but the device is already invalid
+ ///
+ /// As above, when the callback is provided, wgpu guarantees that it
+ /// will eventually be called. If the device is already invalid, wgpu
+ /// will call the callback immediately, with this reason.
+ DeviceInvalid = 4,
}
diff --git a/third_party/rust/zip/src/read.rs b/third_party/rust/zip/src/read.rs
index dad20c260b..3f3f41010c 100644
--- a/third_party/rust/zip/src/read.rs
+++ b/third_party/rust/zip/src/read.rs
@@ -334,13 +334,10 @@ impl<R: Read + io::Seek> ZipArchive<R> {
// offsets all being too small. Get the amount of error by comparing
// the actual file position we found the CDE at with the offset
// recorded in the CDE.
- let archive_offset = cde_start_pos
- .checked_sub(footer.central_directory_size as u64)
- .and_then(|x| x.checked_sub(footer.central_directory_offset as u64))
- .ok_or(ZipError::InvalidArchive(
- "Invalid central directory size or offset",
- ))?;
+ // Bug 1895599: omnijars nor other zips we read have data prepended to them; trust
+ // the offsets!
+ let archive_offset = 0;
let directory_start = footer.central_directory_offset as u64 + archive_offset;
let number_of_files = footer.number_of_files_on_this_disk as usize;
Ok((archive_offset, directory_start, number_of_files))
diff --git a/third_party/rust/zip/src/types.rs b/third_party/rust/zip/src/types.rs
index ad3a5700b2..896c9a6ff5 100644
--- a/third_party/rust/zip/src/types.rs
+++ b/third_party/rust/zip/src/types.rs
@@ -7,8 +7,6 @@ use std::convert::{TryFrom, TryInto};
target_arch = "powerpc"
)))]
use std::sync::atomic;
-#[cfg(not(feature = "time"))]
-use std::time::SystemTime;
#[cfg(doc)]
use {crate::read::ZipFile, crate::write::FileOptions};
diff --git a/third_party/rust/zip/src/write.rs b/third_party/rust/zip/src/write.rs
index 14252b4d59..214aafecd0 100644
--- a/third_party/rust/zip/src/write.rs
+++ b/third_party/rust/zip/src/write.rs
@@ -7,6 +7,7 @@ use crate::spec;
use crate::types::{AtomicU64, DateTime, System, ZipFileData, DEFAULT_VERSION};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use crc32fast::Hasher;
+#[cfg(feature = "time")]
use std::convert::TryInto;
use std::default::Default;
use std::io;
diff --git a/third_party/xsimd/include/xsimd/config/xsimd_arch.hpp b/third_party/xsimd/include/xsimd/config/xsimd_arch.hpp
index ea48aa057d..ac51daca7b 100644
--- a/third_party/xsimd/include/xsimd/config/xsimd_arch.hpp
+++ b/third_party/xsimd/include/xsimd/config/xsimd_arch.hpp
@@ -33,7 +33,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return false; }
static constexpr bool available() noexcept { return false; }
- static constexpr unsigned version() noexcept { return 0; }
static constexpr std::size_t alignment() noexcept { return 0; }
static constexpr bool requires_alignment() noexcept { return false; }
static constexpr char const* name() noexcept { return "<none>"; }
@@ -57,26 +56,6 @@ namespace xsimd
{
};
- template <unsigned... Vals>
- struct is_sorted;
-
- template <>
- struct is_sorted<> : std::true_type
- {
- };
-
- template <unsigned Val>
- struct is_sorted<Val> : std::true_type
- {
- };
-
- template <unsigned V0, unsigned V1, unsigned... Vals>
- struct is_sorted<V0, V1, Vals...>
- : std::conditional<(V0 >= V1), is_sorted<V1, Vals...>,
- std::false_type>::type
- {
- };
-
template <typename T>
inline constexpr T max_of(T value) noexcept
{
@@ -106,15 +85,10 @@ namespace xsimd
} // namespace detail
- // An arch_list is a list of architectures, sorted by version number.
+ // An arch_list is a list of architectures.
template <class... Archs>
struct arch_list
{
-#ifndef NDEBUG
- static_assert(detail::is_sorted<Archs::version()...>::value,
- "architecture list must be sorted by version");
-#endif
-
using best = typename detail::head<Archs...>::type;
template <class Arch>
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avx2_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avx2_register.hpp
index cd10383e2b..264b7c3eda 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avx2_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avx2_register.hpp
@@ -25,7 +25,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVX2; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(2, 2, 0); }
static constexpr char const* name() noexcept { return "avx2"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avx512bw_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avx512bw_register.hpp
index 15c19832ae..9d4d33b64e 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avx512bw_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avx512bw_register.hpp
@@ -26,7 +26,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVX512BW; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(3, 4, 0); }
static constexpr char const* name() noexcept { return "avx512bw"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avx512cd_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avx512cd_register.hpp
index 29efca368c..cf06013955 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avx512cd_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avx512cd_register.hpp
@@ -26,7 +26,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVX512CD; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(3, 2, 0); }
static constexpr char const* name() noexcept { return "avx512cd"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avx512dq_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avx512dq_register.hpp
index 25a255ec15..f8a8dc5434 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avx512dq_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avx512dq_register.hpp
@@ -26,7 +26,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVX512DQ; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(3, 3, 0); }
static constexpr char const* name() noexcept { return "avx512dq"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avx512er_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avx512er_register.hpp
index a99157cf37..a52bd0064e 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avx512er_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avx512er_register.hpp
@@ -26,7 +26,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVX512ER; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(3, 3, 1); }
static constexpr char const* name() noexcept { return "avx512er"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avx512f_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avx512f_register.hpp
index c1f80a122d..1a11b6c92a 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avx512f_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avx512f_register.hpp
@@ -26,7 +26,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVX512F; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(3, 1, 0); }
static constexpr std::size_t alignment() noexcept { return 64; }
static constexpr bool requires_alignment() noexcept { return true; }
static constexpr char const* name() noexcept { return "avx512f"; }
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avx512ifma_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avx512ifma_register.hpp
index ba76ea147b..a8bc8885fb 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avx512ifma_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avx512ifma_register.hpp
@@ -26,7 +26,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVX512IFMA; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(3, 5, 0); }
static constexpr char const* name() noexcept { return "avx512ifma"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avx512pf_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avx512pf_register.hpp
index 38a10f0227..4838a8a461 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avx512pf_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avx512pf_register.hpp
@@ -26,7 +26,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVX512PF; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(3, 4, 1); }
static constexpr char const* name() noexcept { return "avx512pf"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avx512vbmi_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avx512vbmi_register.hpp
index 19ff744d72..40f51e9b19 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avx512vbmi_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avx512vbmi_register.hpp
@@ -26,7 +26,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVX512VBMI; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(3, 6, 0); }
static constexpr char const* name() noexcept { return "avx512vbmi"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avx512vnni_avx512bw_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avx512vnni_avx512bw_register.hpp
index 85edbdf230..a19b949f8b 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avx512vnni_avx512bw_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avx512vnni_avx512bw_register.hpp
@@ -29,7 +29,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVX512VNNI_AVX512BW; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(3, 4, 1); }
static constexpr char const* name() noexcept { return "avx512vnni+avx512bw"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avx512vnni_avx512vbmi_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avx512vnni_avx512vbmi_register.hpp
index 232b19a5cb..0a6b45f76c 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avx512vnni_avx512vbmi_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avx512vnni_avx512vbmi_register.hpp
@@ -29,7 +29,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVX512VNNI_AVX512VBMI; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(3, 6, 1); }
static constexpr char const* name() noexcept { return "avx512vnni+avx512vbmi"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avx_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avx_register.hpp
index 6b1951f964..7357304d5d 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avx_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avx_register.hpp
@@ -26,7 +26,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVX; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(2, 1, 0); }
static constexpr std::size_t alignment() noexcept { return 32; }
static constexpr bool requires_alignment() noexcept { return true; }
static constexpr char const* name() noexcept { return "avx"; }
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_avxvnni_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_avxvnni_register.hpp
index f68fe16bad..419547b1cf 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_avxvnni_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_avxvnni_register.hpp
@@ -25,7 +25,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_AVXVNNI; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(2, 3, 0); }
static constexpr char const* name() noexcept { return "avxvnni"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_batch.hpp b/third_party/xsimd/include/xsimd/types/xsimd_batch.hpp
index b4989fc88d..d9108823ab 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_batch.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_batch.hpp
@@ -1347,8 +1347,8 @@ namespace xsimd
template <class T, class A>
inline batch<std::complex<T>, A>& batch<std::complex<T>, A>::operator*=(batch const& other) noexcept
{
- real_batch new_real = real() * other.real() - imag() * other.imag();
- real_batch new_imag = real() * other.imag() + imag() * other.real();
+ real_batch new_real = fms(real(), other.real(), imag() * other.imag());
+ real_batch new_imag = fma(real(), other.imag(), imag() * other.real());
m_real = new_real;
m_imag = new_imag;
return *this;
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_fma3_avx2_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_fma3_avx2_register.hpp
index b9a5995414..cf3e26d08d 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_fma3_avx2_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_fma3_avx2_register.hpp
@@ -29,7 +29,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_FMA3_AVX2; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(2, 2, 1); }
static constexpr char const* name() noexcept { return "fma3+avx2"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_fma3_avx_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_fma3_avx_register.hpp
index ae10598f2c..5012d25a06 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_fma3_avx_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_fma3_avx_register.hpp
@@ -29,7 +29,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_FMA3_AVX; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(2, 1, 1); }
static constexpr char const* name() noexcept { return "fma3+avx"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_fma3_sse_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_fma3_sse_register.hpp
index a267490d66..87ebc27b55 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_fma3_sse_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_fma3_sse_register.hpp
@@ -29,7 +29,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_FMA3_SSE; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(1, 4, 3); }
static constexpr char const* name() noexcept { return "fma3+sse4.2"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_fma4_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_fma4_register.hpp
index 3684bbb401..1a066cd206 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_fma4_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_fma4_register.hpp
@@ -25,7 +25,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_FMA4; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(1, 4, 4); }
static constexpr char const* name() noexcept { return "fma4"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_generic_arch.hpp b/third_party/xsimd/include/xsimd/types/xsimd_generic_arch.hpp
index f4a2ca6aad..d16a37fea7 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_generic_arch.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_generic_arch.hpp
@@ -35,13 +35,8 @@ namespace xsimd
static constexpr std::size_t alignment() noexcept { return 0; }
/// Whether this architecture requires aligned memory access.
static constexpr bool requires_alignment() noexcept { return false; }
- /// Unique identifier for this architecture.
- static constexpr unsigned version() noexcept { return generic::version(0, 0, 0); }
/// Name of the architecture.
static constexpr char const* name() noexcept { return "generic"; }
-
- protected:
- static constexpr unsigned version(unsigned major, unsigned minor, unsigned patch, unsigned multiplier = 100u) noexcept { return major * multiplier * multiplier + minor * multiplier + patch; }
};
struct unsupported
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_i8mm_neon64_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_i8mm_neon64_register.hpp
index fc0c884d0b..0e2b42d8ea 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_i8mm_neon64_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_i8mm_neon64_register.hpp
@@ -29,7 +29,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_I8MM_NEON64; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(8, 2, 0); }
static constexpr char const* name() noexcept { return "i8mm+neon64"; }
};
@@ -39,6 +38,11 @@ namespace xsimd
XSIMD_DECLARE_SIMD_REGISTER_ALIAS(i8mm<neon64>, neon64);
+ template <class T>
+ struct get_bool_simd_register<T, i8mm<neon64>>
+ : detail::neon_bool_simd_register<T, i8mm<neon64>>
+ {
+ };
}
#endif
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_neon64_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_neon64_register.hpp
index 3aa8973b63..709f601a3f 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_neon64_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_neon64_register.hpp
@@ -27,7 +27,6 @@ namespace xsimd
static constexpr bool available() noexcept { return true; }
static constexpr bool requires_alignment() noexcept { return true; }
static constexpr std::size_t alignment() noexcept { return 16; }
- static constexpr unsigned version() noexcept { return generic::version(8, 1, 0); }
static constexpr char const* name() noexcept { return "arm64+neon"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_neon_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_neon_register.hpp
index 0ef4b381d3..a9f4a46c8b 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_neon_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_neon_register.hpp
@@ -32,7 +32,6 @@ namespace xsimd
static constexpr bool available() noexcept { return true; }
static constexpr bool requires_alignment() noexcept { return true; }
static constexpr std::size_t alignment() noexcept { return 16; }
- static constexpr unsigned version() noexcept { return generic::version(7, 0, 0); }
static constexpr char const* name() noexcept { return "arm32+neon"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_rvv_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_rvv_register.hpp
index bdc0ef3b87..ff03b6508a 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_rvv_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_rvv_register.hpp
@@ -37,7 +37,6 @@ namespace xsimd
static constexpr bool available() noexcept { return true; }
static constexpr bool requires_alignment() noexcept { return true; }
static constexpr std::size_t alignment() noexcept { return 16; }
- static constexpr unsigned version() noexcept { return generic::version(1, 0, 0, /*multiplier=*/1000); }
static constexpr char const* name() noexcept { return "riscv+rvv"; }
};
}
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_sse2_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_sse2_register.hpp
index a9dc8960b6..e6eabec7ad 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_sse2_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_sse2_register.hpp
@@ -32,7 +32,6 @@ namespace xsimd
static constexpr bool supported() noexcept { return XSIMD_WITH_SSE2; }
static constexpr bool available() noexcept { return true; }
static constexpr bool requires_alignment() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(1, 2, 0); }
static constexpr std::size_t alignment() noexcept { return 16; }
static constexpr char const* name() noexcept { return "sse2"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_sse3_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_sse3_register.hpp
index 1a7708a896..6f216bb812 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_sse3_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_sse3_register.hpp
@@ -29,7 +29,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_SSE3; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(1, 3, 0); }
static constexpr char const* name() noexcept { return "sse3"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_sse4_1_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_sse4_1_register.hpp
index d906712d56..f7f6c06575 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_sse4_1_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_sse4_1_register.hpp
@@ -29,7 +29,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_SSE4_1; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(1, 4, 1); }
static constexpr char const* name() noexcept { return "sse4.1"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_sse4_2_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_sse4_2_register.hpp
index b3446c9091..e92e498724 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_sse4_2_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_sse4_2_register.hpp
@@ -29,7 +29,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_SSE4_2; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(1, 4, 2); }
static constexpr char const* name() noexcept { return "sse4.2"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_ssse3_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_ssse3_register.hpp
index 50ffac1e06..fc1c0f82de 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_ssse3_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_ssse3_register.hpp
@@ -29,7 +29,6 @@ namespace xsimd
{
static constexpr bool supported() noexcept { return XSIMD_WITH_SSSE3; }
static constexpr bool available() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(1, 3, 1); }
static constexpr char const* name() noexcept { return "ssse3"; }
};
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_sve_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_sve_register.hpp
index 4f75c607e8..29564d0237 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_sve_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_sve_register.hpp
@@ -36,7 +36,6 @@ namespace xsimd
static constexpr bool available() noexcept { return true; }
static constexpr bool requires_alignment() noexcept { return true; }
static constexpr std::size_t alignment() noexcept { return 16; }
- static constexpr unsigned version() noexcept { return generic::version(9, Width / 32, 0); }
static constexpr char const* name() noexcept { return "arm64+sve"; }
};
}
diff --git a/third_party/xsimd/include/xsimd/types/xsimd_wasm_register.hpp b/third_party/xsimd/include/xsimd/types/xsimd_wasm_register.hpp
index 237db95c6e..a1b8403603 100644
--- a/third_party/xsimd/include/xsimd/types/xsimd_wasm_register.hpp
+++ b/third_party/xsimd/include/xsimd/types/xsimd_wasm_register.hpp
@@ -32,7 +32,6 @@ namespace xsimd
static constexpr bool supported() noexcept { return XSIMD_WITH_WASM; }
static constexpr bool available() noexcept { return true; }
static constexpr bool requires_alignment() noexcept { return true; }
- static constexpr unsigned version() noexcept { return generic::version(10, 0, 0); }
static constexpr std::size_t alignment() noexcept { return 16; }
static constexpr char const* name() noexcept { return "wasm"; }
};
diff --git a/third_party/xsimd/moz.yaml b/third_party/xsimd/moz.yaml
index 7bd3d2fd13..6385b68fa5 100644
--- a/third_party/xsimd/moz.yaml
+++ b/third_party/xsimd/moz.yaml
@@ -10,8 +10,8 @@ origin:
url: https://github.com/QuantStack/xsimd
- release: ce58d62666c315140eb54042498d93114edbaa68 (2024-02-27T16:05:37Z).
- revision: ce58d62666c315140eb54042498d93114edbaa68
+ release: 7080469620c2145fbedf4ef8950406066e1ca2d6 (2024-03-17T21:35:00Z).
+ revision: 7080469620c2145fbedf4ef8950406066e1ca2d6
license: BSD-3-Clause
diff --git a/third_party/zstd/COPYING b/third_party/zstd/COPYING
new file mode 100644
index 0000000000..ecbc059373
--- /dev/null
+++ b/third_party/zstd/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. \ No newline at end of file
diff --git a/third_party/zstd/LICENSE b/third_party/zstd/LICENSE
new file mode 100644
index 0000000000..75800288cc
--- /dev/null
+++ b/third_party/zstd/LICENSE
@@ -0,0 +1,30 @@
+BSD License
+
+For Zstandard software
+
+Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name Facebook, nor Meta, nor the names of its contributors may
+ be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/third_party/zstd/lib/.gitignore b/third_party/zstd/lib/.gitignore
new file mode 100644
index 0000000000..4cd50ac61e
--- /dev/null
+++ b/third_party/zstd/lib/.gitignore
@@ -0,0 +1,3 @@
+# make install artefact
+libzstd.pc
+libzstd-nomt
diff --git a/third_party/zstd/lib/BUCK b/third_party/zstd/lib/BUCK
new file mode 100644
index 0000000000..60c6bbb54d
--- /dev/null
+++ b/third_party/zstd/lib/BUCK
@@ -0,0 +1,232 @@
+cxx_library(
+ name='zstd',
+ header_namespace='',
+ exported_headers=['zstd.h'],
+ visibility=['PUBLIC'],
+ deps=[
+ ':common',
+ ':compress',
+ ':decompress',
+ ':deprecated',
+ ],
+)
+
+cxx_library(
+ name='compress',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('compress', 'zstd*.h'),
+ ]),
+ srcs=glob(['compress/zstd*.c', 'compress/hist.c']),
+ deps=[':common'],
+)
+
+cxx_library(
+ name='decompress',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ headers=subdir_glob([
+ ('decompress', '*_impl.h'),
+ ]),
+ srcs=glob(['decompress/zstd*.c']),
+ deps=[
+ ':common',
+ ':legacy',
+ ],
+)
+
+cxx_library(
+ name='deprecated',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('deprecated', '*.h'),
+ ]),
+ srcs=glob(['deprecated/*.c']),
+ deps=[':common'],
+)
+
+cxx_library(
+ name='legacy',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('legacy', '*.h'),
+ ]),
+ srcs=glob(['legacy/*.c']),
+ deps=[':common'],
+ exported_preprocessor_flags=[
+ '-DZSTD_LEGACY_SUPPORT=4',
+ ],
+)
+
+cxx_library(
+ name='zdict',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=['zdict.h'],
+ headers=subdir_glob([
+ ('dictBuilder', 'divsufsort.h'),
+ ('dictBuilder', 'cover.h'),
+ ]),
+ srcs=glob(['dictBuilder/*.c']),
+ deps=[':common'],
+)
+
+cxx_library(
+ name='compiler',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('common', 'compiler.h'),
+ ]),
+)
+
+cxx_library(
+ name='cpu',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('common', 'cpu.h'),
+ ]),
+)
+
+cxx_library(
+ name='bitstream',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('common', 'bitstream.h'),
+ ]),
+)
+
+cxx_library(
+ name='entropy',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('common', 'fse.h'),
+ ('common', 'huf.h'),
+ ]),
+ srcs=[
+ 'common/entropy_common.c',
+ 'common/fse_decompress.c',
+ 'compress/fse_compress.c',
+ 'compress/huf_compress.c',
+ 'decompress/huf_decompress.c',
+ ],
+ deps=[
+ ':debug',
+ ':bitstream',
+ ':compiler',
+ ':errors',
+ ':mem',
+ ],
+)
+
+cxx_library(
+ name='errors',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=[
+ 'zstd_errors.h',
+ 'common/error_private.h',
+ ]
+ srcs=['common/error_private.c'],
+)
+
+cxx_library(
+ name='mem',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('common', 'mem.h'),
+ ]),
+)
+
+cxx_library(
+ name='pool',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('common', 'pool.h'),
+ ]),
+ srcs=['common/pool.c'],
+ deps=[
+ ':threading',
+ ':zstd_common',
+ ],
+)
+
+cxx_library(
+ name='threading',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('common', 'threading.h'),
+ ]),
+ srcs=['common/threading.c'],
+ exported_preprocessor_flags=[
+ '-DZSTD_MULTITHREAD',
+ ],
+ exported_linker_flags=[
+ '-pthread',
+ ],
+)
+
+cxx_library(
+ name='xxhash',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('common', 'xxhash.h'),
+ ]),
+ srcs=['common/xxhash.c'],
+ exported_preprocessor_flags=[
+ '-DXXH_NAMESPACE=ZSTD_',
+ ],
+)
+
+cxx_library(
+ name='zstd_common',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('', 'zstd.h'),
+ ('common', 'zstd_internal.h'),
+ ]),
+ srcs=['common/zstd_common.c'],
+ deps=[
+ ':compiler',
+ ':errors',
+ ':mem',
+ ],
+)
+
+cxx_library(
+ name='debug',
+ header_namespace='',
+ visibility=['PUBLIC'],
+ exported_headers=subdir_glob([
+ ('common', 'debug.h'),
+ ]),
+ srcs=['common/debug.c'],
+)
+
+cxx_library(
+ name='common',
+ deps=[
+ ':debug',
+ ':bitstream',
+ ':compiler',
+ ':cpu',
+ ':entropy',
+ ':errors',
+ ':mem',
+ ':pool',
+ ':threading',
+ ':xxhash',
+ ':zstd_common',
+ ]
+)
diff --git a/third_party/zstd/lib/Makefile b/third_party/zstd/lib/Makefile
new file mode 100644
index 0000000000..8bfdade9f1
--- /dev/null
+++ b/third_party/zstd/lib/Makefile
@@ -0,0 +1,369 @@
+# ################################################################
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+#
+# This source code is licensed under both the BSD-style license (found in the
+# LICENSE file in the root directory of this source tree) and the GPLv2 (found
+# in the COPYING file in the root directory of this source tree).
+# You may select, at your option, one of the above-listed licenses.
+# ################################################################
+
+# default target (when running `make` with no argument)
+lib-release:
+
+# Modules
+ZSTD_LIB_COMPRESSION ?= 1
+ZSTD_LIB_DECOMPRESSION ?= 1
+ZSTD_LIB_DICTBUILDER ?= 1
+ZSTD_LIB_DEPRECATED ?= 0
+
+# Input variables for libzstd.mk
+ifeq ($(ZSTD_LIB_COMPRESSION), 0)
+ ZSTD_LIB_DICTBUILDER = 0
+ ZSTD_LIB_DEPRECATED = 0
+endif
+
+ifeq ($(ZSTD_LIB_DECOMPRESSION), 0)
+ ZSTD_LEGACY_SUPPORT = 0
+ ZSTD_LIB_DEPRECATED = 0
+endif
+
+include libzstd.mk
+
+ZSTD_FILES := $(ZSTD_COMMON_FILES) $(ZSTD_LEGACY_FILES)
+
+ifneq ($(ZSTD_LIB_COMPRESSION), 0)
+ ZSTD_FILES += $(ZSTD_COMPRESS_FILES)
+endif
+
+ifneq ($(ZSTD_LIB_DECOMPRESSION), 0)
+ ZSTD_FILES += $(ZSTD_DECOMPRESS_FILES)
+endif
+
+ifneq ($(ZSTD_LIB_DEPRECATED), 0)
+ ZSTD_FILES += $(ZSTD_DEPRECATED_FILES)
+endif
+
+ifneq ($(ZSTD_LIB_DICTBUILDER), 0)
+ ZSTD_FILES += $(ZSTD_DICTBUILDER_FILES)
+endif
+
+ZSTD_LOCAL_SRC := $(notdir $(ZSTD_FILES))
+ZSTD_LOCAL_OBJ0 := $(ZSTD_LOCAL_SRC:.c=.o)
+ZSTD_LOCAL_OBJ := $(ZSTD_LOCAL_OBJ0:.S=.o)
+
+VERSION := $(ZSTD_VERSION)
+
+# Note: by default, the static library is built single-threaded and dynamic library is built
+# multi-threaded. It is possible to force multi or single threaded builds by appending
+# -mt or -nomt to the build target (like lib-mt for multi-threaded, lib-nomt for single-threaded).
+
+
+CPPFLAGS_DYNLIB += -DZSTD_MULTITHREAD # dynamic library build defaults to multi-threaded
+LDFLAGS_DYNLIB += -pthread
+CPPFLAGS_STATICLIB += # static library build defaults to single-threaded
+
+
+ifeq ($(findstring GCC,$(CCVER)),GCC)
+decompress/zstd_decompress_block.o : CFLAGS+=-fno-tree-vectorize
+endif
+
+
+# macOS linker doesn't support -soname, and use different extension
+# see : https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/DynamicLibraries/100-Articles/DynamicLibraryDesignGuidelines.html
+ifeq ($(UNAME), Darwin)
+ SHARED_EXT = dylib
+ SHARED_EXT_MAJOR = $(LIBVER_MAJOR).$(SHARED_EXT)
+ SHARED_EXT_VER = $(LIBVER).$(SHARED_EXT)
+ SONAME_FLAGS = -install_name $(LIBDIR)/libzstd.$(SHARED_EXT_MAJOR) -compatibility_version $(LIBVER_MAJOR) -current_version $(LIBVER)
+else
+ ifeq ($(UNAME), AIX)
+ SONAME_FLAGS =
+ else
+ SONAME_FLAGS = -Wl,-soname=libzstd.$(SHARED_EXT).$(LIBVER_MAJOR)
+ endif
+ SHARED_EXT = so
+ SHARED_EXT_MAJOR = $(SHARED_EXT).$(LIBVER_MAJOR)
+ SHARED_EXT_VER = $(SHARED_EXT).$(LIBVER)
+endif
+
+
+.PHONY: all
+all: lib
+
+
+.PHONY: libzstd.a # must be run every time
+libzstd.a: CPPFLAGS += $(CPPFLAGS_STATICLIB)
+
+SET_CACHE_DIRECTORY = \
+ +$(MAKE) --no-print-directory $@ \
+ BUILD_DIR=obj/$(HASH_DIR) \
+ CPPFLAGS="$(CPPFLAGS)" \
+ CFLAGS="$(CFLAGS)" \
+ LDFLAGS="$(LDFLAGS)"
+
+ifndef BUILD_DIR
+# determine BUILD_DIR from compilation flags
+
+libzstd.a:
+ $(SET_CACHE_DIRECTORY)
+
+else
+# BUILD_DIR is defined
+
+ZSTD_STATICLIB_DIR := $(BUILD_DIR)/static
+ZSTD_STATICLIB := $(ZSTD_STATICLIB_DIR)/libzstd.a
+ZSTD_STATICLIB_OBJ := $(addprefix $(ZSTD_STATICLIB_DIR)/,$(ZSTD_LOCAL_OBJ))
+$(ZSTD_STATICLIB): ARFLAGS = rcs
+$(ZSTD_STATICLIB): | $(ZSTD_STATICLIB_DIR)
+$(ZSTD_STATICLIB): $(ZSTD_STATICLIB_OBJ)
+ # Check for multithread flag at target execution time
+ $(if $(filter -DZSTD_MULTITHREAD,$(CPPFLAGS)),\
+ @echo compiling multi-threaded static library $(LIBVER),\
+ @echo compiling single-threaded static library $(LIBVER))
+ $(AR) $(ARFLAGS) $@ $^
+
+libzstd.a: $(ZSTD_STATICLIB)
+ cp -f $< $@
+
+endif
+
+ifneq (,$(filter Windows%,$(TARGET_SYSTEM)))
+
+LIBZSTD = dll/libzstd.dll
+$(LIBZSTD): $(ZSTD_FILES)
+ @echo compiling dynamic library $(LIBVER)
+ $(CC) $(FLAGS) -DZSTD_DLL_EXPORT=1 -Wl,--out-implib,dll/libzstd.dll.a -shared $^ -o $@
+
+else # not Windows
+
+LIBZSTD = libzstd.$(SHARED_EXT_VER)
+.PHONY: $(LIBZSTD) # must be run every time
+$(LIBZSTD): CPPFLAGS += $(CPPFLAGS_DYNLIB)
+$(LIBZSTD): CFLAGS += -fPIC -fvisibility=hidden
+$(LIBZSTD): LDFLAGS += -shared $(LDFLAGS_DYNLIB)
+
+ifndef BUILD_DIR
+# determine BUILD_DIR from compilation flags
+
+$(LIBZSTD):
+ $(SET_CACHE_DIRECTORY)
+
+else
+# BUILD_DIR is defined
+
+ZSTD_DYNLIB_DIR := $(BUILD_DIR)/dynamic
+ZSTD_DYNLIB := $(ZSTD_DYNLIB_DIR)/$(LIBZSTD)
+ZSTD_DYNLIB_OBJ := $(addprefix $(ZSTD_DYNLIB_DIR)/,$(ZSTD_LOCAL_OBJ))
+
+$(ZSTD_DYNLIB): | $(ZSTD_DYNLIB_DIR)
+$(ZSTD_DYNLIB): $(ZSTD_DYNLIB_OBJ)
+# Check for multithread flag at target execution time
+ $(if $(filter -DZSTD_MULTITHREAD,$(CPPFLAGS)),\
+ @echo compiling multi-threaded dynamic library $(LIBVER),\
+ @echo compiling single-threaded dynamic library $(LIBVER))
+ $(CC) $(FLAGS) $^ $(LDFLAGS) $(SONAME_FLAGS) -o $@
+ @echo creating versioned links
+ ln -sf $@ libzstd.$(SHARED_EXT_MAJOR)
+ ln -sf $@ libzstd.$(SHARED_EXT)
+
+$(LIBZSTD): $(ZSTD_DYNLIB)
+ cp -f $< $@
+
+endif # ifndef BUILD_DIR
+endif # if windows
+
+.PHONY: libzstd
+libzstd : $(LIBZSTD)
+
+.PHONY: lib
+lib : libzstd.a libzstd
+
+
+# note : do not define lib-mt or lib-release as .PHONY
+# make does not consider implicit pattern rule for .PHONY target
+
+%-mt : CPPFLAGS_DYNLIB := -DZSTD_MULTITHREAD
+%-mt : CPPFLAGS_STATICLIB := -DZSTD_MULTITHREAD
+%-mt : LDFLAGS_DYNLIB := -pthread
+%-mt : %
+ @echo multi-threaded build completed
+
+%-nomt : CPPFLAGS_DYNLIB :=
+%-nomt : LDFLAGS_DYNLIB :=
+%-nomt : CPPFLAGS_STATICLIB :=
+%-nomt : %
+ @echo single-threaded build completed
+
+%-release : DEBUGFLAGS :=
+%-release : %
+ @echo release build completed
+
+
+# Generate .h dependencies automatically
+
+# -MMD: compiler generates dependency information as a side-effect of compilation, without system headers
+# -MP: adds phony target for each dependency other than main file.
+DEPFLAGS = -MMD -MP
+
+# ensure that ZSTD_DYNLIB_DIR exists prior to generating %.o
+$(ZSTD_DYNLIB_DIR)/%.o : %.c | $(ZSTD_DYNLIB_DIR)
+ @echo CC $@
+ $(COMPILE.c) $(DEPFLAGS) $(OUTPUT_OPTION) $<
+
+$(ZSTD_STATICLIB_DIR)/%.o : %.c | $(ZSTD_STATICLIB_DIR)
+ @echo CC $@
+ $(COMPILE.c) $(DEPFLAGS) $(OUTPUT_OPTION) $<
+
+$(ZSTD_DYNLIB_DIR)/%.o : %.S | $(ZSTD_DYNLIB_DIR)
+ @echo AS $@
+ $(COMPILE.S) $(OUTPUT_OPTION) $<
+
+$(ZSTD_STATICLIB_DIR)/%.o : %.S | $(ZSTD_STATICLIB_DIR)
+ @echo AS $@
+ $(COMPILE.S) $(OUTPUT_OPTION) $<
+
+MKDIR ?= mkdir -p
+$(BUILD_DIR) $(ZSTD_DYNLIB_DIR) $(ZSTD_STATICLIB_DIR):
+ $(MKDIR) $@
+
+DEPFILES := $(ZSTD_DYNLIB_OBJ:.o=.d) $(ZSTD_STATICLIB_OBJ:.o=.d)
+$(DEPFILES):
+
+# The leading '-' means: do not fail is include fails (ex: directory does not exist yet)
+-include $(wildcard $(DEPFILES))
+
+
+# Special case : build library in single-thread mode _and_ without zstdmt_compress.c
+# Note : we still need threading.c and pool.c for the dictionary builder,
+# but they will correctly behave single-threaded.
+ZSTDMT_FILES = zstdmt_compress.c
+ZSTD_NOMT_FILES = $(filter-out $(ZSTDMT_FILES),$(notdir $(ZSTD_FILES)))
+libzstd-nomt: CFLAGS += -fPIC -fvisibility=hidden
+libzstd-nomt: LDFLAGS += -shared
+libzstd-nomt: $(ZSTD_NOMT_FILES)
+ @echo compiling single-thread dynamic library $(LIBVER)
+ @echo files : $(ZSTD_NOMT_FILES)
+ @if echo "$(ZSTD_NOMT_FILES)" | tr ' ' '\n' | $(GREP) -q zstdmt; then \
+ echo "Error: Found zstdmt in list."; \
+ exit 1; \
+ fi
+ $(CC) $(FLAGS) $^ $(LDFLAGS) $(SONAME_FLAGS) -o $@
+
+.PHONY: clean
+clean:
+ $(RM) -r *.dSYM # macOS-specific
+ $(RM) core *.o *.a *.gcda *.$(SHARED_EXT) *.$(SHARED_EXT).* libzstd.pc
+ $(RM) dll/libzstd.dll dll/libzstd.lib libzstd-nomt*
+ $(RM) -r obj/*
+ @echo Cleaning library completed
+
+#-----------------------------------------------------------------------------
+# make install is validated only for below listed environments
+#-----------------------------------------------------------------------------
+ifneq (,$(filter $(UNAME),Linux Darwin GNU/kFreeBSD GNU OpenBSD FreeBSD NetBSD DragonFly SunOS Haiku AIX MSYS_NT CYGWIN_NT))
+
+lib: libzstd.pc
+
+HAS_EXPLICIT_EXEC_PREFIX := $(if $(or $(EXEC_PREFIX),$(exec_prefix)),1,)
+
+DESTDIR ?=
+# directory variables : GNU conventions prefer lowercase
+# see https://www.gnu.org/prep/standards/html_node/Makefile-Conventions.html
+# support both lower and uppercase (BSD), use uppercase in script
+prefix ?= /usr/local
+PREFIX ?= $(prefix)
+exec_prefix ?= $(PREFIX)
+EXEC_PREFIX ?= $(exec_prefix)
+libdir ?= $(EXEC_PREFIX)/lib
+LIBDIR ?= $(libdir)
+includedir ?= $(PREFIX)/include
+INCLUDEDIR ?= $(includedir)
+
+PCINCDIR := $(patsubst $(PREFIX)%,%,$(INCLUDEDIR))
+PCLIBDIR := $(patsubst $(EXEC_PREFIX)%,%,$(LIBDIR))
+
+# If we successfully stripped off a prefix, we'll add a reference to the
+# relevant pc variable.
+PCINCPREFIX := $(if $(findstring $(INCLUDEDIR),$(PCINCDIR)),,$${prefix})
+PCLIBPREFIX := $(if $(findstring $(LIBDIR),$(PCLIBDIR)),,$${exec_prefix})
+
+# If no explicit EXEC_PREFIX was set by the caller, write it out as a reference
+# to PREFIX, rather than as a resolved value.
+PCEXEC_PREFIX := $(if $(HAS_EXPLICIT_EXEC_PREFIX),$(EXEC_PREFIX),$${prefix})
+
+ifneq (,$(filter $(UNAME),FreeBSD NetBSD DragonFly))
+ PKGCONFIGDIR ?= $(PREFIX)/libdata/pkgconfig
+else
+ PKGCONFIGDIR ?= $(LIBDIR)/pkgconfig
+endif
+
+ifneq (,$(filter $(UNAME),SunOS))
+ INSTALL ?= ginstall
+else
+ INSTALL ?= install
+endif
+
+INSTALL_PROGRAM ?= $(INSTALL)
+INSTALL_DATA ?= $(INSTALL) -m 644
+
+
+libzstd.pc: libzstd.pc.in
+ @echo creating pkgconfig
+ @sed \
+ -e 's|@PREFIX@|$(PREFIX)|' \
+ -e 's|@EXEC_PREFIX@|$(PCEXEC_PREFIX)|' \
+ -e 's|@INCLUDEDIR@|$(PCINCPREFIX)$(PCINCDIR)|' \
+ -e 's|@LIBDIR@|$(PCLIBPREFIX)$(PCLIBDIR)|' \
+ -e 's|@VERSION@|$(VERSION)|' \
+ -e 's|@LIBS_PRIVATE@|$(LDFLAGS_DYNLIB)|' \
+ $< >$@
+
+.PHONY: install
+install: install-pc install-static install-shared install-includes
+ @echo zstd static and shared library installed
+
+.PHONY: install-pc
+install-pc: libzstd.pc
+ [ -e $(DESTDIR)$(PKGCONFIGDIR) ] || $(INSTALL) -d -m 755 $(DESTDIR)$(PKGCONFIGDIR)/
+ $(INSTALL_DATA) libzstd.pc $(DESTDIR)$(PKGCONFIGDIR)/
+
+.PHONY: install-static
+install-static:
+ # only generate libzstd.a if it's not already present
+ [ -e libzstd.a ] || $(MAKE) libzstd.a-release
+ [ -e $(DESTDIR)$(LIBDIR) ] || $(INSTALL) -d -m 755 $(DESTDIR)$(LIBDIR)/
+ @echo Installing static library
+ $(INSTALL_DATA) libzstd.a $(DESTDIR)$(LIBDIR)
+
+.PHONY: install-shared
+install-shared:
+ # only generate libzstd.so if it's not already present
+ [ -e $(LIBZSTD) ] || $(MAKE) libzstd-release
+ [ -e $(DESTDIR)$(LIBDIR) ] || $(INSTALL) -d -m 755 $(DESTDIR)$(LIBDIR)/
+ @echo Installing shared library
+ $(INSTALL_PROGRAM) $(LIBZSTD) $(DESTDIR)$(LIBDIR)
+ ln -sf $(LIBZSTD) $(DESTDIR)$(LIBDIR)/libzstd.$(SHARED_EXT_MAJOR)
+ ln -sf $(LIBZSTD) $(DESTDIR)$(LIBDIR)/libzstd.$(SHARED_EXT)
+
+.PHONY: install-includes
+install-includes:
+ [ -e $(DESTDIR)$(INCLUDEDIR) ] || $(INSTALL) -d -m 755 $(DESTDIR)$(INCLUDEDIR)/
+ @echo Installing includes
+ $(INSTALL_DATA) zstd.h $(DESTDIR)$(INCLUDEDIR)
+ $(INSTALL_DATA) zstd_errors.h $(DESTDIR)$(INCLUDEDIR)
+ $(INSTALL_DATA) zdict.h $(DESTDIR)$(INCLUDEDIR)
+
+.PHONY: uninstall
+uninstall:
+ $(RM) $(DESTDIR)$(LIBDIR)/libzstd.a
+ $(RM) $(DESTDIR)$(LIBDIR)/libzstd.$(SHARED_EXT)
+ $(RM) $(DESTDIR)$(LIBDIR)/libzstd.$(SHARED_EXT_MAJOR)
+ $(RM) $(DESTDIR)$(LIBDIR)/$(LIBZSTD)
+ $(RM) $(DESTDIR)$(PKGCONFIGDIR)/libzstd.pc
+ $(RM) $(DESTDIR)$(INCLUDEDIR)/zstd.h
+ $(RM) $(DESTDIR)$(INCLUDEDIR)/zstd_errors.h
+ $(RM) $(DESTDIR)$(INCLUDEDIR)/zdict.h
+ @echo zstd libraries successfully uninstalled
+
+endif
diff --git a/third_party/zstd/lib/README.md b/third_party/zstd/lib/README.md
new file mode 100644
index 0000000000..a560f06cad
--- /dev/null
+++ b/third_party/zstd/lib/README.md
@@ -0,0 +1,237 @@
+Zstandard library files
+================================
+
+The __lib__ directory is split into several sub-directories,
+in order to make it easier to select or exclude features.
+
+
+#### Building
+
+`Makefile` script is provided, supporting [Makefile conventions](https://www.gnu.org/prep/standards/html_node/Makefile-Conventions.html#Makefile-Conventions),
+including commands variables, staged install, directory variables and standard targets.
+- `make` : generates both static and dynamic libraries
+- `make install` : install libraries and headers in target system directories
+
+`libzstd` default scope is pretty large, including compression, decompression, dictionary builder,
+and support for decoding legacy formats >= v0.5.0.
+The scope can be reduced on demand (see paragraph _modular build_).
+
+
+#### Multithreading support
+
+When building with `make`, by default the dynamic library is multithreaded and static library is single-threaded (for compatibility reasons).
+
+Enabling multithreading requires 2 conditions :
+- set build macro `ZSTD_MULTITHREAD` (`-DZSTD_MULTITHREAD` for `gcc`)
+- for POSIX systems : compile with pthread (`-pthread` compilation flag for `gcc`)
+
+For convenience, we provide a build target to generate multi and single threaded libraries:
+- Force enable multithreading on both dynamic and static libraries by appending `-mt` to the target, e.g. `make lib-mt`.
+- Force disable multithreading on both dynamic and static libraries by appending `-nomt` to the target, e.g. `make lib-nomt`.
+- By default, as mentioned before, dynamic library is multithreaded, and static library is single-threaded, e.g. `make lib`.
+
+When linking a POSIX program with a multithreaded version of `libzstd`,
+note that it's necessary to invoke the `-pthread` flag during link stage.
+
+Multithreading capabilities are exposed
+via the [advanced API defined in `lib/zstd.h`](https://github.com/facebook/zstd/blob/v1.4.3/lib/zstd.h#L351).
+
+
+#### API
+
+Zstandard's stable API is exposed within [lib/zstd.h](zstd.h).
+
+
+#### Advanced API
+
+Optional advanced features are exposed via :
+
+- `lib/zstd_errors.h` : translates `size_t` function results
+ into a `ZSTD_ErrorCode`, for accurate error handling.
+
+- `ZSTD_STATIC_LINKING_ONLY` : if this macro is defined _before_ including `zstd.h`,
+ it unlocks access to the experimental API,
+ exposed in the second part of `zstd.h`.
+ All definitions in the experimental APIs are unstable,
+ they may still change in the future, or even be removed.
+ As a consequence, experimental definitions shall ___never be used with dynamic library___ !
+ Only static linking is allowed.
+
+
+#### Modular build
+
+It's possible to compile only a limited set of features within `libzstd`.
+The file structure is designed to make this selection manually achievable for any build system :
+
+- Directory `lib/common` is always required, for all variants.
+
+- Compression source code lies in `lib/compress`
+
+- Decompression source code lies in `lib/decompress`
+
+- It's possible to include only `compress` or only `decompress`, they don't depend on each other.
+
+- `lib/dictBuilder` : makes it possible to generate dictionaries from a set of samples.
+ The API is exposed in `lib/dictBuilder/zdict.h`.
+ This module depends on both `lib/common` and `lib/compress` .
+
+- `lib/legacy` : makes it possible to decompress legacy zstd formats, starting from `v0.1.0`.
+ This module depends on `lib/common` and `lib/decompress`.
+ To enable this feature, define `ZSTD_LEGACY_SUPPORT` during compilation.
+ Specifying a number limits versions supported to that version onward.
+ For example, `ZSTD_LEGACY_SUPPORT=2` means : "support legacy formats >= v0.2.0".
+ Conversely, `ZSTD_LEGACY_SUPPORT=0` means "do __not__ support legacy formats".
+ By default, this build macro is set as `ZSTD_LEGACY_SUPPORT=5`.
+ Decoding supported legacy format is a transparent capability triggered within decompression functions.
+ It's also allowed to invoke legacy API directly, exposed in `lib/legacy/zstd_legacy.h`.
+ Each version does also provide its own set of advanced API.
+ For example, advanced API for version `v0.4` is exposed in `lib/legacy/zstd_v04.h` .
+
+- While invoking `make libzstd`, it's possible to define build macros
+ `ZSTD_LIB_COMPRESSION`, `ZSTD_LIB_DECOMPRESSION`, `ZSTD_LIB_DICTBUILDER`,
+ and `ZSTD_LIB_DEPRECATED` as `0` to forgo compilation of the
+ corresponding features. This will also disable compilation of all
+ dependencies (e.g. `ZSTD_LIB_COMPRESSION=0` will also disable
+ dictBuilder).
+
+- There are a number of options that can help minimize the binary size of
+ `libzstd`.
+
+ The first step is to select the components needed (using the above-described
+ `ZSTD_LIB_COMPRESSION` etc.).
+
+ The next step is to set `ZSTD_LIB_MINIFY` to `1` when invoking `make`. This
+ disables various optional components and changes the compilation flags to
+ prioritize space-saving.
+
+ Detailed options: Zstandard's code and build environment is set up by default
+ to optimize above all else for performance. In pursuit of this goal, Zstandard
+ makes significant trade-offs in code size. For example, Zstandard often has
+ more than one implementation of a particular component, with each
+ implementation optimized for different scenarios. For example, the Huffman
+ decoder has complementary implementations that decode the stream one symbol at
+ a time or two symbols at a time. Zstd normally includes both (and dispatches
+ between them at runtime), but by defining `HUF_FORCE_DECOMPRESS_X1` or
+ `HUF_FORCE_DECOMPRESS_X2`, you can force the use of one or the other, avoiding
+ compilation of the other. Similarly, `ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT`
+ and `ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG` force the compilation and use of
+ only one or the other of two decompression implementations. The smallest
+ binary is achieved by using `HUF_FORCE_DECOMPRESS_X1` and
+ `ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT` (implied by `ZSTD_LIB_MINIFY`).
+
+ On the compressor side, Zstd's compression levels map to several internal
+ strategies. In environments where the higher compression levels aren't used,
+ it is possible to exclude all but the fastest strategy with
+ `ZSTD_LIB_EXCLUDE_COMPRESSORS_DFAST_AND_UP=1`. (Note that this will change
+ the behavior of the default compression level.) Or if you want to retain the
+ default compressor as well, you can set
+ `ZSTD_LIB_EXCLUDE_COMPRESSORS_GREEDY_AND_UP=1`, at the cost of an additional
+ ~20KB or so.
+
+ For squeezing the last ounce of size out, you can also define
+ `ZSTD_NO_INLINE`, which disables inlining, and `ZSTD_STRIP_ERROR_STRINGS`,
+ which removes the error messages that are otherwise returned by
+ `ZSTD_getErrorName` (implied by `ZSTD_LIB_MINIFY`).
+
+ Finally, when integrating into your application, make sure you're doing link-
+ time optimization and unused symbol garbage collection (via some combination of,
+ e.g., `-flto`, `-ffat-lto-objects`, `-fuse-linker-plugin`,
+ `-ffunction-sections`, `-fdata-sections`, `-fmerge-all-constants`,
+ `-Wl,--gc-sections`, `-Wl,-z,norelro`, and an archiver that understands
+ the compiler's intermediate representation, e.g., `AR=gcc-ar`). Consult your
+ compiler's documentation.
+
+- While invoking `make libzstd`, the build macro `ZSTD_LEGACY_MULTITHREADED_API=1`
+ will expose the deprecated `ZSTDMT` API exposed by `zstdmt_compress.h` in
+ the shared library, which is now hidden by default.
+
+- The build macro `DYNAMIC_BMI2` can be set to 1 or 0 in order to generate binaries
+ which can detect at runtime the presence of BMI2 instructions, and use them only if present.
+ These instructions contribute to better performance, notably on the decoder side.
+ By default, this feature is automatically enabled on detecting
+ the right instruction set (x64) and compiler (clang or gcc >= 5).
+ It's obviously disabled for different cpus,
+ or when BMI2 instruction set is _required_ by the compiler command line
+ (in this case, only the BMI2 code path is generated).
+ Setting this macro will either force to generate the BMI2 dispatcher (1)
+ or prevent it (0). It overrides automatic detection.
+
+- The build macro `ZSTD_NO_UNUSED_FUNCTIONS` can be defined to hide the definitions of functions
+ that zstd does not use. Not all unused functions are hidden, but they can be if needed.
+ Currently, this macro will hide function definitions in FSE and HUF that use an excessive
+ amount of stack space.
+
+- The build macro `ZSTD_NO_INTRINSICS` can be defined to disable all explicit intrinsics.
+ Compiler builtins are still used.
+
+- The build macro `ZSTD_DECODER_INTERNAL_BUFFER` can be set to control
+ the amount of extra memory used during decompression to store literals.
+ This defaults to 64kB. Reducing this value reduces the memory footprint of
+ `ZSTD_DCtx` decompression contexts,
+ but might also result in a small decompression speed cost.
+
+- The C compiler macros `ZSTDLIB_VISIBLE`, `ZSTDERRORLIB_VISIBLE` and `ZDICTLIB_VISIBLE`
+ can be overridden to control the visibility of zstd's API. Additionally,
+ `ZSTDLIB_STATIC_API` and `ZDICTLIB_STATIC_API` can be overridden to control the visibility
+ of zstd's static API. Specifically, it can be set to `ZSTDLIB_HIDDEN` to hide the symbols
+ from the shared library. These macros default to `ZSTDLIB_VISIBILITY`,
+ `ZSTDERRORLIB_VSIBILITY`, and `ZDICTLIB_VISIBILITY` if unset, for backwards compatibility
+ with the old macro names.
+
+- The C compiler macro `HUF_DISABLE_FAST_DECODE` disables the newer Huffman fast C
+ and assembly decoding loops. You may want to use this macro if these loops are
+ slower on your platform.
+
+#### Windows : using MinGW+MSYS to create DLL
+
+DLL can be created using MinGW+MSYS with the `make libzstd` command.
+This command creates `dll\libzstd.dll` and the import library `dll\libzstd.lib`.
+The import library is only required with Visual C++.
+The header file `zstd.h` and the dynamic library `dll\libzstd.dll` are required to
+compile a project using gcc/MinGW.
+The dynamic library has to be added to linking options.
+It means that if a project that uses ZSTD consists of a single `test-dll.c`
+file it should be linked with `dll\libzstd.dll`. For example:
+```
+ gcc $(CFLAGS) -Iinclude/ test-dll.c -o test-dll dll\libzstd.dll
+```
+The compiled executable will require ZSTD DLL which is available at `dll\libzstd.dll`.
+
+
+#### Advanced Build options
+
+The build system requires a hash function in order to
+separate object files created with different compilation flags.
+By default, it tries to use `md5sum` or equivalent.
+The hash function can be manually switched by setting the `HASH` variable.
+For example : `make HASH=xxhsum`
+The hash function needs to generate at least 64-bit using hexadecimal format.
+When no hash function is found,
+the Makefile just generates all object files into the same default directory,
+irrespective of compilation flags.
+This functionality only matters if `libzstd` is compiled multiple times
+with different build flags.
+
+The build directory, where object files are stored
+can also be manually controlled using variable `BUILD_DIR`,
+for example `make BUILD_DIR=objectDir/v1`.
+In which case, the hash function doesn't matter.
+
+
+#### Deprecated API
+
+Obsolete API on their way out are stored in directory `lib/deprecated`.
+At this stage, it contains older streaming prototypes, in `lib/deprecated/zbuff.h`.
+These prototypes will be removed in some future version.
+Consider migrating code towards supported streaming API exposed in `zstd.h`.
+
+
+#### Miscellaneous
+
+The other files are not source code. There are :
+
+ - `BUCK` : support for `buck` build system (https://buckbuild.com/)
+ - `Makefile` : `make` script to build and install zstd library (static and dynamic)
+ - `README.md` : this file
+ - `dll/` : resources directory for Windows compilation
+ - `libzstd.pc.in` : script for `pkg-config` (used in `make install`)
diff --git a/third_party/zstd/lib/common/allocations.h b/third_party/zstd/lib/common/allocations.h
new file mode 100644
index 0000000000..5e89955010
--- /dev/null
+++ b/third_party/zstd/lib/common/allocations.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+/* This file provides custom allocation primitives
+ */
+
+#define ZSTD_DEPS_NEED_MALLOC
+#include "zstd_deps.h" /* ZSTD_malloc, ZSTD_calloc, ZSTD_free, ZSTD_memset */
+
+#include "compiler.h" /* MEM_STATIC */
+#define ZSTD_STATIC_LINKING_ONLY
+#include "../zstd.h" /* ZSTD_customMem */
+
+#ifndef ZSTD_ALLOCATIONS_H
+#define ZSTD_ALLOCATIONS_H
+
+/* custom memory allocation functions */
+
+MEM_STATIC void* ZSTD_customMalloc(size_t size, ZSTD_customMem customMem)
+{
+ if (customMem.customAlloc)
+ return customMem.customAlloc(customMem.opaque, size);
+ return ZSTD_malloc(size);
+}
+
+MEM_STATIC void* ZSTD_customCalloc(size_t size, ZSTD_customMem customMem)
+{
+ if (customMem.customAlloc) {
+ /* calloc implemented as malloc+memset;
+ * not as efficient as calloc, but next best guess for custom malloc */
+ void* const ptr = customMem.customAlloc(customMem.opaque, size);
+ ZSTD_memset(ptr, 0, size);
+ return ptr;
+ }
+ return ZSTD_calloc(1, size);
+}
+
+MEM_STATIC void ZSTD_customFree(void* ptr, ZSTD_customMem customMem)
+{
+ if (ptr!=NULL) {
+ if (customMem.customFree)
+ customMem.customFree(customMem.opaque, ptr);
+ else
+ ZSTD_free(ptr);
+ }
+}
+
+#endif /* ZSTD_ALLOCATIONS_H */
diff --git a/third_party/zstd/lib/common/bits.h b/third_party/zstd/lib/common/bits.h
new file mode 100644
index 0000000000..def56c474c
--- /dev/null
+++ b/third_party/zstd/lib/common/bits.h
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#ifndef ZSTD_BITS_H
+#define ZSTD_BITS_H
+
+#include "mem.h"
+
+MEM_STATIC unsigned ZSTD_countTrailingZeros32_fallback(U32 val)
+{
+ assert(val != 0);
+ {
+ static const U32 DeBruijnBytePos[32] = {0, 1, 28, 2, 29, 14, 24, 3,
+ 30, 22, 20, 15, 25, 17, 4, 8,
+ 31, 27, 13, 23, 21, 19, 16, 7,
+ 26, 12, 18, 6, 11, 5, 10, 9};
+ return DeBruijnBytePos[((U32) ((val & -(S32) val) * 0x077CB531U)) >> 27];
+ }
+}
+
+MEM_STATIC unsigned ZSTD_countTrailingZeros32(U32 val)
+{
+ assert(val != 0);
+# if defined(_MSC_VER)
+# if STATIC_BMI2 == 1
+ return (unsigned)_tzcnt_u32(val);
+# else
+ if (val != 0) {
+ unsigned long r;
+ _BitScanForward(&r, val);
+ return (unsigned)r;
+ } else {
+ /* Should not reach this code path */
+ __assume(0);
+ }
+# endif
+# elif defined(__GNUC__) && (__GNUC__ >= 4)
+ return (unsigned)__builtin_ctz(val);
+# else
+ return ZSTD_countTrailingZeros32_fallback(val);
+# endif
+}
+
+MEM_STATIC unsigned ZSTD_countLeadingZeros32_fallback(U32 val) {
+ assert(val != 0);
+ {
+ static const U32 DeBruijnClz[32] = {0, 9, 1, 10, 13, 21, 2, 29,
+ 11, 14, 16, 18, 22, 25, 3, 30,
+ 8, 12, 20, 28, 15, 17, 24, 7,
+ 19, 27, 23, 6, 26, 5, 4, 31};
+ val |= val >> 1;
+ val |= val >> 2;
+ val |= val >> 4;
+ val |= val >> 8;
+ val |= val >> 16;
+ return 31 - DeBruijnClz[(val * 0x07C4ACDDU) >> 27];
+ }
+}
+
+MEM_STATIC unsigned ZSTD_countLeadingZeros32(U32 val)
+{
+ assert(val != 0);
+# if defined(_MSC_VER)
+# if STATIC_BMI2 == 1
+ return (unsigned)_lzcnt_u32(val);
+# else
+ if (val != 0) {
+ unsigned long r;
+ _BitScanReverse(&r, val);
+ return (unsigned)(31 - r);
+ } else {
+ /* Should not reach this code path */
+ __assume(0);
+ }
+# endif
+# elif defined(__GNUC__) && (__GNUC__ >= 4)
+ return (unsigned)__builtin_clz(val);
+# else
+ return ZSTD_countLeadingZeros32_fallback(val);
+# endif
+}
+
+MEM_STATIC unsigned ZSTD_countTrailingZeros64(U64 val)
+{
+ assert(val != 0);
+# if defined(_MSC_VER) && defined(_WIN64)
+# if STATIC_BMI2 == 1
+ return (unsigned)_tzcnt_u64(val);
+# else
+ if (val != 0) {
+ unsigned long r;
+ _BitScanForward64(&r, val);
+ return (unsigned)r;
+ } else {
+ /* Should not reach this code path */
+ __assume(0);
+ }
+# endif
+# elif defined(__GNUC__) && (__GNUC__ >= 4) && defined(__LP64__)
+ return (unsigned)__builtin_ctzll(val);
+# else
+ {
+ U32 mostSignificantWord = (U32)(val >> 32);
+ U32 leastSignificantWord = (U32)val;
+ if (leastSignificantWord == 0) {
+ return 32 + ZSTD_countTrailingZeros32(mostSignificantWord);
+ } else {
+ return ZSTD_countTrailingZeros32(leastSignificantWord);
+ }
+ }
+# endif
+}
+
+MEM_STATIC unsigned ZSTD_countLeadingZeros64(U64 val)
+{
+ assert(val != 0);
+# if defined(_MSC_VER) && defined(_WIN64)
+# if STATIC_BMI2 == 1
+ return (unsigned)_lzcnt_u64(val);
+# else
+ if (val != 0) {
+ unsigned long r;
+ _BitScanReverse64(&r, val);
+ return (unsigned)(63 - r);
+ } else {
+ /* Should not reach this code path */
+ __assume(0);
+ }
+# endif
+# elif defined(__GNUC__) && (__GNUC__ >= 4)
+ return (unsigned)(__builtin_clzll(val));
+# else
+ {
+ U32 mostSignificantWord = (U32)(val >> 32);
+ U32 leastSignificantWord = (U32)val;
+ if (mostSignificantWord == 0) {
+ return 32 + ZSTD_countLeadingZeros32(leastSignificantWord);
+ } else {
+ return ZSTD_countLeadingZeros32(mostSignificantWord);
+ }
+ }
+# endif
+}
+
+MEM_STATIC unsigned ZSTD_NbCommonBytes(size_t val)
+{
+ if (MEM_isLittleEndian()) {
+ if (MEM_64bits()) {
+ return ZSTD_countTrailingZeros64((U64)val) >> 3;
+ } else {
+ return ZSTD_countTrailingZeros32((U32)val) >> 3;
+ }
+ } else { /* Big Endian CPU */
+ if (MEM_64bits()) {
+ return ZSTD_countLeadingZeros64((U64)val) >> 3;
+ } else {
+ return ZSTD_countLeadingZeros32((U32)val) >> 3;
+ }
+ }
+}
+
+MEM_STATIC unsigned ZSTD_highbit32(U32 val) /* compress, dictBuilder, decodeCorpus */
+{
+ assert(val != 0);
+ return 31 - ZSTD_countLeadingZeros32(val);
+}
+
+/* ZSTD_rotateRight_*():
+ * Rotates a bitfield to the right by "count" bits.
+ * https://en.wikipedia.org/w/index.php?title=Circular_shift&oldid=991635599#Implementing_circular_shifts
+ */
+MEM_STATIC
+U64 ZSTD_rotateRight_U64(U64 const value, U32 count) {
+ assert(count < 64);
+ count &= 0x3F; /* for fickle pattern recognition */
+ return (value >> count) | (U64)(value << ((0U - count) & 0x3F));
+}
+
+MEM_STATIC
+U32 ZSTD_rotateRight_U32(U32 const value, U32 count) {
+ assert(count < 32);
+ count &= 0x1F; /* for fickle pattern recognition */
+ return (value >> count) | (U32)(value << ((0U - count) & 0x1F));
+}
+
+MEM_STATIC
+U16 ZSTD_rotateRight_U16(U16 const value, U32 count) {
+ assert(count < 16);
+ count &= 0x0F; /* for fickle pattern recognition */
+ return (value >> count) | (U16)(value << ((0U - count) & 0x0F));
+}
+
+#endif /* ZSTD_BITS_H */
diff --git a/third_party/zstd/lib/common/bitstream.h b/third_party/zstd/lib/common/bitstream.h
new file mode 100644
index 0000000000..676044989c
--- /dev/null
+++ b/third_party/zstd/lib/common/bitstream.h
@@ -0,0 +1,457 @@
+/* ******************************************************************
+ * bitstream
+ * Part of FSE library
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * You can contact the author at :
+ * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+****************************************************************** */
+#ifndef BITSTREAM_H_MODULE
+#define BITSTREAM_H_MODULE
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+/*
+* This API consists of small unitary functions, which must be inlined for best performance.
+* Since link-time-optimization is not available for all compilers,
+* these functions are defined into a .h to be included.
+*/
+
+/*-****************************************
+* Dependencies
+******************************************/
+#include "mem.h" /* unaligned access routines */
+#include "compiler.h" /* UNLIKELY() */
+#include "debug.h" /* assert(), DEBUGLOG(), RAWLOG() */
+#include "error_private.h" /* error codes and messages */
+#include "bits.h" /* ZSTD_highbit32 */
+
+
+/*=========================================
+* Target specific
+=========================================*/
+#ifndef ZSTD_NO_INTRINSICS
+# if (defined(__BMI__) || defined(__BMI2__)) && defined(__GNUC__)
+# include <immintrin.h> /* support for bextr (experimental)/bzhi */
+# elif defined(__ICCARM__)
+# include <intrinsics.h>
+# endif
+#endif
+
+#define STREAM_ACCUMULATOR_MIN_32 25
+#define STREAM_ACCUMULATOR_MIN_64 57
+#define STREAM_ACCUMULATOR_MIN ((U32)(MEM_32bits() ? STREAM_ACCUMULATOR_MIN_32 : STREAM_ACCUMULATOR_MIN_64))
+
+
+/*-******************************************
+* bitStream encoding API (write forward)
+********************************************/
+/* bitStream can mix input from multiple sources.
+ * A critical property of these streams is that they encode and decode in **reverse** direction.
+ * So the first bit sequence you add will be the last to be read, like a LIFO stack.
+ */
+typedef struct {
+ size_t bitContainer;
+ unsigned bitPos;
+ char* startPtr;
+ char* ptr;
+ char* endPtr;
+} BIT_CStream_t;
+
+MEM_STATIC size_t BIT_initCStream(BIT_CStream_t* bitC, void* dstBuffer, size_t dstCapacity);
+MEM_STATIC void BIT_addBits(BIT_CStream_t* bitC, size_t value, unsigned nbBits);
+MEM_STATIC void BIT_flushBits(BIT_CStream_t* bitC);
+MEM_STATIC size_t BIT_closeCStream(BIT_CStream_t* bitC);
+
+/* Start with initCStream, providing the size of buffer to write into.
+* bitStream will never write outside of this buffer.
+* `dstCapacity` must be >= sizeof(bitD->bitContainer), otherwise @return will be an error code.
+*
+* bits are first added to a local register.
+* Local register is size_t, hence 64-bits on 64-bits systems, or 32-bits on 32-bits systems.
+* Writing data into memory is an explicit operation, performed by the flushBits function.
+* Hence keep track how many bits are potentially stored into local register to avoid register overflow.
+* After a flushBits, a maximum of 7 bits might still be stored into local register.
+*
+* Avoid storing elements of more than 24 bits if you want compatibility with 32-bits bitstream readers.
+*
+* Last operation is to close the bitStream.
+* The function returns the final size of CStream in bytes.
+* If data couldn't fit into `dstBuffer`, it will return a 0 ( == not storable)
+*/
+
+
+/*-********************************************
+* bitStream decoding API (read backward)
+**********************************************/
+typedef size_t BitContainerType;
+typedef struct {
+ BitContainerType bitContainer;
+ unsigned bitsConsumed;
+ const char* ptr;
+ const char* start;
+ const char* limitPtr;
+} BIT_DStream_t;
+
+typedef enum { BIT_DStream_unfinished = 0, /* fully refilled */
+ BIT_DStream_endOfBuffer = 1, /* still some bits left in bitstream */
+ BIT_DStream_completed = 2, /* bitstream entirely consumed, bit-exact */
+ BIT_DStream_overflow = 3 /* user requested more bits than present in bitstream */
+ } BIT_DStream_status; /* result of BIT_reloadDStream() */
+
+MEM_STATIC size_t BIT_initDStream(BIT_DStream_t* bitD, const void* srcBuffer, size_t srcSize);
+MEM_STATIC size_t BIT_readBits(BIT_DStream_t* bitD, unsigned nbBits);
+MEM_STATIC BIT_DStream_status BIT_reloadDStream(BIT_DStream_t* bitD);
+MEM_STATIC unsigned BIT_endOfDStream(const BIT_DStream_t* bitD);
+
+
+/* Start by invoking BIT_initDStream().
+* A chunk of the bitStream is then stored into a local register.
+* Local register size is 64-bits on 64-bits systems, 32-bits on 32-bits systems (BitContainerType).
+* You can then retrieve bitFields stored into the local register, **in reverse order**.
+* Local register is explicitly reloaded from memory by the BIT_reloadDStream() method.
+* A reload guarantee a minimum of ((8*sizeof(bitD->bitContainer))-7) bits when its result is BIT_DStream_unfinished.
+* Otherwise, it can be less than that, so proceed accordingly.
+* Checking if DStream has reached its end can be performed with BIT_endOfDStream().
+*/
+
+
+/*-****************************************
+* unsafe API
+******************************************/
+MEM_STATIC void BIT_addBitsFast(BIT_CStream_t* bitC, size_t value, unsigned nbBits);
+/* faster, but works only if value is "clean", meaning all high bits above nbBits are 0 */
+
+MEM_STATIC void BIT_flushBitsFast(BIT_CStream_t* bitC);
+/* unsafe version; does not check buffer overflow */
+
+MEM_STATIC size_t BIT_readBitsFast(BIT_DStream_t* bitD, unsigned nbBits);
+/* faster, but works only if nbBits >= 1 */
+
+/*===== Local Constants =====*/
+static const unsigned BIT_mask[] = {
+ 0, 1, 3, 7, 0xF, 0x1F,
+ 0x3F, 0x7F, 0xFF, 0x1FF, 0x3FF, 0x7FF,
+ 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF, 0x1FFFF,
+ 0x3FFFF, 0x7FFFF, 0xFFFFF, 0x1FFFFF, 0x3FFFFF, 0x7FFFFF,
+ 0xFFFFFF, 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF, 0x1FFFFFFF,
+ 0x3FFFFFFF, 0x7FFFFFFF}; /* up to 31 bits */
+#define BIT_MASK_SIZE (sizeof(BIT_mask) / sizeof(BIT_mask[0]))
+
+/*-**************************************************************
+* bitStream encoding
+****************************************************************/
+/*! BIT_initCStream() :
+ * `dstCapacity` must be > sizeof(size_t)
+ * @return : 0 if success,
+ * otherwise an error code (can be tested using ERR_isError()) */
+MEM_STATIC size_t BIT_initCStream(BIT_CStream_t* bitC,
+ void* startPtr, size_t dstCapacity)
+{
+ bitC->bitContainer = 0;
+ bitC->bitPos = 0;
+ bitC->startPtr = (char*)startPtr;
+ bitC->ptr = bitC->startPtr;
+ bitC->endPtr = bitC->startPtr + dstCapacity - sizeof(bitC->bitContainer);
+ if (dstCapacity <= sizeof(bitC->bitContainer)) return ERROR(dstSize_tooSmall);
+ return 0;
+}
+
+FORCE_INLINE_TEMPLATE size_t BIT_getLowerBits(size_t bitContainer, U32 const nbBits)
+{
+#if defined(STATIC_BMI2) && STATIC_BMI2 == 1 && !defined(ZSTD_NO_INTRINSICS)
+ return _bzhi_u64(bitContainer, nbBits);
+#else
+ assert(nbBits < BIT_MASK_SIZE);
+ return bitContainer & BIT_mask[nbBits];
+#endif
+}
+
+/*! BIT_addBits() :
+ * can add up to 31 bits into `bitC`.
+ * Note : does not check for register overflow ! */
+MEM_STATIC void BIT_addBits(BIT_CStream_t* bitC,
+ size_t value, unsigned nbBits)
+{
+ DEBUG_STATIC_ASSERT(BIT_MASK_SIZE == 32);
+ assert(nbBits < BIT_MASK_SIZE);
+ assert(nbBits + bitC->bitPos < sizeof(bitC->bitContainer) * 8);
+ bitC->bitContainer |= BIT_getLowerBits(value, nbBits) << bitC->bitPos;
+ bitC->bitPos += nbBits;
+}
+
+/*! BIT_addBitsFast() :
+ * works only if `value` is _clean_,
+ * meaning all high bits above nbBits are 0 */
+MEM_STATIC void BIT_addBitsFast(BIT_CStream_t* bitC,
+ size_t value, unsigned nbBits)
+{
+ assert((value>>nbBits) == 0);
+ assert(nbBits + bitC->bitPos < sizeof(bitC->bitContainer) * 8);
+ bitC->bitContainer |= value << bitC->bitPos;
+ bitC->bitPos += nbBits;
+}
+
+/*! BIT_flushBitsFast() :
+ * assumption : bitContainer has not overflowed
+ * unsafe version; does not check buffer overflow */
+MEM_STATIC void BIT_flushBitsFast(BIT_CStream_t* bitC)
+{
+ size_t const nbBytes = bitC->bitPos >> 3;
+ assert(bitC->bitPos < sizeof(bitC->bitContainer) * 8);
+ assert(bitC->ptr <= bitC->endPtr);
+ MEM_writeLEST(bitC->ptr, bitC->bitContainer);
+ bitC->ptr += nbBytes;
+ bitC->bitPos &= 7;
+ bitC->bitContainer >>= nbBytes*8;
+}
+
+/*! BIT_flushBits() :
+ * assumption : bitContainer has not overflowed
+ * safe version; check for buffer overflow, and prevents it.
+ * note : does not signal buffer overflow.
+ * overflow will be revealed later on using BIT_closeCStream() */
+MEM_STATIC void BIT_flushBits(BIT_CStream_t* bitC)
+{
+ size_t const nbBytes = bitC->bitPos >> 3;
+ assert(bitC->bitPos < sizeof(bitC->bitContainer) * 8);
+ assert(bitC->ptr <= bitC->endPtr);
+ MEM_writeLEST(bitC->ptr, bitC->bitContainer);
+ bitC->ptr += nbBytes;
+ if (bitC->ptr > bitC->endPtr) bitC->ptr = bitC->endPtr;
+ bitC->bitPos &= 7;
+ bitC->bitContainer >>= nbBytes*8;
+}
+
+/*! BIT_closeCStream() :
+ * @return : size of CStream, in bytes,
+ * or 0 if it could not fit into dstBuffer */
+MEM_STATIC size_t BIT_closeCStream(BIT_CStream_t* bitC)
+{
+ BIT_addBitsFast(bitC, 1, 1); /* endMark */
+ BIT_flushBits(bitC);
+ if (bitC->ptr >= bitC->endPtr) return 0; /* overflow detected */
+ return (bitC->ptr - bitC->startPtr) + (bitC->bitPos > 0);
+}
+
+
+/*-********************************************************
+* bitStream decoding
+**********************************************************/
+/*! BIT_initDStream() :
+ * Initialize a BIT_DStream_t.
+ * `bitD` : a pointer to an already allocated BIT_DStream_t structure.
+ * `srcSize` must be the *exact* size of the bitStream, in bytes.
+ * @return : size of stream (== srcSize), or an errorCode if a problem is detected
+ */
+MEM_STATIC size_t BIT_initDStream(BIT_DStream_t* bitD, const void* srcBuffer, size_t srcSize)
+{
+ if (srcSize < 1) { ZSTD_memset(bitD, 0, sizeof(*bitD)); return ERROR(srcSize_wrong); }
+
+ bitD->start = (const char*)srcBuffer;
+ bitD->limitPtr = bitD->start + sizeof(bitD->bitContainer);
+
+ if (srcSize >= sizeof(bitD->bitContainer)) { /* normal case */
+ bitD->ptr = (const char*)srcBuffer + srcSize - sizeof(bitD->bitContainer);
+ bitD->bitContainer = MEM_readLEST(bitD->ptr);
+ { BYTE const lastByte = ((const BYTE*)srcBuffer)[srcSize-1];
+ bitD->bitsConsumed = lastByte ? 8 - ZSTD_highbit32(lastByte) : 0; /* ensures bitsConsumed is always set */
+ if (lastByte == 0) return ERROR(GENERIC); /* endMark not present */ }
+ } else {
+ bitD->ptr = bitD->start;
+ bitD->bitContainer = *(const BYTE*)(bitD->start);
+ switch(srcSize)
+ {
+ case 7: bitD->bitContainer += (BitContainerType)(((const BYTE*)(srcBuffer))[6]) << (sizeof(bitD->bitContainer)*8 - 16);
+ ZSTD_FALLTHROUGH;
+
+ case 6: bitD->bitContainer += (BitContainerType)(((const BYTE*)(srcBuffer))[5]) << (sizeof(bitD->bitContainer)*8 - 24);
+ ZSTD_FALLTHROUGH;
+
+ case 5: bitD->bitContainer += (BitContainerType)(((const BYTE*)(srcBuffer))[4]) << (sizeof(bitD->bitContainer)*8 - 32);
+ ZSTD_FALLTHROUGH;
+
+ case 4: bitD->bitContainer += (BitContainerType)(((const BYTE*)(srcBuffer))[3]) << 24;
+ ZSTD_FALLTHROUGH;
+
+ case 3: bitD->bitContainer += (BitContainerType)(((const BYTE*)(srcBuffer))[2]) << 16;
+ ZSTD_FALLTHROUGH;
+
+ case 2: bitD->bitContainer += (BitContainerType)(((const BYTE*)(srcBuffer))[1]) << 8;
+ ZSTD_FALLTHROUGH;
+
+ default: break;
+ }
+ { BYTE const lastByte = ((const BYTE*)srcBuffer)[srcSize-1];
+ bitD->bitsConsumed = lastByte ? 8 - ZSTD_highbit32(lastByte) : 0;
+ if (lastByte == 0) return ERROR(corruption_detected); /* endMark not present */
+ }
+ bitD->bitsConsumed += (U32)(sizeof(bitD->bitContainer) - srcSize)*8;
+ }
+
+ return srcSize;
+}
+
+FORCE_INLINE_TEMPLATE size_t BIT_getUpperBits(BitContainerType bitContainer, U32 const start)
+{
+ return bitContainer >> start;
+}
+
+FORCE_INLINE_TEMPLATE size_t BIT_getMiddleBits(BitContainerType bitContainer, U32 const start, U32 const nbBits)
+{
+ U32 const regMask = sizeof(bitContainer)*8 - 1;
+ /* if start > regMask, bitstream is corrupted, and result is undefined */
+ assert(nbBits < BIT_MASK_SIZE);
+ /* x86 transform & ((1 << nbBits) - 1) to bzhi instruction, it is better
+ * than accessing memory. When bmi2 instruction is not present, we consider
+ * such cpus old (pre-Haswell, 2013) and their performance is not of that
+ * importance.
+ */
+#if defined(__x86_64__) || defined(_M_X86)
+ return (bitContainer >> (start & regMask)) & ((((U64)1) << nbBits) - 1);
+#else
+ return (bitContainer >> (start & regMask)) & BIT_mask[nbBits];
+#endif
+}
+
+/*! BIT_lookBits() :
+ * Provides next n bits from local register.
+ * local register is not modified.
+ * On 32-bits, maxNbBits==24.
+ * On 64-bits, maxNbBits==56.
+ * @return : value extracted */
+FORCE_INLINE_TEMPLATE size_t BIT_lookBits(const BIT_DStream_t* bitD, U32 nbBits)
+{
+ /* arbitrate between double-shift and shift+mask */
+#if 1
+ /* if bitD->bitsConsumed + nbBits > sizeof(bitD->bitContainer)*8,
+ * bitstream is likely corrupted, and result is undefined */
+ return BIT_getMiddleBits(bitD->bitContainer, (sizeof(bitD->bitContainer)*8) - bitD->bitsConsumed - nbBits, nbBits);
+#else
+ /* this code path is slower on my os-x laptop */
+ U32 const regMask = sizeof(bitD->bitContainer)*8 - 1;
+ return ((bitD->bitContainer << (bitD->bitsConsumed & regMask)) >> 1) >> ((regMask-nbBits) & regMask);
+#endif
+}
+
+/*! BIT_lookBitsFast() :
+ * unsafe version; only works if nbBits >= 1 */
+MEM_STATIC size_t BIT_lookBitsFast(const BIT_DStream_t* bitD, U32 nbBits)
+{
+ U32 const regMask = sizeof(bitD->bitContainer)*8 - 1;
+ assert(nbBits >= 1);
+ return (bitD->bitContainer << (bitD->bitsConsumed & regMask)) >> (((regMask+1)-nbBits) & regMask);
+}
+
+FORCE_INLINE_TEMPLATE void BIT_skipBits(BIT_DStream_t* bitD, U32 nbBits)
+{
+ bitD->bitsConsumed += nbBits;
+}
+
+/*! BIT_readBits() :
+ * Read (consume) next n bits from local register and update.
+ * Pay attention to not read more than nbBits contained into local register.
+ * @return : extracted value. */
+FORCE_INLINE_TEMPLATE size_t BIT_readBits(BIT_DStream_t* bitD, unsigned nbBits)
+{
+ size_t const value = BIT_lookBits(bitD, nbBits);
+ BIT_skipBits(bitD, nbBits);
+ return value;
+}
+
+/*! BIT_readBitsFast() :
+ * unsafe version; only works if nbBits >= 1 */
+MEM_STATIC size_t BIT_readBitsFast(BIT_DStream_t* bitD, unsigned nbBits)
+{
+ size_t const value = BIT_lookBitsFast(bitD, nbBits);
+ assert(nbBits >= 1);
+ BIT_skipBits(bitD, nbBits);
+ return value;
+}
+
+/*! BIT_reloadDStream_internal() :
+ * Simple variant of BIT_reloadDStream(), with two conditions:
+ * 1. bitstream is valid : bitsConsumed <= sizeof(bitD->bitContainer)*8
+ * 2. look window is valid after shifted down : bitD->ptr >= bitD->start
+ */
+MEM_STATIC BIT_DStream_status BIT_reloadDStream_internal(BIT_DStream_t* bitD)
+{
+ assert(bitD->bitsConsumed <= sizeof(bitD->bitContainer)*8);
+ bitD->ptr -= bitD->bitsConsumed >> 3;
+ assert(bitD->ptr >= bitD->start);
+ bitD->bitsConsumed &= 7;
+ bitD->bitContainer = MEM_readLEST(bitD->ptr);
+ return BIT_DStream_unfinished;
+}
+
+/*! BIT_reloadDStreamFast() :
+ * Similar to BIT_reloadDStream(), but with two differences:
+ * 1. bitsConsumed <= sizeof(bitD->bitContainer)*8 must hold!
+ * 2. Returns BIT_DStream_overflow when bitD->ptr < bitD->limitPtr, at this
+ * point you must use BIT_reloadDStream() to reload.
+ */
+MEM_STATIC BIT_DStream_status BIT_reloadDStreamFast(BIT_DStream_t* bitD)
+{
+ if (UNLIKELY(bitD->ptr < bitD->limitPtr))
+ return BIT_DStream_overflow;
+ return BIT_reloadDStream_internal(bitD);
+}
+
+/*! BIT_reloadDStream() :
+ * Refill `bitD` from buffer previously set in BIT_initDStream() .
+ * This function is safe, it guarantees it will not never beyond src buffer.
+ * @return : status of `BIT_DStream_t` internal register.
+ * when status == BIT_DStream_unfinished, internal register is filled with at least 25 or 57 bits */
+FORCE_INLINE_TEMPLATE BIT_DStream_status BIT_reloadDStream(BIT_DStream_t* bitD)
+{
+ /* note : once in overflow mode, a bitstream remains in this mode until it's reset */
+ if (UNLIKELY(bitD->bitsConsumed > (sizeof(bitD->bitContainer)*8))) {
+ static const BitContainerType zeroFilled = 0;
+ bitD->ptr = (const char*)&zeroFilled; /* aliasing is allowed for char */
+ /* overflow detected, erroneous scenario or end of stream: no update */
+ return BIT_DStream_overflow;
+ }
+
+ assert(bitD->ptr >= bitD->start);
+
+ if (bitD->ptr >= bitD->limitPtr) {
+ return BIT_reloadDStream_internal(bitD);
+ }
+ if (bitD->ptr == bitD->start) {
+ /* reached end of bitStream => no update */
+ if (bitD->bitsConsumed < sizeof(bitD->bitContainer)*8) return BIT_DStream_endOfBuffer;
+ return BIT_DStream_completed;
+ }
+ /* start < ptr < limitPtr => cautious update */
+ { U32 nbBytes = bitD->bitsConsumed >> 3;
+ BIT_DStream_status result = BIT_DStream_unfinished;
+ if (bitD->ptr - nbBytes < bitD->start) {
+ nbBytes = (U32)(bitD->ptr - bitD->start); /* ptr > start */
+ result = BIT_DStream_endOfBuffer;
+ }
+ bitD->ptr -= nbBytes;
+ bitD->bitsConsumed -= nbBytes*8;
+ bitD->bitContainer = MEM_readLEST(bitD->ptr); /* reminder : srcSize > sizeof(bitD->bitContainer), otherwise bitD->ptr == bitD->start */
+ return result;
+ }
+}
+
+/*! BIT_endOfDStream() :
+ * @return : 1 if DStream has _exactly_ reached its end (all bits consumed).
+ */
+MEM_STATIC unsigned BIT_endOfDStream(const BIT_DStream_t* DStream)
+{
+ return ((DStream->ptr == DStream->start) && (DStream->bitsConsumed == sizeof(DStream->bitContainer)*8));
+}
+
+#if defined (__cplusplus)
+}
+#endif
+
+#endif /* BITSTREAM_H_MODULE */
diff --git a/third_party/zstd/lib/common/compiler.h b/third_party/zstd/lib/common/compiler.h
new file mode 100644
index 0000000000..31880ecbe1
--- /dev/null
+++ b/third_party/zstd/lib/common/compiler.h
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#ifndef ZSTD_COMPILER_H
+#define ZSTD_COMPILER_H
+
+#include <stddef.h>
+
+#include "portability_macros.h"
+
+/*-*******************************************************
+* Compiler specifics
+*********************************************************/
+/* force inlining */
+
+#if !defined(ZSTD_NO_INLINE)
+#if (defined(__GNUC__) && !defined(__STRICT_ANSI__)) || defined(__cplusplus) || defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */
+# define INLINE_KEYWORD inline
+#else
+# define INLINE_KEYWORD
+#endif
+
+#if defined(__GNUC__) || defined(__ICCARM__)
+# define FORCE_INLINE_ATTR __attribute__((always_inline))
+#elif defined(_MSC_VER)
+# define FORCE_INLINE_ATTR __forceinline
+#else
+# define FORCE_INLINE_ATTR
+#endif
+
+#else
+
+#define INLINE_KEYWORD
+#define FORCE_INLINE_ATTR
+
+#endif
+
+/**
+ On MSVC qsort requires that functions passed into it use the __cdecl calling conversion(CC).
+ This explicitly marks such functions as __cdecl so that the code will still compile
+ if a CC other than __cdecl has been made the default.
+*/
+#if defined(_MSC_VER)
+# define WIN_CDECL __cdecl
+#else
+# define WIN_CDECL
+#endif
+
+/* UNUSED_ATTR tells the compiler it is okay if the function is unused. */
+#if defined(__GNUC__)
+# define UNUSED_ATTR __attribute__((unused))
+#else
+# define UNUSED_ATTR
+#endif
+
+/**
+ * FORCE_INLINE_TEMPLATE is used to define C "templates", which take constant
+ * parameters. They must be inlined for the compiler to eliminate the constant
+ * branches.
+ */
+#define FORCE_INLINE_TEMPLATE static INLINE_KEYWORD FORCE_INLINE_ATTR UNUSED_ATTR
+/**
+ * HINT_INLINE is used to help the compiler generate better code. It is *not*
+ * used for "templates", so it can be tweaked based on the compilers
+ * performance.
+ *
+ * gcc-4.8 and gcc-4.9 have been shown to benefit from leaving off the
+ * always_inline attribute.
+ *
+ * clang up to 5.0.0 (trunk) benefit tremendously from the always_inline
+ * attribute.
+ */
+#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 8 && __GNUC__ < 5
+# define HINT_INLINE static INLINE_KEYWORD
+#else
+# define HINT_INLINE FORCE_INLINE_TEMPLATE
+#endif
+
+/* "soft" inline :
+ * The compiler is free to select if it's a good idea to inline or not.
+ * The main objective is to silence compiler warnings
+ * when a defined function in included but not used.
+ *
+ * Note : this macro is prefixed `MEM_` because it used to be provided by `mem.h` unit.
+ * Updating the prefix is probably preferable, but requires a fairly large codemod,
+ * since this name is used everywhere.
+ */
+#ifndef MEM_STATIC /* already defined in Linux Kernel mem.h */
+#if defined(__GNUC__)
+# define MEM_STATIC static __inline UNUSED_ATTR
+#elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
+# define MEM_STATIC static inline
+#elif defined(_MSC_VER)
+# define MEM_STATIC static __inline
+#else
+# define MEM_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */
+#endif
+#endif
+
+/* force no inlining */
+#ifdef _MSC_VER
+# define FORCE_NOINLINE static __declspec(noinline)
+#else
+# if defined(__GNUC__) || defined(__ICCARM__)
+# define FORCE_NOINLINE static __attribute__((__noinline__))
+# else
+# define FORCE_NOINLINE static
+# endif
+#endif
+
+
+/* target attribute */
+#if defined(__GNUC__) || defined(__ICCARM__)
+# define TARGET_ATTRIBUTE(target) __attribute__((__target__(target)))
+#else
+# define TARGET_ATTRIBUTE(target)
+#endif
+
+/* Target attribute for BMI2 dynamic dispatch.
+ * Enable lzcnt, bmi, and bmi2.
+ * We test for bmi1 & bmi2. lzcnt is included in bmi1.
+ */
+#define BMI2_TARGET_ATTRIBUTE TARGET_ATTRIBUTE("lzcnt,bmi,bmi2")
+
+/* prefetch
+ * can be disabled, by declaring NO_PREFETCH build macro */
+#if defined(NO_PREFETCH)
+# define PREFETCH_L1(ptr) do { (void)(ptr); } while (0) /* disabled */
+# define PREFETCH_L2(ptr) do { (void)(ptr); } while (0) /* disabled */
+#else
+# if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_I86)) && !defined(_M_ARM64EC) /* _mm_prefetch() is not defined outside of x86/x64 */
+# include <mmintrin.h> /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */
+# define PREFETCH_L1(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T0)
+# define PREFETCH_L2(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T1)
+# elif defined(__GNUC__) && ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) )
+# define PREFETCH_L1(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 3 /* locality */)
+# define PREFETCH_L2(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 2 /* locality */)
+# elif defined(__aarch64__)
+# define PREFETCH_L1(ptr) do { __asm__ __volatile__("prfm pldl1keep, %0" ::"Q"(*(ptr))); } while (0)
+# define PREFETCH_L2(ptr) do { __asm__ __volatile__("prfm pldl2keep, %0" ::"Q"(*(ptr))); } while (0)
+# else
+# define PREFETCH_L1(ptr) do { (void)(ptr); } while (0) /* disabled */
+# define PREFETCH_L2(ptr) do { (void)(ptr); } while (0) /* disabled */
+# endif
+#endif /* NO_PREFETCH */
+
+#define CACHELINE_SIZE 64
+
+#define PREFETCH_AREA(p, s) \
+ do { \
+ const char* const _ptr = (const char*)(p); \
+ size_t const _size = (size_t)(s); \
+ size_t _pos; \
+ for (_pos=0; _pos<_size; _pos+=CACHELINE_SIZE) { \
+ PREFETCH_L2(_ptr + _pos); \
+ } \
+ } while (0)
+
+/* vectorization
+ * older GCC (pre gcc-4.3 picked as the cutoff) uses a different syntax,
+ * and some compilers, like Intel ICC and MCST LCC, do not support it at all. */
+#if !defined(__INTEL_COMPILER) && !defined(__clang__) && defined(__GNUC__) && !defined(__LCC__)
+# if (__GNUC__ == 4 && __GNUC_MINOR__ > 3) || (__GNUC__ >= 5)
+# define DONT_VECTORIZE __attribute__((optimize("no-tree-vectorize")))
+# else
+# define DONT_VECTORIZE _Pragma("GCC optimize(\"no-tree-vectorize\")")
+# endif
+#else
+# define DONT_VECTORIZE
+#endif
+
+/* Tell the compiler that a branch is likely or unlikely.
+ * Only use these macros if it causes the compiler to generate better code.
+ * If you can remove a LIKELY/UNLIKELY annotation without speed changes in gcc
+ * and clang, please do.
+ */
+#if defined(__GNUC__)
+#define LIKELY(x) (__builtin_expect((x), 1))
+#define UNLIKELY(x) (__builtin_expect((x), 0))
+#else
+#define LIKELY(x) (x)
+#define UNLIKELY(x) (x)
+#endif
+
+#if __has_builtin(__builtin_unreachable) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)))
+# define ZSTD_UNREACHABLE do { assert(0), __builtin_unreachable(); } while (0)
+#else
+# define ZSTD_UNREACHABLE do { assert(0); } while (0)
+#endif
+
+/* disable warnings */
+#ifdef _MSC_VER /* Visual Studio */
+# include <intrin.h> /* For Visual 2005 */
+# pragma warning(disable : 4100) /* disable: C4100: unreferenced formal parameter */
+# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
+# pragma warning(disable : 4204) /* disable: C4204: non-constant aggregate initializer */
+# pragma warning(disable : 4214) /* disable: C4214: non-int bitfields */
+# pragma warning(disable : 4324) /* disable: C4324: padded structure */
+#endif
+
+/*Like DYNAMIC_BMI2 but for compile time determination of BMI2 support*/
+#ifndef STATIC_BMI2
+# if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_I86))
+# ifdef __AVX2__ //MSVC does not have a BMI2 specific flag, but every CPU that supports AVX2 also supports BMI2
+# define STATIC_BMI2 1
+# endif
+# elif defined(__BMI2__) && defined(__x86_64__) && defined(__GNUC__)
+# define STATIC_BMI2 1
+# endif
+#endif
+
+#ifndef STATIC_BMI2
+ #define STATIC_BMI2 0
+#endif
+
+/* compile time determination of SIMD support */
+#if !defined(ZSTD_NO_INTRINSICS)
+# if defined(__SSE2__) || defined(_M_AMD64) || (defined (_M_IX86) && defined(_M_IX86_FP) && (_M_IX86_FP >= 2))
+# define ZSTD_ARCH_X86_SSE2
+# endif
+# if defined(__ARM_NEON) || defined(_M_ARM64)
+# define ZSTD_ARCH_ARM_NEON
+# endif
+#
+# if defined(ZSTD_ARCH_X86_SSE2)
+# include <emmintrin.h>
+# elif defined(ZSTD_ARCH_ARM_NEON)
+# include <arm_neon.h>
+# endif
+#endif
+
+/* C-language Attributes are added in C23. */
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ > 201710L) && defined(__has_c_attribute)
+# define ZSTD_HAS_C_ATTRIBUTE(x) __has_c_attribute(x)
+#else
+# define ZSTD_HAS_C_ATTRIBUTE(x) 0
+#endif
+
+/* Only use C++ attributes in C++. Some compilers report support for C++
+ * attributes when compiling with C.
+ */
+#if defined(__cplusplus) && defined(__has_cpp_attribute)
+# define ZSTD_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
+#else
+# define ZSTD_HAS_CPP_ATTRIBUTE(x) 0
+#endif
+
+/* Define ZSTD_FALLTHROUGH macro for annotating switch case with the 'fallthrough' attribute.
+ * - C23: https://en.cppreference.com/w/c/language/attributes/fallthrough
+ * - CPP17: https://en.cppreference.com/w/cpp/language/attributes/fallthrough
+ * - Else: __attribute__((__fallthrough__))
+ */
+#ifndef ZSTD_FALLTHROUGH
+# if ZSTD_HAS_C_ATTRIBUTE(fallthrough)
+# define ZSTD_FALLTHROUGH [[fallthrough]]
+# elif ZSTD_HAS_CPP_ATTRIBUTE(fallthrough)
+# define ZSTD_FALLTHROUGH [[fallthrough]]
+# elif __has_attribute(__fallthrough__)
+/* Leading semicolon is to satisfy gcc-11 with -pedantic. Without the semicolon
+ * gcc complains about: a label can only be part of a statement and a declaration is not a statement.
+ */
+# define ZSTD_FALLTHROUGH ; __attribute__((__fallthrough__))
+# else
+# define ZSTD_FALLTHROUGH
+# endif
+#endif
+
+/*-**************************************************************
+* Alignment check
+*****************************************************************/
+
+/* this test was initially positioned in mem.h,
+ * but this file is removed (or replaced) for linux kernel
+ * so it's now hosted in compiler.h,
+ * which remains valid for both user & kernel spaces.
+ */
+
+#ifndef ZSTD_ALIGNOF
+# if defined(__GNUC__) || defined(_MSC_VER)
+/* covers gcc, clang & MSVC */
+/* note : this section must come first, before C11,
+ * due to a limitation in the kernel source generator */
+# define ZSTD_ALIGNOF(T) __alignof(T)
+
+# elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
+/* C11 support */
+# include <stdalign.h>
+# define ZSTD_ALIGNOF(T) alignof(T)
+
+# else
+/* No known support for alignof() - imperfect backup */
+# define ZSTD_ALIGNOF(T) (sizeof(void*) < sizeof(T) ? sizeof(void*) : sizeof(T))
+
+# endif
+#endif /* ZSTD_ALIGNOF */
+
+/*-**************************************************************
+* Sanitizer
+*****************************************************************/
+
+/**
+ * Zstd relies on pointer overflow in its decompressor.
+ * We add this attribute to functions that rely on pointer overflow.
+ */
+#ifndef ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
+# if __has_attribute(no_sanitize)
+# if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 8
+ /* gcc < 8 only has signed-integer-overlow which triggers on pointer overflow */
+# define ZSTD_ALLOW_POINTER_OVERFLOW_ATTR __attribute__((no_sanitize("signed-integer-overflow")))
+# else
+ /* older versions of clang [3.7, 5.0) will warn that pointer-overflow is ignored. */
+# define ZSTD_ALLOW_POINTER_OVERFLOW_ATTR __attribute__((no_sanitize("pointer-overflow")))
+# endif
+# else
+# define ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
+# endif
+#endif
+
+/**
+ * Helper function to perform a wrapped pointer difference without trigging
+ * UBSAN.
+ *
+ * @returns lhs - rhs with wrapping
+ */
+MEM_STATIC
+ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
+ptrdiff_t ZSTD_wrappedPtrDiff(unsigned char const* lhs, unsigned char const* rhs)
+{
+ return lhs - rhs;
+}
+
+/**
+ * Helper function to perform a wrapped pointer add without triggering UBSAN.
+ *
+ * @return ptr + add with wrapping
+ */
+MEM_STATIC
+ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
+unsigned char const* ZSTD_wrappedPtrAdd(unsigned char const* ptr, ptrdiff_t add)
+{
+ return ptr + add;
+}
+
+/**
+ * Helper function to perform a wrapped pointer subtraction without triggering
+ * UBSAN.
+ *
+ * @return ptr - sub with wrapping
+ */
+MEM_STATIC
+ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
+unsigned char const* ZSTD_wrappedPtrSub(unsigned char const* ptr, ptrdiff_t sub)
+{
+ return ptr - sub;
+}
+
+/**
+ * Helper function to add to a pointer that works around C's undefined behavior
+ * of adding 0 to NULL.
+ *
+ * @returns `ptr + add` except it defines `NULL + 0 == NULL`.
+ */
+MEM_STATIC
+unsigned char* ZSTD_maybeNullPtrAdd(unsigned char* ptr, ptrdiff_t add)
+{
+ return add > 0 ? ptr + add : ptr;
+}
+
+/* Issue #3240 reports an ASAN failure on an llvm-mingw build. Out of an
+ * abundance of caution, disable our custom poisoning on mingw. */
+#ifdef __MINGW32__
+#ifndef ZSTD_ASAN_DONT_POISON_WORKSPACE
+#define ZSTD_ASAN_DONT_POISON_WORKSPACE 1
+#endif
+#ifndef ZSTD_MSAN_DONT_POISON_WORKSPACE
+#define ZSTD_MSAN_DONT_POISON_WORKSPACE 1
+#endif
+#endif
+
+#if ZSTD_MEMORY_SANITIZER && !defined(ZSTD_MSAN_DONT_POISON_WORKSPACE)
+/* Not all platforms that support msan provide sanitizers/msan_interface.h.
+ * We therefore declare the functions we need ourselves, rather than trying to
+ * include the header file... */
+#include <stddef.h> /* size_t */
+#define ZSTD_DEPS_NEED_STDINT
+#include "zstd_deps.h" /* intptr_t */
+
+/* Make memory region fully initialized (without changing its contents). */
+void __msan_unpoison(const volatile void *a, size_t size);
+
+/* Make memory region fully uninitialized (without changing its contents).
+ This is a legacy interface that does not update origin information. Use
+ __msan_allocated_memory() instead. */
+void __msan_poison(const volatile void *a, size_t size);
+
+/* Returns the offset of the first (at least partially) poisoned byte in the
+ memory range, or -1 if the whole range is good. */
+intptr_t __msan_test_shadow(const volatile void *x, size_t size);
+
+/* Print shadow and origin for the memory range to stderr in a human-readable
+ format. */
+void __msan_print_shadow(const volatile void *x, size_t size);
+#endif
+
+#if ZSTD_ADDRESS_SANITIZER && !defined(ZSTD_ASAN_DONT_POISON_WORKSPACE)
+/* Not all platforms that support asan provide sanitizers/asan_interface.h.
+ * We therefore declare the functions we need ourselves, rather than trying to
+ * include the header file... */
+#include <stddef.h> /* size_t */
+
+/**
+ * Marks a memory region (<c>[addr, addr+size)</c>) as unaddressable.
+ *
+ * This memory must be previously allocated by your program. Instrumented
+ * code is forbidden from accessing addresses in this region until it is
+ * unpoisoned. This function is not guaranteed to poison the entire region -
+ * it could poison only a subregion of <c>[addr, addr+size)</c> due to ASan
+ * alignment restrictions.
+ *
+ * \note This function is not thread-safe because no two threads can poison or
+ * unpoison memory in the same memory region simultaneously.
+ *
+ * \param addr Start of memory region.
+ * \param size Size of memory region. */
+void __asan_poison_memory_region(void const volatile *addr, size_t size);
+
+/**
+ * Marks a memory region (<c>[addr, addr+size)</c>) as addressable.
+ *
+ * This memory must be previously allocated by your program. Accessing
+ * addresses in this region is allowed until this region is poisoned again.
+ * This function could unpoison a super-region of <c>[addr, addr+size)</c> due
+ * to ASan alignment restrictions.
+ *
+ * \note This function is not thread-safe because no two threads can
+ * poison or unpoison memory in the same memory region simultaneously.
+ *
+ * \param addr Start of memory region.
+ * \param size Size of memory region. */
+void __asan_unpoison_memory_region(void const volatile *addr, size_t size);
+#endif
+
+#endif /* ZSTD_COMPILER_H */
diff --git a/third_party/zstd/lib/common/cpu.h b/third_party/zstd/lib/common/cpu.h
new file mode 100644
index 0000000000..d9cdf8febe
--- /dev/null
+++ b/third_party/zstd/lib/common/cpu.h
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#ifndef ZSTD_COMMON_CPU_H
+#define ZSTD_COMMON_CPU_H
+
+/**
+ * Implementation taken from folly/CpuId.h
+ * https://github.com/facebook/folly/blob/master/folly/CpuId.h
+ */
+
+#include "mem.h"
+
+#ifdef _MSC_VER
+#include <intrin.h>
+#endif
+
+typedef struct {
+ U32 f1c;
+ U32 f1d;
+ U32 f7b;
+ U32 f7c;
+} ZSTD_cpuid_t;
+
+MEM_STATIC ZSTD_cpuid_t ZSTD_cpuid(void) {
+ U32 f1c = 0;
+ U32 f1d = 0;
+ U32 f7b = 0;
+ U32 f7c = 0;
+#if defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86))
+#if !defined(__clang__) || __clang_major__ >= 16
+ int reg[4];
+ __cpuid((int*)reg, 0);
+ {
+ int const n = reg[0];
+ if (n >= 1) {
+ __cpuid((int*)reg, 1);
+ f1c = (U32)reg[2];
+ f1d = (U32)reg[3];
+ }
+ if (n >= 7) {
+ __cpuidex((int*)reg, 7, 0);
+ f7b = (U32)reg[1];
+ f7c = (U32)reg[2];
+ }
+ }
+#else
+ /* Clang compiler has a bug (fixed in https://reviews.llvm.org/D101338) in
+ * which the `__cpuid` intrinsic does not save and restore `rbx` as it needs
+ * to due to being a reserved register. So in that case, do the `cpuid`
+ * ourselves. Clang supports inline assembly anyway.
+ */
+ U32 n;
+ __asm__(
+ "pushq %%rbx\n\t"
+ "cpuid\n\t"
+ "popq %%rbx\n\t"
+ : "=a"(n)
+ : "a"(0)
+ : "rcx", "rdx");
+ if (n >= 1) {
+ U32 f1a;
+ __asm__(
+ "pushq %%rbx\n\t"
+ "cpuid\n\t"
+ "popq %%rbx\n\t"
+ : "=a"(f1a), "=c"(f1c), "=d"(f1d)
+ : "a"(1)
+ :);
+ }
+ if (n >= 7) {
+ __asm__(
+ "pushq %%rbx\n\t"
+ "cpuid\n\t"
+ "movq %%rbx, %%rax\n\t"
+ "popq %%rbx"
+ : "=a"(f7b), "=c"(f7c)
+ : "a"(7), "c"(0)
+ : "rdx");
+ }
+#endif
+#elif defined(__i386__) && defined(__PIC__) && !defined(__clang__) && defined(__GNUC__)
+ /* The following block like the normal cpuid branch below, but gcc
+ * reserves ebx for use of its pic register so we must specially
+ * handle the save and restore to avoid clobbering the register
+ */
+ U32 n;
+ __asm__(
+ "pushl %%ebx\n\t"
+ "cpuid\n\t"
+ "popl %%ebx\n\t"
+ : "=a"(n)
+ : "a"(0)
+ : "ecx", "edx");
+ if (n >= 1) {
+ U32 f1a;
+ __asm__(
+ "pushl %%ebx\n\t"
+ "cpuid\n\t"
+ "popl %%ebx\n\t"
+ : "=a"(f1a), "=c"(f1c), "=d"(f1d)
+ : "a"(1));
+ }
+ if (n >= 7) {
+ __asm__(
+ "pushl %%ebx\n\t"
+ "cpuid\n\t"
+ "movl %%ebx, %%eax\n\t"
+ "popl %%ebx"
+ : "=a"(f7b), "=c"(f7c)
+ : "a"(7), "c"(0)
+ : "edx");
+ }
+#elif defined(__x86_64__) || defined(_M_X64) || defined(__i386__)
+ U32 n;
+ __asm__("cpuid" : "=a"(n) : "a"(0) : "ebx", "ecx", "edx");
+ if (n >= 1) {
+ U32 f1a;
+ __asm__("cpuid" : "=a"(f1a), "=c"(f1c), "=d"(f1d) : "a"(1) : "ebx");
+ }
+ if (n >= 7) {
+ U32 f7a;
+ __asm__("cpuid"
+ : "=a"(f7a), "=b"(f7b), "=c"(f7c)
+ : "a"(7), "c"(0)
+ : "edx");
+ }
+#endif
+ {
+ ZSTD_cpuid_t cpuid;
+ cpuid.f1c = f1c;
+ cpuid.f1d = f1d;
+ cpuid.f7b = f7b;
+ cpuid.f7c = f7c;
+ return cpuid;
+ }
+}
+
+#define X(name, r, bit) \
+ MEM_STATIC int ZSTD_cpuid_##name(ZSTD_cpuid_t const cpuid) { \
+ return ((cpuid.r) & (1U << bit)) != 0; \
+ }
+
+/* cpuid(1): Processor Info and Feature Bits. */
+#define C(name, bit) X(name, f1c, bit)
+ C(sse3, 0)
+ C(pclmuldq, 1)
+ C(dtes64, 2)
+ C(monitor, 3)
+ C(dscpl, 4)
+ C(vmx, 5)
+ C(smx, 6)
+ C(eist, 7)
+ C(tm2, 8)
+ C(ssse3, 9)
+ C(cnxtid, 10)
+ C(fma, 12)
+ C(cx16, 13)
+ C(xtpr, 14)
+ C(pdcm, 15)
+ C(pcid, 17)
+ C(dca, 18)
+ C(sse41, 19)
+ C(sse42, 20)
+ C(x2apic, 21)
+ C(movbe, 22)
+ C(popcnt, 23)
+ C(tscdeadline, 24)
+ C(aes, 25)
+ C(xsave, 26)
+ C(osxsave, 27)
+ C(avx, 28)
+ C(f16c, 29)
+ C(rdrand, 30)
+#undef C
+#define D(name, bit) X(name, f1d, bit)
+ D(fpu, 0)
+ D(vme, 1)
+ D(de, 2)
+ D(pse, 3)
+ D(tsc, 4)
+ D(msr, 5)
+ D(pae, 6)
+ D(mce, 7)
+ D(cx8, 8)
+ D(apic, 9)
+ D(sep, 11)
+ D(mtrr, 12)
+ D(pge, 13)
+ D(mca, 14)
+ D(cmov, 15)
+ D(pat, 16)
+ D(pse36, 17)
+ D(psn, 18)
+ D(clfsh, 19)
+ D(ds, 21)
+ D(acpi, 22)
+ D(mmx, 23)
+ D(fxsr, 24)
+ D(sse, 25)
+ D(sse2, 26)
+ D(ss, 27)
+ D(htt, 28)
+ D(tm, 29)
+ D(pbe, 31)
+#undef D
+
+/* cpuid(7): Extended Features. */
+#define B(name, bit) X(name, f7b, bit)
+ B(bmi1, 3)
+ B(hle, 4)
+ B(avx2, 5)
+ B(smep, 7)
+ B(bmi2, 8)
+ B(erms, 9)
+ B(invpcid, 10)
+ B(rtm, 11)
+ B(mpx, 14)
+ B(avx512f, 16)
+ B(avx512dq, 17)
+ B(rdseed, 18)
+ B(adx, 19)
+ B(smap, 20)
+ B(avx512ifma, 21)
+ B(pcommit, 22)
+ B(clflushopt, 23)
+ B(clwb, 24)
+ B(avx512pf, 26)
+ B(avx512er, 27)
+ B(avx512cd, 28)
+ B(sha, 29)
+ B(avx512bw, 30)
+ B(avx512vl, 31)
+#undef B
+#define C(name, bit) X(name, f7c, bit)
+ C(prefetchwt1, 0)
+ C(avx512vbmi, 1)
+#undef C
+
+#undef X
+
+#endif /* ZSTD_COMMON_CPU_H */
diff --git a/third_party/zstd/lib/common/debug.c b/third_party/zstd/lib/common/debug.c
new file mode 100644
index 0000000000..9d0b7d229c
--- /dev/null
+++ b/third_party/zstd/lib/common/debug.c
@@ -0,0 +1,30 @@
+/* ******************************************************************
+ * debug
+ * Part of FSE library
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * You can contact the author at :
+ * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+****************************************************************** */
+
+
+/*
+ * This module only hosts one global variable
+ * which can be used to dynamically influence the verbosity of traces,
+ * such as DEBUGLOG and RAWLOG
+ */
+
+#include "debug.h"
+
+#if !defined(ZSTD_LINUX_KERNEL) || (DEBUGLEVEL>=2)
+/* We only use this when DEBUGLEVEL>=2, but we get -Werror=pedantic errors if a
+ * translation unit is empty. So remove this from Linux kernel builds, but
+ * otherwise just leave it in.
+ */
+int g_debuglevel = DEBUGLEVEL;
+#endif
diff --git a/third_party/zstd/lib/common/debug.h b/third_party/zstd/lib/common/debug.h
new file mode 100644
index 0000000000..a16b69e574
--- /dev/null
+++ b/third_party/zstd/lib/common/debug.h
@@ -0,0 +1,116 @@
+/* ******************************************************************
+ * debug
+ * Part of FSE library
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * You can contact the author at :
+ * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+****************************************************************** */
+
+
+/*
+ * The purpose of this header is to enable debug functions.
+ * They regroup assert(), DEBUGLOG() and RAWLOG() for run-time,
+ * and DEBUG_STATIC_ASSERT() for compile-time.
+ *
+ * By default, DEBUGLEVEL==0, which means run-time debug is disabled.
+ *
+ * Level 1 enables assert() only.
+ * Starting level 2, traces can be generated and pushed to stderr.
+ * The higher the level, the more verbose the traces.
+ *
+ * It's possible to dynamically adjust level using variable g_debug_level,
+ * which is only declared if DEBUGLEVEL>=2,
+ * and is a global variable, not multi-thread protected (use with care)
+ */
+
+#ifndef DEBUG_H_12987983217
+#define DEBUG_H_12987983217
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+
+/* static assert is triggered at compile time, leaving no runtime artefact.
+ * static assert only works with compile-time constants.
+ * Also, this variant can only be used inside a function. */
+#define DEBUG_STATIC_ASSERT(c) (void)sizeof(char[(c) ? 1 : -1])
+
+
+/* DEBUGLEVEL is expected to be defined externally,
+ * typically through compiler command line.
+ * Value must be a number. */
+#ifndef DEBUGLEVEL
+# define DEBUGLEVEL 0
+#endif
+
+
+/* recommended values for DEBUGLEVEL :
+ * 0 : release mode, no debug, all run-time checks disabled
+ * 1 : enables assert() only, no display
+ * 2 : reserved, for currently active debug path
+ * 3 : events once per object lifetime (CCtx, CDict, etc.)
+ * 4 : events once per frame
+ * 5 : events once per block
+ * 6 : events once per sequence (verbose)
+ * 7+: events at every position (*very* verbose)
+ *
+ * It's generally inconvenient to output traces > 5.
+ * In which case, it's possible to selectively trigger high verbosity levels
+ * by modifying g_debug_level.
+ */
+
+#if (DEBUGLEVEL>=1)
+# define ZSTD_DEPS_NEED_ASSERT
+# include "zstd_deps.h"
+#else
+# ifndef assert /* assert may be already defined, due to prior #include <assert.h> */
+# define assert(condition) ((void)0) /* disable assert (default) */
+# endif
+#endif
+
+#if (DEBUGLEVEL>=2)
+# define ZSTD_DEPS_NEED_IO
+# include "zstd_deps.h"
+extern int g_debuglevel; /* the variable is only declared,
+ it actually lives in debug.c,
+ and is shared by the whole process.
+ It's not thread-safe.
+ It's useful when enabling very verbose levels
+ on selective conditions (such as position in src) */
+
+# define RAWLOG(l, ...) \
+ do { \
+ if (l<=g_debuglevel) { \
+ ZSTD_DEBUG_PRINT(__VA_ARGS__); \
+ } \
+ } while (0)
+
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+#define LINE_AS_STRING TOSTRING(__LINE__)
+
+# define DEBUGLOG(l, ...) \
+ do { \
+ if (l<=g_debuglevel) { \
+ ZSTD_DEBUG_PRINT(__FILE__ ":" LINE_AS_STRING ": " __VA_ARGS__); \
+ ZSTD_DEBUG_PRINT(" \n"); \
+ } \
+ } while (0)
+#else
+# define RAWLOG(l, ...) do { } while (0) /* disabled */
+# define DEBUGLOG(l, ...) do { } while (0) /* disabled */
+#endif
+
+
+#if defined (__cplusplus)
+}
+#endif
+
+#endif /* DEBUG_H_12987983217 */
diff --git a/third_party/zstd/lib/common/entropy_common.c b/third_party/zstd/lib/common/entropy_common.c
new file mode 100644
index 0000000000..e2173afb0a
--- /dev/null
+++ b/third_party/zstd/lib/common/entropy_common.c
@@ -0,0 +1,340 @@
+/* ******************************************************************
+ * Common functions of New Generation Entropy library
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * You can contact the author at :
+ * - FSE+HUF source repository : https://github.com/Cyan4973/FiniteStateEntropy
+ * - Public forum : https://groups.google.com/forum/#!forum/lz4c
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+****************************************************************** */
+
+/* *************************************
+* Dependencies
+***************************************/
+#include "mem.h"
+#include "error_private.h" /* ERR_*, ERROR */
+#define FSE_STATIC_LINKING_ONLY /* FSE_MIN_TABLELOG */
+#include "fse.h"
+#include "huf.h"
+#include "bits.h" /* ZSDT_highbit32, ZSTD_countTrailingZeros32 */
+
+
+/*=== Version ===*/
+unsigned FSE_versionNumber(void) { return FSE_VERSION_NUMBER; }
+
+
+/*=== Error Management ===*/
+unsigned FSE_isError(size_t code) { return ERR_isError(code); }
+const char* FSE_getErrorName(size_t code) { return ERR_getErrorName(code); }
+
+unsigned HUF_isError(size_t code) { return ERR_isError(code); }
+const char* HUF_getErrorName(size_t code) { return ERR_getErrorName(code); }
+
+
+/*-**************************************************************
+* FSE NCount encoding-decoding
+****************************************************************/
+FORCE_INLINE_TEMPLATE
+size_t FSE_readNCount_body(short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr,
+ const void* headerBuffer, size_t hbSize)
+{
+ const BYTE* const istart = (const BYTE*) headerBuffer;
+ const BYTE* const iend = istart + hbSize;
+ const BYTE* ip = istart;
+ int nbBits;
+ int remaining;
+ int threshold;
+ U32 bitStream;
+ int bitCount;
+ unsigned charnum = 0;
+ unsigned const maxSV1 = *maxSVPtr + 1;
+ int previous0 = 0;
+
+ if (hbSize < 8) {
+ /* This function only works when hbSize >= 8 */
+ char buffer[8] = {0};
+ ZSTD_memcpy(buffer, headerBuffer, hbSize);
+ { size_t const countSize = FSE_readNCount(normalizedCounter, maxSVPtr, tableLogPtr,
+ buffer, sizeof(buffer));
+ if (FSE_isError(countSize)) return countSize;
+ if (countSize > hbSize) return ERROR(corruption_detected);
+ return countSize;
+ } }
+ assert(hbSize >= 8);
+
+ /* init */
+ ZSTD_memset(normalizedCounter, 0, (*maxSVPtr+1) * sizeof(normalizedCounter[0])); /* all symbols not present in NCount have a frequency of 0 */
+ bitStream = MEM_readLE32(ip);
+ nbBits = (bitStream & 0xF) + FSE_MIN_TABLELOG; /* extract tableLog */
+ if (nbBits > FSE_TABLELOG_ABSOLUTE_MAX) return ERROR(tableLog_tooLarge);
+ bitStream >>= 4;
+ bitCount = 4;
+ *tableLogPtr = nbBits;
+ remaining = (1<<nbBits)+1;
+ threshold = 1<<nbBits;
+ nbBits++;
+
+ for (;;) {
+ if (previous0) {
+ /* Count the number of repeats. Each time the
+ * 2-bit repeat code is 0b11 there is another
+ * repeat.
+ * Avoid UB by setting the high bit to 1.
+ */
+ int repeats = ZSTD_countTrailingZeros32(~bitStream | 0x80000000) >> 1;
+ while (repeats >= 12) {
+ charnum += 3 * 12;
+ if (LIKELY(ip <= iend-7)) {
+ ip += 3;
+ } else {
+ bitCount -= (int)(8 * (iend - 7 - ip));
+ bitCount &= 31;
+ ip = iend - 4;
+ }
+ bitStream = MEM_readLE32(ip) >> bitCount;
+ repeats = ZSTD_countTrailingZeros32(~bitStream | 0x80000000) >> 1;
+ }
+ charnum += 3 * repeats;
+ bitStream >>= 2 * repeats;
+ bitCount += 2 * repeats;
+
+ /* Add the final repeat which isn't 0b11. */
+ assert((bitStream & 3) < 3);
+ charnum += bitStream & 3;
+ bitCount += 2;
+
+ /* This is an error, but break and return an error
+ * at the end, because returning out of a loop makes
+ * it harder for the compiler to optimize.
+ */
+ if (charnum >= maxSV1) break;
+
+ /* We don't need to set the normalized count to 0
+ * because we already memset the whole buffer to 0.
+ */
+
+ if (LIKELY(ip <= iend-7) || (ip + (bitCount>>3) <= iend-4)) {
+ assert((bitCount >> 3) <= 3); /* For first condition to work */
+ ip += bitCount>>3;
+ bitCount &= 7;
+ } else {
+ bitCount -= (int)(8 * (iend - 4 - ip));
+ bitCount &= 31;
+ ip = iend - 4;
+ }
+ bitStream = MEM_readLE32(ip) >> bitCount;
+ }
+ {
+ int const max = (2*threshold-1) - remaining;
+ int count;
+
+ if ((bitStream & (threshold-1)) < (U32)max) {
+ count = bitStream & (threshold-1);
+ bitCount += nbBits-1;
+ } else {
+ count = bitStream & (2*threshold-1);
+ if (count >= threshold) count -= max;
+ bitCount += nbBits;
+ }
+
+ count--; /* extra accuracy */
+ /* When it matters (small blocks), this is a
+ * predictable branch, because we don't use -1.
+ */
+ if (count >= 0) {
+ remaining -= count;
+ } else {
+ assert(count == -1);
+ remaining += count;
+ }
+ normalizedCounter[charnum++] = (short)count;
+ previous0 = !count;
+
+ assert(threshold > 1);
+ if (remaining < threshold) {
+ /* This branch can be folded into the
+ * threshold update condition because we
+ * know that threshold > 1.
+ */
+ if (remaining <= 1) break;
+ nbBits = ZSTD_highbit32(remaining) + 1;
+ threshold = 1 << (nbBits - 1);
+ }
+ if (charnum >= maxSV1) break;
+
+ if (LIKELY(ip <= iend-7) || (ip + (bitCount>>3) <= iend-4)) {
+ ip += bitCount>>3;
+ bitCount &= 7;
+ } else {
+ bitCount -= (int)(8 * (iend - 4 - ip));
+ bitCount &= 31;
+ ip = iend - 4;
+ }
+ bitStream = MEM_readLE32(ip) >> bitCount;
+ } }
+ if (remaining != 1) return ERROR(corruption_detected);
+ /* Only possible when there are too many zeros. */
+ if (charnum > maxSV1) return ERROR(maxSymbolValue_tooSmall);
+ if (bitCount > 32) return ERROR(corruption_detected);
+ *maxSVPtr = charnum-1;
+
+ ip += (bitCount+7)>>3;
+ return ip-istart;
+}
+
+/* Avoids the FORCE_INLINE of the _body() function. */
+static size_t FSE_readNCount_body_default(
+ short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr,
+ const void* headerBuffer, size_t hbSize)
+{
+ return FSE_readNCount_body(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize);
+}
+
+#if DYNAMIC_BMI2
+BMI2_TARGET_ATTRIBUTE static size_t FSE_readNCount_body_bmi2(
+ short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr,
+ const void* headerBuffer, size_t hbSize)
+{
+ return FSE_readNCount_body(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize);
+}
+#endif
+
+size_t FSE_readNCount_bmi2(
+ short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr,
+ const void* headerBuffer, size_t hbSize, int bmi2)
+{
+#if DYNAMIC_BMI2
+ if (bmi2) {
+ return FSE_readNCount_body_bmi2(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize);
+ }
+#endif
+ (void)bmi2;
+ return FSE_readNCount_body_default(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize);
+}
+
+size_t FSE_readNCount(
+ short* normalizedCounter, unsigned* maxSVPtr, unsigned* tableLogPtr,
+ const void* headerBuffer, size_t hbSize)
+{
+ return FSE_readNCount_bmi2(normalizedCounter, maxSVPtr, tableLogPtr, headerBuffer, hbSize, /* bmi2 */ 0);
+}
+
+
+/*! HUF_readStats() :
+ Read compact Huffman tree, saved by HUF_writeCTable().
+ `huffWeight` is destination buffer.
+ `rankStats` is assumed to be a table of at least HUF_TABLELOG_MAX U32.
+ @return : size read from `src` , or an error Code .
+ Note : Needed by HUF_readCTable() and HUF_readDTableX?() .
+*/
+size_t HUF_readStats(BYTE* huffWeight, size_t hwSize, U32* rankStats,
+ U32* nbSymbolsPtr, U32* tableLogPtr,
+ const void* src, size_t srcSize)
+{
+ U32 wksp[HUF_READ_STATS_WORKSPACE_SIZE_U32];
+ return HUF_readStats_wksp(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, wksp, sizeof(wksp), /* flags */ 0);
+}
+
+FORCE_INLINE_TEMPLATE size_t
+HUF_readStats_body(BYTE* huffWeight, size_t hwSize, U32* rankStats,
+ U32* nbSymbolsPtr, U32* tableLogPtr,
+ const void* src, size_t srcSize,
+ void* workSpace, size_t wkspSize,
+ int bmi2)
+{
+ U32 weightTotal;
+ const BYTE* ip = (const BYTE*) src;
+ size_t iSize;
+ size_t oSize;
+
+ if (!srcSize) return ERROR(srcSize_wrong);
+ iSize = ip[0];
+ /* ZSTD_memset(huffWeight, 0, hwSize); *//* is not necessary, even though some analyzer complain ... */
+
+ if (iSize >= 128) { /* special header */
+ oSize = iSize - 127;
+ iSize = ((oSize+1)/2);
+ if (iSize+1 > srcSize) return ERROR(srcSize_wrong);
+ if (oSize >= hwSize) return ERROR(corruption_detected);
+ ip += 1;
+ { U32 n;
+ for (n=0; n<oSize; n+=2) {
+ huffWeight[n] = ip[n/2] >> 4;
+ huffWeight[n+1] = ip[n/2] & 15;
+ } } }
+ else { /* header compressed with FSE (normal case) */
+ if (iSize+1 > srcSize) return ERROR(srcSize_wrong);
+ /* max (hwSize-1) values decoded, as last one is implied */
+ oSize = FSE_decompress_wksp_bmi2(huffWeight, hwSize-1, ip+1, iSize, 6, workSpace, wkspSize, bmi2);
+ if (FSE_isError(oSize)) return oSize;
+ }
+
+ /* collect weight stats */
+ ZSTD_memset(rankStats, 0, (HUF_TABLELOG_MAX + 1) * sizeof(U32));
+ weightTotal = 0;
+ { U32 n; for (n=0; n<oSize; n++) {
+ if (huffWeight[n] > HUF_TABLELOG_MAX) return ERROR(corruption_detected);
+ rankStats[huffWeight[n]]++;
+ weightTotal += (1 << huffWeight[n]) >> 1;
+ } }
+ if (weightTotal == 0) return ERROR(corruption_detected);
+
+ /* get last non-null symbol weight (implied, total must be 2^n) */
+ { U32 const tableLog = ZSTD_highbit32(weightTotal) + 1;
+ if (tableLog > HUF_TABLELOG_MAX) return ERROR(corruption_detected);
+ *tableLogPtr = tableLog;
+ /* determine last weight */
+ { U32 const total = 1 << tableLog;
+ U32 const rest = total - weightTotal;
+ U32 const verif = 1 << ZSTD_highbit32(rest);
+ U32 const lastWeight = ZSTD_highbit32(rest) + 1;
+ if (verif != rest) return ERROR(corruption_detected); /* last value must be a clean power of 2 */
+ huffWeight[oSize] = (BYTE)lastWeight;
+ rankStats[lastWeight]++;
+ } }
+
+ /* check tree construction validity */
+ if ((rankStats[1] < 2) || (rankStats[1] & 1)) return ERROR(corruption_detected); /* by construction : at least 2 elts of rank 1, must be even */
+
+ /* results */
+ *nbSymbolsPtr = (U32)(oSize+1);
+ return iSize+1;
+}
+
+/* Avoids the FORCE_INLINE of the _body() function. */
+static size_t HUF_readStats_body_default(BYTE* huffWeight, size_t hwSize, U32* rankStats,
+ U32* nbSymbolsPtr, U32* tableLogPtr,
+ const void* src, size_t srcSize,
+ void* workSpace, size_t wkspSize)
+{
+ return HUF_readStats_body(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize, 0);
+}
+
+#if DYNAMIC_BMI2
+static BMI2_TARGET_ATTRIBUTE size_t HUF_readStats_body_bmi2(BYTE* huffWeight, size_t hwSize, U32* rankStats,
+ U32* nbSymbolsPtr, U32* tableLogPtr,
+ const void* src, size_t srcSize,
+ void* workSpace, size_t wkspSize)
+{
+ return HUF_readStats_body(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize, 1);
+}
+#endif
+
+size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize, U32* rankStats,
+ U32* nbSymbolsPtr, U32* tableLogPtr,
+ const void* src, size_t srcSize,
+ void* workSpace, size_t wkspSize,
+ int flags)
+{
+#if DYNAMIC_BMI2
+ if (flags & HUF_flags_bmi2) {
+ return HUF_readStats_body_bmi2(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize);
+ }
+#endif
+ (void)flags;
+ return HUF_readStats_body_default(huffWeight, hwSize, rankStats, nbSymbolsPtr, tableLogPtr, src, srcSize, workSpace, wkspSize);
+}
diff --git a/third_party/zstd/lib/common/error_private.c b/third_party/zstd/lib/common/error_private.c
new file mode 100644
index 0000000000..075fc5ef42
--- /dev/null
+++ b/third_party/zstd/lib/common/error_private.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+/* The purpose of this file is to have a single list of error strings embedded in binary */
+
+#include "error_private.h"
+
+const char* ERR_getErrorString(ERR_enum code)
+{
+#ifdef ZSTD_STRIP_ERROR_STRINGS
+ (void)code;
+ return "Error strings stripped";
+#else
+ static const char* const notErrorCode = "Unspecified error code";
+ switch( code )
+ {
+ case PREFIX(no_error): return "No error detected";
+ case PREFIX(GENERIC): return "Error (generic)";
+ case PREFIX(prefix_unknown): return "Unknown frame descriptor";
+ case PREFIX(version_unsupported): return "Version not supported";
+ case PREFIX(frameParameter_unsupported): return "Unsupported frame parameter";
+ case PREFIX(frameParameter_windowTooLarge): return "Frame requires too much memory for decoding";
+ case PREFIX(corruption_detected): return "Data corruption detected";
+ case PREFIX(checksum_wrong): return "Restored data doesn't match checksum";
+ case PREFIX(literals_headerWrong): return "Header of Literals' block doesn't respect format specification";
+ case PREFIX(parameter_unsupported): return "Unsupported parameter";
+ case PREFIX(parameter_combination_unsupported): return "Unsupported combination of parameters";
+ case PREFIX(parameter_outOfBound): return "Parameter is out of bound";
+ case PREFIX(init_missing): return "Context should be init first";
+ case PREFIX(memory_allocation): return "Allocation error : not enough memory";
+ case PREFIX(workSpace_tooSmall): return "workSpace buffer is not large enough";
+ case PREFIX(stage_wrong): return "Operation not authorized at current processing stage";
+ case PREFIX(tableLog_tooLarge): return "tableLog requires too much memory : unsupported";
+ case PREFIX(maxSymbolValue_tooLarge): return "Unsupported max Symbol Value : too large";
+ case PREFIX(maxSymbolValue_tooSmall): return "Specified maxSymbolValue is too small";
+ case PREFIX(stabilityCondition_notRespected): return "pledged buffer stability condition is not respected";
+ case PREFIX(dictionary_corrupted): return "Dictionary is corrupted";
+ case PREFIX(dictionary_wrong): return "Dictionary mismatch";
+ case PREFIX(dictionaryCreation_failed): return "Cannot create Dictionary from provided samples";
+ case PREFIX(dstSize_tooSmall): return "Destination buffer is too small";
+ case PREFIX(srcSize_wrong): return "Src size is incorrect";
+ case PREFIX(dstBuffer_null): return "Operation on NULL destination buffer";
+ case PREFIX(noForwardProgress_destFull): return "Operation made no progress over multiple calls, due to output buffer being full";
+ case PREFIX(noForwardProgress_inputEmpty): return "Operation made no progress over multiple calls, due to input being empty";
+ /* following error codes are not stable and may be removed or changed in a future version */
+ case PREFIX(frameIndex_tooLarge): return "Frame index is too large";
+ case PREFIX(seekableIO): return "An I/O error occurred when reading/seeking";
+ case PREFIX(dstBuffer_wrong): return "Destination buffer is wrong";
+ case PREFIX(srcBuffer_wrong): return "Source buffer is wrong";
+ case PREFIX(sequenceProducer_failed): return "Block-level external sequence producer returned an error code";
+ case PREFIX(externalSequences_invalid): return "External sequences are not valid";
+ case PREFIX(maxCode):
+ default: return notErrorCode;
+ }
+#endif
+}
diff --git a/third_party/zstd/lib/common/error_private.h b/third_party/zstd/lib/common/error_private.h
new file mode 100644
index 0000000000..0156010c74
--- /dev/null
+++ b/third_party/zstd/lib/common/error_private.h
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+/* Note : this module is expected to remain private, do not expose it */
+
+#ifndef ERROR_H_MODULE
+#define ERROR_H_MODULE
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+
+/* ****************************************
+* Dependencies
+******************************************/
+#include "../zstd_errors.h" /* enum list */
+#include "compiler.h"
+#include "debug.h"
+#include "zstd_deps.h" /* size_t */
+
+
+/* ****************************************
+* Compiler-specific
+******************************************/
+#if defined(__GNUC__)
+# define ERR_STATIC static __attribute__((unused))
+#elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
+# define ERR_STATIC static inline
+#elif defined(_MSC_VER)
+# define ERR_STATIC static __inline
+#else
+# define ERR_STATIC static /* this version may generate warnings for unused static functions; disable the relevant warning */
+#endif
+
+
+/*-****************************************
+* Customization (error_public.h)
+******************************************/
+typedef ZSTD_ErrorCode ERR_enum;
+#define PREFIX(name) ZSTD_error_##name
+
+
+/*-****************************************
+* Error codes handling
+******************************************/
+#undef ERROR /* already defined on Visual Studio */
+#define ERROR(name) ZSTD_ERROR(name)
+#define ZSTD_ERROR(name) ((size_t)-PREFIX(name))
+
+ERR_STATIC unsigned ERR_isError(size_t code) { return (code > ERROR(maxCode)); }
+
+ERR_STATIC ERR_enum ERR_getErrorCode(size_t code) { if (!ERR_isError(code)) return (ERR_enum)0; return (ERR_enum) (0-code); }
+
+/* check and forward error code */
+#define CHECK_V_F(e, f) \
+ size_t const e = f; \
+ do { \
+ if (ERR_isError(e)) \
+ return e; \
+ } while (0)
+#define CHECK_F(f) do { CHECK_V_F(_var_err__, f); } while (0)
+
+
+/*-****************************************
+* Error Strings
+******************************************/
+
+const char* ERR_getErrorString(ERR_enum code); /* error_private.c */
+
+ERR_STATIC const char* ERR_getErrorName(size_t code)
+{
+ return ERR_getErrorString(ERR_getErrorCode(code));
+}
+
+/**
+ * Ignore: this is an internal helper.
+ *
+ * This is a helper function to help force C99-correctness during compilation.
+ * Under strict compilation modes, variadic macro arguments can't be empty.
+ * However, variadic function arguments can be. Using a function therefore lets
+ * us statically check that at least one (string) argument was passed,
+ * independent of the compilation flags.
+ */
+static INLINE_KEYWORD UNUSED_ATTR
+void _force_has_format_string(const char *format, ...) {
+ (void)format;
+}
+
+/**
+ * Ignore: this is an internal helper.
+ *
+ * We want to force this function invocation to be syntactically correct, but
+ * we don't want to force runtime evaluation of its arguments.
+ */
+#define _FORCE_HAS_FORMAT_STRING(...) \
+ do { \
+ if (0) { \
+ _force_has_format_string(__VA_ARGS__); \
+ } \
+ } while (0)
+
+#define ERR_QUOTE(str) #str
+
+/**
+ * Return the specified error if the condition evaluates to true.
+ *
+ * In debug modes, prints additional information.
+ * In order to do that (particularly, printing the conditional that failed),
+ * this can't just wrap RETURN_ERROR().
+ */
+#define RETURN_ERROR_IF(cond, err, ...) \
+ do { \
+ if (cond) { \
+ RAWLOG(3, "%s:%d: ERROR!: check %s failed, returning %s", \
+ __FILE__, __LINE__, ERR_QUOTE(cond), ERR_QUOTE(ERROR(err))); \
+ _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \
+ RAWLOG(3, ": " __VA_ARGS__); \
+ RAWLOG(3, "\n"); \
+ return ERROR(err); \
+ } \
+ } while (0)
+
+/**
+ * Unconditionally return the specified error.
+ *
+ * In debug modes, prints additional information.
+ */
+#define RETURN_ERROR(err, ...) \
+ do { \
+ RAWLOG(3, "%s:%d: ERROR!: unconditional check failed, returning %s", \
+ __FILE__, __LINE__, ERR_QUOTE(ERROR(err))); \
+ _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \
+ RAWLOG(3, ": " __VA_ARGS__); \
+ RAWLOG(3, "\n"); \
+ return ERROR(err); \
+ } while(0)
+
+/**
+ * If the provided expression evaluates to an error code, returns that error code.
+ *
+ * In debug modes, prints additional information.
+ */
+#define FORWARD_IF_ERROR(err, ...) \
+ do { \
+ size_t const err_code = (err); \
+ if (ERR_isError(err_code)) { \
+ RAWLOG(3, "%s:%d: ERROR!: forwarding error in %s: %s", \
+ __FILE__, __LINE__, ERR_QUOTE(err), ERR_getErrorName(err_code)); \
+ _FORCE_HAS_FORMAT_STRING(__VA_ARGS__); \
+ RAWLOG(3, ": " __VA_ARGS__); \
+ RAWLOG(3, "\n"); \
+ return err_code; \
+ } \
+ } while(0)
+
+#if defined (__cplusplus)
+}
+#endif
+
+#endif /* ERROR_H_MODULE */
diff --git a/third_party/zstd/lib/common/fse.h b/third_party/zstd/lib/common/fse.h
new file mode 100644
index 0000000000..2ae128e60d
--- /dev/null
+++ b/third_party/zstd/lib/common/fse.h
@@ -0,0 +1,640 @@
+/* ******************************************************************
+ * FSE : Finite State Entropy codec
+ * Public Prototypes declaration
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * You can contact the author at :
+ * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+****************************************************************** */
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+#ifndef FSE_H
+#define FSE_H
+
+
+/*-*****************************************
+* Dependencies
+******************************************/
+#include "zstd_deps.h" /* size_t, ptrdiff_t */
+
+
+/*-*****************************************
+* FSE_PUBLIC_API : control library symbols visibility
+******************************************/
+#if defined(FSE_DLL_EXPORT) && (FSE_DLL_EXPORT==1) && defined(__GNUC__) && (__GNUC__ >= 4)
+# define FSE_PUBLIC_API __attribute__ ((visibility ("default")))
+#elif defined(FSE_DLL_EXPORT) && (FSE_DLL_EXPORT==1) /* Visual expected */
+# define FSE_PUBLIC_API __declspec(dllexport)
+#elif defined(FSE_DLL_IMPORT) && (FSE_DLL_IMPORT==1)
+# define FSE_PUBLIC_API __declspec(dllimport) /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
+#else
+# define FSE_PUBLIC_API
+#endif
+
+/*------ Version ------*/
+#define FSE_VERSION_MAJOR 0
+#define FSE_VERSION_MINOR 9
+#define FSE_VERSION_RELEASE 0
+
+#define FSE_LIB_VERSION FSE_VERSION_MAJOR.FSE_VERSION_MINOR.FSE_VERSION_RELEASE
+#define FSE_QUOTE(str) #str
+#define FSE_EXPAND_AND_QUOTE(str) FSE_QUOTE(str)
+#define FSE_VERSION_STRING FSE_EXPAND_AND_QUOTE(FSE_LIB_VERSION)
+
+#define FSE_VERSION_NUMBER (FSE_VERSION_MAJOR *100*100 + FSE_VERSION_MINOR *100 + FSE_VERSION_RELEASE)
+FSE_PUBLIC_API unsigned FSE_versionNumber(void); /**< library version number; to be used when checking dll version */
+
+
+/*-*****************************************
+* Tool functions
+******************************************/
+FSE_PUBLIC_API size_t FSE_compressBound(size_t size); /* maximum compressed size */
+
+/* Error Management */
+FSE_PUBLIC_API unsigned FSE_isError(size_t code); /* tells if a return value is an error code */
+FSE_PUBLIC_API const char* FSE_getErrorName(size_t code); /* provides error code string (useful for debugging) */
+
+
+/*-*****************************************
+* FSE detailed API
+******************************************/
+/*!
+FSE_compress() does the following:
+1. count symbol occurrence from source[] into table count[] (see hist.h)
+2. normalize counters so that sum(count[]) == Power_of_2 (2^tableLog)
+3. save normalized counters to memory buffer using writeNCount()
+4. build encoding table 'CTable' from normalized counters
+5. encode the data stream using encoding table 'CTable'
+
+FSE_decompress() does the following:
+1. read normalized counters with readNCount()
+2. build decoding table 'DTable' from normalized counters
+3. decode the data stream using decoding table 'DTable'
+
+The following API allows targeting specific sub-functions for advanced tasks.
+For example, it's possible to compress several blocks using the same 'CTable',
+or to save and provide normalized distribution using external method.
+*/
+
+/* *** COMPRESSION *** */
+
+/*! FSE_optimalTableLog():
+ dynamically downsize 'tableLog' when conditions are met.
+ It saves CPU time, by using smaller tables, while preserving or even improving compression ratio.
+ @return : recommended tableLog (necessarily <= 'maxTableLog') */
+FSE_PUBLIC_API unsigned FSE_optimalTableLog(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue);
+
+/*! FSE_normalizeCount():
+ normalize counts so that sum(count[]) == Power_of_2 (2^tableLog)
+ 'normalizedCounter' is a table of short, of minimum size (maxSymbolValue+1).
+ useLowProbCount is a boolean parameter which trades off compressed size for
+ faster header decoding. When it is set to 1, the compressed data will be slightly
+ smaller. And when it is set to 0, FSE_readNCount() and FSE_buildDTable() will be
+ faster. If you are compressing a small amount of data (< 2 KB) then useLowProbCount=0
+ is a good default, since header deserialization makes a big speed difference.
+ Otherwise, useLowProbCount=1 is a good default, since the speed difference is small.
+ @return : tableLog,
+ or an errorCode, which can be tested using FSE_isError() */
+FSE_PUBLIC_API size_t FSE_normalizeCount(short* normalizedCounter, unsigned tableLog,
+ const unsigned* count, size_t srcSize, unsigned maxSymbolValue, unsigned useLowProbCount);
+
+/*! FSE_NCountWriteBound():
+ Provides the maximum possible size of an FSE normalized table, given 'maxSymbolValue' and 'tableLog'.
+ Typically useful for allocation purpose. */
+FSE_PUBLIC_API size_t FSE_NCountWriteBound(unsigned maxSymbolValue, unsigned tableLog);
+
+/*! FSE_writeNCount():
+ Compactly save 'normalizedCounter' into 'buffer'.
+ @return : size of the compressed table,
+ or an errorCode, which can be tested using FSE_isError(). */
+FSE_PUBLIC_API size_t FSE_writeNCount (void* buffer, size_t bufferSize,
+ const short* normalizedCounter,
+ unsigned maxSymbolValue, unsigned tableLog);
+
+/*! Constructor and Destructor of FSE_CTable.
+ Note that FSE_CTable size depends on 'tableLog' and 'maxSymbolValue' */
+typedef unsigned FSE_CTable; /* don't allocate that. It's only meant to be more restrictive than void* */
+
+/*! FSE_buildCTable():
+ Builds `ct`, which must be already allocated, using FSE_createCTable().
+ @return : 0, or an errorCode, which can be tested using FSE_isError() */
+FSE_PUBLIC_API size_t FSE_buildCTable(FSE_CTable* ct, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog);
+
+/*! FSE_compress_usingCTable():
+ Compress `src` using `ct` into `dst` which must be already allocated.
+ @return : size of compressed data (<= `dstCapacity`),
+ or 0 if compressed data could not fit into `dst`,
+ or an errorCode, which can be tested using FSE_isError() */
+FSE_PUBLIC_API size_t FSE_compress_usingCTable (void* dst, size_t dstCapacity, const void* src, size_t srcSize, const FSE_CTable* ct);
+
+/*!
+Tutorial :
+----------
+The first step is to count all symbols. FSE_count() does this job very fast.
+Result will be saved into 'count', a table of unsigned int, which must be already allocated, and have 'maxSymbolValuePtr[0]+1' cells.
+'src' is a table of bytes of size 'srcSize'. All values within 'src' MUST be <= maxSymbolValuePtr[0]
+maxSymbolValuePtr[0] will be updated, with its real value (necessarily <= original value)
+FSE_count() will return the number of occurrence of the most frequent symbol.
+This can be used to know if there is a single symbol within 'src', and to quickly evaluate its compressibility.
+If there is an error, the function will return an ErrorCode (which can be tested using FSE_isError()).
+
+The next step is to normalize the frequencies.
+FSE_normalizeCount() will ensure that sum of frequencies is == 2 ^'tableLog'.
+It also guarantees a minimum of 1 to any Symbol with frequency >= 1.
+You can use 'tableLog'==0 to mean "use default tableLog value".
+If you are unsure of which tableLog value to use, you can ask FSE_optimalTableLog(),
+which will provide the optimal valid tableLog given sourceSize, maxSymbolValue, and a user-defined maximum (0 means "default").
+
+The result of FSE_normalizeCount() will be saved into a table,
+called 'normalizedCounter', which is a table of signed short.
+'normalizedCounter' must be already allocated, and have at least 'maxSymbolValue+1' cells.
+The return value is tableLog if everything proceeded as expected.
+It is 0 if there is a single symbol within distribution.
+If there is an error (ex: invalid tableLog value), the function will return an ErrorCode (which can be tested using FSE_isError()).
+
+'normalizedCounter' can be saved in a compact manner to a memory area using FSE_writeNCount().
+'buffer' must be already allocated.
+For guaranteed success, buffer size must be at least FSE_headerBound().
+The result of the function is the number of bytes written into 'buffer'.
+If there is an error, the function will return an ErrorCode (which can be tested using FSE_isError(); ex : buffer size too small).
+
+'normalizedCounter' can then be used to create the compression table 'CTable'.
+The space required by 'CTable' must be already allocated, using FSE_createCTable().
+You can then use FSE_buildCTable() to fill 'CTable'.
+If there is an error, both functions will return an ErrorCode (which can be tested using FSE_isError()).
+
+'CTable' can then be used to compress 'src', with FSE_compress_usingCTable().
+Similar to FSE_count(), the convention is that 'src' is assumed to be a table of char of size 'srcSize'
+The function returns the size of compressed data (without header), necessarily <= `dstCapacity`.
+If it returns '0', compressed data could not fit into 'dst'.
+If there is an error, the function will return an ErrorCode (which can be tested using FSE_isError()).
+*/
+
+
+/* *** DECOMPRESSION *** */
+
+/*! FSE_readNCount():
+ Read compactly saved 'normalizedCounter' from 'rBuffer'.
+ @return : size read from 'rBuffer',
+ or an errorCode, which can be tested using FSE_isError().
+ maxSymbolValuePtr[0] and tableLogPtr[0] will also be updated with their respective values */
+FSE_PUBLIC_API size_t FSE_readNCount (short* normalizedCounter,
+ unsigned* maxSymbolValuePtr, unsigned* tableLogPtr,
+ const void* rBuffer, size_t rBuffSize);
+
+/*! FSE_readNCount_bmi2():
+ * Same as FSE_readNCount() but pass bmi2=1 when your CPU supports BMI2 and 0 otherwise.
+ */
+FSE_PUBLIC_API size_t FSE_readNCount_bmi2(short* normalizedCounter,
+ unsigned* maxSymbolValuePtr, unsigned* tableLogPtr,
+ const void* rBuffer, size_t rBuffSize, int bmi2);
+
+typedef unsigned FSE_DTable; /* don't allocate that. It's just a way to be more restrictive than void* */
+
+/*!
+Tutorial :
+----------
+(Note : these functions only decompress FSE-compressed blocks.
+ If block is uncompressed, use memcpy() instead
+ If block is a single repeated byte, use memset() instead )
+
+The first step is to obtain the normalized frequencies of symbols.
+This can be performed by FSE_readNCount() if it was saved using FSE_writeNCount().
+'normalizedCounter' must be already allocated, and have at least 'maxSymbolValuePtr[0]+1' cells of signed short.
+In practice, that means it's necessary to know 'maxSymbolValue' beforehand,
+or size the table to handle worst case situations (typically 256).
+FSE_readNCount() will provide 'tableLog' and 'maxSymbolValue'.
+The result of FSE_readNCount() is the number of bytes read from 'rBuffer'.
+Note that 'rBufferSize' must be at least 4 bytes, even if useful information is less than that.
+If there is an error, the function will return an error code, which can be tested using FSE_isError().
+
+The next step is to build the decompression tables 'FSE_DTable' from 'normalizedCounter'.
+This is performed by the function FSE_buildDTable().
+The space required by 'FSE_DTable' must be already allocated using FSE_createDTable().
+If there is an error, the function will return an error code, which can be tested using FSE_isError().
+
+`FSE_DTable` can then be used to decompress `cSrc`, with FSE_decompress_usingDTable().
+`cSrcSize` must be strictly correct, otherwise decompression will fail.
+FSE_decompress_usingDTable() result will tell how many bytes were regenerated (<=`dstCapacity`).
+If there is an error, the function will return an error code, which can be tested using FSE_isError(). (ex: dst buffer too small)
+*/
+
+#endif /* FSE_H */
+
+
+#if defined(FSE_STATIC_LINKING_ONLY) && !defined(FSE_H_FSE_STATIC_LINKING_ONLY)
+#define FSE_H_FSE_STATIC_LINKING_ONLY
+
+/* *** Dependency *** */
+#include "bitstream.h"
+
+
+/* *****************************************
+* Static allocation
+*******************************************/
+/* FSE buffer bounds */
+#define FSE_NCOUNTBOUND 512
+#define FSE_BLOCKBOUND(size) ((size) + ((size)>>7) + 4 /* fse states */ + sizeof(size_t) /* bitContainer */)
+#define FSE_COMPRESSBOUND(size) (FSE_NCOUNTBOUND + FSE_BLOCKBOUND(size)) /* Macro version, useful for static allocation */
+
+/* It is possible to statically allocate FSE CTable/DTable as a table of FSE_CTable/FSE_DTable using below macros */
+#define FSE_CTABLE_SIZE_U32(maxTableLog, maxSymbolValue) (1 + (1<<((maxTableLog)-1)) + (((maxSymbolValue)+1)*2))
+#define FSE_DTABLE_SIZE_U32(maxTableLog) (1 + (1<<(maxTableLog)))
+
+/* or use the size to malloc() space directly. Pay attention to alignment restrictions though */
+#define FSE_CTABLE_SIZE(maxTableLog, maxSymbolValue) (FSE_CTABLE_SIZE_U32(maxTableLog, maxSymbolValue) * sizeof(FSE_CTable))
+#define FSE_DTABLE_SIZE(maxTableLog) (FSE_DTABLE_SIZE_U32(maxTableLog) * sizeof(FSE_DTable))
+
+
+/* *****************************************
+ * FSE advanced API
+ ***************************************** */
+
+unsigned FSE_optimalTableLog_internal(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue, unsigned minus);
+/**< same as FSE_optimalTableLog(), which used `minus==2` */
+
+size_t FSE_buildCTable_rle (FSE_CTable* ct, unsigned char symbolValue);
+/**< build a fake FSE_CTable, designed to compress always the same symbolValue */
+
+/* FSE_buildCTable_wksp() :
+ * Same as FSE_buildCTable(), but using an externally allocated scratch buffer (`workSpace`).
+ * `wkspSize` must be >= `FSE_BUILD_CTABLE_WORKSPACE_SIZE_U32(maxSymbolValue, tableLog)` of `unsigned`.
+ * See FSE_buildCTable_wksp() for breakdown of workspace usage.
+ */
+#define FSE_BUILD_CTABLE_WORKSPACE_SIZE_U32(maxSymbolValue, tableLog) (((maxSymbolValue + 2) + (1ull << (tableLog)))/2 + sizeof(U64)/sizeof(U32) /* additional 8 bytes for potential table overwrite */)
+#define FSE_BUILD_CTABLE_WORKSPACE_SIZE(maxSymbolValue, tableLog) (sizeof(unsigned) * FSE_BUILD_CTABLE_WORKSPACE_SIZE_U32(maxSymbolValue, tableLog))
+size_t FSE_buildCTable_wksp(FSE_CTable* ct, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize);
+
+#define FSE_BUILD_DTABLE_WKSP_SIZE(maxTableLog, maxSymbolValue) (sizeof(short) * (maxSymbolValue + 1) + (1ULL << maxTableLog) + 8)
+#define FSE_BUILD_DTABLE_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) ((FSE_BUILD_DTABLE_WKSP_SIZE(maxTableLog, maxSymbolValue) + sizeof(unsigned) - 1) / sizeof(unsigned))
+FSE_PUBLIC_API size_t FSE_buildDTable_wksp(FSE_DTable* dt, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize);
+/**< Same as FSE_buildDTable(), using an externally allocated `workspace` produced with `FSE_BUILD_DTABLE_WKSP_SIZE_U32(maxSymbolValue)` */
+
+#define FSE_DECOMPRESS_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) (FSE_DTABLE_SIZE_U32(maxTableLog) + 1 + FSE_BUILD_DTABLE_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) + (FSE_MAX_SYMBOL_VALUE + 1) / 2 + 1)
+#define FSE_DECOMPRESS_WKSP_SIZE(maxTableLog, maxSymbolValue) (FSE_DECOMPRESS_WKSP_SIZE_U32(maxTableLog, maxSymbolValue) * sizeof(unsigned))
+size_t FSE_decompress_wksp_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize, int bmi2);
+/**< same as FSE_decompress(), using an externally allocated `workSpace` produced with `FSE_DECOMPRESS_WKSP_SIZE_U32(maxLog, maxSymbolValue)`.
+ * Set bmi2 to 1 if your CPU supports BMI2 or 0 if it doesn't */
+
+typedef enum {
+ FSE_repeat_none, /**< Cannot use the previous table */
+ FSE_repeat_check, /**< Can use the previous table but it must be checked */
+ FSE_repeat_valid /**< Can use the previous table and it is assumed to be valid */
+ } FSE_repeat;
+
+/* *****************************************
+* FSE symbol compression API
+*******************************************/
+/*!
+ This API consists of small unitary functions, which highly benefit from being inlined.
+ Hence their body are included in next section.
+*/
+typedef struct {
+ ptrdiff_t value;
+ const void* stateTable;
+ const void* symbolTT;
+ unsigned stateLog;
+} FSE_CState_t;
+
+static void FSE_initCState(FSE_CState_t* CStatePtr, const FSE_CTable* ct);
+
+static void FSE_encodeSymbol(BIT_CStream_t* bitC, FSE_CState_t* CStatePtr, unsigned symbol);
+
+static void FSE_flushCState(BIT_CStream_t* bitC, const FSE_CState_t* CStatePtr);
+
+/**<
+These functions are inner components of FSE_compress_usingCTable().
+They allow the creation of custom streams, mixing multiple tables and bit sources.
+
+A key property to keep in mind is that encoding and decoding are done **in reverse direction**.
+So the first symbol you will encode is the last you will decode, like a LIFO stack.
+
+You will need a few variables to track your CStream. They are :
+
+FSE_CTable ct; // Provided by FSE_buildCTable()
+BIT_CStream_t bitStream; // bitStream tracking structure
+FSE_CState_t state; // State tracking structure (can have several)
+
+
+The first thing to do is to init bitStream and state.
+ size_t errorCode = BIT_initCStream(&bitStream, dstBuffer, maxDstSize);
+ FSE_initCState(&state, ct);
+
+Note that BIT_initCStream() can produce an error code, so its result should be tested, using FSE_isError();
+You can then encode your input data, byte after byte.
+FSE_encodeSymbol() outputs a maximum of 'tableLog' bits at a time.
+Remember decoding will be done in reverse direction.
+ FSE_encodeByte(&bitStream, &state, symbol);
+
+At any time, you can also add any bit sequence.
+Note : maximum allowed nbBits is 25, for compatibility with 32-bits decoders
+ BIT_addBits(&bitStream, bitField, nbBits);
+
+The above methods don't commit data to memory, they just store it into local register, for speed.
+Local register size is 64-bits on 64-bits systems, 32-bits on 32-bits systems (size_t).
+Writing data to memory is a manual operation, performed by the flushBits function.
+ BIT_flushBits(&bitStream);
+
+Your last FSE encoding operation shall be to flush your last state value(s).
+ FSE_flushState(&bitStream, &state);
+
+Finally, you must close the bitStream.
+The function returns the size of CStream in bytes.
+If data couldn't fit into dstBuffer, it will return a 0 ( == not compressible)
+If there is an error, it returns an errorCode (which can be tested using FSE_isError()).
+ size_t size = BIT_closeCStream(&bitStream);
+*/
+
+
+/* *****************************************
+* FSE symbol decompression API
+*******************************************/
+typedef struct {
+ size_t state;
+ const void* table; /* precise table may vary, depending on U16 */
+} FSE_DState_t;
+
+
+static void FSE_initDState(FSE_DState_t* DStatePtr, BIT_DStream_t* bitD, const FSE_DTable* dt);
+
+static unsigned char FSE_decodeSymbol(FSE_DState_t* DStatePtr, BIT_DStream_t* bitD);
+
+static unsigned FSE_endOfDState(const FSE_DState_t* DStatePtr);
+
+/**<
+Let's now decompose FSE_decompress_usingDTable() into its unitary components.
+You will decode FSE-encoded symbols from the bitStream,
+and also any other bitFields you put in, **in reverse order**.
+
+You will need a few variables to track your bitStream. They are :
+
+BIT_DStream_t DStream; // Stream context
+FSE_DState_t DState; // State context. Multiple ones are possible
+FSE_DTable* DTablePtr; // Decoding table, provided by FSE_buildDTable()
+
+The first thing to do is to init the bitStream.
+ errorCode = BIT_initDStream(&DStream, srcBuffer, srcSize);
+
+You should then retrieve your initial state(s)
+(in reverse flushing order if you have several ones) :
+ errorCode = FSE_initDState(&DState, &DStream, DTablePtr);
+
+You can then decode your data, symbol after symbol.
+For information the maximum number of bits read by FSE_decodeSymbol() is 'tableLog'.
+Keep in mind that symbols are decoded in reverse order, like a LIFO stack (last in, first out).
+ unsigned char symbol = FSE_decodeSymbol(&DState, &DStream);
+
+You can retrieve any bitfield you eventually stored into the bitStream (in reverse order)
+Note : maximum allowed nbBits is 25, for 32-bits compatibility
+ size_t bitField = BIT_readBits(&DStream, nbBits);
+
+All above operations only read from local register (which size depends on size_t).
+Refueling the register from memory is manually performed by the reload method.
+ endSignal = FSE_reloadDStream(&DStream);
+
+BIT_reloadDStream() result tells if there is still some more data to read from DStream.
+BIT_DStream_unfinished : there is still some data left into the DStream.
+BIT_DStream_endOfBuffer : Dstream reached end of buffer. Its container may no longer be completely filled.
+BIT_DStream_completed : Dstream reached its exact end, corresponding in general to decompression completed.
+BIT_DStream_tooFar : Dstream went too far. Decompression result is corrupted.
+
+When reaching end of buffer (BIT_DStream_endOfBuffer), progress slowly, notably if you decode multiple symbols per loop,
+to properly detect the exact end of stream.
+After each decoded symbol, check if DStream is fully consumed using this simple test :
+ BIT_reloadDStream(&DStream) >= BIT_DStream_completed
+
+When it's done, verify decompression is fully completed, by checking both DStream and the relevant states.
+Checking if DStream has reached its end is performed by :
+ BIT_endOfDStream(&DStream);
+Check also the states. There might be some symbols left there, if some high probability ones (>50%) are possible.
+ FSE_endOfDState(&DState);
+*/
+
+
+/* *****************************************
+* FSE unsafe API
+*******************************************/
+static unsigned char FSE_decodeSymbolFast(FSE_DState_t* DStatePtr, BIT_DStream_t* bitD);
+/* faster, but works only if nbBits is always >= 1 (otherwise, result will be corrupted) */
+
+
+/* *****************************************
+* Implementation of inlined functions
+*******************************************/
+typedef struct {
+ int deltaFindState;
+ U32 deltaNbBits;
+} FSE_symbolCompressionTransform; /* total 8 bytes */
+
+MEM_STATIC void FSE_initCState(FSE_CState_t* statePtr, const FSE_CTable* ct)
+{
+ const void* ptr = ct;
+ const U16* u16ptr = (const U16*) ptr;
+ const U32 tableLog = MEM_read16(ptr);
+ statePtr->value = (ptrdiff_t)1<<tableLog;
+ statePtr->stateTable = u16ptr+2;
+ statePtr->symbolTT = ct + 1 + (tableLog ? (1<<(tableLog-1)) : 1);
+ statePtr->stateLog = tableLog;
+}
+
+
+/*! FSE_initCState2() :
+* Same as FSE_initCState(), but the first symbol to include (which will be the last to be read)
+* uses the smallest state value possible, saving the cost of this symbol */
+MEM_STATIC void FSE_initCState2(FSE_CState_t* statePtr, const FSE_CTable* ct, U32 symbol)
+{
+ FSE_initCState(statePtr, ct);
+ { const FSE_symbolCompressionTransform symbolTT = ((const FSE_symbolCompressionTransform*)(statePtr->symbolTT))[symbol];
+ const U16* stateTable = (const U16*)(statePtr->stateTable);
+ U32 nbBitsOut = (U32)((symbolTT.deltaNbBits + (1<<15)) >> 16);
+ statePtr->value = (nbBitsOut << 16) - symbolTT.deltaNbBits;
+ statePtr->value = stateTable[(statePtr->value >> nbBitsOut) + symbolTT.deltaFindState];
+ }
+}
+
+MEM_STATIC void FSE_encodeSymbol(BIT_CStream_t* bitC, FSE_CState_t* statePtr, unsigned symbol)
+{
+ FSE_symbolCompressionTransform const symbolTT = ((const FSE_symbolCompressionTransform*)(statePtr->symbolTT))[symbol];
+ const U16* const stateTable = (const U16*)(statePtr->stateTable);
+ U32 const nbBitsOut = (U32)((statePtr->value + symbolTT.deltaNbBits) >> 16);
+ BIT_addBits(bitC, (size_t)statePtr->value, nbBitsOut);
+ statePtr->value = stateTable[ (statePtr->value >> nbBitsOut) + symbolTT.deltaFindState];
+}
+
+MEM_STATIC void FSE_flushCState(BIT_CStream_t* bitC, const FSE_CState_t* statePtr)
+{
+ BIT_addBits(bitC, (size_t)statePtr->value, statePtr->stateLog);
+ BIT_flushBits(bitC);
+}
+
+
+/* FSE_getMaxNbBits() :
+ * Approximate maximum cost of a symbol, in bits.
+ * Fractional get rounded up (i.e. a symbol with a normalized frequency of 3 gives the same result as a frequency of 2)
+ * note 1 : assume symbolValue is valid (<= maxSymbolValue)
+ * note 2 : if freq[symbolValue]==0, @return a fake cost of tableLog+1 bits */
+MEM_STATIC U32 FSE_getMaxNbBits(const void* symbolTTPtr, U32 symbolValue)
+{
+ const FSE_symbolCompressionTransform* symbolTT = (const FSE_symbolCompressionTransform*) symbolTTPtr;
+ return (symbolTT[symbolValue].deltaNbBits + ((1<<16)-1)) >> 16;
+}
+
+/* FSE_bitCost() :
+ * Approximate symbol cost, as fractional value, using fixed-point format (accuracyLog fractional bits)
+ * note 1 : assume symbolValue is valid (<= maxSymbolValue)
+ * note 2 : if freq[symbolValue]==0, @return a fake cost of tableLog+1 bits */
+MEM_STATIC U32 FSE_bitCost(const void* symbolTTPtr, U32 tableLog, U32 symbolValue, U32 accuracyLog)
+{
+ const FSE_symbolCompressionTransform* symbolTT = (const FSE_symbolCompressionTransform*) symbolTTPtr;
+ U32 const minNbBits = symbolTT[symbolValue].deltaNbBits >> 16;
+ U32 const threshold = (minNbBits+1) << 16;
+ assert(tableLog < 16);
+ assert(accuracyLog < 31-tableLog); /* ensure enough room for renormalization double shift */
+ { U32 const tableSize = 1 << tableLog;
+ U32 const deltaFromThreshold = threshold - (symbolTT[symbolValue].deltaNbBits + tableSize);
+ U32 const normalizedDeltaFromThreshold = (deltaFromThreshold << accuracyLog) >> tableLog; /* linear interpolation (very approximate) */
+ U32 const bitMultiplier = 1 << accuracyLog;
+ assert(symbolTT[symbolValue].deltaNbBits + tableSize <= threshold);
+ assert(normalizedDeltaFromThreshold <= bitMultiplier);
+ return (minNbBits+1)*bitMultiplier - normalizedDeltaFromThreshold;
+ }
+}
+
+
+/* ====== Decompression ====== */
+
+typedef struct {
+ U16 tableLog;
+ U16 fastMode;
+} FSE_DTableHeader; /* sizeof U32 */
+
+typedef struct
+{
+ unsigned short newState;
+ unsigned char symbol;
+ unsigned char nbBits;
+} FSE_decode_t; /* size == U32 */
+
+MEM_STATIC void FSE_initDState(FSE_DState_t* DStatePtr, BIT_DStream_t* bitD, const FSE_DTable* dt)
+{
+ const void* ptr = dt;
+ const FSE_DTableHeader* const DTableH = (const FSE_DTableHeader*)ptr;
+ DStatePtr->state = BIT_readBits(bitD, DTableH->tableLog);
+ BIT_reloadDStream(bitD);
+ DStatePtr->table = dt + 1;
+}
+
+MEM_STATIC BYTE FSE_peekSymbol(const FSE_DState_t* DStatePtr)
+{
+ FSE_decode_t const DInfo = ((const FSE_decode_t*)(DStatePtr->table))[DStatePtr->state];
+ return DInfo.symbol;
+}
+
+MEM_STATIC void FSE_updateState(FSE_DState_t* DStatePtr, BIT_DStream_t* bitD)
+{
+ FSE_decode_t const DInfo = ((const FSE_decode_t*)(DStatePtr->table))[DStatePtr->state];
+ U32 const nbBits = DInfo.nbBits;
+ size_t const lowBits = BIT_readBits(bitD, nbBits);
+ DStatePtr->state = DInfo.newState + lowBits;
+}
+
+MEM_STATIC BYTE FSE_decodeSymbol(FSE_DState_t* DStatePtr, BIT_DStream_t* bitD)
+{
+ FSE_decode_t const DInfo = ((const FSE_decode_t*)(DStatePtr->table))[DStatePtr->state];
+ U32 const nbBits = DInfo.nbBits;
+ BYTE const symbol = DInfo.symbol;
+ size_t const lowBits = BIT_readBits(bitD, nbBits);
+
+ DStatePtr->state = DInfo.newState + lowBits;
+ return symbol;
+}
+
+/*! FSE_decodeSymbolFast() :
+ unsafe, only works if no symbol has a probability > 50% */
+MEM_STATIC BYTE FSE_decodeSymbolFast(FSE_DState_t* DStatePtr, BIT_DStream_t* bitD)
+{
+ FSE_decode_t const DInfo = ((const FSE_decode_t*)(DStatePtr->table))[DStatePtr->state];
+ U32 const nbBits = DInfo.nbBits;
+ BYTE const symbol = DInfo.symbol;
+ size_t const lowBits = BIT_readBitsFast(bitD, nbBits);
+
+ DStatePtr->state = DInfo.newState + lowBits;
+ return symbol;
+}
+
+MEM_STATIC unsigned FSE_endOfDState(const FSE_DState_t* DStatePtr)
+{
+ return DStatePtr->state == 0;
+}
+
+
+
+#ifndef FSE_COMMONDEFS_ONLY
+
+/* **************************************************************
+* Tuning parameters
+****************************************************************/
+/*!MEMORY_USAGE :
+* Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.)
+* Increasing memory usage improves compression ratio
+* Reduced memory usage can improve speed, due to cache effect
+* Recommended max value is 14, for 16KB, which nicely fits into Intel x86 L1 cache */
+#ifndef FSE_MAX_MEMORY_USAGE
+# define FSE_MAX_MEMORY_USAGE 14
+#endif
+#ifndef FSE_DEFAULT_MEMORY_USAGE
+# define FSE_DEFAULT_MEMORY_USAGE 13
+#endif
+#if (FSE_DEFAULT_MEMORY_USAGE > FSE_MAX_MEMORY_USAGE)
+# error "FSE_DEFAULT_MEMORY_USAGE must be <= FSE_MAX_MEMORY_USAGE"
+#endif
+
+/*!FSE_MAX_SYMBOL_VALUE :
+* Maximum symbol value authorized.
+* Required for proper stack allocation */
+#ifndef FSE_MAX_SYMBOL_VALUE
+# define FSE_MAX_SYMBOL_VALUE 255
+#endif
+
+/* **************************************************************
+* template functions type & suffix
+****************************************************************/
+#define FSE_FUNCTION_TYPE BYTE
+#define FSE_FUNCTION_EXTENSION
+#define FSE_DECODE_TYPE FSE_decode_t
+
+
+#endif /* !FSE_COMMONDEFS_ONLY */
+
+
+/* ***************************************************************
+* Constants
+*****************************************************************/
+#define FSE_MAX_TABLELOG (FSE_MAX_MEMORY_USAGE-2)
+#define FSE_MAX_TABLESIZE (1U<<FSE_MAX_TABLELOG)
+#define FSE_MAXTABLESIZE_MASK (FSE_MAX_TABLESIZE-1)
+#define FSE_DEFAULT_TABLELOG (FSE_DEFAULT_MEMORY_USAGE-2)
+#define FSE_MIN_TABLELOG 5
+
+#define FSE_TABLELOG_ABSOLUTE_MAX 15
+#if FSE_MAX_TABLELOG > FSE_TABLELOG_ABSOLUTE_MAX
+# error "FSE_MAX_TABLELOG > FSE_TABLELOG_ABSOLUTE_MAX is not supported"
+#endif
+
+#define FSE_TABLESTEP(tableSize) (((tableSize)>>1) + ((tableSize)>>3) + 3)
+
+
+#endif /* FSE_STATIC_LINKING_ONLY */
+
+
+#if defined (__cplusplus)
+}
+#endif
diff --git a/third_party/zstd/lib/common/fse_decompress.c b/third_party/zstd/lib/common/fse_decompress.c
new file mode 100644
index 0000000000..0dcc4640d0
--- /dev/null
+++ b/third_party/zstd/lib/common/fse_decompress.c
@@ -0,0 +1,313 @@
+/* ******************************************************************
+ * FSE : Finite State Entropy decoder
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * You can contact the author at :
+ * - FSE source repository : https://github.com/Cyan4973/FiniteStateEntropy
+ * - Public forum : https://groups.google.com/forum/#!forum/lz4c
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+****************************************************************** */
+
+
+/* **************************************************************
+* Includes
+****************************************************************/
+#include "debug.h" /* assert */
+#include "bitstream.h"
+#include "compiler.h"
+#define FSE_STATIC_LINKING_ONLY
+#include "fse.h"
+#include "error_private.h"
+#include "zstd_deps.h" /* ZSTD_memcpy */
+#include "bits.h" /* ZSTD_highbit32 */
+
+
+/* **************************************************************
+* Error Management
+****************************************************************/
+#define FSE_isError ERR_isError
+#define FSE_STATIC_ASSERT(c) DEBUG_STATIC_ASSERT(c) /* use only *after* variable declarations */
+
+
+/* **************************************************************
+* Templates
+****************************************************************/
+/*
+ designed to be included
+ for type-specific functions (template emulation in C)
+ Objective is to write these functions only once, for improved maintenance
+*/
+
+/* safety checks */
+#ifndef FSE_FUNCTION_EXTENSION
+# error "FSE_FUNCTION_EXTENSION must be defined"
+#endif
+#ifndef FSE_FUNCTION_TYPE
+# error "FSE_FUNCTION_TYPE must be defined"
+#endif
+
+/* Function names */
+#define FSE_CAT(X,Y) X##Y
+#define FSE_FUNCTION_NAME(X,Y) FSE_CAT(X,Y)
+#define FSE_TYPE_NAME(X,Y) FSE_CAT(X,Y)
+
+static size_t FSE_buildDTable_internal(FSE_DTable* dt, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize)
+{
+ void* const tdPtr = dt+1; /* because *dt is unsigned, 32-bits aligned on 32-bits */
+ FSE_DECODE_TYPE* const tableDecode = (FSE_DECODE_TYPE*) (tdPtr);
+ U16* symbolNext = (U16*)workSpace;
+ BYTE* spread = (BYTE*)(symbolNext + maxSymbolValue + 1);
+
+ U32 const maxSV1 = maxSymbolValue + 1;
+ U32 const tableSize = 1 << tableLog;
+ U32 highThreshold = tableSize-1;
+
+ /* Sanity Checks */
+ if (FSE_BUILD_DTABLE_WKSP_SIZE(tableLog, maxSymbolValue) > wkspSize) return ERROR(maxSymbolValue_tooLarge);
+ if (maxSymbolValue > FSE_MAX_SYMBOL_VALUE) return ERROR(maxSymbolValue_tooLarge);
+ if (tableLog > FSE_MAX_TABLELOG) return ERROR(tableLog_tooLarge);
+
+ /* Init, lay down lowprob symbols */
+ { FSE_DTableHeader DTableH;
+ DTableH.tableLog = (U16)tableLog;
+ DTableH.fastMode = 1;
+ { S16 const largeLimit= (S16)(1 << (tableLog-1));
+ U32 s;
+ for (s=0; s<maxSV1; s++) {
+ if (normalizedCounter[s]==-1) {
+ tableDecode[highThreshold--].symbol = (FSE_FUNCTION_TYPE)s;
+ symbolNext[s] = 1;
+ } else {
+ if (normalizedCounter[s] >= largeLimit) DTableH.fastMode=0;
+ symbolNext[s] = (U16)normalizedCounter[s];
+ } } }
+ ZSTD_memcpy(dt, &DTableH, sizeof(DTableH));
+ }
+
+ /* Spread symbols */
+ if (highThreshold == tableSize - 1) {
+ size_t const tableMask = tableSize-1;
+ size_t const step = FSE_TABLESTEP(tableSize);
+ /* First lay down the symbols in order.
+ * We use a uint64_t to lay down 8 bytes at a time. This reduces branch
+ * misses since small blocks generally have small table logs, so nearly
+ * all symbols have counts <= 8. We ensure we have 8 bytes at the end of
+ * our buffer to handle the over-write.
+ */
+ { U64 const add = 0x0101010101010101ull;
+ size_t pos = 0;
+ U64 sv = 0;
+ U32 s;
+ for (s=0; s<maxSV1; ++s, sv += add) {
+ int i;
+ int const n = normalizedCounter[s];
+ MEM_write64(spread + pos, sv);
+ for (i = 8; i < n; i += 8) {
+ MEM_write64(spread + pos + i, sv);
+ }
+ pos += (size_t)n;
+ } }
+ /* Now we spread those positions across the table.
+ * The benefit of doing it in two stages is that we avoid the
+ * variable size inner loop, which caused lots of branch misses.
+ * Now we can run through all the positions without any branch misses.
+ * We unroll the loop twice, since that is what empirically worked best.
+ */
+ {
+ size_t position = 0;
+ size_t s;
+ size_t const unroll = 2;
+ assert(tableSize % unroll == 0); /* FSE_MIN_TABLELOG is 5 */
+ for (s = 0; s < (size_t)tableSize; s += unroll) {
+ size_t u;
+ for (u = 0; u < unroll; ++u) {
+ size_t const uPosition = (position + (u * step)) & tableMask;
+ tableDecode[uPosition].symbol = spread[s + u];
+ }
+ position = (position + (unroll * step)) & tableMask;
+ }
+ assert(position == 0);
+ }
+ } else {
+ U32 const tableMask = tableSize-1;
+ U32 const step = FSE_TABLESTEP(tableSize);
+ U32 s, position = 0;
+ for (s=0; s<maxSV1; s++) {
+ int i;
+ for (i=0; i<normalizedCounter[s]; i++) {
+ tableDecode[position].symbol = (FSE_FUNCTION_TYPE)s;
+ position = (position + step) & tableMask;
+ while (position > highThreshold) position = (position + step) & tableMask; /* lowprob area */
+ } }
+ if (position!=0) return ERROR(GENERIC); /* position must reach all cells once, otherwise normalizedCounter is incorrect */
+ }
+
+ /* Build Decoding table */
+ { U32 u;
+ for (u=0; u<tableSize; u++) {
+ FSE_FUNCTION_TYPE const symbol = (FSE_FUNCTION_TYPE)(tableDecode[u].symbol);
+ U32 const nextState = symbolNext[symbol]++;
+ tableDecode[u].nbBits = (BYTE) (tableLog - ZSTD_highbit32(nextState) );
+ tableDecode[u].newState = (U16) ( (nextState << tableDecode[u].nbBits) - tableSize);
+ } }
+
+ return 0;
+}
+
+size_t FSE_buildDTable_wksp(FSE_DTable* dt, const short* normalizedCounter, unsigned maxSymbolValue, unsigned tableLog, void* workSpace, size_t wkspSize)
+{
+ return FSE_buildDTable_internal(dt, normalizedCounter, maxSymbolValue, tableLog, workSpace, wkspSize);
+}
+
+
+#ifndef FSE_COMMONDEFS_ONLY
+
+/*-*******************************************************
+* Decompression (Byte symbols)
+*********************************************************/
+
+FORCE_INLINE_TEMPLATE size_t FSE_decompress_usingDTable_generic(
+ void* dst, size_t maxDstSize,
+ const void* cSrc, size_t cSrcSize,
+ const FSE_DTable* dt, const unsigned fast)
+{
+ BYTE* const ostart = (BYTE*) dst;
+ BYTE* op = ostart;
+ BYTE* const omax = op + maxDstSize;
+ BYTE* const olimit = omax-3;
+
+ BIT_DStream_t bitD;
+ FSE_DState_t state1;
+ FSE_DState_t state2;
+
+ /* Init */
+ CHECK_F(BIT_initDStream(&bitD, cSrc, cSrcSize));
+
+ FSE_initDState(&state1, &bitD, dt);
+ FSE_initDState(&state2, &bitD, dt);
+
+#define FSE_GETSYMBOL(statePtr) fast ? FSE_decodeSymbolFast(statePtr, &bitD) : FSE_decodeSymbol(statePtr, &bitD)
+
+ /* 4 symbols per loop */
+ for ( ; (BIT_reloadDStream(&bitD)==BIT_DStream_unfinished) & (op<olimit) ; op+=4) {
+ op[0] = FSE_GETSYMBOL(&state1);
+
+ if (FSE_MAX_TABLELOG*2+7 > sizeof(bitD.bitContainer)*8) /* This test must be static */
+ BIT_reloadDStream(&bitD);
+
+ op[1] = FSE_GETSYMBOL(&state2);
+
+ if (FSE_MAX_TABLELOG*4+7 > sizeof(bitD.bitContainer)*8) /* This test must be static */
+ { if (BIT_reloadDStream(&bitD) > BIT_DStream_unfinished) { op+=2; break; } }
+
+ op[2] = FSE_GETSYMBOL(&state1);
+
+ if (FSE_MAX_TABLELOG*2+7 > sizeof(bitD.bitContainer)*8) /* This test must be static */
+ BIT_reloadDStream(&bitD);
+
+ op[3] = FSE_GETSYMBOL(&state2);
+ }
+
+ /* tail */
+ /* note : BIT_reloadDStream(&bitD) >= FSE_DStream_partiallyFilled; Ends at exactly BIT_DStream_completed */
+ while (1) {
+ if (op>(omax-2)) return ERROR(dstSize_tooSmall);
+ *op++ = FSE_GETSYMBOL(&state1);
+ if (BIT_reloadDStream(&bitD)==BIT_DStream_overflow) {
+ *op++ = FSE_GETSYMBOL(&state2);
+ break;
+ }
+
+ if (op>(omax-2)) return ERROR(dstSize_tooSmall);
+ *op++ = FSE_GETSYMBOL(&state2);
+ if (BIT_reloadDStream(&bitD)==BIT_DStream_overflow) {
+ *op++ = FSE_GETSYMBOL(&state1);
+ break;
+ } }
+
+ assert(op >= ostart);
+ return (size_t)(op-ostart);
+}
+
+typedef struct {
+ short ncount[FSE_MAX_SYMBOL_VALUE + 1];
+} FSE_DecompressWksp;
+
+
+FORCE_INLINE_TEMPLATE size_t FSE_decompress_wksp_body(
+ void* dst, size_t dstCapacity,
+ const void* cSrc, size_t cSrcSize,
+ unsigned maxLog, void* workSpace, size_t wkspSize,
+ int bmi2)
+{
+ const BYTE* const istart = (const BYTE*)cSrc;
+ const BYTE* ip = istart;
+ unsigned tableLog;
+ unsigned maxSymbolValue = FSE_MAX_SYMBOL_VALUE;
+ FSE_DecompressWksp* const wksp = (FSE_DecompressWksp*)workSpace;
+ size_t const dtablePos = sizeof(FSE_DecompressWksp) / sizeof(FSE_DTable);
+ FSE_DTable* const dtable = (FSE_DTable*)workSpace + dtablePos;
+
+ FSE_STATIC_ASSERT((FSE_MAX_SYMBOL_VALUE + 1) % 2 == 0);
+ if (wkspSize < sizeof(*wksp)) return ERROR(GENERIC);
+
+ /* correct offset to dtable depends on this property */
+ FSE_STATIC_ASSERT(sizeof(FSE_DecompressWksp) % sizeof(FSE_DTable) == 0);
+
+ /* normal FSE decoding mode */
+ { size_t const NCountLength =
+ FSE_readNCount_bmi2(wksp->ncount, &maxSymbolValue, &tableLog, istart, cSrcSize, bmi2);
+ if (FSE_isError(NCountLength)) return NCountLength;
+ if (tableLog > maxLog) return ERROR(tableLog_tooLarge);
+ assert(NCountLength <= cSrcSize);
+ ip += NCountLength;
+ cSrcSize -= NCountLength;
+ }
+
+ if (FSE_DECOMPRESS_WKSP_SIZE(tableLog, maxSymbolValue) > wkspSize) return ERROR(tableLog_tooLarge);
+ assert(sizeof(*wksp) + FSE_DTABLE_SIZE(tableLog) <= wkspSize);
+ workSpace = (BYTE*)workSpace + sizeof(*wksp) + FSE_DTABLE_SIZE(tableLog);
+ wkspSize -= sizeof(*wksp) + FSE_DTABLE_SIZE(tableLog);
+
+ CHECK_F( FSE_buildDTable_internal(dtable, wksp->ncount, maxSymbolValue, tableLog, workSpace, wkspSize) );
+
+ {
+ const void* ptr = dtable;
+ const FSE_DTableHeader* DTableH = (const FSE_DTableHeader*)ptr;
+ const U32 fastMode = DTableH->fastMode;
+
+ /* select fast mode (static) */
+ if (fastMode) return FSE_decompress_usingDTable_generic(dst, dstCapacity, ip, cSrcSize, dtable, 1);
+ return FSE_decompress_usingDTable_generic(dst, dstCapacity, ip, cSrcSize, dtable, 0);
+ }
+}
+
+/* Avoids the FORCE_INLINE of the _body() function. */
+static size_t FSE_decompress_wksp_body_default(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize)
+{
+ return FSE_decompress_wksp_body(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize, 0);
+}
+
+#if DYNAMIC_BMI2
+BMI2_TARGET_ATTRIBUTE static size_t FSE_decompress_wksp_body_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize)
+{
+ return FSE_decompress_wksp_body(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize, 1);
+}
+#endif
+
+size_t FSE_decompress_wksp_bmi2(void* dst, size_t dstCapacity, const void* cSrc, size_t cSrcSize, unsigned maxLog, void* workSpace, size_t wkspSize, int bmi2)
+{
+#if DYNAMIC_BMI2
+ if (bmi2) {
+ return FSE_decompress_wksp_body_bmi2(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize);
+ }
+#endif
+ (void)bmi2;
+ return FSE_decompress_wksp_body_default(dst, dstCapacity, cSrc, cSrcSize, maxLog, workSpace, wkspSize);
+}
+
+#endif /* FSE_COMMONDEFS_ONLY */
diff --git a/third_party/zstd/lib/common/huf.h b/third_party/zstd/lib/common/huf.h
new file mode 100644
index 0000000000..99bf85d6f4
--- /dev/null
+++ b/third_party/zstd/lib/common/huf.h
@@ -0,0 +1,286 @@
+/* ******************************************************************
+ * huff0 huffman codec,
+ * part of Finite State Entropy library
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * You can contact the author at :
+ * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+****************************************************************** */
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+#ifndef HUF_H_298734234
+#define HUF_H_298734234
+
+/* *** Dependencies *** */
+#include "zstd_deps.h" /* size_t */
+#include "mem.h" /* U32 */
+#define FSE_STATIC_LINKING_ONLY
+#include "fse.h"
+
+
+/* *** Tool functions *** */
+#define HUF_BLOCKSIZE_MAX (128 * 1024) /**< maximum input size for a single block compressed with HUF_compress */
+size_t HUF_compressBound(size_t size); /**< maximum compressed size (worst case) */
+
+/* Error Management */
+unsigned HUF_isError(size_t code); /**< tells if a return value is an error code */
+const char* HUF_getErrorName(size_t code); /**< provides error code string (useful for debugging) */
+
+
+#define HUF_WORKSPACE_SIZE ((8 << 10) + 512 /* sorting scratch space */)
+#define HUF_WORKSPACE_SIZE_U64 (HUF_WORKSPACE_SIZE / sizeof(U64))
+
+/* *** Constants *** */
+#define HUF_TABLELOG_MAX 12 /* max runtime value of tableLog (due to static allocation); can be modified up to HUF_TABLELOG_ABSOLUTEMAX */
+#define HUF_TABLELOG_DEFAULT 11 /* default tableLog value when none specified */
+#define HUF_SYMBOLVALUE_MAX 255
+
+#define HUF_TABLELOG_ABSOLUTEMAX 12 /* absolute limit of HUF_MAX_TABLELOG. Beyond that value, code does not work */
+#if (HUF_TABLELOG_MAX > HUF_TABLELOG_ABSOLUTEMAX)
+# error "HUF_TABLELOG_MAX is too large !"
+#endif
+
+
+/* ****************************************
+* Static allocation
+******************************************/
+/* HUF buffer bounds */
+#define HUF_CTABLEBOUND 129
+#define HUF_BLOCKBOUND(size) (size + (size>>8) + 8) /* only true when incompressible is pre-filtered with fast heuristic */
+#define HUF_COMPRESSBOUND(size) (HUF_CTABLEBOUND + HUF_BLOCKBOUND(size)) /* Macro version, useful for static allocation */
+
+/* static allocation of HUF's Compression Table */
+/* this is a private definition, just exposed for allocation and strict aliasing purpose. never EVER access its members directly */
+typedef size_t HUF_CElt; /* consider it an incomplete type */
+#define HUF_CTABLE_SIZE_ST(maxSymbolValue) ((maxSymbolValue)+2) /* Use tables of size_t, for proper alignment */
+#define HUF_CTABLE_SIZE(maxSymbolValue) (HUF_CTABLE_SIZE_ST(maxSymbolValue) * sizeof(size_t))
+#define HUF_CREATE_STATIC_CTABLE(name, maxSymbolValue) \
+ HUF_CElt name[HUF_CTABLE_SIZE_ST(maxSymbolValue)] /* no final ; */
+
+/* static allocation of HUF's DTable */
+typedef U32 HUF_DTable;
+#define HUF_DTABLE_SIZE(maxTableLog) (1 + (1<<(maxTableLog)))
+#define HUF_CREATE_STATIC_DTABLEX1(DTable, maxTableLog) \
+ HUF_DTable DTable[HUF_DTABLE_SIZE((maxTableLog)-1)] = { ((U32)((maxTableLog)-1) * 0x01000001) }
+#define HUF_CREATE_STATIC_DTABLEX2(DTable, maxTableLog) \
+ HUF_DTable DTable[HUF_DTABLE_SIZE(maxTableLog)] = { ((U32)(maxTableLog) * 0x01000001) }
+
+
+/* ****************************************
+* Advanced decompression functions
+******************************************/
+
+/**
+ * Huffman flags bitset.
+ * For all flags, 0 is the default value.
+ */
+typedef enum {
+ /**
+ * If compiled with DYNAMIC_BMI2: Set flag only if the CPU supports BMI2 at runtime.
+ * Otherwise: Ignored.
+ */
+ HUF_flags_bmi2 = (1 << 0),
+ /**
+ * If set: Test possible table depths to find the one that produces the smallest header + encoded size.
+ * If unset: Use heuristic to find the table depth.
+ */
+ HUF_flags_optimalDepth = (1 << 1),
+ /**
+ * If set: If the previous table can encode the input, always reuse the previous table.
+ * If unset: If the previous table can encode the input, reuse the previous table if it results in a smaller output.
+ */
+ HUF_flags_preferRepeat = (1 << 2),
+ /**
+ * If set: Sample the input and check if the sample is uncompressible, if it is then don't attempt to compress.
+ * If unset: Always histogram the entire input.
+ */
+ HUF_flags_suspectUncompressible = (1 << 3),
+ /**
+ * If set: Don't use assembly implementations
+ * If unset: Allow using assembly implementations
+ */
+ HUF_flags_disableAsm = (1 << 4),
+ /**
+ * If set: Don't use the fast decoding loop, always use the fallback decoding loop.
+ * If unset: Use the fast decoding loop when possible.
+ */
+ HUF_flags_disableFast = (1 << 5)
+} HUF_flags_e;
+
+
+/* ****************************************
+ * HUF detailed API
+ * ****************************************/
+#define HUF_OPTIMAL_DEPTH_THRESHOLD ZSTD_btultra
+
+/*! HUF_compress() does the following:
+ * 1. count symbol occurrence from source[] into table count[] using FSE_count() (exposed within "fse.h")
+ * 2. (optional) refine tableLog using HUF_optimalTableLog()
+ * 3. build Huffman table from count using HUF_buildCTable()
+ * 4. save Huffman table to memory buffer using HUF_writeCTable()
+ * 5. encode the data stream using HUF_compress4X_usingCTable()
+ *
+ * The following API allows targeting specific sub-functions for advanced tasks.
+ * For example, it's possible to compress several blocks using the same 'CTable',
+ * or to save and regenerate 'CTable' using external methods.
+ */
+unsigned HUF_minTableLog(unsigned symbolCardinality);
+unsigned HUF_cardinality(const unsigned* count, unsigned maxSymbolValue);
+unsigned HUF_optimalTableLog(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue, void* workSpace,
+ size_t wkspSize, HUF_CElt* table, const unsigned* count, int flags); /* table is used as scratch space for building and testing tables, not a return value */
+size_t HUF_writeCTable_wksp(void* dst, size_t maxDstSize, const HUF_CElt* CTable, unsigned maxSymbolValue, unsigned huffLog, void* workspace, size_t workspaceSize);
+size_t HUF_compress4X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int flags);
+size_t HUF_estimateCompressedSize(const HUF_CElt* CTable, const unsigned* count, unsigned maxSymbolValue);
+int HUF_validateCTable(const HUF_CElt* CTable, const unsigned* count, unsigned maxSymbolValue);
+
+typedef enum {
+ HUF_repeat_none, /**< Cannot use the previous table */
+ HUF_repeat_check, /**< Can use the previous table but it must be checked. Note : The previous table must have been constructed by HUF_compress{1, 4}X_repeat */
+ HUF_repeat_valid /**< Can use the previous table and it is assumed to be valid */
+ } HUF_repeat;
+
+/** HUF_compress4X_repeat() :
+ * Same as HUF_compress4X_wksp(), but considers using hufTable if *repeat != HUF_repeat_none.
+ * If it uses hufTable it does not modify hufTable or repeat.
+ * If it doesn't, it sets *repeat = HUF_repeat_none, and it sets hufTable to the table used.
+ * If preferRepeat then the old table will always be used if valid.
+ * If suspectUncompressible then some sampling checks will be run to potentially skip huffman coding */
+size_t HUF_compress4X_repeat(void* dst, size_t dstSize,
+ const void* src, size_t srcSize,
+ unsigned maxSymbolValue, unsigned tableLog,
+ void* workSpace, size_t wkspSize, /**< `workSpace` must be aligned on 4-bytes boundaries, `wkspSize` must be >= HUF_WORKSPACE_SIZE */
+ HUF_CElt* hufTable, HUF_repeat* repeat, int flags);
+
+/** HUF_buildCTable_wksp() :
+ * Same as HUF_buildCTable(), but using externally allocated scratch buffer.
+ * `workSpace` must be aligned on 4-bytes boundaries, and its size must be >= HUF_CTABLE_WORKSPACE_SIZE.
+ */
+#define HUF_CTABLE_WORKSPACE_SIZE_U32 ((4 * (HUF_SYMBOLVALUE_MAX + 1)) + 192)
+#define HUF_CTABLE_WORKSPACE_SIZE (HUF_CTABLE_WORKSPACE_SIZE_U32 * sizeof(unsigned))
+size_t HUF_buildCTable_wksp (HUF_CElt* tree,
+ const unsigned* count, U32 maxSymbolValue, U32 maxNbBits,
+ void* workSpace, size_t wkspSize);
+
+/*! HUF_readStats() :
+ * Read compact Huffman tree, saved by HUF_writeCTable().
+ * `huffWeight` is destination buffer.
+ * @return : size read from `src` , or an error Code .
+ * Note : Needed by HUF_readCTable() and HUF_readDTableXn() . */
+size_t HUF_readStats(BYTE* huffWeight, size_t hwSize,
+ U32* rankStats, U32* nbSymbolsPtr, U32* tableLogPtr,
+ const void* src, size_t srcSize);
+
+/*! HUF_readStats_wksp() :
+ * Same as HUF_readStats() but takes an external workspace which must be
+ * 4-byte aligned and its size must be >= HUF_READ_STATS_WORKSPACE_SIZE.
+ * If the CPU has BMI2 support, pass bmi2=1, otherwise pass bmi2=0.
+ */
+#define HUF_READ_STATS_WORKSPACE_SIZE_U32 FSE_DECOMPRESS_WKSP_SIZE_U32(6, HUF_TABLELOG_MAX-1)
+#define HUF_READ_STATS_WORKSPACE_SIZE (HUF_READ_STATS_WORKSPACE_SIZE_U32 * sizeof(unsigned))
+size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize,
+ U32* rankStats, U32* nbSymbolsPtr, U32* tableLogPtr,
+ const void* src, size_t srcSize,
+ void* workspace, size_t wkspSize,
+ int flags);
+
+/** HUF_readCTable() :
+ * Loading a CTable saved with HUF_writeCTable() */
+size_t HUF_readCTable (HUF_CElt* CTable, unsigned* maxSymbolValuePtr, const void* src, size_t srcSize, unsigned *hasZeroWeights);
+
+/** HUF_getNbBitsFromCTable() :
+ * Read nbBits from CTable symbolTable, for symbol `symbolValue` presumed <= HUF_SYMBOLVALUE_MAX
+ * Note 1 : If symbolValue > HUF_readCTableHeader(symbolTable).maxSymbolValue, returns 0
+ * Note 2 : is not inlined, as HUF_CElt definition is private
+ */
+U32 HUF_getNbBitsFromCTable(const HUF_CElt* symbolTable, U32 symbolValue);
+
+typedef struct {
+ BYTE tableLog;
+ BYTE maxSymbolValue;
+ BYTE unused[sizeof(size_t) - 2];
+} HUF_CTableHeader;
+
+/** HUF_readCTableHeader() :
+ * @returns The header from the CTable specifying the tableLog and the maxSymbolValue.
+ */
+HUF_CTableHeader HUF_readCTableHeader(HUF_CElt const* ctable);
+
+/*
+ * HUF_decompress() does the following:
+ * 1. select the decompression algorithm (X1, X2) based on pre-computed heuristics
+ * 2. build Huffman table from save, using HUF_readDTableX?()
+ * 3. decode 1 or 4 segments in parallel using HUF_decompress?X?_usingDTable()
+ */
+
+/** HUF_selectDecoder() :
+ * Tells which decoder is likely to decode faster,
+ * based on a set of pre-computed metrics.
+ * @return : 0==HUF_decompress4X1, 1==HUF_decompress4X2 .
+ * Assumption : 0 < dstSize <= 128 KB */
+U32 HUF_selectDecoder (size_t dstSize, size_t cSrcSize);
+
+/**
+ * The minimum workspace size for the `workSpace` used in
+ * HUF_readDTableX1_wksp() and HUF_readDTableX2_wksp().
+ *
+ * The space used depends on HUF_TABLELOG_MAX, ranging from ~1500 bytes when
+ * HUF_TABLE_LOG_MAX=12 to ~1850 bytes when HUF_TABLE_LOG_MAX=15.
+ * Buffer overflow errors may potentially occur if code modifications result in
+ * a required workspace size greater than that specified in the following
+ * macro.
+ */
+#define HUF_DECOMPRESS_WORKSPACE_SIZE ((2 << 10) + (1 << 9))
+#define HUF_DECOMPRESS_WORKSPACE_SIZE_U32 (HUF_DECOMPRESS_WORKSPACE_SIZE / sizeof(U32))
+
+
+/* ====================== */
+/* single stream variants */
+/* ====================== */
+
+size_t HUF_compress1X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int flags);
+/** HUF_compress1X_repeat() :
+ * Same as HUF_compress1X_wksp(), but considers using hufTable if *repeat != HUF_repeat_none.
+ * If it uses hufTable it does not modify hufTable or repeat.
+ * If it doesn't, it sets *repeat = HUF_repeat_none, and it sets hufTable to the table used.
+ * If preferRepeat then the old table will always be used if valid.
+ * If suspectUncompressible then some sampling checks will be run to potentially skip huffman coding */
+size_t HUF_compress1X_repeat(void* dst, size_t dstSize,
+ const void* src, size_t srcSize,
+ unsigned maxSymbolValue, unsigned tableLog,
+ void* workSpace, size_t wkspSize, /**< `workSpace` must be aligned on 4-bytes boundaries, `wkspSize` must be >= HUF_WORKSPACE_SIZE */
+ HUF_CElt* hufTable, HUF_repeat* repeat, int flags);
+
+size_t HUF_decompress1X_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags);
+#ifndef HUF_FORCE_DECOMPRESS_X1
+size_t HUF_decompress1X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags); /**< double-symbols decoder */
+#endif
+
+/* BMI2 variants.
+ * If the CPU has BMI2 support, pass bmi2=1, otherwise pass bmi2=0.
+ */
+size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags);
+#ifndef HUF_FORCE_DECOMPRESS_X2
+size_t HUF_decompress1X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags);
+#endif
+size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags);
+size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags);
+#ifndef HUF_FORCE_DECOMPRESS_X2
+size_t HUF_readDTableX1_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int flags);
+#endif
+#ifndef HUF_FORCE_DECOMPRESS_X1
+size_t HUF_readDTableX2_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int flags);
+#endif
+
+#endif /* HUF_H_298734234 */
+
+#if defined (__cplusplus)
+}
+#endif
diff --git a/third_party/zstd/lib/common/mem.h b/third_party/zstd/lib/common/mem.h
new file mode 100644
index 0000000000..096f4be519
--- /dev/null
+++ b/third_party/zstd/lib/common/mem.h
@@ -0,0 +1,426 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#ifndef MEM_H_MODULE
+#define MEM_H_MODULE
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+/*-****************************************
+* Dependencies
+******************************************/
+#include <stddef.h> /* size_t, ptrdiff_t */
+#include "compiler.h" /* __has_builtin */
+#include "debug.h" /* DEBUG_STATIC_ASSERT */
+#include "zstd_deps.h" /* ZSTD_memcpy */
+
+
+/*-****************************************
+* Compiler specifics
+******************************************/
+#if defined(_MSC_VER) /* Visual Studio */
+# include <stdlib.h> /* _byteswap_ulong */
+# include <intrin.h> /* _byteswap_* */
+#endif
+
+/*-**************************************************************
+* Basic Types
+*****************************************************************/
+#if !defined (__VMS) && (defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+# if defined(_AIX)
+# include <inttypes.h>
+# else
+# include <stdint.h> /* intptr_t */
+# endif
+ typedef uint8_t BYTE;
+ typedef uint8_t U8;
+ typedef int8_t S8;
+ typedef uint16_t U16;
+ typedef int16_t S16;
+ typedef uint32_t U32;
+ typedef int32_t S32;
+ typedef uint64_t U64;
+ typedef int64_t S64;
+#else
+# include <limits.h>
+#if CHAR_BIT != 8
+# error "this implementation requires char to be exactly 8-bit type"
+#endif
+ typedef unsigned char BYTE;
+ typedef unsigned char U8;
+ typedef signed char S8;
+#if USHRT_MAX != 65535
+# error "this implementation requires short to be exactly 16-bit type"
+#endif
+ typedef unsigned short U16;
+ typedef signed short S16;
+#if UINT_MAX != 4294967295
+# error "this implementation requires int to be exactly 32-bit type"
+#endif
+ typedef unsigned int U32;
+ typedef signed int S32;
+/* note : there are no limits defined for long long type in C90.
+ * limits exist in C99, however, in such case, <stdint.h> is preferred */
+ typedef unsigned long long U64;
+ typedef signed long long S64;
+#endif
+
+
+/*-**************************************************************
+* Memory I/O API
+*****************************************************************/
+/*=== Static platform detection ===*/
+MEM_STATIC unsigned MEM_32bits(void);
+MEM_STATIC unsigned MEM_64bits(void);
+MEM_STATIC unsigned MEM_isLittleEndian(void);
+
+/*=== Native unaligned read/write ===*/
+MEM_STATIC U16 MEM_read16(const void* memPtr);
+MEM_STATIC U32 MEM_read32(const void* memPtr);
+MEM_STATIC U64 MEM_read64(const void* memPtr);
+MEM_STATIC size_t MEM_readST(const void* memPtr);
+
+MEM_STATIC void MEM_write16(void* memPtr, U16 value);
+MEM_STATIC void MEM_write32(void* memPtr, U32 value);
+MEM_STATIC void MEM_write64(void* memPtr, U64 value);
+
+/*=== Little endian unaligned read/write ===*/
+MEM_STATIC U16 MEM_readLE16(const void* memPtr);
+MEM_STATIC U32 MEM_readLE24(const void* memPtr);
+MEM_STATIC U32 MEM_readLE32(const void* memPtr);
+MEM_STATIC U64 MEM_readLE64(const void* memPtr);
+MEM_STATIC size_t MEM_readLEST(const void* memPtr);
+
+MEM_STATIC void MEM_writeLE16(void* memPtr, U16 val);
+MEM_STATIC void MEM_writeLE24(void* memPtr, U32 val);
+MEM_STATIC void MEM_writeLE32(void* memPtr, U32 val32);
+MEM_STATIC void MEM_writeLE64(void* memPtr, U64 val64);
+MEM_STATIC void MEM_writeLEST(void* memPtr, size_t val);
+
+/*=== Big endian unaligned read/write ===*/
+MEM_STATIC U32 MEM_readBE32(const void* memPtr);
+MEM_STATIC U64 MEM_readBE64(const void* memPtr);
+MEM_STATIC size_t MEM_readBEST(const void* memPtr);
+
+MEM_STATIC void MEM_writeBE32(void* memPtr, U32 val32);
+MEM_STATIC void MEM_writeBE64(void* memPtr, U64 val64);
+MEM_STATIC void MEM_writeBEST(void* memPtr, size_t val);
+
+/*=== Byteswap ===*/
+MEM_STATIC U32 MEM_swap32(U32 in);
+MEM_STATIC U64 MEM_swap64(U64 in);
+MEM_STATIC size_t MEM_swapST(size_t in);
+
+
+/*-**************************************************************
+* Memory I/O Implementation
+*****************************************************************/
+/* MEM_FORCE_MEMORY_ACCESS : For accessing unaligned memory:
+ * Method 0 : always use `memcpy()`. Safe and portable.
+ * Method 1 : Use compiler extension to set unaligned access.
+ * Method 2 : direct access. This method is portable but violate C standard.
+ * It can generate buggy code on targets depending on alignment.
+ * Default : method 1 if supported, else method 0
+ */
+#ifndef MEM_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */
+# ifdef __GNUC__
+# define MEM_FORCE_MEMORY_ACCESS 1
+# endif
+#endif
+
+MEM_STATIC unsigned MEM_32bits(void) { return sizeof(size_t)==4; }
+MEM_STATIC unsigned MEM_64bits(void) { return sizeof(size_t)==8; }
+
+MEM_STATIC unsigned MEM_isLittleEndian(void)
+{
+#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+ return 1;
+#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+ return 0;
+#elif defined(__clang__) && __LITTLE_ENDIAN__
+ return 1;
+#elif defined(__clang__) && __BIG_ENDIAN__
+ return 0;
+#elif defined(_MSC_VER) && (_M_AMD64 || _M_IX86)
+ return 1;
+#elif defined(__DMC__) && defined(_M_IX86)
+ return 1;
+#else
+ const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */
+ return one.c[0];
+#endif
+}
+
+#if defined(MEM_FORCE_MEMORY_ACCESS) && (MEM_FORCE_MEMORY_ACCESS==2)
+
+/* violates C standard, by lying on structure alignment.
+Only use if no other choice to achieve best performance on target platform */
+MEM_STATIC U16 MEM_read16(const void* memPtr) { return *(const U16*) memPtr; }
+MEM_STATIC U32 MEM_read32(const void* memPtr) { return *(const U32*) memPtr; }
+MEM_STATIC U64 MEM_read64(const void* memPtr) { return *(const U64*) memPtr; }
+MEM_STATIC size_t MEM_readST(const void* memPtr) { return *(const size_t*) memPtr; }
+
+MEM_STATIC void MEM_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; }
+MEM_STATIC void MEM_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; }
+MEM_STATIC void MEM_write64(void* memPtr, U64 value) { *(U64*)memPtr = value; }
+
+#elif defined(MEM_FORCE_MEMORY_ACCESS) && (MEM_FORCE_MEMORY_ACCESS==1)
+
+typedef __attribute__((aligned(1))) U16 unalign16;
+typedef __attribute__((aligned(1))) U32 unalign32;
+typedef __attribute__((aligned(1))) U64 unalign64;
+typedef __attribute__((aligned(1))) size_t unalignArch;
+
+MEM_STATIC U16 MEM_read16(const void* ptr) { return *(const unalign16*)ptr; }
+MEM_STATIC U32 MEM_read32(const void* ptr) { return *(const unalign32*)ptr; }
+MEM_STATIC U64 MEM_read64(const void* ptr) { return *(const unalign64*)ptr; }
+MEM_STATIC size_t MEM_readST(const void* ptr) { return *(const unalignArch*)ptr; }
+
+MEM_STATIC void MEM_write16(void* memPtr, U16 value) { *(unalign16*)memPtr = value; }
+MEM_STATIC void MEM_write32(void* memPtr, U32 value) { *(unalign32*)memPtr = value; }
+MEM_STATIC void MEM_write64(void* memPtr, U64 value) { *(unalign64*)memPtr = value; }
+
+#else
+
+/* default method, safe and standard.
+ can sometimes prove slower */
+
+MEM_STATIC U16 MEM_read16(const void* memPtr)
+{
+ U16 val; ZSTD_memcpy(&val, memPtr, sizeof(val)); return val;
+}
+
+MEM_STATIC U32 MEM_read32(const void* memPtr)
+{
+ U32 val; ZSTD_memcpy(&val, memPtr, sizeof(val)); return val;
+}
+
+MEM_STATIC U64 MEM_read64(const void* memPtr)
+{
+ U64 val; ZSTD_memcpy(&val, memPtr, sizeof(val)); return val;
+}
+
+MEM_STATIC size_t MEM_readST(const void* memPtr)
+{
+ size_t val; ZSTD_memcpy(&val, memPtr, sizeof(val)); return val;
+}
+
+MEM_STATIC void MEM_write16(void* memPtr, U16 value)
+{
+ ZSTD_memcpy(memPtr, &value, sizeof(value));
+}
+
+MEM_STATIC void MEM_write32(void* memPtr, U32 value)
+{
+ ZSTD_memcpy(memPtr, &value, sizeof(value));
+}
+
+MEM_STATIC void MEM_write64(void* memPtr, U64 value)
+{
+ ZSTD_memcpy(memPtr, &value, sizeof(value));
+}
+
+#endif /* MEM_FORCE_MEMORY_ACCESS */
+
+MEM_STATIC U32 MEM_swap32_fallback(U32 in)
+{
+ return ((in << 24) & 0xff000000 ) |
+ ((in << 8) & 0x00ff0000 ) |
+ ((in >> 8) & 0x0000ff00 ) |
+ ((in >> 24) & 0x000000ff );
+}
+
+MEM_STATIC U32 MEM_swap32(U32 in)
+{
+#if defined(_MSC_VER) /* Visual Studio */
+ return _byteswap_ulong(in);
+#elif (defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) \
+ || (defined(__clang__) && __has_builtin(__builtin_bswap32))
+ return __builtin_bswap32(in);
+#else
+ return MEM_swap32_fallback(in);
+#endif
+}
+
+MEM_STATIC U64 MEM_swap64_fallback(U64 in)
+{
+ return ((in << 56) & 0xff00000000000000ULL) |
+ ((in << 40) & 0x00ff000000000000ULL) |
+ ((in << 24) & 0x0000ff0000000000ULL) |
+ ((in << 8) & 0x000000ff00000000ULL) |
+ ((in >> 8) & 0x00000000ff000000ULL) |
+ ((in >> 24) & 0x0000000000ff0000ULL) |
+ ((in >> 40) & 0x000000000000ff00ULL) |
+ ((in >> 56) & 0x00000000000000ffULL);
+}
+
+MEM_STATIC U64 MEM_swap64(U64 in)
+{
+#if defined(_MSC_VER) /* Visual Studio */
+ return _byteswap_uint64(in);
+#elif (defined (__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) \
+ || (defined(__clang__) && __has_builtin(__builtin_bswap64))
+ return __builtin_bswap64(in);
+#else
+ return MEM_swap64_fallback(in);
+#endif
+}
+
+MEM_STATIC size_t MEM_swapST(size_t in)
+{
+ if (MEM_32bits())
+ return (size_t)MEM_swap32((U32)in);
+ else
+ return (size_t)MEM_swap64((U64)in);
+}
+
+/*=== Little endian r/w ===*/
+
+MEM_STATIC U16 MEM_readLE16(const void* memPtr)
+{
+ if (MEM_isLittleEndian())
+ return MEM_read16(memPtr);
+ else {
+ const BYTE* p = (const BYTE*)memPtr;
+ return (U16)(p[0] + (p[1]<<8));
+ }
+}
+
+MEM_STATIC void MEM_writeLE16(void* memPtr, U16 val)
+{
+ if (MEM_isLittleEndian()) {
+ MEM_write16(memPtr, val);
+ } else {
+ BYTE* p = (BYTE*)memPtr;
+ p[0] = (BYTE)val;
+ p[1] = (BYTE)(val>>8);
+ }
+}
+
+MEM_STATIC U32 MEM_readLE24(const void* memPtr)
+{
+ return (U32)MEM_readLE16(memPtr) + ((U32)(((const BYTE*)memPtr)[2]) << 16);
+}
+
+MEM_STATIC void MEM_writeLE24(void* memPtr, U32 val)
+{
+ MEM_writeLE16(memPtr, (U16)val);
+ ((BYTE*)memPtr)[2] = (BYTE)(val>>16);
+}
+
+MEM_STATIC U32 MEM_readLE32(const void* memPtr)
+{
+ if (MEM_isLittleEndian())
+ return MEM_read32(memPtr);
+ else
+ return MEM_swap32(MEM_read32(memPtr));
+}
+
+MEM_STATIC void MEM_writeLE32(void* memPtr, U32 val32)
+{
+ if (MEM_isLittleEndian())
+ MEM_write32(memPtr, val32);
+ else
+ MEM_write32(memPtr, MEM_swap32(val32));
+}
+
+MEM_STATIC U64 MEM_readLE64(const void* memPtr)
+{
+ if (MEM_isLittleEndian())
+ return MEM_read64(memPtr);
+ else
+ return MEM_swap64(MEM_read64(memPtr));
+}
+
+MEM_STATIC void MEM_writeLE64(void* memPtr, U64 val64)
+{
+ if (MEM_isLittleEndian())
+ MEM_write64(memPtr, val64);
+ else
+ MEM_write64(memPtr, MEM_swap64(val64));
+}
+
+MEM_STATIC size_t MEM_readLEST(const void* memPtr)
+{
+ if (MEM_32bits())
+ return (size_t)MEM_readLE32(memPtr);
+ else
+ return (size_t)MEM_readLE64(memPtr);
+}
+
+MEM_STATIC void MEM_writeLEST(void* memPtr, size_t val)
+{
+ if (MEM_32bits())
+ MEM_writeLE32(memPtr, (U32)val);
+ else
+ MEM_writeLE64(memPtr, (U64)val);
+}
+
+/*=== Big endian r/w ===*/
+
+MEM_STATIC U32 MEM_readBE32(const void* memPtr)
+{
+ if (MEM_isLittleEndian())
+ return MEM_swap32(MEM_read32(memPtr));
+ else
+ return MEM_read32(memPtr);
+}
+
+MEM_STATIC void MEM_writeBE32(void* memPtr, U32 val32)
+{
+ if (MEM_isLittleEndian())
+ MEM_write32(memPtr, MEM_swap32(val32));
+ else
+ MEM_write32(memPtr, val32);
+}
+
+MEM_STATIC U64 MEM_readBE64(const void* memPtr)
+{
+ if (MEM_isLittleEndian())
+ return MEM_swap64(MEM_read64(memPtr));
+ else
+ return MEM_read64(memPtr);
+}
+
+MEM_STATIC void MEM_writeBE64(void* memPtr, U64 val64)
+{
+ if (MEM_isLittleEndian())
+ MEM_write64(memPtr, MEM_swap64(val64));
+ else
+ MEM_write64(memPtr, val64);
+}
+
+MEM_STATIC size_t MEM_readBEST(const void* memPtr)
+{
+ if (MEM_32bits())
+ return (size_t)MEM_readBE32(memPtr);
+ else
+ return (size_t)MEM_readBE64(memPtr);
+}
+
+MEM_STATIC void MEM_writeBEST(void* memPtr, size_t val)
+{
+ if (MEM_32bits())
+ MEM_writeBE32(memPtr, (U32)val);
+ else
+ MEM_writeBE64(memPtr, (U64)val);
+}
+
+/* code only tested on 32 and 64 bits systems */
+MEM_STATIC void MEM_check(void) { DEBUG_STATIC_ASSERT((sizeof(size_t)==4) || (sizeof(size_t)==8)); }
+
+
+#if defined (__cplusplus)
+}
+#endif
+
+#endif /* MEM_H_MODULE */
diff --git a/third_party/zstd/lib/common/pool.c b/third_party/zstd/lib/common/pool.c
new file mode 100644
index 0000000000..3adcefc9a5
--- /dev/null
+++ b/third_party/zstd/lib/common/pool.c
@@ -0,0 +1,371 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+
+/* ====== Dependencies ======= */
+#include "../common/allocations.h" /* ZSTD_customCalloc, ZSTD_customFree */
+#include "zstd_deps.h" /* size_t */
+#include "debug.h" /* assert */
+#include "pool.h"
+
+/* ====== Compiler specifics ====== */
+#if defined(_MSC_VER)
+# pragma warning(disable : 4204) /* disable: C4204: non-constant aggregate initializer */
+#endif
+
+
+#ifdef ZSTD_MULTITHREAD
+
+#include "threading.h" /* pthread adaptation */
+
+/* A job is a function and an opaque argument */
+typedef struct POOL_job_s {
+ POOL_function function;
+ void *opaque;
+} POOL_job;
+
+struct POOL_ctx_s {
+ ZSTD_customMem customMem;
+ /* Keep track of the threads */
+ ZSTD_pthread_t* threads;
+ size_t threadCapacity;
+ size_t threadLimit;
+
+ /* The queue is a circular buffer */
+ POOL_job *queue;
+ size_t queueHead;
+ size_t queueTail;
+ size_t queueSize;
+
+ /* The number of threads working on jobs */
+ size_t numThreadsBusy;
+ /* Indicates if the queue is empty */
+ int queueEmpty;
+
+ /* The mutex protects the queue */
+ ZSTD_pthread_mutex_t queueMutex;
+ /* Condition variable for pushers to wait on when the queue is full */
+ ZSTD_pthread_cond_t queuePushCond;
+ /* Condition variables for poppers to wait on when the queue is empty */
+ ZSTD_pthread_cond_t queuePopCond;
+ /* Indicates if the queue is shutting down */
+ int shutdown;
+};
+
+/* POOL_thread() :
+ * Work thread for the thread pool.
+ * Waits for jobs and executes them.
+ * @returns : NULL on failure else non-null.
+ */
+static void* POOL_thread(void* opaque) {
+ POOL_ctx* const ctx = (POOL_ctx*)opaque;
+ if (!ctx) { return NULL; }
+ for (;;) {
+ /* Lock the mutex and wait for a non-empty queue or until shutdown */
+ ZSTD_pthread_mutex_lock(&ctx->queueMutex);
+
+ while ( ctx->queueEmpty
+ || (ctx->numThreadsBusy >= ctx->threadLimit) ) {
+ if (ctx->shutdown) {
+ /* even if !queueEmpty, (possible if numThreadsBusy >= threadLimit),
+ * a few threads will be shutdown while !queueEmpty,
+ * but enough threads will remain active to finish the queue */
+ ZSTD_pthread_mutex_unlock(&ctx->queueMutex);
+ return opaque;
+ }
+ ZSTD_pthread_cond_wait(&ctx->queuePopCond, &ctx->queueMutex);
+ }
+ /* Pop a job off the queue */
+ { POOL_job const job = ctx->queue[ctx->queueHead];
+ ctx->queueHead = (ctx->queueHead + 1) % ctx->queueSize;
+ ctx->numThreadsBusy++;
+ ctx->queueEmpty = (ctx->queueHead == ctx->queueTail);
+ /* Unlock the mutex, signal a pusher, and run the job */
+ ZSTD_pthread_cond_signal(&ctx->queuePushCond);
+ ZSTD_pthread_mutex_unlock(&ctx->queueMutex);
+
+ job.function(job.opaque);
+
+ /* If the intended queue size was 0, signal after finishing job */
+ ZSTD_pthread_mutex_lock(&ctx->queueMutex);
+ ctx->numThreadsBusy--;
+ ZSTD_pthread_cond_signal(&ctx->queuePushCond);
+ ZSTD_pthread_mutex_unlock(&ctx->queueMutex);
+ }
+ } /* for (;;) */
+ assert(0); /* Unreachable */
+}
+
+/* ZSTD_createThreadPool() : public access point */
+POOL_ctx* ZSTD_createThreadPool(size_t numThreads) {
+ return POOL_create (numThreads, 0);
+}
+
+POOL_ctx* POOL_create(size_t numThreads, size_t queueSize) {
+ return POOL_create_advanced(numThreads, queueSize, ZSTD_defaultCMem);
+}
+
+POOL_ctx* POOL_create_advanced(size_t numThreads, size_t queueSize,
+ ZSTD_customMem customMem)
+{
+ POOL_ctx* ctx;
+ /* Check parameters */
+ if (!numThreads) { return NULL; }
+ /* Allocate the context and zero initialize */
+ ctx = (POOL_ctx*)ZSTD_customCalloc(sizeof(POOL_ctx), customMem);
+ if (!ctx) { return NULL; }
+ /* Initialize the job queue.
+ * It needs one extra space since one space is wasted to differentiate
+ * empty and full queues.
+ */
+ ctx->queueSize = queueSize + 1;
+ ctx->queue = (POOL_job*)ZSTD_customCalloc(ctx->queueSize * sizeof(POOL_job), customMem);
+ ctx->queueHead = 0;
+ ctx->queueTail = 0;
+ ctx->numThreadsBusy = 0;
+ ctx->queueEmpty = 1;
+ {
+ int error = 0;
+ error |= ZSTD_pthread_mutex_init(&ctx->queueMutex, NULL);
+ error |= ZSTD_pthread_cond_init(&ctx->queuePushCond, NULL);
+ error |= ZSTD_pthread_cond_init(&ctx->queuePopCond, NULL);
+ if (error) { POOL_free(ctx); return NULL; }
+ }
+ ctx->shutdown = 0;
+ /* Allocate space for the thread handles */
+ ctx->threads = (ZSTD_pthread_t*)ZSTD_customCalloc(numThreads * sizeof(ZSTD_pthread_t), customMem);
+ ctx->threadCapacity = 0;
+ ctx->customMem = customMem;
+ /* Check for errors */
+ if (!ctx->threads || !ctx->queue) { POOL_free(ctx); return NULL; }
+ /* Initialize the threads */
+ { size_t i;
+ for (i = 0; i < numThreads; ++i) {
+ if (ZSTD_pthread_create(&ctx->threads[i], NULL, &POOL_thread, ctx)) {
+ ctx->threadCapacity = i;
+ POOL_free(ctx);
+ return NULL;
+ } }
+ ctx->threadCapacity = numThreads;
+ ctx->threadLimit = numThreads;
+ }
+ return ctx;
+}
+
+/*! POOL_join() :
+ Shutdown the queue, wake any sleeping threads, and join all of the threads.
+*/
+static void POOL_join(POOL_ctx* ctx) {
+ /* Shut down the queue */
+ ZSTD_pthread_mutex_lock(&ctx->queueMutex);
+ ctx->shutdown = 1;
+ ZSTD_pthread_mutex_unlock(&ctx->queueMutex);
+ /* Wake up sleeping threads */
+ ZSTD_pthread_cond_broadcast(&ctx->queuePushCond);
+ ZSTD_pthread_cond_broadcast(&ctx->queuePopCond);
+ /* Join all of the threads */
+ { size_t i;
+ for (i = 0; i < ctx->threadCapacity; ++i) {
+ ZSTD_pthread_join(ctx->threads[i]); /* note : could fail */
+ } }
+}
+
+void POOL_free(POOL_ctx *ctx) {
+ if (!ctx) { return; }
+ POOL_join(ctx);
+ ZSTD_pthread_mutex_destroy(&ctx->queueMutex);
+ ZSTD_pthread_cond_destroy(&ctx->queuePushCond);
+ ZSTD_pthread_cond_destroy(&ctx->queuePopCond);
+ ZSTD_customFree(ctx->queue, ctx->customMem);
+ ZSTD_customFree(ctx->threads, ctx->customMem);
+ ZSTD_customFree(ctx, ctx->customMem);
+}
+
+/*! POOL_joinJobs() :
+ * Waits for all queued jobs to finish executing.
+ */
+void POOL_joinJobs(POOL_ctx* ctx) {
+ ZSTD_pthread_mutex_lock(&ctx->queueMutex);
+ while(!ctx->queueEmpty || ctx->numThreadsBusy > 0) {
+ ZSTD_pthread_cond_wait(&ctx->queuePushCond, &ctx->queueMutex);
+ }
+ ZSTD_pthread_mutex_unlock(&ctx->queueMutex);
+}
+
+void ZSTD_freeThreadPool (ZSTD_threadPool* pool) {
+ POOL_free (pool);
+}
+
+size_t POOL_sizeof(const POOL_ctx* ctx) {
+ if (ctx==NULL) return 0; /* supports sizeof NULL */
+ return sizeof(*ctx)
+ + ctx->queueSize * sizeof(POOL_job)
+ + ctx->threadCapacity * sizeof(ZSTD_pthread_t);
+}
+
+
+/* @return : 0 on success, 1 on error */
+static int POOL_resize_internal(POOL_ctx* ctx, size_t numThreads)
+{
+ if (numThreads <= ctx->threadCapacity) {
+ if (!numThreads) return 1;
+ ctx->threadLimit = numThreads;
+ return 0;
+ }
+ /* numThreads > threadCapacity */
+ { ZSTD_pthread_t* const threadPool = (ZSTD_pthread_t*)ZSTD_customCalloc(numThreads * sizeof(ZSTD_pthread_t), ctx->customMem);
+ if (!threadPool) return 1;
+ /* replace existing thread pool */
+ ZSTD_memcpy(threadPool, ctx->threads, ctx->threadCapacity * sizeof(ZSTD_pthread_t));
+ ZSTD_customFree(ctx->threads, ctx->customMem);
+ ctx->threads = threadPool;
+ /* Initialize additional threads */
+ { size_t threadId;
+ for (threadId = ctx->threadCapacity; threadId < numThreads; ++threadId) {
+ if (ZSTD_pthread_create(&threadPool[threadId], NULL, &POOL_thread, ctx)) {
+ ctx->threadCapacity = threadId;
+ return 1;
+ } }
+ } }
+ /* successfully expanded */
+ ctx->threadCapacity = numThreads;
+ ctx->threadLimit = numThreads;
+ return 0;
+}
+
+/* @return : 0 on success, 1 on error */
+int POOL_resize(POOL_ctx* ctx, size_t numThreads)
+{
+ int result;
+ if (ctx==NULL) return 1;
+ ZSTD_pthread_mutex_lock(&ctx->queueMutex);
+ result = POOL_resize_internal(ctx, numThreads);
+ ZSTD_pthread_cond_broadcast(&ctx->queuePopCond);
+ ZSTD_pthread_mutex_unlock(&ctx->queueMutex);
+ return result;
+}
+
+/**
+ * Returns 1 if the queue is full and 0 otherwise.
+ *
+ * When queueSize is 1 (pool was created with an intended queueSize of 0),
+ * then a queue is empty if there is a thread free _and_ no job is waiting.
+ */
+static int isQueueFull(POOL_ctx const* ctx) {
+ if (ctx->queueSize > 1) {
+ return ctx->queueHead == ((ctx->queueTail + 1) % ctx->queueSize);
+ } else {
+ return (ctx->numThreadsBusy == ctx->threadLimit) ||
+ !ctx->queueEmpty;
+ }
+}
+
+
+static void
+POOL_add_internal(POOL_ctx* ctx, POOL_function function, void *opaque)
+{
+ POOL_job job;
+ job.function = function;
+ job.opaque = opaque;
+ assert(ctx != NULL);
+ if (ctx->shutdown) return;
+
+ ctx->queueEmpty = 0;
+ ctx->queue[ctx->queueTail] = job;
+ ctx->queueTail = (ctx->queueTail + 1) % ctx->queueSize;
+ ZSTD_pthread_cond_signal(&ctx->queuePopCond);
+}
+
+void POOL_add(POOL_ctx* ctx, POOL_function function, void* opaque)
+{
+ assert(ctx != NULL);
+ ZSTD_pthread_mutex_lock(&ctx->queueMutex);
+ /* Wait until there is space in the queue for the new job */
+ while (isQueueFull(ctx) && (!ctx->shutdown)) {
+ ZSTD_pthread_cond_wait(&ctx->queuePushCond, &ctx->queueMutex);
+ }
+ POOL_add_internal(ctx, function, opaque);
+ ZSTD_pthread_mutex_unlock(&ctx->queueMutex);
+}
+
+
+int POOL_tryAdd(POOL_ctx* ctx, POOL_function function, void* opaque)
+{
+ assert(ctx != NULL);
+ ZSTD_pthread_mutex_lock(&ctx->queueMutex);
+ if (isQueueFull(ctx)) {
+ ZSTD_pthread_mutex_unlock(&ctx->queueMutex);
+ return 0;
+ }
+ POOL_add_internal(ctx, function, opaque);
+ ZSTD_pthread_mutex_unlock(&ctx->queueMutex);
+ return 1;
+}
+
+
+#else /* ZSTD_MULTITHREAD not defined */
+
+/* ========================== */
+/* No multi-threading support */
+/* ========================== */
+
+
+/* We don't need any data, but if it is empty, malloc() might return NULL. */
+struct POOL_ctx_s {
+ int dummy;
+};
+static POOL_ctx g_poolCtx;
+
+POOL_ctx* POOL_create(size_t numThreads, size_t queueSize) {
+ return POOL_create_advanced(numThreads, queueSize, ZSTD_defaultCMem);
+}
+
+POOL_ctx*
+POOL_create_advanced(size_t numThreads, size_t queueSize, ZSTD_customMem customMem)
+{
+ (void)numThreads;
+ (void)queueSize;
+ (void)customMem;
+ return &g_poolCtx;
+}
+
+void POOL_free(POOL_ctx* ctx) {
+ assert(!ctx || ctx == &g_poolCtx);
+ (void)ctx;
+}
+
+void POOL_joinJobs(POOL_ctx* ctx){
+ assert(!ctx || ctx == &g_poolCtx);
+ (void)ctx;
+}
+
+int POOL_resize(POOL_ctx* ctx, size_t numThreads) {
+ (void)ctx; (void)numThreads;
+ return 0;
+}
+
+void POOL_add(POOL_ctx* ctx, POOL_function function, void* opaque) {
+ (void)ctx;
+ function(opaque);
+}
+
+int POOL_tryAdd(POOL_ctx* ctx, POOL_function function, void* opaque) {
+ (void)ctx;
+ function(opaque);
+ return 1;
+}
+
+size_t POOL_sizeof(const POOL_ctx* ctx) {
+ if (ctx==NULL) return 0; /* supports sizeof NULL */
+ assert(ctx == &g_poolCtx);
+ return sizeof(*ctx);
+}
+
+#endif /* ZSTD_MULTITHREAD */
diff --git a/third_party/zstd/lib/common/pool.h b/third_party/zstd/lib/common/pool.h
new file mode 100644
index 0000000000..cca4de73a8
--- /dev/null
+++ b/third_party/zstd/lib/common/pool.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#ifndef POOL_H
+#define POOL_H
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+
+#include "zstd_deps.h"
+#define ZSTD_STATIC_LINKING_ONLY /* ZSTD_customMem */
+#include "../zstd.h"
+
+typedef struct POOL_ctx_s POOL_ctx;
+
+/*! POOL_create() :
+ * Create a thread pool with at most `numThreads` threads.
+ * `numThreads` must be at least 1.
+ * The maximum number of queued jobs before blocking is `queueSize`.
+ * @return : POOL_ctx pointer on success, else NULL.
+*/
+POOL_ctx* POOL_create(size_t numThreads, size_t queueSize);
+
+POOL_ctx* POOL_create_advanced(size_t numThreads, size_t queueSize,
+ ZSTD_customMem customMem);
+
+/*! POOL_free() :
+ * Free a thread pool returned by POOL_create().
+ */
+void POOL_free(POOL_ctx* ctx);
+
+
+/*! POOL_joinJobs() :
+ * Waits for all queued jobs to finish executing.
+ */
+void POOL_joinJobs(POOL_ctx* ctx);
+
+/*! POOL_resize() :
+ * Expands or shrinks pool's number of threads.
+ * This is more efficient than releasing + creating a new context,
+ * since it tries to preserve and reuse existing threads.
+ * `numThreads` must be at least 1.
+ * @return : 0 when resize was successful,
+ * !0 (typically 1) if there is an error.
+ * note : only numThreads can be resized, queueSize remains unchanged.
+ */
+int POOL_resize(POOL_ctx* ctx, size_t numThreads);
+
+/*! POOL_sizeof() :
+ * @return threadpool memory usage
+ * note : compatible with NULL (returns 0 in this case)
+ */
+size_t POOL_sizeof(const POOL_ctx* ctx);
+
+/*! POOL_function :
+ * The function type that can be added to a thread pool.
+ */
+typedef void (*POOL_function)(void*);
+
+/*! POOL_add() :
+ * Add the job `function(opaque)` to the thread pool. `ctx` must be valid.
+ * Possibly blocks until there is room in the queue.
+ * Note : The function may be executed asynchronously,
+ * therefore, `opaque` must live until function has been completed.
+ */
+void POOL_add(POOL_ctx* ctx, POOL_function function, void* opaque);
+
+
+/*! POOL_tryAdd() :
+ * Add the job `function(opaque)` to thread pool _if_ a queue slot is available.
+ * Returns immediately even if not (does not block).
+ * @return : 1 if successful, 0 if not.
+ */
+int POOL_tryAdd(POOL_ctx* ctx, POOL_function function, void* opaque);
+
+
+#if defined (__cplusplus)
+}
+#endif
+
+#endif
diff --git a/third_party/zstd/lib/common/portability_macros.h b/third_party/zstd/lib/common/portability_macros.h
new file mode 100644
index 0000000000..e50314a78e
--- /dev/null
+++ b/third_party/zstd/lib/common/portability_macros.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#ifndef ZSTD_PORTABILITY_MACROS_H
+#define ZSTD_PORTABILITY_MACROS_H
+
+/**
+ * This header file contains macro definitions to support portability.
+ * This header is shared between C and ASM code, so it MUST only
+ * contain macro definitions. It MUST not contain any C code.
+ *
+ * This header ONLY defines macros to detect platforms/feature support.
+ *
+ */
+
+
+/* compat. with non-clang compilers */
+#ifndef __has_attribute
+ #define __has_attribute(x) 0
+#endif
+
+/* compat. with non-clang compilers */
+#ifndef __has_builtin
+# define __has_builtin(x) 0
+#endif
+
+/* compat. with non-clang compilers */
+#ifndef __has_feature
+# define __has_feature(x) 0
+#endif
+
+/* detects whether we are being compiled under msan */
+#ifndef ZSTD_MEMORY_SANITIZER
+# if __has_feature(memory_sanitizer)
+# define ZSTD_MEMORY_SANITIZER 1
+# else
+# define ZSTD_MEMORY_SANITIZER 0
+# endif
+#endif
+
+/* detects whether we are being compiled under asan */
+#ifndef ZSTD_ADDRESS_SANITIZER
+# if __has_feature(address_sanitizer)
+# define ZSTD_ADDRESS_SANITIZER 1
+# elif defined(__SANITIZE_ADDRESS__)
+# define ZSTD_ADDRESS_SANITIZER 1
+# else
+# define ZSTD_ADDRESS_SANITIZER 0
+# endif
+#endif
+
+/* detects whether we are being compiled under dfsan */
+#ifndef ZSTD_DATAFLOW_SANITIZER
+# if __has_feature(dataflow_sanitizer)
+# define ZSTD_DATAFLOW_SANITIZER 1
+# else
+# define ZSTD_DATAFLOW_SANITIZER 0
+# endif
+#endif
+
+/* Mark the internal assembly functions as hidden */
+#ifdef __ELF__
+# define ZSTD_HIDE_ASM_FUNCTION(func) .hidden func
+#elif defined(__APPLE__)
+# define ZSTD_HIDE_ASM_FUNCTION(func) .private_extern func
+#else
+# define ZSTD_HIDE_ASM_FUNCTION(func)
+#endif
+
+/* Enable runtime BMI2 dispatch based on the CPU.
+ * Enabled for clang & gcc >=4.8 on x86 when BMI2 isn't enabled by default.
+ */
+#ifndef DYNAMIC_BMI2
+ #if ((defined(__clang__) && __has_attribute(__target__)) \
+ || (defined(__GNUC__) \
+ && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)))) \
+ && (defined(__x86_64__) || defined(_M_X64)) \
+ && !defined(__BMI2__)
+ # define DYNAMIC_BMI2 1
+ #else
+ # define DYNAMIC_BMI2 0
+ #endif
+#endif
+
+/**
+ * Only enable assembly for GNUC compatible compilers,
+ * because other platforms may not support GAS assembly syntax.
+ *
+ * Only enable assembly for Linux / MacOS, other platforms may
+ * work, but they haven't been tested. This could likely be
+ * extended to BSD systems.
+ *
+ * Disable assembly when MSAN is enabled, because MSAN requires
+ * 100% of code to be instrumented to work.
+ */
+#if defined(__GNUC__)
+# if defined(__linux__) || defined(__linux) || defined(__APPLE__)
+# if ZSTD_MEMORY_SANITIZER
+# define ZSTD_ASM_SUPPORTED 0
+# elif ZSTD_DATAFLOW_SANITIZER
+# define ZSTD_ASM_SUPPORTED 0
+# else
+# define ZSTD_ASM_SUPPORTED 1
+# endif
+# else
+# define ZSTD_ASM_SUPPORTED 0
+# endif
+#else
+# define ZSTD_ASM_SUPPORTED 0
+#endif
+
+/**
+ * Determines whether we should enable assembly for x86-64
+ * with BMI2.
+ *
+ * Enable if all of the following conditions hold:
+ * - ASM hasn't been explicitly disabled by defining ZSTD_DISABLE_ASM
+ * - Assembly is supported
+ * - We are compiling for x86-64 and either:
+ * - DYNAMIC_BMI2 is enabled
+ * - BMI2 is supported at compile time
+ */
+#if !defined(ZSTD_DISABLE_ASM) && \
+ ZSTD_ASM_SUPPORTED && \
+ defined(__x86_64__) && \
+ (DYNAMIC_BMI2 || defined(__BMI2__))
+# define ZSTD_ENABLE_ASM_X86_64_BMI2 1
+#else
+# define ZSTD_ENABLE_ASM_X86_64_BMI2 0
+#endif
+
+/*
+ * For x86 ELF targets, add .note.gnu.property section for Intel CET in
+ * assembly sources when CET is enabled.
+ *
+ * Additionally, any function that may be called indirectly must begin
+ * with ZSTD_CET_ENDBRANCH.
+ */
+#if defined(__ELF__) && (defined(__x86_64__) || defined(__i386__)) \
+ && defined(__has_include)
+# if __has_include(<cet.h>)
+# include <cet.h>
+# define ZSTD_CET_ENDBRANCH _CET_ENDBR
+# endif
+#endif
+
+#ifndef ZSTD_CET_ENDBRANCH
+# define ZSTD_CET_ENDBRANCH
+#endif
+
+#endif /* ZSTD_PORTABILITY_MACROS_H */
diff --git a/third_party/zstd/lib/common/threading.c b/third_party/zstd/lib/common/threading.c
new file mode 100644
index 0000000000..25bb8b9810
--- /dev/null
+++ b/third_party/zstd/lib/common/threading.c
@@ -0,0 +1,182 @@
+/**
+ * Copyright (c) 2016 Tino Reichardt
+ * All rights reserved.
+ *
+ * You can contact the author at:
+ * - zstdmt source repository: https://github.com/mcmilk/zstdmt
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+/**
+ * This file will hold wrapper for systems, which do not support pthreads
+ */
+
+#include "threading.h"
+
+/* create fake symbol to avoid empty translation unit warning */
+int g_ZSTD_threading_useless_symbol;
+
+#if defined(ZSTD_MULTITHREAD) && defined(_WIN32)
+
+/**
+ * Windows minimalist Pthread Wrapper
+ */
+
+
+/* === Dependencies === */
+#include <process.h>
+#include <errno.h>
+
+
+/* === Implementation === */
+
+typedef struct {
+ void* (*start_routine)(void*);
+ void* arg;
+ int initialized;
+ ZSTD_pthread_cond_t initialized_cond;
+ ZSTD_pthread_mutex_t initialized_mutex;
+} ZSTD_thread_params_t;
+
+static unsigned __stdcall worker(void *arg)
+{
+ void* (*start_routine)(void*);
+ void* thread_arg;
+
+ /* Initialized thread_arg and start_routine and signal main thread that we don't need it
+ * to wait any longer.
+ */
+ {
+ ZSTD_thread_params_t* thread_param = (ZSTD_thread_params_t*)arg;
+ thread_arg = thread_param->arg;
+ start_routine = thread_param->start_routine;
+
+ /* Signal main thread that we are running and do not depend on its memory anymore */
+ ZSTD_pthread_mutex_lock(&thread_param->initialized_mutex);
+ thread_param->initialized = 1;
+ ZSTD_pthread_cond_signal(&thread_param->initialized_cond);
+ ZSTD_pthread_mutex_unlock(&thread_param->initialized_mutex);
+ }
+
+ start_routine(thread_arg);
+
+ return 0;
+}
+
+int ZSTD_pthread_create(ZSTD_pthread_t* thread, const void* unused,
+ void* (*start_routine) (void*), void* arg)
+{
+ ZSTD_thread_params_t thread_param;
+ (void)unused;
+
+ if (thread==NULL) return -1;
+ *thread = NULL;
+
+ thread_param.start_routine = start_routine;
+ thread_param.arg = arg;
+ thread_param.initialized = 0;
+
+ /* Setup thread initialization synchronization */
+ if(ZSTD_pthread_cond_init(&thread_param.initialized_cond, NULL)) {
+ /* Should never happen on Windows */
+ return -1;
+ }
+ if(ZSTD_pthread_mutex_init(&thread_param.initialized_mutex, NULL)) {
+ /* Should never happen on Windows */
+ ZSTD_pthread_cond_destroy(&thread_param.initialized_cond);
+ return -1;
+ }
+
+ /* Spawn thread */
+ *thread = (HANDLE)_beginthreadex(NULL, 0, worker, &thread_param, 0, NULL);
+ if (*thread==NULL) {
+ ZSTD_pthread_mutex_destroy(&thread_param.initialized_mutex);
+ ZSTD_pthread_cond_destroy(&thread_param.initialized_cond);
+ return errno;
+ }
+
+ /* Wait for thread to be initialized */
+ ZSTD_pthread_mutex_lock(&thread_param.initialized_mutex);
+ while(!thread_param.initialized) {
+ ZSTD_pthread_cond_wait(&thread_param.initialized_cond, &thread_param.initialized_mutex);
+ }
+ ZSTD_pthread_mutex_unlock(&thread_param.initialized_mutex);
+ ZSTD_pthread_mutex_destroy(&thread_param.initialized_mutex);
+ ZSTD_pthread_cond_destroy(&thread_param.initialized_cond);
+
+ return 0;
+}
+
+int ZSTD_pthread_join(ZSTD_pthread_t thread)
+{
+ DWORD result;
+
+ if (!thread) return 0;
+
+ result = WaitForSingleObject(thread, INFINITE);
+ CloseHandle(thread);
+
+ switch (result) {
+ case WAIT_OBJECT_0:
+ return 0;
+ case WAIT_ABANDONED:
+ return EINVAL;
+ default:
+ return GetLastError();
+ }
+}
+
+#endif /* ZSTD_MULTITHREAD */
+
+#if defined(ZSTD_MULTITHREAD) && DEBUGLEVEL >= 1 && !defined(_WIN32)
+
+#define ZSTD_DEPS_NEED_MALLOC
+#include "zstd_deps.h"
+
+int ZSTD_pthread_mutex_init(ZSTD_pthread_mutex_t* mutex, pthread_mutexattr_t const* attr)
+{
+ assert(mutex != NULL);
+ *mutex = (pthread_mutex_t*)ZSTD_malloc(sizeof(pthread_mutex_t));
+ if (!*mutex)
+ return 1;
+ return pthread_mutex_init(*mutex, attr);
+}
+
+int ZSTD_pthread_mutex_destroy(ZSTD_pthread_mutex_t* mutex)
+{
+ assert(mutex != NULL);
+ if (!*mutex)
+ return 0;
+ {
+ int const ret = pthread_mutex_destroy(*mutex);
+ ZSTD_free(*mutex);
+ return ret;
+ }
+}
+
+int ZSTD_pthread_cond_init(ZSTD_pthread_cond_t* cond, pthread_condattr_t const* attr)
+{
+ assert(cond != NULL);
+ *cond = (pthread_cond_t*)ZSTD_malloc(sizeof(pthread_cond_t));
+ if (!*cond)
+ return 1;
+ return pthread_cond_init(*cond, attr);
+}
+
+int ZSTD_pthread_cond_destroy(ZSTD_pthread_cond_t* cond)
+{
+ assert(cond != NULL);
+ if (!*cond)
+ return 0;
+ {
+ int const ret = pthread_cond_destroy(*cond);
+ ZSTD_free(*cond);
+ return ret;
+ }
+}
+
+#endif
diff --git a/third_party/zstd/lib/common/threading.h b/third_party/zstd/lib/common/threading.h
new file mode 100644
index 0000000000..fb5c1c8787
--- /dev/null
+++ b/third_party/zstd/lib/common/threading.h
@@ -0,0 +1,150 @@
+/**
+ * Copyright (c) 2016 Tino Reichardt
+ * All rights reserved.
+ *
+ * You can contact the author at:
+ * - zstdmt source repository: https://github.com/mcmilk/zstdmt
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#ifndef THREADING_H_938743
+#define THREADING_H_938743
+
+#include "debug.h"
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+#if defined(ZSTD_MULTITHREAD) && defined(_WIN32)
+
+/**
+ * Windows minimalist Pthread Wrapper
+ */
+#ifdef WINVER
+# undef WINVER
+#endif
+#define WINVER 0x0600
+
+#ifdef _WIN32_WINNT
+# undef _WIN32_WINNT
+#endif
+#define _WIN32_WINNT 0x0600
+
+#ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+
+#undef ERROR /* reported already defined on VS 2015 (Rich Geldreich) */
+#include <windows.h>
+#undef ERROR
+#define ERROR(name) ZSTD_ERROR(name)
+
+
+/* mutex */
+#define ZSTD_pthread_mutex_t CRITICAL_SECTION
+#define ZSTD_pthread_mutex_init(a, b) ((void)(b), InitializeCriticalSection((a)), 0)
+#define ZSTD_pthread_mutex_destroy(a) DeleteCriticalSection((a))
+#define ZSTD_pthread_mutex_lock(a) EnterCriticalSection((a))
+#define ZSTD_pthread_mutex_unlock(a) LeaveCriticalSection((a))
+
+/* condition variable */
+#define ZSTD_pthread_cond_t CONDITION_VARIABLE
+#define ZSTD_pthread_cond_init(a, b) ((void)(b), InitializeConditionVariable((a)), 0)
+#define ZSTD_pthread_cond_destroy(a) ((void)(a))
+#define ZSTD_pthread_cond_wait(a, b) SleepConditionVariableCS((a), (b), INFINITE)
+#define ZSTD_pthread_cond_signal(a) WakeConditionVariable((a))
+#define ZSTD_pthread_cond_broadcast(a) WakeAllConditionVariable((a))
+
+/* ZSTD_pthread_create() and ZSTD_pthread_join() */
+typedef HANDLE ZSTD_pthread_t;
+
+int ZSTD_pthread_create(ZSTD_pthread_t* thread, const void* unused,
+ void* (*start_routine) (void*), void* arg);
+
+int ZSTD_pthread_join(ZSTD_pthread_t thread);
+
+/**
+ * add here more wrappers as required
+ */
+
+
+#elif defined(ZSTD_MULTITHREAD) /* posix assumed ; need a better detection method */
+/* === POSIX Systems === */
+# include <pthread.h>
+
+#if DEBUGLEVEL < 1
+
+#define ZSTD_pthread_mutex_t pthread_mutex_t
+#define ZSTD_pthread_mutex_init(a, b) pthread_mutex_init((a), (b))
+#define ZSTD_pthread_mutex_destroy(a) pthread_mutex_destroy((a))
+#define ZSTD_pthread_mutex_lock(a) pthread_mutex_lock((a))
+#define ZSTD_pthread_mutex_unlock(a) pthread_mutex_unlock((a))
+
+#define ZSTD_pthread_cond_t pthread_cond_t
+#define ZSTD_pthread_cond_init(a, b) pthread_cond_init((a), (b))
+#define ZSTD_pthread_cond_destroy(a) pthread_cond_destroy((a))
+#define ZSTD_pthread_cond_wait(a, b) pthread_cond_wait((a), (b))
+#define ZSTD_pthread_cond_signal(a) pthread_cond_signal((a))
+#define ZSTD_pthread_cond_broadcast(a) pthread_cond_broadcast((a))
+
+#define ZSTD_pthread_t pthread_t
+#define ZSTD_pthread_create(a, b, c, d) pthread_create((a), (b), (c), (d))
+#define ZSTD_pthread_join(a) pthread_join((a),NULL)
+
+#else /* DEBUGLEVEL >= 1 */
+
+/* Debug implementation of threading.
+ * In this implementation we use pointers for mutexes and condition variables.
+ * This way, if we forget to init/destroy them the program will crash or ASAN
+ * will report leaks.
+ */
+
+#define ZSTD_pthread_mutex_t pthread_mutex_t*
+int ZSTD_pthread_mutex_init(ZSTD_pthread_mutex_t* mutex, pthread_mutexattr_t const* attr);
+int ZSTD_pthread_mutex_destroy(ZSTD_pthread_mutex_t* mutex);
+#define ZSTD_pthread_mutex_lock(a) pthread_mutex_lock(*(a))
+#define ZSTD_pthread_mutex_unlock(a) pthread_mutex_unlock(*(a))
+
+#define ZSTD_pthread_cond_t pthread_cond_t*
+int ZSTD_pthread_cond_init(ZSTD_pthread_cond_t* cond, pthread_condattr_t const* attr);
+int ZSTD_pthread_cond_destroy(ZSTD_pthread_cond_t* cond);
+#define ZSTD_pthread_cond_wait(a, b) pthread_cond_wait(*(a), *(b))
+#define ZSTD_pthread_cond_signal(a) pthread_cond_signal(*(a))
+#define ZSTD_pthread_cond_broadcast(a) pthread_cond_broadcast(*(a))
+
+#define ZSTD_pthread_t pthread_t
+#define ZSTD_pthread_create(a, b, c, d) pthread_create((a), (b), (c), (d))
+#define ZSTD_pthread_join(a) pthread_join((a),NULL)
+
+#endif
+
+#else /* ZSTD_MULTITHREAD not defined */
+/* No multithreading support */
+
+typedef int ZSTD_pthread_mutex_t;
+#define ZSTD_pthread_mutex_init(a, b) ((void)(a), (void)(b), 0)
+#define ZSTD_pthread_mutex_destroy(a) ((void)(a))
+#define ZSTD_pthread_mutex_lock(a) ((void)(a))
+#define ZSTD_pthread_mutex_unlock(a) ((void)(a))
+
+typedef int ZSTD_pthread_cond_t;
+#define ZSTD_pthread_cond_init(a, b) ((void)(a), (void)(b), 0)
+#define ZSTD_pthread_cond_destroy(a) ((void)(a))
+#define ZSTD_pthread_cond_wait(a, b) ((void)(a), (void)(b))
+#define ZSTD_pthread_cond_signal(a) ((void)(a))
+#define ZSTD_pthread_cond_broadcast(a) ((void)(a))
+
+/* do not use ZSTD_pthread_t */
+
+#endif /* ZSTD_MULTITHREAD */
+
+#if defined (__cplusplus)
+}
+#endif
+
+#endif /* THREADING_H_938743 */
diff --git a/third_party/zstd/lib/common/xxhash.c b/third_party/zstd/lib/common/xxhash.c
new file mode 100644
index 0000000000..052cd52282
--- /dev/null
+++ b/third_party/zstd/lib/common/xxhash.c
@@ -0,0 +1,18 @@
+/*
+ * xxHash - Extremely Fast Hash algorithm
+ * Copyright (c) Yann Collet - Meta Platforms, Inc
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+/*
+ * xxhash.c instantiates functions defined in xxhash.h
+ */
+
+#define XXH_STATIC_LINKING_ONLY /* access advanced declarations */
+#define XXH_IMPLEMENTATION /* access definitions */
+
+#include "xxhash.h"
diff --git a/third_party/zstd/lib/common/xxhash.h b/third_party/zstd/lib/common/xxhash.h
new file mode 100644
index 0000000000..e59e44267c
--- /dev/null
+++ b/third_party/zstd/lib/common/xxhash.h
@@ -0,0 +1,7020 @@
+/*
+ * xxHash - Extremely Fast Hash algorithm
+ * Header File
+ * Copyright (c) Yann Collet - Meta Platforms, Inc
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+/* Local adaptations for Zstandard */
+
+#ifndef XXH_NO_XXH3
+# define XXH_NO_XXH3
+#endif
+
+#ifndef XXH_NAMESPACE
+# define XXH_NAMESPACE ZSTD_
+#endif
+
+/*!
+ * @mainpage xxHash
+ *
+ * xxHash is an extremely fast non-cryptographic hash algorithm, working at RAM speed
+ * limits.
+ *
+ * It is proposed in four flavors, in three families:
+ * 1. @ref XXH32_family
+ * - Classic 32-bit hash function. Simple, compact, and runs on almost all
+ * 32-bit and 64-bit systems.
+ * 2. @ref XXH64_family
+ * - Classic 64-bit adaptation of XXH32. Just as simple, and runs well on most
+ * 64-bit systems (but _not_ 32-bit systems).
+ * 3. @ref XXH3_family
+ * - Modern 64-bit and 128-bit hash function family which features improved
+ * strength and performance across the board, especially on smaller data.
+ * It benefits greatly from SIMD and 64-bit without requiring it.
+ *
+ * Benchmarks
+ * ---
+ * The reference system uses an Intel i7-9700K CPU, and runs Ubuntu x64 20.04.
+ * The open source benchmark program is compiled with clang v10.0 using -O3 flag.
+ *
+ * | Hash Name | ISA ext | Width | Large Data Speed | Small Data Velocity |
+ * | -------------------- | ------- | ----: | ---------------: | ------------------: |
+ * | XXH3_64bits() | @b AVX2 | 64 | 59.4 GB/s | 133.1 |
+ * | MeowHash | AES-NI | 128 | 58.2 GB/s | 52.5 |
+ * | XXH3_128bits() | @b AVX2 | 128 | 57.9 GB/s | 118.1 |
+ * | CLHash | PCLMUL | 64 | 37.1 GB/s | 58.1 |
+ * | XXH3_64bits() | @b SSE2 | 64 | 31.5 GB/s | 133.1 |
+ * | XXH3_128bits() | @b SSE2 | 128 | 29.6 GB/s | 118.1 |
+ * | RAM sequential read | | N/A | 28.0 GB/s | N/A |
+ * | ahash | AES-NI | 64 | 22.5 GB/s | 107.2 |
+ * | City64 | | 64 | 22.0 GB/s | 76.6 |
+ * | T1ha2 | | 64 | 22.0 GB/s | 99.0 |
+ * | City128 | | 128 | 21.7 GB/s | 57.7 |
+ * | FarmHash | AES-NI | 64 | 21.3 GB/s | 71.9 |
+ * | XXH64() | | 64 | 19.4 GB/s | 71.0 |
+ * | SpookyHash | | 64 | 19.3 GB/s | 53.2 |
+ * | Mum | | 64 | 18.0 GB/s | 67.0 |
+ * | CRC32C | SSE4.2 | 32 | 13.0 GB/s | 57.9 |
+ * | XXH32() | | 32 | 9.7 GB/s | 71.9 |
+ * | City32 | | 32 | 9.1 GB/s | 66.0 |
+ * | Blake3* | @b AVX2 | 256 | 4.4 GB/s | 8.1 |
+ * | Murmur3 | | 32 | 3.9 GB/s | 56.1 |
+ * | SipHash* | | 64 | 3.0 GB/s | 43.2 |
+ * | Blake3* | @b SSE2 | 256 | 2.4 GB/s | 8.1 |
+ * | HighwayHash | | 64 | 1.4 GB/s | 6.0 |
+ * | FNV64 | | 64 | 1.2 GB/s | 62.7 |
+ * | Blake2* | | 256 | 1.1 GB/s | 5.1 |
+ * | SHA1* | | 160 | 0.8 GB/s | 5.6 |
+ * | MD5* | | 128 | 0.6 GB/s | 7.8 |
+ * @note
+ * - Hashes which require a specific ISA extension are noted. SSE2 is also noted,
+ * even though it is mandatory on x64.
+ * - Hashes with an asterisk are cryptographic. Note that MD5 is non-cryptographic
+ * by modern standards.
+ * - Small data velocity is a rough average of algorithm's efficiency for small
+ * data. For more accurate information, see the wiki.
+ * - More benchmarks and strength tests are found on the wiki:
+ * https://github.com/Cyan4973/xxHash/wiki
+ *
+ * Usage
+ * ------
+ * All xxHash variants use a similar API. Changing the algorithm is a trivial
+ * substitution.
+ *
+ * @pre
+ * For functions which take an input and length parameter, the following
+ * requirements are assumed:
+ * - The range from [`input`, `input + length`) is valid, readable memory.
+ * - The only exception is if the `length` is `0`, `input` may be `NULL`.
+ * - For C++, the objects must have the *TriviallyCopyable* property, as the
+ * functions access bytes directly as if it was an array of `unsigned char`.
+ *
+ * @anchor single_shot_example
+ * **Single Shot**
+ *
+ * These functions are stateless functions which hash a contiguous block of memory,
+ * immediately returning the result. They are the easiest and usually the fastest
+ * option.
+ *
+ * XXH32(), XXH64(), XXH3_64bits(), XXH3_128bits()
+ *
+ * @code{.c}
+ * #include <string.h>
+ * #include "xxhash.h"
+ *
+ * // Example for a function which hashes a null terminated string with XXH32().
+ * XXH32_hash_t hash_string(const char* string, XXH32_hash_t seed)
+ * {
+ * // NULL pointers are only valid if the length is zero
+ * size_t length = (string == NULL) ? 0 : strlen(string);
+ * return XXH32(string, length, seed);
+ * }
+ * @endcode
+ *
+ *
+ * @anchor streaming_example
+ * **Streaming**
+ *
+ * These groups of functions allow incremental hashing of unknown size, even
+ * more than what would fit in a size_t.
+ *
+ * XXH32_reset(), XXH64_reset(), XXH3_64bits_reset(), XXH3_128bits_reset()
+ *
+ * @code{.c}
+ * #include <stdio.h>
+ * #include <assert.h>
+ * #include "xxhash.h"
+ * // Example for a function which hashes a FILE incrementally with XXH3_64bits().
+ * XXH64_hash_t hashFile(FILE* f)
+ * {
+ * // Allocate a state struct. Do not just use malloc() or new.
+ * XXH3_state_t* state = XXH3_createState();
+ * assert(state != NULL && "Out of memory!");
+ * // Reset the state to start a new hashing session.
+ * XXH3_64bits_reset(state);
+ * char buffer[4096];
+ * size_t count;
+ * // Read the file in chunks
+ * while ((count = fread(buffer, 1, sizeof(buffer), f)) != 0) {
+ * // Run update() as many times as necessary to process the data
+ * XXH3_64bits_update(state, buffer, count);
+ * }
+ * // Retrieve the finalized hash. This will not change the state.
+ * XXH64_hash_t result = XXH3_64bits_digest(state);
+ * // Free the state. Do not use free().
+ * XXH3_freeState(state);
+ * return result;
+ * }
+ * @endcode
+ *
+ * Streaming functions generate the xxHash value from an incremental input.
+ * This method is slower than single-call functions, due to state management.
+ * For small inputs, prefer `XXH32()` and `XXH64()`, which are better optimized.
+ *
+ * An XXH state must first be allocated using `XXH*_createState()`.
+ *
+ * Start a new hash by initializing the state with a seed using `XXH*_reset()`.
+ *
+ * Then, feed the hash state by calling `XXH*_update()` as many times as necessary.
+ *
+ * The function returns an error code, with 0 meaning OK, and any other value
+ * meaning there is an error.
+ *
+ * Finally, a hash value can be produced anytime, by using `XXH*_digest()`.
+ * This function returns the nn-bits hash as an int or long long.
+ *
+ * It's still possible to continue inserting input into the hash state after a
+ * digest, and generate new hash values later on by invoking `XXH*_digest()`.
+ *
+ * When done, release the state using `XXH*_freeState()`.
+ *
+ *
+ * @anchor canonical_representation_example
+ * **Canonical Representation**
+ *
+ * The default return values from XXH functions are unsigned 32, 64 and 128 bit
+ * integers.
+ * This the simplest and fastest format for further post-processing.
+ *
+ * However, this leaves open the question of what is the order on the byte level,
+ * since little and big endian conventions will store the same number differently.
+ *
+ * The canonical representation settles this issue by mandating big-endian
+ * convention, the same convention as human-readable numbers (large digits first).
+ *
+ * When writing hash values to storage, sending them over a network, or printing
+ * them, it's highly recommended to use the canonical representation to ensure
+ * portability across a wider range of systems, present and future.
+ *
+ * The following functions allow transformation of hash values to and from
+ * canonical format.
+ *
+ * XXH32_canonicalFromHash(), XXH32_hashFromCanonical(),
+ * XXH64_canonicalFromHash(), XXH64_hashFromCanonical(),
+ * XXH128_canonicalFromHash(), XXH128_hashFromCanonical(),
+ *
+ * @code{.c}
+ * #include <stdio.h>
+ * #include "xxhash.h"
+ *
+ * // Example for a function which prints XXH32_hash_t in human readable format
+ * void printXxh32(XXH32_hash_t hash)
+ * {
+ * XXH32_canonical_t cano;
+ * XXH32_canonicalFromHash(&cano, hash);
+ * size_t i;
+ * for(i = 0; i < sizeof(cano.digest); ++i) {
+ * printf("%02x", cano.digest[i]);
+ * }
+ * printf("\n");
+ * }
+ *
+ * // Example for a function which converts XXH32_canonical_t to XXH32_hash_t
+ * XXH32_hash_t convertCanonicalToXxh32(XXH32_canonical_t cano)
+ * {
+ * XXH32_hash_t hash = XXH32_hashFromCanonical(&cano);
+ * return hash;
+ * }
+ * @endcode
+ *
+ *
+ * @file xxhash.h
+ * xxHash prototypes and implementation
+ */
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+/* ****************************
+ * INLINE mode
+ ******************************/
+/*!
+ * @defgroup public Public API
+ * Contains details on the public xxHash functions.
+ * @{
+ */
+#ifdef XXH_DOXYGEN
+/*!
+ * @brief Gives access to internal state declaration, required for static allocation.
+ *
+ * Incompatible with dynamic linking, due to risks of ABI changes.
+ *
+ * Usage:
+ * @code{.c}
+ * #define XXH_STATIC_LINKING_ONLY
+ * #include "xxhash.h"
+ * @endcode
+ */
+# define XXH_STATIC_LINKING_ONLY
+/* Do not undef XXH_STATIC_LINKING_ONLY for Doxygen */
+
+/*!
+ * @brief Gives access to internal definitions.
+ *
+ * Usage:
+ * @code{.c}
+ * #define XXH_STATIC_LINKING_ONLY
+ * #define XXH_IMPLEMENTATION
+ * #include "xxhash.h"
+ * @endcode
+ */
+# define XXH_IMPLEMENTATION
+/* Do not undef XXH_IMPLEMENTATION for Doxygen */
+
+/*!
+ * @brief Exposes the implementation and marks all functions as `inline`.
+ *
+ * Use these build macros to inline xxhash into the target unit.
+ * Inlining improves performance on small inputs, especially when the length is
+ * expressed as a compile-time constant:
+ *
+ * https://fastcompression.blogspot.com/2018/03/xxhash-for-small-keys-impressive-power.html
+ *
+ * It also keeps xxHash symbols private to the unit, so they are not exported.
+ *
+ * Usage:
+ * @code{.c}
+ * #define XXH_INLINE_ALL
+ * #include "xxhash.h"
+ * @endcode
+ * Do not compile and link xxhash.o as a separate object, as it is not useful.
+ */
+# define XXH_INLINE_ALL
+# undef XXH_INLINE_ALL
+/*!
+ * @brief Exposes the implementation without marking functions as inline.
+ */
+# define XXH_PRIVATE_API
+# undef XXH_PRIVATE_API
+/*!
+ * @brief Emulate a namespace by transparently prefixing all symbols.
+ *
+ * If you want to include _and expose_ xxHash functions from within your own
+ * library, but also want to avoid symbol collisions with other libraries which
+ * may also include xxHash, you can use @ref XXH_NAMESPACE to automatically prefix
+ * any public symbol from xxhash library with the value of @ref XXH_NAMESPACE
+ * (therefore, avoid empty or numeric values).
+ *
+ * Note that no change is required within the calling program as long as it
+ * includes `xxhash.h`: Regular symbol names will be automatically translated
+ * by this header.
+ */
+# define XXH_NAMESPACE /* YOUR NAME HERE */
+# undef XXH_NAMESPACE
+#endif
+
+#if (defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)) \
+ && !defined(XXH_INLINE_ALL_31684351384)
+ /* this section should be traversed only once */
+# define XXH_INLINE_ALL_31684351384
+ /* give access to the advanced API, required to compile implementations */
+# undef XXH_STATIC_LINKING_ONLY /* avoid macro redef */
+# define XXH_STATIC_LINKING_ONLY
+ /* make all functions private */
+# undef XXH_PUBLIC_API
+# if defined(__GNUC__)
+# define XXH_PUBLIC_API static __inline __attribute__((unused))
+# elif defined (__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */)
+# define XXH_PUBLIC_API static inline
+# elif defined(_MSC_VER)
+# define XXH_PUBLIC_API static __inline
+# else
+ /* note: this version may generate warnings for unused static functions */
+# define XXH_PUBLIC_API static
+# endif
+
+ /*
+ * This part deals with the special case where a unit wants to inline xxHash,
+ * but "xxhash.h" has previously been included without XXH_INLINE_ALL,
+ * such as part of some previously included *.h header file.
+ * Without further action, the new include would just be ignored,
+ * and functions would effectively _not_ be inlined (silent failure).
+ * The following macros solve this situation by prefixing all inlined names,
+ * avoiding naming collision with previous inclusions.
+ */
+ /* Before that, we unconditionally #undef all symbols,
+ * in case they were already defined with XXH_NAMESPACE.
+ * They will then be redefined for XXH_INLINE_ALL
+ */
+# undef XXH_versionNumber
+ /* XXH32 */
+# undef XXH32
+# undef XXH32_createState
+# undef XXH32_freeState
+# undef XXH32_reset
+# undef XXH32_update
+# undef XXH32_digest
+# undef XXH32_copyState
+# undef XXH32_canonicalFromHash
+# undef XXH32_hashFromCanonical
+ /* XXH64 */
+# undef XXH64
+# undef XXH64_createState
+# undef XXH64_freeState
+# undef XXH64_reset
+# undef XXH64_update
+# undef XXH64_digest
+# undef XXH64_copyState
+# undef XXH64_canonicalFromHash
+# undef XXH64_hashFromCanonical
+ /* XXH3_64bits */
+# undef XXH3_64bits
+# undef XXH3_64bits_withSecret
+# undef XXH3_64bits_withSeed
+# undef XXH3_64bits_withSecretandSeed
+# undef XXH3_createState
+# undef XXH3_freeState
+# undef XXH3_copyState
+# undef XXH3_64bits_reset
+# undef XXH3_64bits_reset_withSeed
+# undef XXH3_64bits_reset_withSecret
+# undef XXH3_64bits_update
+# undef XXH3_64bits_digest
+# undef XXH3_generateSecret
+ /* XXH3_128bits */
+# undef XXH128
+# undef XXH3_128bits
+# undef XXH3_128bits_withSeed
+# undef XXH3_128bits_withSecret
+# undef XXH3_128bits_reset
+# undef XXH3_128bits_reset_withSeed
+# undef XXH3_128bits_reset_withSecret
+# undef XXH3_128bits_reset_withSecretandSeed
+# undef XXH3_128bits_update
+# undef XXH3_128bits_digest
+# undef XXH128_isEqual
+# undef XXH128_cmp
+# undef XXH128_canonicalFromHash
+# undef XXH128_hashFromCanonical
+ /* Finally, free the namespace itself */
+# undef XXH_NAMESPACE
+
+ /* employ the namespace for XXH_INLINE_ALL */
+# define XXH_NAMESPACE XXH_INLINE_
+ /*
+ * Some identifiers (enums, type names) are not symbols,
+ * but they must nonetheless be renamed to avoid redeclaration.
+ * Alternative solution: do not redeclare them.
+ * However, this requires some #ifdefs, and has a more dispersed impact.
+ * Meanwhile, renaming can be achieved in a single place.
+ */
+# define XXH_IPREF(Id) XXH_NAMESPACE ## Id
+# define XXH_OK XXH_IPREF(XXH_OK)
+# define XXH_ERROR XXH_IPREF(XXH_ERROR)
+# define XXH_errorcode XXH_IPREF(XXH_errorcode)
+# define XXH32_canonical_t XXH_IPREF(XXH32_canonical_t)
+# define XXH64_canonical_t XXH_IPREF(XXH64_canonical_t)
+# define XXH128_canonical_t XXH_IPREF(XXH128_canonical_t)
+# define XXH32_state_s XXH_IPREF(XXH32_state_s)
+# define XXH32_state_t XXH_IPREF(XXH32_state_t)
+# define XXH64_state_s XXH_IPREF(XXH64_state_s)
+# define XXH64_state_t XXH_IPREF(XXH64_state_t)
+# define XXH3_state_s XXH_IPREF(XXH3_state_s)
+# define XXH3_state_t XXH_IPREF(XXH3_state_t)
+# define XXH128_hash_t XXH_IPREF(XXH128_hash_t)
+ /* Ensure the header is parsed again, even if it was previously included */
+# undef XXHASH_H_5627135585666179
+# undef XXHASH_H_STATIC_13879238742
+#endif /* XXH_INLINE_ALL || XXH_PRIVATE_API */
+
+/* ****************************************************************
+ * Stable API
+ *****************************************************************/
+#ifndef XXHASH_H_5627135585666179
+#define XXHASH_H_5627135585666179 1
+
+/*! @brief Marks a global symbol. */
+#if !defined(XXH_INLINE_ALL) && !defined(XXH_PRIVATE_API)
+# if defined(WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT))
+# ifdef XXH_EXPORT
+# define XXH_PUBLIC_API __declspec(dllexport)
+# elif XXH_IMPORT
+# define XXH_PUBLIC_API __declspec(dllimport)
+# endif
+# else
+# define XXH_PUBLIC_API /* do nothing */
+# endif
+#endif
+
+#ifdef XXH_NAMESPACE
+# define XXH_CAT(A,B) A##B
+# define XXH_NAME2(A,B) XXH_CAT(A,B)
+# define XXH_versionNumber XXH_NAME2(XXH_NAMESPACE, XXH_versionNumber)
+/* XXH32 */
+# define XXH32 XXH_NAME2(XXH_NAMESPACE, XXH32)
+# define XXH32_createState XXH_NAME2(XXH_NAMESPACE, XXH32_createState)
+# define XXH32_freeState XXH_NAME2(XXH_NAMESPACE, XXH32_freeState)
+# define XXH32_reset XXH_NAME2(XXH_NAMESPACE, XXH32_reset)
+# define XXH32_update XXH_NAME2(XXH_NAMESPACE, XXH32_update)
+# define XXH32_digest XXH_NAME2(XXH_NAMESPACE, XXH32_digest)
+# define XXH32_copyState XXH_NAME2(XXH_NAMESPACE, XXH32_copyState)
+# define XXH32_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH32_canonicalFromHash)
+# define XXH32_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH32_hashFromCanonical)
+/* XXH64 */
+# define XXH64 XXH_NAME2(XXH_NAMESPACE, XXH64)
+# define XXH64_createState XXH_NAME2(XXH_NAMESPACE, XXH64_createState)
+# define XXH64_freeState XXH_NAME2(XXH_NAMESPACE, XXH64_freeState)
+# define XXH64_reset XXH_NAME2(XXH_NAMESPACE, XXH64_reset)
+# define XXH64_update XXH_NAME2(XXH_NAMESPACE, XXH64_update)
+# define XXH64_digest XXH_NAME2(XXH_NAMESPACE, XXH64_digest)
+# define XXH64_copyState XXH_NAME2(XXH_NAMESPACE, XXH64_copyState)
+# define XXH64_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH64_canonicalFromHash)
+# define XXH64_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH64_hashFromCanonical)
+/* XXH3_64bits */
+# define XXH3_64bits XXH_NAME2(XXH_NAMESPACE, XXH3_64bits)
+# define XXH3_64bits_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_withSecret)
+# define XXH3_64bits_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_withSeed)
+# define XXH3_64bits_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_withSecretandSeed)
+# define XXH3_createState XXH_NAME2(XXH_NAMESPACE, XXH3_createState)
+# define XXH3_freeState XXH_NAME2(XXH_NAMESPACE, XXH3_freeState)
+# define XXH3_copyState XXH_NAME2(XXH_NAMESPACE, XXH3_copyState)
+# define XXH3_64bits_reset XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset)
+# define XXH3_64bits_reset_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset_withSeed)
+# define XXH3_64bits_reset_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset_withSecret)
+# define XXH3_64bits_reset_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_reset_withSecretandSeed)
+# define XXH3_64bits_update XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_update)
+# define XXH3_64bits_digest XXH_NAME2(XXH_NAMESPACE, XXH3_64bits_digest)
+# define XXH3_generateSecret XXH_NAME2(XXH_NAMESPACE, XXH3_generateSecret)
+# define XXH3_generateSecret_fromSeed XXH_NAME2(XXH_NAMESPACE, XXH3_generateSecret_fromSeed)
+/* XXH3_128bits */
+# define XXH128 XXH_NAME2(XXH_NAMESPACE, XXH128)
+# define XXH3_128bits XXH_NAME2(XXH_NAMESPACE, XXH3_128bits)
+# define XXH3_128bits_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_withSeed)
+# define XXH3_128bits_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_withSecret)
+# define XXH3_128bits_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_withSecretandSeed)
+# define XXH3_128bits_reset XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset)
+# define XXH3_128bits_reset_withSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset_withSeed)
+# define XXH3_128bits_reset_withSecret XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset_withSecret)
+# define XXH3_128bits_reset_withSecretandSeed XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_reset_withSecretandSeed)
+# define XXH3_128bits_update XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_update)
+# define XXH3_128bits_digest XXH_NAME2(XXH_NAMESPACE, XXH3_128bits_digest)
+# define XXH128_isEqual XXH_NAME2(XXH_NAMESPACE, XXH128_isEqual)
+# define XXH128_cmp XXH_NAME2(XXH_NAMESPACE, XXH128_cmp)
+# define XXH128_canonicalFromHash XXH_NAME2(XXH_NAMESPACE, XXH128_canonicalFromHash)
+# define XXH128_hashFromCanonical XXH_NAME2(XXH_NAMESPACE, XXH128_hashFromCanonical)
+#endif
+
+
+/* *************************************
+* Compiler specifics
+***************************************/
+
+/* specific declaration modes for Windows */
+#if !defined(XXH_INLINE_ALL) && !defined(XXH_PRIVATE_API)
+# if defined(WIN32) && defined(_MSC_VER) && (defined(XXH_IMPORT) || defined(XXH_EXPORT))
+# ifdef XXH_EXPORT
+# define XXH_PUBLIC_API __declspec(dllexport)
+# elif XXH_IMPORT
+# define XXH_PUBLIC_API __declspec(dllimport)
+# endif
+# else
+# define XXH_PUBLIC_API /* do nothing */
+# endif
+#endif
+
+#if defined (__GNUC__)
+# define XXH_CONSTF __attribute__((const))
+# define XXH_PUREF __attribute__((pure))
+# define XXH_MALLOCF __attribute__((malloc))
+#else
+# define XXH_CONSTF /* disable */
+# define XXH_PUREF
+# define XXH_MALLOCF
+#endif
+
+/* *************************************
+* Version
+***************************************/
+#define XXH_VERSION_MAJOR 0
+#define XXH_VERSION_MINOR 8
+#define XXH_VERSION_RELEASE 2
+/*! @brief Version number, encoded as two digits each */
+#define XXH_VERSION_NUMBER (XXH_VERSION_MAJOR *100*100 + XXH_VERSION_MINOR *100 + XXH_VERSION_RELEASE)
+
+/*!
+ * @brief Obtains the xxHash version.
+ *
+ * This is mostly useful when xxHash is compiled as a shared library,
+ * since the returned value comes from the library, as opposed to header file.
+ *
+ * @return @ref XXH_VERSION_NUMBER of the invoked library.
+ */
+XXH_PUBLIC_API XXH_CONSTF unsigned XXH_versionNumber (void);
+
+
+/* ****************************
+* Common basic types
+******************************/
+#include <stddef.h> /* size_t */
+/*!
+ * @brief Exit code for the streaming API.
+ */
+typedef enum {
+ XXH_OK = 0, /*!< OK */
+ XXH_ERROR /*!< Error */
+} XXH_errorcode;
+
+
+/*-**********************************************************************
+* 32-bit hash
+************************************************************************/
+#if defined(XXH_DOXYGEN) /* Don't show <stdint.h> include */
+/*!
+ * @brief An unsigned 32-bit integer.
+ *
+ * Not necessarily defined to `uint32_t` but functionally equivalent.
+ */
+typedef uint32_t XXH32_hash_t;
+
+#elif !defined (__VMS) \
+ && (defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+# ifdef _AIX
+# include <inttypes.h>
+# else
+# include <stdint.h>
+# endif
+ typedef uint32_t XXH32_hash_t;
+
+#else
+# include <limits.h>
+# if UINT_MAX == 0xFFFFFFFFUL
+ typedef unsigned int XXH32_hash_t;
+# elif ULONG_MAX == 0xFFFFFFFFUL
+ typedef unsigned long XXH32_hash_t;
+# else
+# error "unsupported platform: need a 32-bit type"
+# endif
+#endif
+
+/*!
+ * @}
+ *
+ * @defgroup XXH32_family XXH32 family
+ * @ingroup public
+ * Contains functions used in the classic 32-bit xxHash algorithm.
+ *
+ * @note
+ * XXH32 is useful for older platforms, with no or poor 64-bit performance.
+ * Note that the @ref XXH3_family provides competitive speed for both 32-bit
+ * and 64-bit systems, and offers true 64/128 bit hash results.
+ *
+ * @see @ref XXH64_family, @ref XXH3_family : Other xxHash families
+ * @see @ref XXH32_impl for implementation details
+ * @{
+ */
+
+/*!
+ * @brief Calculates the 32-bit hash of @p input using xxHash32.
+ *
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ * @param seed The 32-bit seed to alter the hash's output predictably.
+ *
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return The calculated 32-bit xxHash32 value.
+ *
+ * @see @ref single_shot_example "Single Shot Example" for an example.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32 (const void* input, size_t length, XXH32_hash_t seed);
+
+#ifndef XXH_NO_STREAM
+/*!
+ * @typedef struct XXH32_state_s XXH32_state_t
+ * @brief The opaque state struct for the XXH32 streaming API.
+ *
+ * @see XXH32_state_s for details.
+ */
+typedef struct XXH32_state_s XXH32_state_t;
+
+/*!
+ * @brief Allocates an @ref XXH32_state_t.
+ *
+ * @return An allocated pointer of @ref XXH32_state_t on success.
+ * @return `NULL` on failure.
+ *
+ * @note Must be freed with XXH32_freeState().
+ */
+XXH_PUBLIC_API XXH_MALLOCF XXH32_state_t* XXH32_createState(void);
+/*!
+ * @brief Frees an @ref XXH32_state_t.
+ *
+ * @param statePtr A pointer to an @ref XXH32_state_t allocated with @ref XXH32_createState().
+ *
+ * @return @ref XXH_OK.
+ *
+ * @note @p statePtr must be allocated with XXH32_createState().
+ *
+ */
+XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr);
+/*!
+ * @brief Copies one @ref XXH32_state_t to another.
+ *
+ * @param dst_state The state to copy to.
+ * @param src_state The state to copy from.
+ * @pre
+ * @p dst_state and @p src_state must not be `NULL` and must not overlap.
+ */
+XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dst_state, const XXH32_state_t* src_state);
+
+/*!
+ * @brief Resets an @ref XXH32_state_t to begin a new hash.
+ *
+ * @param statePtr The state struct to reset.
+ * @param seed The 32-bit seed to alter the hash result predictably.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @note This function resets and seeds a state. Call it before @ref XXH32_update().
+ */
+XXH_PUBLIC_API XXH_errorcode XXH32_reset (XXH32_state_t* statePtr, XXH32_hash_t seed);
+
+/*!
+ * @brief Consumes a block of @p input to an @ref XXH32_state_t.
+ *
+ * @param statePtr The state struct to update.
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @note Call this to incrementally consume blocks of data.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH32_update (XXH32_state_t* statePtr, const void* input, size_t length);
+
+/*!
+ * @brief Returns the calculated hash value from an @ref XXH32_state_t.
+ *
+ * @param statePtr The state struct to calculate the hash from.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return The calculated 32-bit xxHash32 value from that state.
+ *
+ * @note
+ * Calling XXH32_digest() will not affect @p statePtr, so you can update,
+ * digest, and update again.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32_digest (const XXH32_state_t* statePtr);
+#endif /* !XXH_NO_STREAM */
+
+/******* Canonical representation *******/
+
+/*!
+ * @brief Canonical (big endian) representation of @ref XXH32_hash_t.
+ */
+typedef struct {
+ unsigned char digest[4]; /*!< Hash bytes, big endian */
+} XXH32_canonical_t;
+
+/*!
+ * @brief Converts an @ref XXH32_hash_t to a big endian @ref XXH32_canonical_t.
+ *
+ * @param dst The @ref XXH32_canonical_t pointer to be stored to.
+ * @param hash The @ref XXH32_hash_t to be converted.
+ *
+ * @pre
+ * @p dst must not be `NULL`.
+ *
+ * @see @ref canonical_representation_example "Canonical Representation Example"
+ */
+XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash);
+
+/*!
+ * @brief Converts an @ref XXH32_canonical_t to a native @ref XXH32_hash_t.
+ *
+ * @param src The @ref XXH32_canonical_t to convert.
+ *
+ * @pre
+ * @p src must not be `NULL`.
+ *
+ * @return The converted hash.
+ *
+ * @see @ref canonical_representation_example "Canonical Representation Example"
+ */
+XXH_PUBLIC_API XXH_PUREF XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src);
+
+
+/*! @cond Doxygen ignores this part */
+#ifdef __has_attribute
+# define XXH_HAS_ATTRIBUTE(x) __has_attribute(x)
+#else
+# define XXH_HAS_ATTRIBUTE(x) 0
+#endif
+/*! @endcond */
+
+/*! @cond Doxygen ignores this part */
+/*
+ * C23 __STDC_VERSION__ number hasn't been specified yet. For now
+ * leave as `201711L` (C17 + 1).
+ * TODO: Update to correct value when its been specified.
+ */
+#define XXH_C23_VN 201711L
+/*! @endcond */
+
+/*! @cond Doxygen ignores this part */
+/* C-language Attributes are added in C23. */
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= XXH_C23_VN) && defined(__has_c_attribute)
+# define XXH_HAS_C_ATTRIBUTE(x) __has_c_attribute(x)
+#else
+# define XXH_HAS_C_ATTRIBUTE(x) 0
+#endif
+/*! @endcond */
+
+/*! @cond Doxygen ignores this part */
+#if defined(__cplusplus) && defined(__has_cpp_attribute)
+# define XXH_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
+#else
+# define XXH_HAS_CPP_ATTRIBUTE(x) 0
+#endif
+/*! @endcond */
+
+/*! @cond Doxygen ignores this part */
+/*
+ * Define XXH_FALLTHROUGH macro for annotating switch case with the 'fallthrough' attribute
+ * introduced in CPP17 and C23.
+ * CPP17 : https://en.cppreference.com/w/cpp/language/attributes/fallthrough
+ * C23 : https://en.cppreference.com/w/c/language/attributes/fallthrough
+ */
+#if XXH_HAS_C_ATTRIBUTE(fallthrough) || XXH_HAS_CPP_ATTRIBUTE(fallthrough)
+# define XXH_FALLTHROUGH [[fallthrough]]
+#elif XXH_HAS_ATTRIBUTE(__fallthrough__)
+# define XXH_FALLTHROUGH __attribute__ ((__fallthrough__))
+#else
+# define XXH_FALLTHROUGH /* fallthrough */
+#endif
+/*! @endcond */
+
+/*! @cond Doxygen ignores this part */
+/*
+ * Define XXH_NOESCAPE for annotated pointers in public API.
+ * https://clang.llvm.org/docs/AttributeReference.html#noescape
+ * As of writing this, only supported by clang.
+ */
+#if XXH_HAS_ATTRIBUTE(noescape)
+# define XXH_NOESCAPE __attribute__((noescape))
+#else
+# define XXH_NOESCAPE
+#endif
+/*! @endcond */
+
+
+/*!
+ * @}
+ * @ingroup public
+ * @{
+ */
+
+#ifndef XXH_NO_LONG_LONG
+/*-**********************************************************************
+* 64-bit hash
+************************************************************************/
+#if defined(XXH_DOXYGEN) /* don't include <stdint.h> */
+/*!
+ * @brief An unsigned 64-bit integer.
+ *
+ * Not necessarily defined to `uint64_t` but functionally equivalent.
+ */
+typedef uint64_t XXH64_hash_t;
+#elif !defined (__VMS) \
+ && (defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+# ifdef _AIX
+# include <inttypes.h>
+# else
+# include <stdint.h>
+# endif
+ typedef uint64_t XXH64_hash_t;
+#else
+# include <limits.h>
+# if defined(__LP64__) && ULONG_MAX == 0xFFFFFFFFFFFFFFFFULL
+ /* LP64 ABI says uint64_t is unsigned long */
+ typedef unsigned long XXH64_hash_t;
+# else
+ /* the following type must have a width of 64-bit */
+ typedef unsigned long long XXH64_hash_t;
+# endif
+#endif
+
+/*!
+ * @}
+ *
+ * @defgroup XXH64_family XXH64 family
+ * @ingroup public
+ * @{
+ * Contains functions used in the classic 64-bit xxHash algorithm.
+ *
+ * @note
+ * XXH3 provides competitive speed for both 32-bit and 64-bit systems,
+ * and offers true 64/128 bit hash results.
+ * It provides better speed for systems with vector processing capabilities.
+ */
+
+/*!
+ * @brief Calculates the 64-bit hash of @p input using xxHash64.
+ *
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ * @param seed The 64-bit seed to alter the hash's output predictably.
+ *
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return The calculated 64-bit xxHash64 value.
+ *
+ * @see @ref single_shot_example "Single Shot Example" for an example.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64(XXH_NOESCAPE const void* input, size_t length, XXH64_hash_t seed);
+
+/******* Streaming *******/
+#ifndef XXH_NO_STREAM
+/*!
+ * @brief The opaque state struct for the XXH64 streaming API.
+ *
+ * @see XXH64_state_s for details.
+ */
+typedef struct XXH64_state_s XXH64_state_t; /* incomplete type */
+
+/*!
+ * @brief Allocates an @ref XXH64_state_t.
+ *
+ * @return An allocated pointer of @ref XXH64_state_t on success.
+ * @return `NULL` on failure.
+ *
+ * @note Must be freed with XXH64_freeState().
+ */
+XXH_PUBLIC_API XXH_MALLOCF XXH64_state_t* XXH64_createState(void);
+
+/*!
+ * @brief Frees an @ref XXH64_state_t.
+ *
+ * @param statePtr A pointer to an @ref XXH64_state_t allocated with @ref XXH64_createState().
+ *
+ * @return @ref XXH_OK.
+ *
+ * @note @p statePtr must be allocated with XXH64_createState().
+ */
+XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr);
+
+/*!
+ * @brief Copies one @ref XXH64_state_t to another.
+ *
+ * @param dst_state The state to copy to.
+ * @param src_state The state to copy from.
+ * @pre
+ * @p dst_state and @p src_state must not be `NULL` and must not overlap.
+ */
+XXH_PUBLIC_API void XXH64_copyState(XXH_NOESCAPE XXH64_state_t* dst_state, const XXH64_state_t* src_state);
+
+/*!
+ * @brief Resets an @ref XXH64_state_t to begin a new hash.
+ *
+ * @param statePtr The state struct to reset.
+ * @param seed The 64-bit seed to alter the hash result predictably.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @note This function resets and seeds a state. Call it before @ref XXH64_update().
+ */
+XXH_PUBLIC_API XXH_errorcode XXH64_reset (XXH_NOESCAPE XXH64_state_t* statePtr, XXH64_hash_t seed);
+
+/*!
+ * @brief Consumes a block of @p input to an @ref XXH64_state_t.
+ *
+ * @param statePtr The state struct to update.
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @note Call this to incrementally consume blocks of data.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH64_update (XXH_NOESCAPE XXH64_state_t* statePtr, XXH_NOESCAPE const void* input, size_t length);
+
+/*!
+ * @brief Returns the calculated hash value from an @ref XXH64_state_t.
+ *
+ * @param statePtr The state struct to calculate the hash from.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return The calculated 64-bit xxHash64 value from that state.
+ *
+ * @note
+ * Calling XXH64_digest() will not affect @p statePtr, so you can update,
+ * digest, and update again.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64_digest (XXH_NOESCAPE const XXH64_state_t* statePtr);
+#endif /* !XXH_NO_STREAM */
+/******* Canonical representation *******/
+
+/*!
+ * @brief Canonical (big endian) representation of @ref XXH64_hash_t.
+ */
+typedef struct { unsigned char digest[sizeof(XXH64_hash_t)]; } XXH64_canonical_t;
+
+/*!
+ * @brief Converts an @ref XXH64_hash_t to a big endian @ref XXH64_canonical_t.
+ *
+ * @param dst The @ref XXH64_canonical_t pointer to be stored to.
+ * @param hash The @ref XXH64_hash_t to be converted.
+ *
+ * @pre
+ * @p dst must not be `NULL`.
+ *
+ * @see @ref canonical_representation_example "Canonical Representation Example"
+ */
+XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH_NOESCAPE XXH64_canonical_t* dst, XXH64_hash_t hash);
+
+/*!
+ * @brief Converts an @ref XXH64_canonical_t to a native @ref XXH64_hash_t.
+ *
+ * @param src The @ref XXH64_canonical_t to convert.
+ *
+ * @pre
+ * @p src must not be `NULL`.
+ *
+ * @return The converted hash.
+ *
+ * @see @ref canonical_representation_example "Canonical Representation Example"
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH64_hashFromCanonical(XXH_NOESCAPE const XXH64_canonical_t* src);
+
+#ifndef XXH_NO_XXH3
+
+/*!
+ * @}
+ * ************************************************************************
+ * @defgroup XXH3_family XXH3 family
+ * @ingroup public
+ * @{
+ *
+ * XXH3 is a more recent hash algorithm featuring:
+ * - Improved speed for both small and large inputs
+ * - True 64-bit and 128-bit outputs
+ * - SIMD acceleration
+ * - Improved 32-bit viability
+ *
+ * Speed analysis methodology is explained here:
+ *
+ * https://fastcompression.blogspot.com/2019/03/presenting-xxh3.html
+ *
+ * Compared to XXH64, expect XXH3 to run approximately
+ * ~2x faster on large inputs and >3x faster on small ones,
+ * exact differences vary depending on platform.
+ *
+ * XXH3's speed benefits greatly from SIMD and 64-bit arithmetic,
+ * but does not require it.
+ * Most 32-bit and 64-bit targets that can run XXH32 smoothly can run XXH3
+ * at competitive speeds, even without vector support. Further details are
+ * explained in the implementation.
+ *
+ * XXH3 has a fast scalar implementation, but it also includes accelerated SIMD
+ * implementations for many common platforms:
+ * - AVX512
+ * - AVX2
+ * - SSE2
+ * - ARM NEON
+ * - WebAssembly SIMD128
+ * - POWER8 VSX
+ * - s390x ZVector
+ * This can be controlled via the @ref XXH_VECTOR macro, but it automatically
+ * selects the best version according to predefined macros. For the x86 family, an
+ * automatic runtime dispatcher is included separately in @ref xxh_x86dispatch.c.
+ *
+ * XXH3 implementation is portable:
+ * it has a generic C90 formulation that can be compiled on any platform,
+ * all implementations generate exactly the same hash value on all platforms.
+ * Starting from v0.8.0, it's also labelled "stable", meaning that
+ * any future version will also generate the same hash value.
+ *
+ * XXH3 offers 2 variants, _64bits and _128bits.
+ *
+ * When only 64 bits are needed, prefer invoking the _64bits variant, as it
+ * reduces the amount of mixing, resulting in faster speed on small inputs.
+ * It's also generally simpler to manipulate a scalar return type than a struct.
+ *
+ * The API supports one-shot hashing, streaming mode, and custom secrets.
+ */
+/*-**********************************************************************
+* XXH3 64-bit variant
+************************************************************************/
+
+/*!
+ * @brief Calculates 64-bit unseeded variant of XXH3 hash of @p input.
+ *
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ *
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return The calculated 64-bit XXH3 hash value.
+ *
+ * @note
+ * This is equivalent to @ref XXH3_64bits_withSeed() with a seed of `0`, however
+ * it may have slightly better performance due to constant propagation of the
+ * defaults.
+ *
+ * @see
+ * XXH3_64bits_withSeed(), XXH3_64bits_withSecret(): other seeding variants
+ * @see @ref single_shot_example "Single Shot Example" for an example.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits(XXH_NOESCAPE const void* input, size_t length);
+
+/*!
+ * @brief Calculates 64-bit seeded variant of XXH3 hash of @p input.
+ *
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ * @param seed The 64-bit seed to alter the hash result predictably.
+ *
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return The calculated 64-bit XXH3 hash value.
+ *
+ * @note
+ * seed == 0 produces the same results as @ref XXH3_64bits().
+ *
+ * This variant generates a custom secret on the fly based on default secret
+ * altered using the @p seed value.
+ *
+ * While this operation is decently fast, note that it's not completely free.
+ *
+ * @see @ref single_shot_example "Single Shot Example" for an example.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_withSeed(XXH_NOESCAPE const void* input, size_t length, XXH64_hash_t seed);
+
+/*!
+ * The bare minimum size for a custom secret.
+ *
+ * @see
+ * XXH3_64bits_withSecret(), XXH3_64bits_reset_withSecret(),
+ * XXH3_128bits_withSecret(), XXH3_128bits_reset_withSecret().
+ */
+#define XXH3_SECRET_SIZE_MIN 136
+
+/*!
+ * @brief Calculates 64-bit variant of XXH3 with a custom "secret".
+ *
+ * @param data The block of data to be hashed, at least @p len bytes in size.
+ * @param len The length of @p data, in bytes.
+ * @param secret The secret data.
+ * @param secretSize The length of @p secret, in bytes.
+ *
+ * @return The calculated 64-bit XXH3 hash value.
+ *
+ * @pre
+ * The memory between @p data and @p data + @p len must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p data may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * It's possible to provide any blob of bytes as a "secret" to generate the hash.
+ * This makes it more difficult for an external actor to prepare an intentional collision.
+ * The main condition is that @p secretSize *must* be large enough (>= @ref XXH3_SECRET_SIZE_MIN).
+ * However, the quality of the secret impacts the dispersion of the hash algorithm.
+ * Therefore, the secret _must_ look like a bunch of random bytes.
+ * Avoid "trivial" or structured data such as repeated sequences or a text document.
+ * Whenever in doubt about the "randomness" of the blob of bytes,
+ * consider employing @ref XXH3_generateSecret() instead (see below).
+ * It will generate a proper high entropy secret derived from the blob of bytes.
+ * Another advantage of using XXH3_generateSecret() is that
+ * it guarantees that all bits within the initial blob of bytes
+ * will impact every bit of the output.
+ * This is not necessarily the case when using the blob of bytes directly
+ * because, when hashing _small_ inputs, only a portion of the secret is employed.
+ *
+ * @see @ref single_shot_example "Single Shot Example" for an example.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_withSecret(XXH_NOESCAPE const void* data, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize);
+
+
+/******* Streaming *******/
+#ifndef XXH_NO_STREAM
+/*
+ * Streaming requires state maintenance.
+ * This operation costs memory and CPU.
+ * As a consequence, streaming is slower than one-shot hashing.
+ * For better performance, prefer one-shot functions whenever applicable.
+ */
+
+/*!
+ * @brief The opaque state struct for the XXH3 streaming API.
+ *
+ * @see XXH3_state_s for details.
+ */
+typedef struct XXH3_state_s XXH3_state_t;
+XXH_PUBLIC_API XXH_MALLOCF XXH3_state_t* XXH3_createState(void);
+XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr);
+
+/*!
+ * @brief Copies one @ref XXH3_state_t to another.
+ *
+ * @param dst_state The state to copy to.
+ * @param src_state The state to copy from.
+ * @pre
+ * @p dst_state and @p src_state must not be `NULL` and must not overlap.
+ */
+XXH_PUBLIC_API void XXH3_copyState(XXH_NOESCAPE XXH3_state_t* dst_state, XXH_NOESCAPE const XXH3_state_t* src_state);
+
+/*!
+ * @brief Resets an @ref XXH3_state_t to begin a new hash.
+ *
+ * @param statePtr The state struct to reset.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @note
+ * - This function resets `statePtr` and generate a secret with default parameters.
+ * - Call this function before @ref XXH3_64bits_update().
+ * - Digest will be equivalent to `XXH3_64bits()`.
+ *
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr);
+
+/*!
+ * @brief Resets an @ref XXH3_state_t with 64-bit seed to begin a new hash.
+ *
+ * @param statePtr The state struct to reset.
+ * @param seed The 64-bit seed to alter the hash result predictably.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @note
+ * - This function resets `statePtr` and generate a secret from `seed`.
+ * - Call this function before @ref XXH3_64bits_update().
+ * - Digest will be equivalent to `XXH3_64bits_withSeed()`.
+ *
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed);
+
+/*!
+ * @brief Resets an @ref XXH3_state_t with secret data to begin a new hash.
+ *
+ * @param statePtr The state struct to reset.
+ * @param secret The secret data.
+ * @param secretSize The length of @p secret, in bytes.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @note
+ * `secret` is referenced, it _must outlive_ the hash streaming session.
+ *
+ * Similar to one-shot API, `secretSize` must be >= @ref XXH3_SECRET_SIZE_MIN,
+ * and the quality of produced hash values depends on secret's entropy
+ * (secret's content should look like a bunch of random bytes).
+ * When in doubt about the randomness of a candidate `secret`,
+ * consider employing `XXH3_generateSecret()` instead (see below).
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize);
+
+/*!
+ * @brief Consumes a block of @p input to an @ref XXH3_state_t.
+ *
+ * @param statePtr The state struct to update.
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ * @pre
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @note Call this to incrementally consume blocks of data.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_64bits_update (XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* input, size_t length);
+
+/*!
+ * @brief Returns the calculated XXH3 64-bit hash value from an @ref XXH3_state_t.
+ *
+ * @param statePtr The state struct to calculate the hash from.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return The calculated XXH3 64-bit hash value from that state.
+ *
+ * @note
+ * Calling XXH3_64bits_digest() will not affect @p statePtr, so you can update,
+ * digest, and update again.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t XXH3_64bits_digest (XXH_NOESCAPE const XXH3_state_t* statePtr);
+#endif /* !XXH_NO_STREAM */
+
+/* note : canonical representation of XXH3 is the same as XXH64
+ * since they both produce XXH64_hash_t values */
+
+
+/*-**********************************************************************
+* XXH3 128-bit variant
+************************************************************************/
+
+/*!
+ * @brief The return value from 128-bit hashes.
+ *
+ * Stored in little endian order, although the fields themselves are in native
+ * endianness.
+ */
+typedef struct {
+ XXH64_hash_t low64; /*!< `value & 0xFFFFFFFFFFFFFFFF` */
+ XXH64_hash_t high64; /*!< `value >> 64` */
+} XXH128_hash_t;
+
+/*!
+ * @brief Calculates 128-bit unseeded variant of XXH3 of @p data.
+ *
+ * @param data The block of data to be hashed, at least @p length bytes in size.
+ * @param len The length of @p data, in bytes.
+ *
+ * @return The calculated 128-bit variant of XXH3 value.
+ *
+ * The 128-bit variant of XXH3 has more strength, but it has a bit of overhead
+ * for shorter inputs.
+ *
+ * This is equivalent to @ref XXH3_128bits_withSeed() with a seed of `0`, however
+ * it may have slightly better performance due to constant propagation of the
+ * defaults.
+ *
+ * @see XXH3_128bits_withSeed(), XXH3_128bits_withSecret(): other seeding variants
+ * @see @ref single_shot_example "Single Shot Example" for an example.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits(XXH_NOESCAPE const void* data, size_t len);
+/*! @brief Calculates 128-bit seeded variant of XXH3 hash of @p data.
+ *
+ * @param data The block of data to be hashed, at least @p length bytes in size.
+ * @param len The length of @p data, in bytes.
+ * @param seed The 64-bit seed to alter the hash result predictably.
+ *
+ * @return The calculated 128-bit variant of XXH3 value.
+ *
+ * @note
+ * seed == 0 produces the same results as @ref XXH3_64bits().
+ *
+ * This variant generates a custom secret on the fly based on default secret
+ * altered using the @p seed value.
+ *
+ * While this operation is decently fast, note that it's not completely free.
+ *
+ * @see XXH3_128bits(), XXH3_128bits_withSecret(): other seeding variants
+ * @see @ref single_shot_example "Single Shot Example" for an example.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_withSeed(XXH_NOESCAPE const void* data, size_t len, XXH64_hash_t seed);
+/*!
+ * @brief Calculates 128-bit variant of XXH3 with a custom "secret".
+ *
+ * @param data The block of data to be hashed, at least @p len bytes in size.
+ * @param len The length of @p data, in bytes.
+ * @param secret The secret data.
+ * @param secretSize The length of @p secret, in bytes.
+ *
+ * @return The calculated 128-bit variant of XXH3 value.
+ *
+ * It's possible to provide any blob of bytes as a "secret" to generate the hash.
+ * This makes it more difficult for an external actor to prepare an intentional collision.
+ * The main condition is that @p secretSize *must* be large enough (>= @ref XXH3_SECRET_SIZE_MIN).
+ * However, the quality of the secret impacts the dispersion of the hash algorithm.
+ * Therefore, the secret _must_ look like a bunch of random bytes.
+ * Avoid "trivial" or structured data such as repeated sequences or a text document.
+ * Whenever in doubt about the "randomness" of the blob of bytes,
+ * consider employing @ref XXH3_generateSecret() instead (see below).
+ * It will generate a proper high entropy secret derived from the blob of bytes.
+ * Another advantage of using XXH3_generateSecret() is that
+ * it guarantees that all bits within the initial blob of bytes
+ * will impact every bit of the output.
+ * This is not necessarily the case when using the blob of bytes directly
+ * because, when hashing _small_ inputs, only a portion of the secret is employed.
+ *
+ * @see @ref single_shot_example "Single Shot Example" for an example.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_withSecret(XXH_NOESCAPE const void* data, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize);
+
+/******* Streaming *******/
+#ifndef XXH_NO_STREAM
+/*
+ * Streaming requires state maintenance.
+ * This operation costs memory and CPU.
+ * As a consequence, streaming is slower than one-shot hashing.
+ * For better performance, prefer one-shot functions whenever applicable.
+ *
+ * XXH3_128bits uses the same XXH3_state_t as XXH3_64bits().
+ * Use already declared XXH3_createState() and XXH3_freeState().
+ *
+ * All reset and streaming functions have same meaning as their 64-bit counterpart.
+ */
+
+/*!
+ * @brief Resets an @ref XXH3_state_t to begin a new hash.
+ *
+ * @param statePtr The state struct to reset.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @note
+ * - This function resets `statePtr` and generate a secret with default parameters.
+ * - Call it before @ref XXH3_128bits_update().
+ * - Digest will be equivalent to `XXH3_128bits()`.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr);
+
+/*!
+ * @brief Resets an @ref XXH3_state_t with 64-bit seed to begin a new hash.
+ *
+ * @param statePtr The state struct to reset.
+ * @param seed The 64-bit seed to alter the hash result predictably.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @note
+ * - This function resets `statePtr` and generate a secret from `seed`.
+ * - Call it before @ref XXH3_128bits_update().
+ * - Digest will be equivalent to `XXH3_128bits_withSeed()`.
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed);
+/*!
+ * @brief Resets an @ref XXH3_state_t with secret data to begin a new hash.
+ *
+ * @param statePtr The state struct to reset.
+ * @param secret The secret data.
+ * @param secretSize The length of @p secret, in bytes.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * `secret` is referenced, it _must outlive_ the hash streaming session.
+ * Similar to one-shot API, `secretSize` must be >= @ref XXH3_SECRET_SIZE_MIN,
+ * and the quality of produced hash values depends on secret's entropy
+ * (secret's content should look like a bunch of random bytes).
+ * When in doubt about the randomness of a candidate `secret`,
+ * consider employing `XXH3_generateSecret()` instead (see below).
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize);
+
+/*!
+ * @brief Consumes a block of @p input to an @ref XXH3_state_t.
+ *
+ * Call this to incrementally consume blocks of data.
+ *
+ * @param statePtr The state struct to update.
+ * @param input The block of data to be hashed, at least @p length bytes in size.
+ * @param length The length of @p input, in bytes.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @note
+ * The memory between @p input and @p input + @p length must be valid,
+ * readable, contiguous memory. However, if @p length is `0`, @p input may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update (XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* input, size_t length);
+
+/*!
+ * @brief Returns the calculated XXH3 128-bit hash value from an @ref XXH3_state_t.
+ *
+ * @param statePtr The state struct to calculate the hash from.
+ *
+ * @pre
+ * @p statePtr must not be `NULL`.
+ *
+ * @return The calculated XXH3 128-bit hash value from that state.
+ *
+ * @note
+ * Calling XXH3_128bits_digest() will not affect @p statePtr, so you can update,
+ * digest, and update again.
+ *
+ */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_digest (XXH_NOESCAPE const XXH3_state_t* statePtr);
+#endif /* !XXH_NO_STREAM */
+
+/* Following helper functions make it possible to compare XXH128_hast_t values.
+ * Since XXH128_hash_t is a structure, this capability is not offered by the language.
+ * Note: For better performance, these functions can be inlined using XXH_INLINE_ALL */
+
+/*!
+ * @brief Check equality of two XXH128_hash_t values
+ *
+ * @param h1 The 128-bit hash value.
+ * @param h2 Another 128-bit hash value.
+ *
+ * @return `1` if `h1` and `h2` are equal.
+ * @return `0` if they are not.
+ */
+XXH_PUBLIC_API XXH_PUREF int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2);
+
+/*!
+ * @brief Compares two @ref XXH128_hash_t
+ *
+ * This comparator is compatible with stdlib's `qsort()`/`bsearch()`.
+ *
+ * @param h128_1 Left-hand side value
+ * @param h128_2 Right-hand side value
+ *
+ * @return >0 if @p h128_1 > @p h128_2
+ * @return =0 if @p h128_1 == @p h128_2
+ * @return <0 if @p h128_1 < @p h128_2
+ */
+XXH_PUBLIC_API XXH_PUREF int XXH128_cmp(XXH_NOESCAPE const void* h128_1, XXH_NOESCAPE const void* h128_2);
+
+
+/******* Canonical representation *******/
+typedef struct { unsigned char digest[sizeof(XXH128_hash_t)]; } XXH128_canonical_t;
+
+
+/*!
+ * @brief Converts an @ref XXH128_hash_t to a big endian @ref XXH128_canonical_t.
+ *
+ * @param dst The @ref XXH128_canonical_t pointer to be stored to.
+ * @param hash The @ref XXH128_hash_t to be converted.
+ *
+ * @pre
+ * @p dst must not be `NULL`.
+ * @see @ref canonical_representation_example "Canonical Representation Example"
+ */
+XXH_PUBLIC_API void XXH128_canonicalFromHash(XXH_NOESCAPE XXH128_canonical_t* dst, XXH128_hash_t hash);
+
+/*!
+ * @brief Converts an @ref XXH128_canonical_t to a native @ref XXH128_hash_t.
+ *
+ * @param src The @ref XXH128_canonical_t to convert.
+ *
+ * @pre
+ * @p src must not be `NULL`.
+ *
+ * @return The converted hash.
+ * @see @ref canonical_representation_example "Canonical Representation Example"
+ */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH128_hashFromCanonical(XXH_NOESCAPE const XXH128_canonical_t* src);
+
+
+#endif /* !XXH_NO_XXH3 */
+#endif /* XXH_NO_LONG_LONG */
+
+/*!
+ * @}
+ */
+#endif /* XXHASH_H_5627135585666179 */
+
+
+
+#if defined(XXH_STATIC_LINKING_ONLY) && !defined(XXHASH_H_STATIC_13879238742)
+#define XXHASH_H_STATIC_13879238742
+/* ****************************************************************************
+ * This section contains declarations which are not guaranteed to remain stable.
+ * They may change in future versions, becoming incompatible with a different
+ * version of the library.
+ * These declarations should only be used with static linking.
+ * Never use them in association with dynamic linking!
+ ***************************************************************************** */
+
+/*
+ * These definitions are only present to allow static allocation
+ * of XXH states, on stack or in a struct, for example.
+ * Never **ever** access their members directly.
+ */
+
+/*!
+ * @internal
+ * @brief Structure for XXH32 streaming API.
+ *
+ * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY,
+ * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined. Otherwise it is
+ * an opaque type. This allows fields to safely be changed.
+ *
+ * Typedef'd to @ref XXH32_state_t.
+ * Do not access the members of this struct directly.
+ * @see XXH64_state_s, XXH3_state_s
+ */
+struct XXH32_state_s {
+ XXH32_hash_t total_len_32; /*!< Total length hashed, modulo 2^32 */
+ XXH32_hash_t large_len; /*!< Whether the hash is >= 16 (handles @ref total_len_32 overflow) */
+ XXH32_hash_t v[4]; /*!< Accumulator lanes */
+ XXH32_hash_t mem32[4]; /*!< Internal buffer for partial reads. Treated as unsigned char[16]. */
+ XXH32_hash_t memsize; /*!< Amount of data in @ref mem32 */
+ XXH32_hash_t reserved; /*!< Reserved field. Do not read nor write to it. */
+}; /* typedef'd to XXH32_state_t */
+
+
+#ifndef XXH_NO_LONG_LONG /* defined when there is no 64-bit support */
+
+/*!
+ * @internal
+ * @brief Structure for XXH64 streaming API.
+ *
+ * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY,
+ * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined. Otherwise it is
+ * an opaque type. This allows fields to safely be changed.
+ *
+ * Typedef'd to @ref XXH64_state_t.
+ * Do not access the members of this struct directly.
+ * @see XXH32_state_s, XXH3_state_s
+ */
+struct XXH64_state_s {
+ XXH64_hash_t total_len; /*!< Total length hashed. This is always 64-bit. */
+ XXH64_hash_t v[4]; /*!< Accumulator lanes */
+ XXH64_hash_t mem64[4]; /*!< Internal buffer for partial reads. Treated as unsigned char[32]. */
+ XXH32_hash_t memsize; /*!< Amount of data in @ref mem64 */
+ XXH32_hash_t reserved32; /*!< Reserved field, needed for padding anyways*/
+ XXH64_hash_t reserved64; /*!< Reserved field. Do not read or write to it. */
+}; /* typedef'd to XXH64_state_t */
+
+#ifndef XXH_NO_XXH3
+
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* >= C11 */
+# include <stdalign.h>
+# define XXH_ALIGN(n) alignas(n)
+#elif defined(__cplusplus) && (__cplusplus >= 201103L) /* >= C++11 */
+/* In C++ alignas() is a keyword */
+# define XXH_ALIGN(n) alignas(n)
+#elif defined(__GNUC__)
+# define XXH_ALIGN(n) __attribute__ ((aligned(n)))
+#elif defined(_MSC_VER)
+# define XXH_ALIGN(n) __declspec(align(n))
+#else
+# define XXH_ALIGN(n) /* disabled */
+#endif
+
+/* Old GCC versions only accept the attribute after the type in structures. */
+#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) /* C11+ */ \
+ && ! (defined(__cplusplus) && (__cplusplus >= 201103L)) /* >= C++11 */ \
+ && defined(__GNUC__)
+# define XXH_ALIGN_MEMBER(align, type) type XXH_ALIGN(align)
+#else
+# define XXH_ALIGN_MEMBER(align, type) XXH_ALIGN(align) type
+#endif
+
+/*!
+ * @brief The size of the internal XXH3 buffer.
+ *
+ * This is the optimal update size for incremental hashing.
+ *
+ * @see XXH3_64b_update(), XXH3_128b_update().
+ */
+#define XXH3_INTERNALBUFFER_SIZE 256
+
+/*!
+ * @internal
+ * @brief Default size of the secret buffer (and @ref XXH3_kSecret).
+ *
+ * This is the size used in @ref XXH3_kSecret and the seeded functions.
+ *
+ * Not to be confused with @ref XXH3_SECRET_SIZE_MIN.
+ */
+#define XXH3_SECRET_DEFAULT_SIZE 192
+
+/*!
+ * @internal
+ * @brief Structure for XXH3 streaming API.
+ *
+ * @note This is only defined when @ref XXH_STATIC_LINKING_ONLY,
+ * @ref XXH_INLINE_ALL, or @ref XXH_IMPLEMENTATION is defined.
+ * Otherwise it is an opaque type.
+ * Never use this definition in combination with dynamic library.
+ * This allows fields to safely be changed in the future.
+ *
+ * @note ** This structure has a strict alignment requirement of 64 bytes!! **
+ * Do not allocate this with `malloc()` or `new`,
+ * it will not be sufficiently aligned.
+ * Use @ref XXH3_createState() and @ref XXH3_freeState(), or stack allocation.
+ *
+ * Typedef'd to @ref XXH3_state_t.
+ * Do never access the members of this struct directly.
+ *
+ * @see XXH3_INITSTATE() for stack initialization.
+ * @see XXH3_createState(), XXH3_freeState().
+ * @see XXH32_state_s, XXH64_state_s
+ */
+struct XXH3_state_s {
+ XXH_ALIGN_MEMBER(64, XXH64_hash_t acc[8]);
+ /*!< The 8 accumulators. See @ref XXH32_state_s::v and @ref XXH64_state_s::v */
+ XXH_ALIGN_MEMBER(64, unsigned char customSecret[XXH3_SECRET_DEFAULT_SIZE]);
+ /*!< Used to store a custom secret generated from a seed. */
+ XXH_ALIGN_MEMBER(64, unsigned char buffer[XXH3_INTERNALBUFFER_SIZE]);
+ /*!< The internal buffer. @see XXH32_state_s::mem32 */
+ XXH32_hash_t bufferedSize;
+ /*!< The amount of memory in @ref buffer, @see XXH32_state_s::memsize */
+ XXH32_hash_t useSeed;
+ /*!< Reserved field. Needed for padding on 64-bit. */
+ size_t nbStripesSoFar;
+ /*!< Number or stripes processed. */
+ XXH64_hash_t totalLen;
+ /*!< Total length hashed. 64-bit even on 32-bit targets. */
+ size_t nbStripesPerBlock;
+ /*!< Number of stripes per block. */
+ size_t secretLimit;
+ /*!< Size of @ref customSecret or @ref extSecret */
+ XXH64_hash_t seed;
+ /*!< Seed for _withSeed variants. Must be zero otherwise, @see XXH3_INITSTATE() */
+ XXH64_hash_t reserved64;
+ /*!< Reserved field. */
+ const unsigned char* extSecret;
+ /*!< Reference to an external secret for the _withSecret variants, NULL
+ * for other variants. */
+ /* note: there may be some padding at the end due to alignment on 64 bytes */
+}; /* typedef'd to XXH3_state_t */
+
+#undef XXH_ALIGN_MEMBER
+
+/*!
+ * @brief Initializes a stack-allocated `XXH3_state_s`.
+ *
+ * When the @ref XXH3_state_t structure is merely emplaced on stack,
+ * it should be initialized with XXH3_INITSTATE() or a memset()
+ * in case its first reset uses XXH3_NNbits_reset_withSeed().
+ * This init can be omitted if the first reset uses default or _withSecret mode.
+ * This operation isn't necessary when the state is created with XXH3_createState().
+ * Note that this doesn't prepare the state for a streaming operation,
+ * it's still necessary to use XXH3_NNbits_reset*() afterwards.
+ */
+#define XXH3_INITSTATE(XXH3_state_ptr) \
+ do { \
+ XXH3_state_t* tmp_xxh3_state_ptr = (XXH3_state_ptr); \
+ tmp_xxh3_state_ptr->seed = 0; \
+ tmp_xxh3_state_ptr->extSecret = NULL; \
+ } while(0)
+
+
+/*!
+ * @brief Calculates the 128-bit hash of @p data using XXH3.
+ *
+ * @param data The block of data to be hashed, at least @p len bytes in size.
+ * @param len The length of @p data, in bytes.
+ * @param seed The 64-bit seed to alter the hash's output predictably.
+ *
+ * @pre
+ * The memory between @p data and @p data + @p len must be valid,
+ * readable, contiguous memory. However, if @p len is `0`, @p data may be
+ * `NULL`. In C++, this also must be *TriviallyCopyable*.
+ *
+ * @return The calculated 128-bit XXH3 value.
+ *
+ * @see @ref single_shot_example "Single Shot Example" for an example.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH128(XXH_NOESCAPE const void* data, size_t len, XXH64_hash_t seed);
+
+
+/* === Experimental API === */
+/* Symbols defined below must be considered tied to a specific library version. */
+
+/*!
+ * @brief Derive a high-entropy secret from any user-defined content, named customSeed.
+ *
+ * @param secretBuffer A writable buffer for derived high-entropy secret data.
+ * @param secretSize Size of secretBuffer, in bytes. Must be >= XXH3_SECRET_DEFAULT_SIZE.
+ * @param customSeed A user-defined content.
+ * @param customSeedSize Size of customSeed, in bytes.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * The generated secret can be used in combination with `*_withSecret()` functions.
+ * The `_withSecret()` variants are useful to provide a higher level of protection
+ * than 64-bit seed, as it becomes much more difficult for an external actor to
+ * guess how to impact the calculation logic.
+ *
+ * The function accepts as input a custom seed of any length and any content,
+ * and derives from it a high-entropy secret of length @p secretSize into an
+ * already allocated buffer @p secretBuffer.
+ *
+ * The generated secret can then be used with any `*_withSecret()` variant.
+ * The functions @ref XXH3_128bits_withSecret(), @ref XXH3_64bits_withSecret(),
+ * @ref XXH3_128bits_reset_withSecret() and @ref XXH3_64bits_reset_withSecret()
+ * are part of this list. They all accept a `secret` parameter
+ * which must be large enough for implementation reasons (>= @ref XXH3_SECRET_SIZE_MIN)
+ * _and_ feature very high entropy (consist of random-looking bytes).
+ * These conditions can be a high bar to meet, so @ref XXH3_generateSecret() can
+ * be employed to ensure proper quality.
+ *
+ * @p customSeed can be anything. It can have any size, even small ones,
+ * and its content can be anything, even "poor entropy" sources such as a bunch
+ * of zeroes. The resulting `secret` will nonetheless provide all required qualities.
+ *
+ * @pre
+ * - @p secretSize must be >= @ref XXH3_SECRET_SIZE_MIN
+ * - When @p customSeedSize > 0, supplying NULL as customSeed is undefined behavior.
+ *
+ * Example code:
+ * @code{.c}
+ * #include <stdio.h>
+ * #include <stdlib.h>
+ * #include <string.h>
+ * #define XXH_STATIC_LINKING_ONLY // expose unstable API
+ * #include "xxhash.h"
+ * // Hashes argv[2] using the entropy from argv[1].
+ * int main(int argc, char* argv[])
+ * {
+ * char secret[XXH3_SECRET_SIZE_MIN];
+ * if (argv != 3) { return 1; }
+ * XXH3_generateSecret(secret, sizeof(secret), argv[1], strlen(argv[1]));
+ * XXH64_hash_t h = XXH3_64bits_withSecret(
+ * argv[2], strlen(argv[2]),
+ * secret, sizeof(secret)
+ * );
+ * printf("%016llx\n", (unsigned long long) h);
+ * }
+ * @endcode
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_generateSecret(XXH_NOESCAPE void* secretBuffer, size_t secretSize, XXH_NOESCAPE const void* customSeed, size_t customSeedSize);
+
+/*!
+ * @brief Generate the same secret as the _withSeed() variants.
+ *
+ * @param secretBuffer A writable buffer of @ref XXH3_SECRET_SIZE_MIN bytes
+ * @param seed The 64-bit seed to alter the hash result predictably.
+ *
+ * The generated secret can be used in combination with
+ *`*_withSecret()` and `_withSecretandSeed()` variants.
+ *
+ * Example C++ `std::string` hash class:
+ * @code{.cpp}
+ * #include <string>
+ * #define XXH_STATIC_LINKING_ONLY // expose unstable API
+ * #include "xxhash.h"
+ * // Slow, seeds each time
+ * class HashSlow {
+ * XXH64_hash_t seed;
+ * public:
+ * HashSlow(XXH64_hash_t s) : seed{s} {}
+ * size_t operator()(const std::string& x) const {
+ * return size_t{XXH3_64bits_withSeed(x.c_str(), x.length(), seed)};
+ * }
+ * };
+ * // Fast, caches the seeded secret for future uses.
+ * class HashFast {
+ * unsigned char secret[XXH3_SECRET_SIZE_MIN];
+ * public:
+ * HashFast(XXH64_hash_t s) {
+ * XXH3_generateSecret_fromSeed(secret, seed);
+ * }
+ * size_t operator()(const std::string& x) const {
+ * return size_t{
+ * XXH3_64bits_withSecret(x.c_str(), x.length(), secret, sizeof(secret))
+ * };
+ * }
+ * };
+ * @endcode
+ */
+XXH_PUBLIC_API void XXH3_generateSecret_fromSeed(XXH_NOESCAPE void* secretBuffer, XXH64_hash_t seed);
+
+/*!
+ * @brief Calculates 64/128-bit seeded variant of XXH3 hash of @p data.
+ *
+ * @param data The block of data to be hashed, at least @p len bytes in size.
+ * @param len The length of @p data, in bytes.
+ * @param secret The secret data.
+ * @param secretSize The length of @p secret, in bytes.
+ * @param seed The 64-bit seed to alter the hash result predictably.
+ *
+ * These variants generate hash values using either
+ * @p seed for "short" keys (< @ref XXH3_MIDSIZE_MAX = 240 bytes)
+ * or @p secret for "large" keys (>= @ref XXH3_MIDSIZE_MAX).
+ *
+ * This generally benefits speed, compared to `_withSeed()` or `_withSecret()`.
+ * `_withSeed()` has to generate the secret on the fly for "large" keys.
+ * It's fast, but can be perceptible for "not so large" keys (< 1 KB).
+ * `_withSecret()` has to generate the masks on the fly for "small" keys,
+ * which requires more instructions than _withSeed() variants.
+ * Therefore, _withSecretandSeed variant combines the best of both worlds.
+ *
+ * When @p secret has been generated by XXH3_generateSecret_fromSeed(),
+ * this variant produces *exactly* the same results as `_withSeed()` variant,
+ * hence offering only a pure speed benefit on "large" input,
+ * by skipping the need to regenerate the secret for every large input.
+ *
+ * Another usage scenario is to hash the secret to a 64-bit hash value,
+ * for example with XXH3_64bits(), which then becomes the seed,
+ * and then employ both the seed and the secret in _withSecretandSeed().
+ * On top of speed, an added benefit is that each bit in the secret
+ * has a 50% chance to swap each bit in the output, via its impact to the seed.
+ *
+ * This is not guaranteed when using the secret directly in "small data" scenarios,
+ * because only portions of the secret are employed for small data.
+ */
+XXH_PUBLIC_API XXH_PUREF XXH64_hash_t
+XXH3_64bits_withSecretandSeed(XXH_NOESCAPE const void* data, size_t len,
+ XXH_NOESCAPE const void* secret, size_t secretSize,
+ XXH64_hash_t seed);
+/*!
+ * @brief Calculates 128-bit seeded variant of XXH3 hash of @p data.
+ *
+ * @param input The block of data to be hashed, at least @p len bytes in size.
+ * @param length The length of @p data, in bytes.
+ * @param secret The secret data.
+ * @param secretSize The length of @p secret, in bytes.
+ * @param seed64 The 64-bit seed to alter the hash result predictably.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @see XXH3_64bits_withSecretandSeed()
+ */
+XXH_PUBLIC_API XXH_PUREF XXH128_hash_t
+XXH3_128bits_withSecretandSeed(XXH_NOESCAPE const void* input, size_t length,
+ XXH_NOESCAPE const void* secret, size_t secretSize,
+ XXH64_hash_t seed64);
+#ifndef XXH_NO_STREAM
+/*!
+ * @brief Resets an @ref XXH3_state_t with secret data to begin a new hash.
+ *
+ * @param statePtr A pointer to an @ref XXH3_state_t allocated with @ref XXH3_createState().
+ * @param secret The secret data.
+ * @param secretSize The length of @p secret, in bytes.
+ * @param seed64 The 64-bit seed to alter the hash result predictably.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @see XXH3_64bits_withSecretandSeed()
+ */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr,
+ XXH_NOESCAPE const void* secret, size_t secretSize,
+ XXH64_hash_t seed64);
+/*!
+ * @brief Resets an @ref XXH3_state_t with secret data to begin a new hash.
+ *
+ * @param statePtr A pointer to an @ref XXH3_state_t allocated with @ref XXH3_createState().
+ * @param secret The secret data.
+ * @param secretSize The length of @p secret, in bytes.
+ * @param seed64 The 64-bit seed to alter the hash result predictably.
+ *
+ * @return @ref XXH_OK on success.
+ * @return @ref XXH_ERROR on failure.
+ *
+ * @see XXH3_64bits_withSecretandSeed()
+ */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr,
+ XXH_NOESCAPE const void* secret, size_t secretSize,
+ XXH64_hash_t seed64);
+#endif /* !XXH_NO_STREAM */
+
+#endif /* !XXH_NO_XXH3 */
+#endif /* XXH_NO_LONG_LONG */
+#if defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API)
+# define XXH_IMPLEMENTATION
+#endif
+
+#endif /* defined(XXH_STATIC_LINKING_ONLY) && !defined(XXHASH_H_STATIC_13879238742) */
+
+
+/* ======================================================================== */
+/* ======================================================================== */
+/* ======================================================================== */
+
+
+/*-**********************************************************************
+ * xxHash implementation
+ *-**********************************************************************
+ * xxHash's implementation used to be hosted inside xxhash.c.
+ *
+ * However, inlining requires implementation to be visible to the compiler,
+ * hence be included alongside the header.
+ * Previously, implementation was hosted inside xxhash.c,
+ * which was then #included when inlining was activated.
+ * This construction created issues with a few build and install systems,
+ * as it required xxhash.c to be stored in /include directory.
+ *
+ * xxHash implementation is now directly integrated within xxhash.h.
+ * As a consequence, xxhash.c is no longer needed in /include.
+ *
+ * xxhash.c is still available and is still useful.
+ * In a "normal" setup, when xxhash is not inlined,
+ * xxhash.h only exposes the prototypes and public symbols,
+ * while xxhash.c can be built into an object file xxhash.o
+ * which can then be linked into the final binary.
+ ************************************************************************/
+
+#if ( defined(XXH_INLINE_ALL) || defined(XXH_PRIVATE_API) \
+ || defined(XXH_IMPLEMENTATION) ) && !defined(XXH_IMPLEM_13a8737387)
+# define XXH_IMPLEM_13a8737387
+
+/* *************************************
+* Tuning parameters
+***************************************/
+
+/*!
+ * @defgroup tuning Tuning parameters
+ * @{
+ *
+ * Various macros to control xxHash's behavior.
+ */
+#ifdef XXH_DOXYGEN
+/*!
+ * @brief Define this to disable 64-bit code.
+ *
+ * Useful if only using the @ref XXH32_family and you have a strict C90 compiler.
+ */
+# define XXH_NO_LONG_LONG
+# undef XXH_NO_LONG_LONG /* don't actually */
+/*!
+ * @brief Controls how unaligned memory is accessed.
+ *
+ * By default, access to unaligned memory is controlled by `memcpy()`, which is
+ * safe and portable.
+ *
+ * Unfortunately, on some target/compiler combinations, the generated assembly
+ * is sub-optimal.
+ *
+ * The below switch allow selection of a different access method
+ * in the search for improved performance.
+ *
+ * @par Possible options:
+ *
+ * - `XXH_FORCE_MEMORY_ACCESS=0` (default): `memcpy`
+ * @par
+ * Use `memcpy()`. Safe and portable. Note that most modern compilers will
+ * eliminate the function call and treat it as an unaligned access.
+ *
+ * - `XXH_FORCE_MEMORY_ACCESS=1`: `__attribute__((aligned(1)))`
+ * @par
+ * Depends on compiler extensions and is therefore not portable.
+ * This method is safe _if_ your compiler supports it,
+ * and *generally* as fast or faster than `memcpy`.
+ *
+ * - `XXH_FORCE_MEMORY_ACCESS=2`: Direct cast
+ * @par
+ * Casts directly and dereferences. This method doesn't depend on the
+ * compiler, but it violates the C standard as it directly dereferences an
+ * unaligned pointer. It can generate buggy code on targets which do not
+ * support unaligned memory accesses, but in some circumstances, it's the
+ * only known way to get the most performance.
+ *
+ * - `XXH_FORCE_MEMORY_ACCESS=3`: Byteshift
+ * @par
+ * Also portable. This can generate the best code on old compilers which don't
+ * inline small `memcpy()` calls, and it might also be faster on big-endian
+ * systems which lack a native byteswap instruction. However, some compilers
+ * will emit literal byteshifts even if the target supports unaligned access.
+ *
+ *
+ * @warning
+ * Methods 1 and 2 rely on implementation-defined behavior. Use these with
+ * care, as what works on one compiler/platform/optimization level may cause
+ * another to read garbage data or even crash.
+ *
+ * See https://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html for details.
+ *
+ * Prefer these methods in priority order (0 > 3 > 1 > 2)
+ */
+# define XXH_FORCE_MEMORY_ACCESS 0
+
+/*!
+ * @def XXH_SIZE_OPT
+ * @brief Controls how much xxHash optimizes for size.
+ *
+ * xxHash, when compiled, tends to result in a rather large binary size. This
+ * is mostly due to heavy usage to forced inlining and constant folding of the
+ * @ref XXH3_family to increase performance.
+ *
+ * However, some developers prefer size over speed. This option can
+ * significantly reduce the size of the generated code. When using the `-Os`
+ * or `-Oz` options on GCC or Clang, this is defined to 1 by default,
+ * otherwise it is defined to 0.
+ *
+ * Most of these size optimizations can be controlled manually.
+ *
+ * This is a number from 0-2.
+ * - `XXH_SIZE_OPT` == 0: Default. xxHash makes no size optimizations. Speed
+ * comes first.
+ * - `XXH_SIZE_OPT` == 1: Default for `-Os` and `-Oz`. xxHash is more
+ * conservative and disables hacks that increase code size. It implies the
+ * options @ref XXH_NO_INLINE_HINTS == 1, @ref XXH_FORCE_ALIGN_CHECK == 0,
+ * and @ref XXH3_NEON_LANES == 8 if they are not already defined.
+ * - `XXH_SIZE_OPT` == 2: xxHash tries to make itself as small as possible.
+ * Performance may cry. For example, the single shot functions just use the
+ * streaming API.
+ */
+# define XXH_SIZE_OPT 0
+
+/*!
+ * @def XXH_FORCE_ALIGN_CHECK
+ * @brief If defined to non-zero, adds a special path for aligned inputs (XXH32()
+ * and XXH64() only).
+ *
+ * This is an important performance trick for architectures without decent
+ * unaligned memory access performance.
+ *
+ * It checks for input alignment, and when conditions are met, uses a "fast
+ * path" employing direct 32-bit/64-bit reads, resulting in _dramatically
+ * faster_ read speed.
+ *
+ * The check costs one initial branch per hash, which is generally negligible,
+ * but not zero.
+ *
+ * Moreover, it's not useful to generate an additional code path if memory
+ * access uses the same instruction for both aligned and unaligned
+ * addresses (e.g. x86 and aarch64).
+ *
+ * In these cases, the alignment check can be removed by setting this macro to 0.
+ * Then the code will always use unaligned memory access.
+ * Align check is automatically disabled on x86, x64, ARM64, and some ARM chips
+ * which are platforms known to offer good unaligned memory accesses performance.
+ *
+ * It is also disabled by default when @ref XXH_SIZE_OPT >= 1.
+ *
+ * This option does not affect XXH3 (only XXH32 and XXH64).
+ */
+# define XXH_FORCE_ALIGN_CHECK 0
+
+/*!
+ * @def XXH_NO_INLINE_HINTS
+ * @brief When non-zero, sets all functions to `static`.
+ *
+ * By default, xxHash tries to force the compiler to inline almost all internal
+ * functions.
+ *
+ * This can usually improve performance due to reduced jumping and improved
+ * constant folding, but significantly increases the size of the binary which
+ * might not be favorable.
+ *
+ * Additionally, sometimes the forced inlining can be detrimental to performance,
+ * depending on the architecture.
+ *
+ * XXH_NO_INLINE_HINTS marks all internal functions as static, giving the
+ * compiler full control on whether to inline or not.
+ *
+ * When not optimizing (-O0), using `-fno-inline` with GCC or Clang, or if
+ * @ref XXH_SIZE_OPT >= 1, this will automatically be defined.
+ */
+# define XXH_NO_INLINE_HINTS 0
+
+/*!
+ * @def XXH3_INLINE_SECRET
+ * @brief Determines whether to inline the XXH3 withSecret code.
+ *
+ * When the secret size is known, the compiler can improve the performance
+ * of XXH3_64bits_withSecret() and XXH3_128bits_withSecret().
+ *
+ * However, if the secret size is not known, it doesn't have any benefit. This
+ * happens when xxHash is compiled into a global symbol. Therefore, if
+ * @ref XXH_INLINE_ALL is *not* defined, this will be defined to 0.
+ *
+ * Additionally, this defaults to 0 on GCC 12+, which has an issue with function pointers
+ * that are *sometimes* force inline on -Og, and it is impossible to automatically
+ * detect this optimization level.
+ */
+# define XXH3_INLINE_SECRET 0
+
+/*!
+ * @def XXH32_ENDJMP
+ * @brief Whether to use a jump for `XXH32_finalize`.
+ *
+ * For performance, `XXH32_finalize` uses multiple branches in the finalizer.
+ * This is generally preferable for performance,
+ * but depending on exact architecture, a jmp may be preferable.
+ *
+ * This setting is only possibly making a difference for very small inputs.
+ */
+# define XXH32_ENDJMP 0
+
+/*!
+ * @internal
+ * @brief Redefines old internal names.
+ *
+ * For compatibility with code that uses xxHash's internals before the names
+ * were changed to improve namespacing. There is no other reason to use this.
+ */
+# define XXH_OLD_NAMES
+# undef XXH_OLD_NAMES /* don't actually use, it is ugly. */
+
+/*!
+ * @def XXH_NO_STREAM
+ * @brief Disables the streaming API.
+ *
+ * When xxHash is not inlined and the streaming functions are not used, disabling
+ * the streaming functions can improve code size significantly, especially with
+ * the @ref XXH3_family which tends to make constant folded copies of itself.
+ */
+# define XXH_NO_STREAM
+# undef XXH_NO_STREAM /* don't actually */
+#endif /* XXH_DOXYGEN */
+/*!
+ * @}
+ */
+
+#ifndef XXH_FORCE_MEMORY_ACCESS /* can be defined externally, on command line for example */
+ /* prefer __packed__ structures (method 1) for GCC
+ * < ARMv7 with unaligned access (e.g. Raspbian armhf) still uses byte shifting, so we use memcpy
+ * which for some reason does unaligned loads. */
+# if defined(__GNUC__) && !(defined(__ARM_ARCH) && __ARM_ARCH < 7 && defined(__ARM_FEATURE_UNALIGNED))
+# define XXH_FORCE_MEMORY_ACCESS 1
+# endif
+#endif
+
+#ifndef XXH_SIZE_OPT
+ /* default to 1 for -Os or -Oz */
+# if (defined(__GNUC__) || defined(__clang__)) && defined(__OPTIMIZE_SIZE__)
+# define XXH_SIZE_OPT 1
+# else
+# define XXH_SIZE_OPT 0
+# endif
+#endif
+
+#ifndef XXH_FORCE_ALIGN_CHECK /* can be defined externally */
+ /* don't check on sizeopt, x86, aarch64, or arm when unaligned access is available */
+# if XXH_SIZE_OPT >= 1 || \
+ defined(__i386) || defined(__x86_64__) || defined(__aarch64__) || defined(__ARM_FEATURE_UNALIGNED) \
+ || defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64) || defined(_M_ARM) /* visual */
+# define XXH_FORCE_ALIGN_CHECK 0
+# else
+# define XXH_FORCE_ALIGN_CHECK 1
+# endif
+#endif
+
+#ifndef XXH_NO_INLINE_HINTS
+# if XXH_SIZE_OPT >= 1 || defined(__NO_INLINE__) /* -O0, -fno-inline */
+# define XXH_NO_INLINE_HINTS 1
+# else
+# define XXH_NO_INLINE_HINTS 0
+# endif
+#endif
+
+#ifndef XXH3_INLINE_SECRET
+# if (defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 12) \
+ || !defined(XXH_INLINE_ALL)
+# define XXH3_INLINE_SECRET 0
+# else
+# define XXH3_INLINE_SECRET 1
+# endif
+#endif
+
+#ifndef XXH32_ENDJMP
+/* generally preferable for performance */
+# define XXH32_ENDJMP 0
+#endif
+
+/*!
+ * @defgroup impl Implementation
+ * @{
+ */
+
+
+/* *************************************
+* Includes & Memory related functions
+***************************************/
+#if defined(XXH_NO_STREAM)
+/* nothing */
+#elif defined(XXH_NO_STDLIB)
+
+/* When requesting to disable any mention of stdlib,
+ * the library loses the ability to invoked malloc / free.
+ * In practice, it means that functions like `XXH*_createState()`
+ * will always fail, and return NULL.
+ * This flag is useful in situations where
+ * xxhash.h is integrated into some kernel, embedded or limited environment
+ * without access to dynamic allocation.
+ */
+
+static XXH_CONSTF void* XXH_malloc(size_t s) { (void)s; return NULL; }
+static void XXH_free(void* p) { (void)p; }
+
+#else
+
+/*
+ * Modify the local functions below should you wish to use
+ * different memory routines for malloc() and free()
+ */
+#include <stdlib.h>
+
+/*!
+ * @internal
+ * @brief Modify this function to use a different routine than malloc().
+ */
+static XXH_MALLOCF void* XXH_malloc(size_t s) { return malloc(s); }
+
+/*!
+ * @internal
+ * @brief Modify this function to use a different routine than free().
+ */
+static void XXH_free(void* p) { free(p); }
+
+#endif /* XXH_NO_STDLIB */
+
+#include <string.h>
+
+/*!
+ * @internal
+ * @brief Modify this function to use a different routine than memcpy().
+ */
+static void* XXH_memcpy(void* dest, const void* src, size_t size)
+{
+ return memcpy(dest,src,size);
+}
+
+#include <limits.h> /* ULLONG_MAX */
+
+
+/* *************************************
+* Compiler Specific Options
+***************************************/
+#ifdef _MSC_VER /* Visual Studio warning fix */
+# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */
+#endif
+
+#if XXH_NO_INLINE_HINTS /* disable inlining hints */
+# if defined(__GNUC__) || defined(__clang__)
+# define XXH_FORCE_INLINE static __attribute__((unused))
+# else
+# define XXH_FORCE_INLINE static
+# endif
+# define XXH_NO_INLINE static
+/* enable inlining hints */
+#elif defined(__GNUC__) || defined(__clang__)
+# define XXH_FORCE_INLINE static __inline__ __attribute__((always_inline, unused))
+# define XXH_NO_INLINE static __attribute__((noinline))
+#elif defined(_MSC_VER) /* Visual Studio */
+# define XXH_FORCE_INLINE static __forceinline
+# define XXH_NO_INLINE static __declspec(noinline)
+#elif defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) /* C99 */
+# define XXH_FORCE_INLINE static inline
+# define XXH_NO_INLINE static
+#else
+# define XXH_FORCE_INLINE static
+# define XXH_NO_INLINE static
+#endif
+
+#if XXH3_INLINE_SECRET
+# define XXH3_WITH_SECRET_INLINE XXH_FORCE_INLINE
+#else
+# define XXH3_WITH_SECRET_INLINE XXH_NO_INLINE
+#endif
+
+
+/* *************************************
+* Debug
+***************************************/
+/*!
+ * @ingroup tuning
+ * @def XXH_DEBUGLEVEL
+ * @brief Sets the debugging level.
+ *
+ * XXH_DEBUGLEVEL is expected to be defined externally, typically via the
+ * compiler's command line options. The value must be a number.
+ */
+#ifndef XXH_DEBUGLEVEL
+# ifdef DEBUGLEVEL /* backwards compat */
+# define XXH_DEBUGLEVEL DEBUGLEVEL
+# else
+# define XXH_DEBUGLEVEL 0
+# endif
+#endif
+
+#if (XXH_DEBUGLEVEL>=1)
+# include <assert.h> /* note: can still be disabled with NDEBUG */
+# define XXH_ASSERT(c) assert(c)
+#else
+# if defined(__INTEL_COMPILER)
+# define XXH_ASSERT(c) XXH_ASSUME((unsigned char) (c))
+# else
+# define XXH_ASSERT(c) XXH_ASSUME(c)
+# endif
+#endif
+
+/* note: use after variable declarations */
+#ifndef XXH_STATIC_ASSERT
+# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) /* C11 */
+# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { _Static_assert((c),m); } while(0)
+# elif defined(__cplusplus) && (__cplusplus >= 201103L) /* C++11 */
+# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { static_assert((c),m); } while(0)
+# else
+# define XXH_STATIC_ASSERT_WITH_MESSAGE(c,m) do { struct xxh_sa { char x[(c) ? 1 : -1]; }; } while(0)
+# endif
+# define XXH_STATIC_ASSERT(c) XXH_STATIC_ASSERT_WITH_MESSAGE((c),#c)
+#endif
+
+/*!
+ * @internal
+ * @def XXH_COMPILER_GUARD(var)
+ * @brief Used to prevent unwanted optimizations for @p var.
+ *
+ * It uses an empty GCC inline assembly statement with a register constraint
+ * which forces @p var into a general purpose register (eg eax, ebx, ecx
+ * on x86) and marks it as modified.
+ *
+ * This is used in a few places to avoid unwanted autovectorization (e.g.
+ * XXH32_round()). All vectorization we want is explicit via intrinsics,
+ * and _usually_ isn't wanted elsewhere.
+ *
+ * We also use it to prevent unwanted constant folding for AArch64 in
+ * XXH3_initCustomSecret_scalar().
+ */
+#if defined(__GNUC__) || defined(__clang__)
+# define XXH_COMPILER_GUARD(var) __asm__("" : "+r" (var))
+#else
+# define XXH_COMPILER_GUARD(var) ((void)0)
+#endif
+
+/* Specifically for NEON vectors which use the "w" constraint, on
+ * Clang. */
+#if defined(__clang__) && defined(__ARM_ARCH) && !defined(__wasm__)
+# define XXH_COMPILER_GUARD_CLANG_NEON(var) __asm__("" : "+w" (var))
+#else
+# define XXH_COMPILER_GUARD_CLANG_NEON(var) ((void)0)
+#endif
+
+/* *************************************
+* Basic Types
+***************************************/
+#if !defined (__VMS) \
+ && (defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+# ifdef _AIX
+# include <inttypes.h>
+# else
+# include <stdint.h>
+# endif
+ typedef uint8_t xxh_u8;
+#else
+ typedef unsigned char xxh_u8;
+#endif
+typedef XXH32_hash_t xxh_u32;
+
+#ifdef XXH_OLD_NAMES
+# warning "XXH_OLD_NAMES is planned to be removed starting v0.9. If the program depends on it, consider moving away from it by employing newer type names directly"
+# define BYTE xxh_u8
+# define U8 xxh_u8
+# define U32 xxh_u32
+#endif
+
+/* *** Memory access *** */
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_read32(const void* ptr)
+ * @brief Reads an unaligned 32-bit integer from @p ptr in native endianness.
+ *
+ * Affected by @ref XXH_FORCE_MEMORY_ACCESS.
+ *
+ * @param ptr The pointer to read from.
+ * @return The 32-bit native endian integer from the bytes at @p ptr.
+ */
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_readLE32(const void* ptr)
+ * @brief Reads an unaligned 32-bit little endian integer from @p ptr.
+ *
+ * Affected by @ref XXH_FORCE_MEMORY_ACCESS.
+ *
+ * @param ptr The pointer to read from.
+ * @return The 32-bit little endian integer from the bytes at @p ptr.
+ */
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_readBE32(const void* ptr)
+ * @brief Reads an unaligned 32-bit big endian integer from @p ptr.
+ *
+ * Affected by @ref XXH_FORCE_MEMORY_ACCESS.
+ *
+ * @param ptr The pointer to read from.
+ * @return The 32-bit big endian integer from the bytes at @p ptr.
+ */
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_readLE32_align(const void* ptr, XXH_alignment align)
+ * @brief Like @ref XXH_readLE32(), but has an option for aligned reads.
+ *
+ * Affected by @ref XXH_FORCE_MEMORY_ACCESS.
+ * Note that when @ref XXH_FORCE_ALIGN_CHECK == 0, the @p align parameter is
+ * always @ref XXH_alignment::XXH_unaligned.
+ *
+ * @param ptr The pointer to read from.
+ * @param align Whether @p ptr is aligned.
+ * @pre
+ * If @p align == @ref XXH_alignment::XXH_aligned, @p ptr must be 4 byte
+ * aligned.
+ * @return The 32-bit little endian integer from the bytes at @p ptr.
+ */
+
+#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3))
+/*
+ * Manual byteshift. Best for old compilers which don't inline memcpy.
+ * We actually directly use XXH_readLE32 and XXH_readBE32.
+ */
+#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2))
+
+/*
+ * Force direct memory access. Only works on CPU which support unaligned memory
+ * access in hardware.
+ */
+static xxh_u32 XXH_read32(const void* memPtr) { return *(const xxh_u32*) memPtr; }
+
+#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))
+
+/*
+ * __attribute__((aligned(1))) is supported by gcc and clang. Originally the
+ * documentation claimed that it only increased the alignment, but actually it
+ * can decrease it on gcc, clang, and icc:
+ * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69502,
+ * https://gcc.godbolt.org/z/xYez1j67Y.
+ */
+#ifdef XXH_OLD_NAMES
+typedef union { xxh_u32 u32; } __attribute__((packed)) unalign;
+#endif
+static xxh_u32 XXH_read32(const void* ptr)
+{
+ typedef __attribute__((aligned(1))) xxh_u32 xxh_unalign32;
+ return *((const xxh_unalign32*)ptr);
+}
+
+#else
+
+/*
+ * Portable and safe solution. Generally efficient.
+ * see: https://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html
+ */
+static xxh_u32 XXH_read32(const void* memPtr)
+{
+ xxh_u32 val;
+ XXH_memcpy(&val, memPtr, sizeof(val));
+ return val;
+}
+
+#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */
+
+
+/* *** Endianness *** */
+
+/*!
+ * @ingroup tuning
+ * @def XXH_CPU_LITTLE_ENDIAN
+ * @brief Whether the target is little endian.
+ *
+ * Defined to 1 if the target is little endian, or 0 if it is big endian.
+ * It can be defined externally, for example on the compiler command line.
+ *
+ * If it is not defined,
+ * a runtime check (which is usually constant folded) is used instead.
+ *
+ * @note
+ * This is not necessarily defined to an integer constant.
+ *
+ * @see XXH_isLittleEndian() for the runtime check.
+ */
+#ifndef XXH_CPU_LITTLE_ENDIAN
+/*
+ * Try to detect endianness automatically, to avoid the nonstandard behavior
+ * in `XXH_isLittleEndian()`
+ */
+# if defined(_WIN32) /* Windows is always little endian */ \
+ || defined(__LITTLE_ENDIAN__) \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+# define XXH_CPU_LITTLE_ENDIAN 1
+# elif defined(__BIG_ENDIAN__) \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+# define XXH_CPU_LITTLE_ENDIAN 0
+# else
+/*!
+ * @internal
+ * @brief Runtime check for @ref XXH_CPU_LITTLE_ENDIAN.
+ *
+ * Most compilers will constant fold this.
+ */
+static int XXH_isLittleEndian(void)
+{
+ /*
+ * Portable and well-defined behavior.
+ * Don't use static: it is detrimental to performance.
+ */
+ const union { xxh_u32 u; xxh_u8 c[4]; } one = { 1 };
+ return one.c[0];
+}
+# define XXH_CPU_LITTLE_ENDIAN XXH_isLittleEndian()
+# endif
+#endif
+
+
+
+
+/* ****************************************
+* Compiler-specific Functions and Macros
+******************************************/
+#define XXH_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
+
+#ifdef __has_builtin
+# define XXH_HAS_BUILTIN(x) __has_builtin(x)
+#else
+# define XXH_HAS_BUILTIN(x) 0
+#endif
+
+
+
+/*
+ * C23 and future versions have standard "unreachable()".
+ * Once it has been implemented reliably we can add it as an
+ * additional case:
+ *
+ * ```
+ * #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= XXH_C23_VN)
+ * # include <stddef.h>
+ * # ifdef unreachable
+ * # define XXH_UNREACHABLE() unreachable()
+ * # endif
+ * #endif
+ * ```
+ *
+ * Note C++23 also has std::unreachable() which can be detected
+ * as follows:
+ * ```
+ * #if defined(__cpp_lib_unreachable) && (__cpp_lib_unreachable >= 202202L)
+ * # include <utility>
+ * # define XXH_UNREACHABLE() std::unreachable()
+ * #endif
+ * ```
+ * NB: `__cpp_lib_unreachable` is defined in the `<version>` header.
+ * We don't use that as including `<utility>` in `extern "C"` blocks
+ * doesn't work on GCC12
+ */
+
+#if XXH_HAS_BUILTIN(__builtin_unreachable)
+# define XXH_UNREACHABLE() __builtin_unreachable()
+
+#elif defined(_MSC_VER)
+# define XXH_UNREACHABLE() __assume(0)
+
+#else
+# define XXH_UNREACHABLE()
+#endif
+
+#if XXH_HAS_BUILTIN(__builtin_assume)
+# define XXH_ASSUME(c) __builtin_assume(c)
+#else
+# define XXH_ASSUME(c) if (!(c)) { XXH_UNREACHABLE(); }
+#endif
+
+/*!
+ * @internal
+ * @def XXH_rotl32(x,r)
+ * @brief 32-bit rotate left.
+ *
+ * @param x The 32-bit integer to be rotated.
+ * @param r The number of bits to rotate.
+ * @pre
+ * @p r > 0 && @p r < 32
+ * @note
+ * @p x and @p r may be evaluated multiple times.
+ * @return The rotated result.
+ */
+#if !defined(NO_CLANG_BUILTIN) && XXH_HAS_BUILTIN(__builtin_rotateleft32) \
+ && XXH_HAS_BUILTIN(__builtin_rotateleft64)
+# define XXH_rotl32 __builtin_rotateleft32
+# define XXH_rotl64 __builtin_rotateleft64
+/* Note: although _rotl exists for minGW (GCC under windows), performance seems poor */
+#elif defined(_MSC_VER)
+# define XXH_rotl32(x,r) _rotl(x,r)
+# define XXH_rotl64(x,r) _rotl64(x,r)
+#else
+# define XXH_rotl32(x,r) (((x) << (r)) | ((x) >> (32 - (r))))
+# define XXH_rotl64(x,r) (((x) << (r)) | ((x) >> (64 - (r))))
+#endif
+
+/*!
+ * @internal
+ * @fn xxh_u32 XXH_swap32(xxh_u32 x)
+ * @brief A 32-bit byteswap.
+ *
+ * @param x The 32-bit integer to byteswap.
+ * @return @p x, byteswapped.
+ */
+#if defined(_MSC_VER) /* Visual Studio */
+# define XXH_swap32 _byteswap_ulong
+#elif XXH_GCC_VERSION >= 403
+# define XXH_swap32 __builtin_bswap32
+#else
+static xxh_u32 XXH_swap32 (xxh_u32 x)
+{
+ return ((x << 24) & 0xff000000 ) |
+ ((x << 8) & 0x00ff0000 ) |
+ ((x >> 8) & 0x0000ff00 ) |
+ ((x >> 24) & 0x000000ff );
+}
+#endif
+
+
+/* ***************************
+* Memory reads
+*****************************/
+
+/*!
+ * @internal
+ * @brief Enum to indicate whether a pointer is aligned.
+ */
+typedef enum {
+ XXH_aligned, /*!< Aligned */
+ XXH_unaligned /*!< Possibly unaligned */
+} XXH_alignment;
+
+/*
+ * XXH_FORCE_MEMORY_ACCESS==3 is an endian-independent byteshift load.
+ *
+ * This is ideal for older compilers which don't inline memcpy.
+ */
+#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3))
+
+XXH_FORCE_INLINE xxh_u32 XXH_readLE32(const void* memPtr)
+{
+ const xxh_u8* bytePtr = (const xxh_u8 *)memPtr;
+ return bytePtr[0]
+ | ((xxh_u32)bytePtr[1] << 8)
+ | ((xxh_u32)bytePtr[2] << 16)
+ | ((xxh_u32)bytePtr[3] << 24);
+}
+
+XXH_FORCE_INLINE xxh_u32 XXH_readBE32(const void* memPtr)
+{
+ const xxh_u8* bytePtr = (const xxh_u8 *)memPtr;
+ return bytePtr[3]
+ | ((xxh_u32)bytePtr[2] << 8)
+ | ((xxh_u32)bytePtr[1] << 16)
+ | ((xxh_u32)bytePtr[0] << 24);
+}
+
+#else
+XXH_FORCE_INLINE xxh_u32 XXH_readLE32(const void* ptr)
+{
+ return XXH_CPU_LITTLE_ENDIAN ? XXH_read32(ptr) : XXH_swap32(XXH_read32(ptr));
+}
+
+static xxh_u32 XXH_readBE32(const void* ptr)
+{
+ return XXH_CPU_LITTLE_ENDIAN ? XXH_swap32(XXH_read32(ptr)) : XXH_read32(ptr);
+}
+#endif
+
+XXH_FORCE_INLINE xxh_u32
+XXH_readLE32_align(const void* ptr, XXH_alignment align)
+{
+ if (align==XXH_unaligned) {
+ return XXH_readLE32(ptr);
+ } else {
+ return XXH_CPU_LITTLE_ENDIAN ? *(const xxh_u32*)ptr : XXH_swap32(*(const xxh_u32*)ptr);
+ }
+}
+
+
+/* *************************************
+* Misc
+***************************************/
+/*! @ingroup public */
+XXH_PUBLIC_API unsigned XXH_versionNumber (void) { return XXH_VERSION_NUMBER; }
+
+
+/* *******************************************************************
+* 32-bit hash functions
+*********************************************************************/
+/*!
+ * @}
+ * @defgroup XXH32_impl XXH32 implementation
+ * @ingroup impl
+ *
+ * Details on the XXH32 implementation.
+ * @{
+ */
+ /* #define instead of static const, to be used as initializers */
+#define XXH_PRIME32_1 0x9E3779B1U /*!< 0b10011110001101110111100110110001 */
+#define XXH_PRIME32_2 0x85EBCA77U /*!< 0b10000101111010111100101001110111 */
+#define XXH_PRIME32_3 0xC2B2AE3DU /*!< 0b11000010101100101010111000111101 */
+#define XXH_PRIME32_4 0x27D4EB2FU /*!< 0b00100111110101001110101100101111 */
+#define XXH_PRIME32_5 0x165667B1U /*!< 0b00010110010101100110011110110001 */
+
+#ifdef XXH_OLD_NAMES
+# define PRIME32_1 XXH_PRIME32_1
+# define PRIME32_2 XXH_PRIME32_2
+# define PRIME32_3 XXH_PRIME32_3
+# define PRIME32_4 XXH_PRIME32_4
+# define PRIME32_5 XXH_PRIME32_5
+#endif
+
+/*!
+ * @internal
+ * @brief Normal stripe processing routine.
+ *
+ * This shuffles the bits so that any bit from @p input impacts several bits in
+ * @p acc.
+ *
+ * @param acc The accumulator lane.
+ * @param input The stripe of input to mix.
+ * @return The mixed accumulator lane.
+ */
+static xxh_u32 XXH32_round(xxh_u32 acc, xxh_u32 input)
+{
+ acc += input * XXH_PRIME32_2;
+ acc = XXH_rotl32(acc, 13);
+ acc *= XXH_PRIME32_1;
+#if (defined(__SSE4_1__) || defined(__aarch64__) || defined(__wasm_simd128__)) && !defined(XXH_ENABLE_AUTOVECTORIZE)
+ /*
+ * UGLY HACK:
+ * A compiler fence is the only thing that prevents GCC and Clang from
+ * autovectorizing the XXH32 loop (pragmas and attributes don't work for some
+ * reason) without globally disabling SSE4.1.
+ *
+ * The reason we want to avoid vectorization is because despite working on
+ * 4 integers at a time, there are multiple factors slowing XXH32 down on
+ * SSE4:
+ * - There's a ridiculous amount of lag from pmulld (10 cycles of latency on
+ * newer chips!) making it slightly slower to multiply four integers at
+ * once compared to four integers independently. Even when pmulld was
+ * fastest, Sandy/Ivy Bridge, it is still not worth it to go into SSE
+ * just to multiply unless doing a long operation.
+ *
+ * - Four instructions are required to rotate,
+ * movqda tmp, v // not required with VEX encoding
+ * pslld tmp, 13 // tmp <<= 13
+ * psrld v, 19 // x >>= 19
+ * por v, tmp // x |= tmp
+ * compared to one for scalar:
+ * roll v, 13 // reliably fast across the board
+ * shldl v, v, 13 // Sandy Bridge and later prefer this for some reason
+ *
+ * - Instruction level parallelism is actually more beneficial here because
+ * the SIMD actually serializes this operation: While v1 is rotating, v2
+ * can load data, while v3 can multiply. SSE forces them to operate
+ * together.
+ *
+ * This is also enabled on AArch64, as Clang is *very aggressive* in vectorizing
+ * the loop. NEON is only faster on the A53, and with the newer cores, it is less
+ * than half the speed.
+ *
+ * Additionally, this is used on WASM SIMD128 because it JITs to the same
+ * SIMD instructions and has the same issue.
+ */
+ XXH_COMPILER_GUARD(acc);
+#endif
+ return acc;
+}
+
+/*!
+ * @internal
+ * @brief Mixes all bits to finalize the hash.
+ *
+ * The final mix ensures that all input bits have a chance to impact any bit in
+ * the output digest, resulting in an unbiased distribution.
+ *
+ * @param hash The hash to avalanche.
+ * @return The avalanched hash.
+ */
+static xxh_u32 XXH32_avalanche(xxh_u32 hash)
+{
+ hash ^= hash >> 15;
+ hash *= XXH_PRIME32_2;
+ hash ^= hash >> 13;
+ hash *= XXH_PRIME32_3;
+ hash ^= hash >> 16;
+ return hash;
+}
+
+#define XXH_get32bits(p) XXH_readLE32_align(p, align)
+
+/*!
+ * @internal
+ * @brief Processes the last 0-15 bytes of @p ptr.
+ *
+ * There may be up to 15 bytes remaining to consume from the input.
+ * This final stage will digest them to ensure that all input bytes are present
+ * in the final mix.
+ *
+ * @param hash The hash to finalize.
+ * @param ptr The pointer to the remaining input.
+ * @param len The remaining length, modulo 16.
+ * @param align Whether @p ptr is aligned.
+ * @return The finalized hash.
+ * @see XXH64_finalize().
+ */
+static XXH_PUREF xxh_u32
+XXH32_finalize(xxh_u32 hash, const xxh_u8* ptr, size_t len, XXH_alignment align)
+{
+#define XXH_PROCESS1 do { \
+ hash += (*ptr++) * XXH_PRIME32_5; \
+ hash = XXH_rotl32(hash, 11) * XXH_PRIME32_1; \
+} while (0)
+
+#define XXH_PROCESS4 do { \
+ hash += XXH_get32bits(ptr) * XXH_PRIME32_3; \
+ ptr += 4; \
+ hash = XXH_rotl32(hash, 17) * XXH_PRIME32_4; \
+} while (0)
+
+ if (ptr==NULL) XXH_ASSERT(len == 0);
+
+ /* Compact rerolled version; generally faster */
+ if (!XXH32_ENDJMP) {
+ len &= 15;
+ while (len >= 4) {
+ XXH_PROCESS4;
+ len -= 4;
+ }
+ while (len > 0) {
+ XXH_PROCESS1;
+ --len;
+ }
+ return XXH32_avalanche(hash);
+ } else {
+ switch(len&15) /* or switch(bEnd - p) */ {
+ case 12: XXH_PROCESS4;
+ XXH_FALLTHROUGH; /* fallthrough */
+ case 8: XXH_PROCESS4;
+ XXH_FALLTHROUGH; /* fallthrough */
+ case 4: XXH_PROCESS4;
+ return XXH32_avalanche(hash);
+
+ case 13: XXH_PROCESS4;
+ XXH_FALLTHROUGH; /* fallthrough */
+ case 9: XXH_PROCESS4;
+ XXH_FALLTHROUGH; /* fallthrough */
+ case 5: XXH_PROCESS4;
+ XXH_PROCESS1;
+ return XXH32_avalanche(hash);
+
+ case 14: XXH_PROCESS4;
+ XXH_FALLTHROUGH; /* fallthrough */
+ case 10: XXH_PROCESS4;
+ XXH_FALLTHROUGH; /* fallthrough */
+ case 6: XXH_PROCESS4;
+ XXH_PROCESS1;
+ XXH_PROCESS1;
+ return XXH32_avalanche(hash);
+
+ case 15: XXH_PROCESS4;
+ XXH_FALLTHROUGH; /* fallthrough */
+ case 11: XXH_PROCESS4;
+ XXH_FALLTHROUGH; /* fallthrough */
+ case 7: XXH_PROCESS4;
+ XXH_FALLTHROUGH; /* fallthrough */
+ case 3: XXH_PROCESS1;
+ XXH_FALLTHROUGH; /* fallthrough */
+ case 2: XXH_PROCESS1;
+ XXH_FALLTHROUGH; /* fallthrough */
+ case 1: XXH_PROCESS1;
+ XXH_FALLTHROUGH; /* fallthrough */
+ case 0: return XXH32_avalanche(hash);
+ }
+ XXH_ASSERT(0);
+ return hash; /* reaching this point is deemed impossible */
+ }
+}
+
+#ifdef XXH_OLD_NAMES
+# define PROCESS1 XXH_PROCESS1
+# define PROCESS4 XXH_PROCESS4
+#else
+# undef XXH_PROCESS1
+# undef XXH_PROCESS4
+#endif
+
+/*!
+ * @internal
+ * @brief The implementation for @ref XXH32().
+ *
+ * @param input , len , seed Directly passed from @ref XXH32().
+ * @param align Whether @p input is aligned.
+ * @return The calculated hash.
+ */
+XXH_FORCE_INLINE XXH_PUREF xxh_u32
+XXH32_endian_align(const xxh_u8* input, size_t len, xxh_u32 seed, XXH_alignment align)
+{
+ xxh_u32 h32;
+
+ if (input==NULL) XXH_ASSERT(len == 0);
+
+ if (len>=16) {
+ const xxh_u8* const bEnd = input + len;
+ const xxh_u8* const limit = bEnd - 15;
+ xxh_u32 v1 = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
+ xxh_u32 v2 = seed + XXH_PRIME32_2;
+ xxh_u32 v3 = seed + 0;
+ xxh_u32 v4 = seed - XXH_PRIME32_1;
+
+ do {
+ v1 = XXH32_round(v1, XXH_get32bits(input)); input += 4;
+ v2 = XXH32_round(v2, XXH_get32bits(input)); input += 4;
+ v3 = XXH32_round(v3, XXH_get32bits(input)); input += 4;
+ v4 = XXH32_round(v4, XXH_get32bits(input)); input += 4;
+ } while (input < limit);
+
+ h32 = XXH_rotl32(v1, 1) + XXH_rotl32(v2, 7)
+ + XXH_rotl32(v3, 12) + XXH_rotl32(v4, 18);
+ } else {
+ h32 = seed + XXH_PRIME32_5;
+ }
+
+ h32 += (xxh_u32)len;
+
+ return XXH32_finalize(h32, input, len&15, align);
+}
+
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH32_hash_t XXH32 (const void* input, size_t len, XXH32_hash_t seed)
+{
+#if !defined(XXH_NO_STREAM) && XXH_SIZE_OPT >= 2
+ /* Simple version, good for code maintenance, but unfortunately slow for small inputs */
+ XXH32_state_t state;
+ XXH32_reset(&state, seed);
+ XXH32_update(&state, (const xxh_u8*)input, len);
+ return XXH32_digest(&state);
+#else
+ if (XXH_FORCE_ALIGN_CHECK) {
+ if ((((size_t)input) & 3) == 0) { /* Input is 4-bytes aligned, leverage the speed benefit */
+ return XXH32_endian_align((const xxh_u8*)input, len, seed, XXH_aligned);
+ } }
+
+ return XXH32_endian_align((const xxh_u8*)input, len, seed, XXH_unaligned);
+#endif
+}
+
+
+
+/******* Hash streaming *******/
+#ifndef XXH_NO_STREAM
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH32_state_t* XXH32_createState(void)
+{
+ return (XXH32_state_t*)XXH_malloc(sizeof(XXH32_state_t));
+}
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH_errorcode XXH32_freeState(XXH32_state_t* statePtr)
+{
+ XXH_free(statePtr);
+ return XXH_OK;
+}
+
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API void XXH32_copyState(XXH32_state_t* dstState, const XXH32_state_t* srcState)
+{
+ XXH_memcpy(dstState, srcState, sizeof(*dstState));
+}
+
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH_errorcode XXH32_reset(XXH32_state_t* statePtr, XXH32_hash_t seed)
+{
+ XXH_ASSERT(statePtr != NULL);
+ memset(statePtr, 0, sizeof(*statePtr));
+ statePtr->v[0] = seed + XXH_PRIME32_1 + XXH_PRIME32_2;
+ statePtr->v[1] = seed + XXH_PRIME32_2;
+ statePtr->v[2] = seed + 0;
+ statePtr->v[3] = seed - XXH_PRIME32_1;
+ return XXH_OK;
+}
+
+
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH32_update(XXH32_state_t* state, const void* input, size_t len)
+{
+ if (input==NULL) {
+ XXH_ASSERT(len == 0);
+ return XXH_OK;
+ }
+
+ { const xxh_u8* p = (const xxh_u8*)input;
+ const xxh_u8* const bEnd = p + len;
+
+ state->total_len_32 += (XXH32_hash_t)len;
+ state->large_len |= (XXH32_hash_t)((len>=16) | (state->total_len_32>=16));
+
+ if (state->memsize + len < 16) { /* fill in tmp buffer */
+ XXH_memcpy((xxh_u8*)(state->mem32) + state->memsize, input, len);
+ state->memsize += (XXH32_hash_t)len;
+ return XXH_OK;
+ }
+
+ if (state->memsize) { /* some data left from previous update */
+ XXH_memcpy((xxh_u8*)(state->mem32) + state->memsize, input, 16-state->memsize);
+ { const xxh_u32* p32 = state->mem32;
+ state->v[0] = XXH32_round(state->v[0], XXH_readLE32(p32)); p32++;
+ state->v[1] = XXH32_round(state->v[1], XXH_readLE32(p32)); p32++;
+ state->v[2] = XXH32_round(state->v[2], XXH_readLE32(p32)); p32++;
+ state->v[3] = XXH32_round(state->v[3], XXH_readLE32(p32));
+ }
+ p += 16-state->memsize;
+ state->memsize = 0;
+ }
+
+ if (p <= bEnd-16) {
+ const xxh_u8* const limit = bEnd - 16;
+
+ do {
+ state->v[0] = XXH32_round(state->v[0], XXH_readLE32(p)); p+=4;
+ state->v[1] = XXH32_round(state->v[1], XXH_readLE32(p)); p+=4;
+ state->v[2] = XXH32_round(state->v[2], XXH_readLE32(p)); p+=4;
+ state->v[3] = XXH32_round(state->v[3], XXH_readLE32(p)); p+=4;
+ } while (p<=limit);
+
+ }
+
+ if (p < bEnd) {
+ XXH_memcpy(state->mem32, p, (size_t)(bEnd-p));
+ state->memsize = (unsigned)(bEnd-p);
+ }
+ }
+
+ return XXH_OK;
+}
+
+
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH32_hash_t XXH32_digest(const XXH32_state_t* state)
+{
+ xxh_u32 h32;
+
+ if (state->large_len) {
+ h32 = XXH_rotl32(state->v[0], 1)
+ + XXH_rotl32(state->v[1], 7)
+ + XXH_rotl32(state->v[2], 12)
+ + XXH_rotl32(state->v[3], 18);
+ } else {
+ h32 = state->v[2] /* == seed */ + XXH_PRIME32_5;
+ }
+
+ h32 += state->total_len_32;
+
+ return XXH32_finalize(h32, (const xxh_u8*)state->mem32, state->memsize, XXH_aligned);
+}
+#endif /* !XXH_NO_STREAM */
+
+/******* Canonical representation *******/
+
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API void XXH32_canonicalFromHash(XXH32_canonical_t* dst, XXH32_hash_t hash)
+{
+ XXH_STATIC_ASSERT(sizeof(XXH32_canonical_t) == sizeof(XXH32_hash_t));
+ if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap32(hash);
+ XXH_memcpy(dst, &hash, sizeof(*dst));
+}
+/*! @ingroup XXH32_family */
+XXH_PUBLIC_API XXH32_hash_t XXH32_hashFromCanonical(const XXH32_canonical_t* src)
+{
+ return XXH_readBE32(src);
+}
+
+
+#ifndef XXH_NO_LONG_LONG
+
+/* *******************************************************************
+* 64-bit hash functions
+*********************************************************************/
+/*!
+ * @}
+ * @ingroup impl
+ * @{
+ */
+/******* Memory access *******/
+
+typedef XXH64_hash_t xxh_u64;
+
+#ifdef XXH_OLD_NAMES
+# define U64 xxh_u64
+#endif
+
+#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3))
+/*
+ * Manual byteshift. Best for old compilers which don't inline memcpy.
+ * We actually directly use XXH_readLE64 and XXH_readBE64.
+ */
+#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==2))
+
+/* Force direct memory access. Only works on CPU which support unaligned memory access in hardware */
+static xxh_u64 XXH_read64(const void* memPtr)
+{
+ return *(const xxh_u64*) memPtr;
+}
+
+#elif (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==1))
+
+/*
+ * __attribute__((aligned(1))) is supported by gcc and clang. Originally the
+ * documentation claimed that it only increased the alignment, but actually it
+ * can decrease it on gcc, clang, and icc:
+ * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69502,
+ * https://gcc.godbolt.org/z/xYez1j67Y.
+ */
+#ifdef XXH_OLD_NAMES
+typedef union { xxh_u32 u32; xxh_u64 u64; } __attribute__((packed)) unalign64;
+#endif
+static xxh_u64 XXH_read64(const void* ptr)
+{
+ typedef __attribute__((aligned(1))) xxh_u64 xxh_unalign64;
+ return *((const xxh_unalign64*)ptr);
+}
+
+#else
+
+/*
+ * Portable and safe solution. Generally efficient.
+ * see: https://fastcompression.blogspot.com/2015/08/accessing-unaligned-memory.html
+ */
+static xxh_u64 XXH_read64(const void* memPtr)
+{
+ xxh_u64 val;
+ XXH_memcpy(&val, memPtr, sizeof(val));
+ return val;
+}
+
+#endif /* XXH_FORCE_DIRECT_MEMORY_ACCESS */
+
+#if defined(_MSC_VER) /* Visual Studio */
+# define XXH_swap64 _byteswap_uint64
+#elif XXH_GCC_VERSION >= 403
+# define XXH_swap64 __builtin_bswap64
+#else
+static xxh_u64 XXH_swap64(xxh_u64 x)
+{
+ return ((x << 56) & 0xff00000000000000ULL) |
+ ((x << 40) & 0x00ff000000000000ULL) |
+ ((x << 24) & 0x0000ff0000000000ULL) |
+ ((x << 8) & 0x000000ff00000000ULL) |
+ ((x >> 8) & 0x00000000ff000000ULL) |
+ ((x >> 24) & 0x0000000000ff0000ULL) |
+ ((x >> 40) & 0x000000000000ff00ULL) |
+ ((x >> 56) & 0x00000000000000ffULL);
+}
+#endif
+
+
+/* XXH_FORCE_MEMORY_ACCESS==3 is an endian-independent byteshift load. */
+#if (defined(XXH_FORCE_MEMORY_ACCESS) && (XXH_FORCE_MEMORY_ACCESS==3))
+
+XXH_FORCE_INLINE xxh_u64 XXH_readLE64(const void* memPtr)
+{
+ const xxh_u8* bytePtr = (const xxh_u8 *)memPtr;
+ return bytePtr[0]
+ | ((xxh_u64)bytePtr[1] << 8)
+ | ((xxh_u64)bytePtr[2] << 16)
+ | ((xxh_u64)bytePtr[3] << 24)
+ | ((xxh_u64)bytePtr[4] << 32)
+ | ((xxh_u64)bytePtr[5] << 40)
+ | ((xxh_u64)bytePtr[6] << 48)
+ | ((xxh_u64)bytePtr[7] << 56);
+}
+
+XXH_FORCE_INLINE xxh_u64 XXH_readBE64(const void* memPtr)
+{
+ const xxh_u8* bytePtr = (const xxh_u8 *)memPtr;
+ return bytePtr[7]
+ | ((xxh_u64)bytePtr[6] << 8)
+ | ((xxh_u64)bytePtr[5] << 16)
+ | ((xxh_u64)bytePtr[4] << 24)
+ | ((xxh_u64)bytePtr[3] << 32)
+ | ((xxh_u64)bytePtr[2] << 40)
+ | ((xxh_u64)bytePtr[1] << 48)
+ | ((xxh_u64)bytePtr[0] << 56);
+}
+
+#else
+XXH_FORCE_INLINE xxh_u64 XXH_readLE64(const void* ptr)
+{
+ return XXH_CPU_LITTLE_ENDIAN ? XXH_read64(ptr) : XXH_swap64(XXH_read64(ptr));
+}
+
+static xxh_u64 XXH_readBE64(const void* ptr)
+{
+ return XXH_CPU_LITTLE_ENDIAN ? XXH_swap64(XXH_read64(ptr)) : XXH_read64(ptr);
+}
+#endif
+
+XXH_FORCE_INLINE xxh_u64
+XXH_readLE64_align(const void* ptr, XXH_alignment align)
+{
+ if (align==XXH_unaligned)
+ return XXH_readLE64(ptr);
+ else
+ return XXH_CPU_LITTLE_ENDIAN ? *(const xxh_u64*)ptr : XXH_swap64(*(const xxh_u64*)ptr);
+}
+
+
+/******* xxh64 *******/
+/*!
+ * @}
+ * @defgroup XXH64_impl XXH64 implementation
+ * @ingroup impl
+ *
+ * Details on the XXH64 implementation.
+ * @{
+ */
+/* #define rather that static const, to be used as initializers */
+#define XXH_PRIME64_1 0x9E3779B185EBCA87ULL /*!< 0b1001111000110111011110011011000110000101111010111100101010000111 */
+#define XXH_PRIME64_2 0xC2B2AE3D27D4EB4FULL /*!< 0b1100001010110010101011100011110100100111110101001110101101001111 */
+#define XXH_PRIME64_3 0x165667B19E3779F9ULL /*!< 0b0001011001010110011001111011000110011110001101110111100111111001 */
+#define XXH_PRIME64_4 0x85EBCA77C2B2AE63ULL /*!< 0b1000010111101011110010100111011111000010101100101010111001100011 */
+#define XXH_PRIME64_5 0x27D4EB2F165667C5ULL /*!< 0b0010011111010100111010110010111100010110010101100110011111000101 */
+
+#ifdef XXH_OLD_NAMES
+# define PRIME64_1 XXH_PRIME64_1
+# define PRIME64_2 XXH_PRIME64_2
+# define PRIME64_3 XXH_PRIME64_3
+# define PRIME64_4 XXH_PRIME64_4
+# define PRIME64_5 XXH_PRIME64_5
+#endif
+
+/*! @copydoc XXH32_round */
+static xxh_u64 XXH64_round(xxh_u64 acc, xxh_u64 input)
+{
+ acc += input * XXH_PRIME64_2;
+ acc = XXH_rotl64(acc, 31);
+ acc *= XXH_PRIME64_1;
+#if (defined(__AVX512F__)) && !defined(XXH_ENABLE_AUTOVECTORIZE)
+ /*
+ * DISABLE AUTOVECTORIZATION:
+ * A compiler fence is used to prevent GCC and Clang from
+ * autovectorizing the XXH64 loop (pragmas and attributes don't work for some
+ * reason) without globally disabling AVX512.
+ *
+ * Autovectorization of XXH64 tends to be detrimental,
+ * though the exact outcome may change depending on exact cpu and compiler version.
+ * For information, it has been reported as detrimental for Skylake-X,
+ * but possibly beneficial for Zen4.
+ *
+ * The default is to disable auto-vectorization,
+ * but you can select to enable it instead using `XXH_ENABLE_AUTOVECTORIZE` build variable.
+ */
+ XXH_COMPILER_GUARD(acc);
+#endif
+ return acc;
+}
+
+static xxh_u64 XXH64_mergeRound(xxh_u64 acc, xxh_u64 val)
+{
+ val = XXH64_round(0, val);
+ acc ^= val;
+ acc = acc * XXH_PRIME64_1 + XXH_PRIME64_4;
+ return acc;
+}
+
+/*! @copydoc XXH32_avalanche */
+static xxh_u64 XXH64_avalanche(xxh_u64 hash)
+{
+ hash ^= hash >> 33;
+ hash *= XXH_PRIME64_2;
+ hash ^= hash >> 29;
+ hash *= XXH_PRIME64_3;
+ hash ^= hash >> 32;
+ return hash;
+}
+
+
+#define XXH_get64bits(p) XXH_readLE64_align(p, align)
+
+/*!
+ * @internal
+ * @brief Processes the last 0-31 bytes of @p ptr.
+ *
+ * There may be up to 31 bytes remaining to consume from the input.
+ * This final stage will digest them to ensure that all input bytes are present
+ * in the final mix.
+ *
+ * @param hash The hash to finalize.
+ * @param ptr The pointer to the remaining input.
+ * @param len The remaining length, modulo 32.
+ * @param align Whether @p ptr is aligned.
+ * @return The finalized hash
+ * @see XXH32_finalize().
+ */
+static XXH_PUREF xxh_u64
+XXH64_finalize(xxh_u64 hash, const xxh_u8* ptr, size_t len, XXH_alignment align)
+{
+ if (ptr==NULL) XXH_ASSERT(len == 0);
+ len &= 31;
+ while (len >= 8) {
+ xxh_u64 const k1 = XXH64_round(0, XXH_get64bits(ptr));
+ ptr += 8;
+ hash ^= k1;
+ hash = XXH_rotl64(hash,27) * XXH_PRIME64_1 + XXH_PRIME64_4;
+ len -= 8;
+ }
+ if (len >= 4) {
+ hash ^= (xxh_u64)(XXH_get32bits(ptr)) * XXH_PRIME64_1;
+ ptr += 4;
+ hash = XXH_rotl64(hash, 23) * XXH_PRIME64_2 + XXH_PRIME64_3;
+ len -= 4;
+ }
+ while (len > 0) {
+ hash ^= (*ptr++) * XXH_PRIME64_5;
+ hash = XXH_rotl64(hash, 11) * XXH_PRIME64_1;
+ --len;
+ }
+ return XXH64_avalanche(hash);
+}
+
+#ifdef XXH_OLD_NAMES
+# define PROCESS1_64 XXH_PROCESS1_64
+# define PROCESS4_64 XXH_PROCESS4_64
+# define PROCESS8_64 XXH_PROCESS8_64
+#else
+# undef XXH_PROCESS1_64
+# undef XXH_PROCESS4_64
+# undef XXH_PROCESS8_64
+#endif
+
+/*!
+ * @internal
+ * @brief The implementation for @ref XXH64().
+ *
+ * @param input , len , seed Directly passed from @ref XXH64().
+ * @param align Whether @p input is aligned.
+ * @return The calculated hash.
+ */
+XXH_FORCE_INLINE XXH_PUREF xxh_u64
+XXH64_endian_align(const xxh_u8* input, size_t len, xxh_u64 seed, XXH_alignment align)
+{
+ xxh_u64 h64;
+ if (input==NULL) XXH_ASSERT(len == 0);
+
+ if (len>=32) {
+ const xxh_u8* const bEnd = input + len;
+ const xxh_u8* const limit = bEnd - 31;
+ xxh_u64 v1 = seed + XXH_PRIME64_1 + XXH_PRIME64_2;
+ xxh_u64 v2 = seed + XXH_PRIME64_2;
+ xxh_u64 v3 = seed + 0;
+ xxh_u64 v4 = seed - XXH_PRIME64_1;
+
+ do {
+ v1 = XXH64_round(v1, XXH_get64bits(input)); input+=8;
+ v2 = XXH64_round(v2, XXH_get64bits(input)); input+=8;
+ v3 = XXH64_round(v3, XXH_get64bits(input)); input+=8;
+ v4 = XXH64_round(v4, XXH_get64bits(input)); input+=8;
+ } while (input<limit);
+
+ h64 = XXH_rotl64(v1, 1) + XXH_rotl64(v2, 7) + XXH_rotl64(v3, 12) + XXH_rotl64(v4, 18);
+ h64 = XXH64_mergeRound(h64, v1);
+ h64 = XXH64_mergeRound(h64, v2);
+ h64 = XXH64_mergeRound(h64, v3);
+ h64 = XXH64_mergeRound(h64, v4);
+
+ } else {
+ h64 = seed + XXH_PRIME64_5;
+ }
+
+ h64 += (xxh_u64) len;
+
+ return XXH64_finalize(h64, input, len, align);
+}
+
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH64_hash_t XXH64 (XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed)
+{
+#if !defined(XXH_NO_STREAM) && XXH_SIZE_OPT >= 2
+ /* Simple version, good for code maintenance, but unfortunately slow for small inputs */
+ XXH64_state_t state;
+ XXH64_reset(&state, seed);
+ XXH64_update(&state, (const xxh_u8*)input, len);
+ return XXH64_digest(&state);
+#else
+ if (XXH_FORCE_ALIGN_CHECK) {
+ if ((((size_t)input) & 7)==0) { /* Input is aligned, let's leverage the speed advantage */
+ return XXH64_endian_align((const xxh_u8*)input, len, seed, XXH_aligned);
+ } }
+
+ return XXH64_endian_align((const xxh_u8*)input, len, seed, XXH_unaligned);
+
+#endif
+}
+
+/******* Hash Streaming *******/
+#ifndef XXH_NO_STREAM
+/*! @ingroup XXH64_family*/
+XXH_PUBLIC_API XXH64_state_t* XXH64_createState(void)
+{
+ return (XXH64_state_t*)XXH_malloc(sizeof(XXH64_state_t));
+}
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH_errorcode XXH64_freeState(XXH64_state_t* statePtr)
+{
+ XXH_free(statePtr);
+ return XXH_OK;
+}
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API void XXH64_copyState(XXH_NOESCAPE XXH64_state_t* dstState, const XXH64_state_t* srcState)
+{
+ XXH_memcpy(dstState, srcState, sizeof(*dstState));
+}
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH_errorcode XXH64_reset(XXH_NOESCAPE XXH64_state_t* statePtr, XXH64_hash_t seed)
+{
+ XXH_ASSERT(statePtr != NULL);
+ memset(statePtr, 0, sizeof(*statePtr));
+ statePtr->v[0] = seed + XXH_PRIME64_1 + XXH_PRIME64_2;
+ statePtr->v[1] = seed + XXH_PRIME64_2;
+ statePtr->v[2] = seed + 0;
+ statePtr->v[3] = seed - XXH_PRIME64_1;
+ return XXH_OK;
+}
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH64_update (XXH_NOESCAPE XXH64_state_t* state, XXH_NOESCAPE const void* input, size_t len)
+{
+ if (input==NULL) {
+ XXH_ASSERT(len == 0);
+ return XXH_OK;
+ }
+
+ { const xxh_u8* p = (const xxh_u8*)input;
+ const xxh_u8* const bEnd = p + len;
+
+ state->total_len += len;
+
+ if (state->memsize + len < 32) { /* fill in tmp buffer */
+ XXH_memcpy(((xxh_u8*)state->mem64) + state->memsize, input, len);
+ state->memsize += (xxh_u32)len;
+ return XXH_OK;
+ }
+
+ if (state->memsize) { /* tmp buffer is full */
+ XXH_memcpy(((xxh_u8*)state->mem64) + state->memsize, input, 32-state->memsize);
+ state->v[0] = XXH64_round(state->v[0], XXH_readLE64(state->mem64+0));
+ state->v[1] = XXH64_round(state->v[1], XXH_readLE64(state->mem64+1));
+ state->v[2] = XXH64_round(state->v[2], XXH_readLE64(state->mem64+2));
+ state->v[3] = XXH64_round(state->v[3], XXH_readLE64(state->mem64+3));
+ p += 32 - state->memsize;
+ state->memsize = 0;
+ }
+
+ if (p+32 <= bEnd) {
+ const xxh_u8* const limit = bEnd - 32;
+
+ do {
+ state->v[0] = XXH64_round(state->v[0], XXH_readLE64(p)); p+=8;
+ state->v[1] = XXH64_round(state->v[1], XXH_readLE64(p)); p+=8;
+ state->v[2] = XXH64_round(state->v[2], XXH_readLE64(p)); p+=8;
+ state->v[3] = XXH64_round(state->v[3], XXH_readLE64(p)); p+=8;
+ } while (p<=limit);
+
+ }
+
+ if (p < bEnd) {
+ XXH_memcpy(state->mem64, p, (size_t)(bEnd-p));
+ state->memsize = (unsigned)(bEnd-p);
+ }
+ }
+
+ return XXH_OK;
+}
+
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH64_hash_t XXH64_digest(XXH_NOESCAPE const XXH64_state_t* state)
+{
+ xxh_u64 h64;
+
+ if (state->total_len >= 32) {
+ h64 = XXH_rotl64(state->v[0], 1) + XXH_rotl64(state->v[1], 7) + XXH_rotl64(state->v[2], 12) + XXH_rotl64(state->v[3], 18);
+ h64 = XXH64_mergeRound(h64, state->v[0]);
+ h64 = XXH64_mergeRound(h64, state->v[1]);
+ h64 = XXH64_mergeRound(h64, state->v[2]);
+ h64 = XXH64_mergeRound(h64, state->v[3]);
+ } else {
+ h64 = state->v[2] /*seed*/ + XXH_PRIME64_5;
+ }
+
+ h64 += (xxh_u64) state->total_len;
+
+ return XXH64_finalize(h64, (const xxh_u8*)state->mem64, (size_t)state->total_len, XXH_aligned);
+}
+#endif /* !XXH_NO_STREAM */
+
+/******* Canonical representation *******/
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API void XXH64_canonicalFromHash(XXH_NOESCAPE XXH64_canonical_t* dst, XXH64_hash_t hash)
+{
+ XXH_STATIC_ASSERT(sizeof(XXH64_canonical_t) == sizeof(XXH64_hash_t));
+ if (XXH_CPU_LITTLE_ENDIAN) hash = XXH_swap64(hash);
+ XXH_memcpy(dst, &hash, sizeof(*dst));
+}
+
+/*! @ingroup XXH64_family */
+XXH_PUBLIC_API XXH64_hash_t XXH64_hashFromCanonical(XXH_NOESCAPE const XXH64_canonical_t* src)
+{
+ return XXH_readBE64(src);
+}
+
+#ifndef XXH_NO_XXH3
+
+/* *********************************************************************
+* XXH3
+* New generation hash designed for speed on small keys and vectorization
+************************************************************************ */
+/*!
+ * @}
+ * @defgroup XXH3_impl XXH3 implementation
+ * @ingroup impl
+ * @{
+ */
+
+/* === Compiler specifics === */
+
+#if ((defined(sun) || defined(__sun)) && __cplusplus) /* Solaris includes __STDC_VERSION__ with C++. Tested with GCC 5.5 */
+# define XXH_RESTRICT /* disable */
+#elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* >= C99 */
+# define XXH_RESTRICT restrict
+#elif (defined (__GNUC__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) \
+ || (defined (__clang__)) \
+ || (defined (_MSC_VER) && (_MSC_VER >= 1400)) \
+ || (defined (__INTEL_COMPILER) && (__INTEL_COMPILER >= 1300))
+/*
+ * There are a LOT more compilers that recognize __restrict but this
+ * covers the major ones.
+ */
+# define XXH_RESTRICT __restrict
+#else
+# define XXH_RESTRICT /* disable */
+#endif
+
+#if (defined(__GNUC__) && (__GNUC__ >= 3)) \
+ || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) \
+ || defined(__clang__)
+# define XXH_likely(x) __builtin_expect(x, 1)
+# define XXH_unlikely(x) __builtin_expect(x, 0)
+#else
+# define XXH_likely(x) (x)
+# define XXH_unlikely(x) (x)
+#endif
+
+#ifndef XXH_HAS_INCLUDE
+# ifdef __has_include
+/*
+ * Not defined as XXH_HAS_INCLUDE(x) (function-like) because
+ * this causes segfaults in Apple Clang 4.2 (on Mac OS X 10.7 Lion)
+ */
+# define XXH_HAS_INCLUDE __has_include
+# else
+# define XXH_HAS_INCLUDE(x) 0
+# endif
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+# if defined(__ARM_FEATURE_SVE)
+# include <arm_sve.h>
+# endif
+# if defined(__ARM_NEON__) || defined(__ARM_NEON) \
+ || (defined(_M_ARM) && _M_ARM >= 7) \
+ || defined(_M_ARM64) || defined(_M_ARM64EC) \
+ || (defined(__wasm_simd128__) && XXH_HAS_INCLUDE(<arm_neon.h>)) /* WASM SIMD128 via SIMDe */
+# define inline __inline__ /* circumvent a clang bug */
+# include <arm_neon.h>
+# undef inline
+# elif defined(__AVX2__)
+# include <immintrin.h>
+# elif defined(__SSE2__)
+# include <emmintrin.h>
+# endif
+#endif
+
+#if defined(_MSC_VER)
+# include <intrin.h>
+#endif
+
+/*
+ * One goal of XXH3 is to make it fast on both 32-bit and 64-bit, while
+ * remaining a true 64-bit/128-bit hash function.
+ *
+ * This is done by prioritizing a subset of 64-bit operations that can be
+ * emulated without too many steps on the average 32-bit machine.
+ *
+ * For example, these two lines seem similar, and run equally fast on 64-bit:
+ *
+ * xxh_u64 x;
+ * x ^= (x >> 47); // good
+ * x ^= (x >> 13); // bad
+ *
+ * However, to a 32-bit machine, there is a major difference.
+ *
+ * x ^= (x >> 47) looks like this:
+ *
+ * x.lo ^= (x.hi >> (47 - 32));
+ *
+ * while x ^= (x >> 13) looks like this:
+ *
+ * // note: funnel shifts are not usually cheap.
+ * x.lo ^= (x.lo >> 13) | (x.hi << (32 - 13));
+ * x.hi ^= (x.hi >> 13);
+ *
+ * The first one is significantly faster than the second, simply because the
+ * shift is larger than 32. This means:
+ * - All the bits we need are in the upper 32 bits, so we can ignore the lower
+ * 32 bits in the shift.
+ * - The shift result will always fit in the lower 32 bits, and therefore,
+ * we can ignore the upper 32 bits in the xor.
+ *
+ * Thanks to this optimization, XXH3 only requires these features to be efficient:
+ *
+ * - Usable unaligned access
+ * - A 32-bit or 64-bit ALU
+ * - If 32-bit, a decent ADC instruction
+ * - A 32 or 64-bit multiply with a 64-bit result
+ * - For the 128-bit variant, a decent byteswap helps short inputs.
+ *
+ * The first two are already required by XXH32, and almost all 32-bit and 64-bit
+ * platforms which can run XXH32 can run XXH3 efficiently.
+ *
+ * Thumb-1, the classic 16-bit only subset of ARM's instruction set, is one
+ * notable exception.
+ *
+ * First of all, Thumb-1 lacks support for the UMULL instruction which
+ * performs the important long multiply. This means numerous __aeabi_lmul
+ * calls.
+ *
+ * Second of all, the 8 functional registers are just not enough.
+ * Setup for __aeabi_lmul, byteshift loads, pointers, and all arithmetic need
+ * Lo registers, and this shuffling results in thousands more MOVs than A32.
+ *
+ * A32 and T32 don't have this limitation. They can access all 14 registers,
+ * do a 32->64 multiply with UMULL, and the flexible operand allowing free
+ * shifts is helpful, too.
+ *
+ * Therefore, we do a quick sanity check.
+ *
+ * If compiling Thumb-1 for a target which supports ARM instructions, we will
+ * emit a warning, as it is not a "sane" platform to compile for.
+ *
+ * Usually, if this happens, it is because of an accident and you probably need
+ * to specify -march, as you likely meant to compile for a newer architecture.
+ *
+ * Credit: large sections of the vectorial and asm source code paths
+ * have been contributed by @easyaspi314
+ */
+#if defined(__thumb__) && !defined(__thumb2__) && defined(__ARM_ARCH_ISA_ARM)
+# warning "XXH3 is highly inefficient without ARM or Thumb-2."
+#endif
+
+/* ==========================================
+ * Vectorization detection
+ * ========================================== */
+
+#ifdef XXH_DOXYGEN
+/*!
+ * @ingroup tuning
+ * @brief Overrides the vectorization implementation chosen for XXH3.
+ *
+ * Can be defined to 0 to disable SIMD or any of the values mentioned in
+ * @ref XXH_VECTOR_TYPE.
+ *
+ * If this is not defined, it uses predefined macros to determine the best
+ * implementation.
+ */
+# define XXH_VECTOR XXH_SCALAR
+/*!
+ * @ingroup tuning
+ * @brief Possible values for @ref XXH_VECTOR.
+ *
+ * Note that these are actually implemented as macros.
+ *
+ * If this is not defined, it is detected automatically.
+ * internal macro XXH_X86DISPATCH overrides this.
+ */
+enum XXH_VECTOR_TYPE /* fake enum */ {
+ XXH_SCALAR = 0, /*!< Portable scalar version */
+ XXH_SSE2 = 1, /*!<
+ * SSE2 for Pentium 4, Opteron, all x86_64.
+ *
+ * @note SSE2 is also guaranteed on Windows 10, macOS, and
+ * Android x86.
+ */
+ XXH_AVX2 = 2, /*!< AVX2 for Haswell and Bulldozer */
+ XXH_AVX512 = 3, /*!< AVX512 for Skylake and Icelake */
+ XXH_NEON = 4, /*!<
+ * NEON for most ARMv7-A, all AArch64, and WASM SIMD128
+ * via the SIMDeverywhere polyfill provided with the
+ * Emscripten SDK.
+ */
+ XXH_VSX = 5, /*!< VSX and ZVector for POWER8/z13 (64-bit) */
+ XXH_SVE = 6, /*!< SVE for some ARMv8-A and ARMv9-A */
+};
+/*!
+ * @ingroup tuning
+ * @brief Selects the minimum alignment for XXH3's accumulators.
+ *
+ * When using SIMD, this should match the alignment required for said vector
+ * type, so, for example, 32 for AVX2.
+ *
+ * Default: Auto detected.
+ */
+# define XXH_ACC_ALIGN 8
+#endif
+
+/* Actual definition */
+#ifndef XXH_DOXYGEN
+# define XXH_SCALAR 0
+# define XXH_SSE2 1
+# define XXH_AVX2 2
+# define XXH_AVX512 3
+# define XXH_NEON 4
+# define XXH_VSX 5
+# define XXH_SVE 6
+#endif
+
+#ifndef XXH_VECTOR /* can be defined on command line */
+# if defined(__ARM_FEATURE_SVE)
+# define XXH_VECTOR XXH_SVE
+# elif ( \
+ defined(__ARM_NEON__) || defined(__ARM_NEON) /* gcc */ \
+ || defined(_M_ARM) || defined(_M_ARM64) || defined(_M_ARM64EC) /* msvc */ \
+ || (defined(__wasm_simd128__) && XXH_HAS_INCLUDE(<arm_neon.h>)) /* wasm simd128 via SIMDe */ \
+ ) && ( \
+ defined(_WIN32) || defined(__LITTLE_ENDIAN__) /* little endian only */ \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) \
+ )
+# define XXH_VECTOR XXH_NEON
+# elif defined(__AVX512F__)
+# define XXH_VECTOR XXH_AVX512
+# elif defined(__AVX2__)
+# define XXH_VECTOR XXH_AVX2
+# elif defined(__SSE2__) || defined(_M_AMD64) || defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP == 2))
+# define XXH_VECTOR XXH_SSE2
+# elif (defined(__PPC64__) && defined(__POWER8_VECTOR__)) \
+ || (defined(__s390x__) && defined(__VEC__)) \
+ && defined(__GNUC__) /* TODO: IBM XL */
+# define XXH_VECTOR XXH_VSX
+# else
+# define XXH_VECTOR XXH_SCALAR
+# endif
+#endif
+
+/* __ARM_FEATURE_SVE is only supported by GCC & Clang. */
+#if (XXH_VECTOR == XXH_SVE) && !defined(__ARM_FEATURE_SVE)
+# ifdef _MSC_VER
+# pragma warning(once : 4606)
+# else
+# warning "__ARM_FEATURE_SVE isn't supported. Use SCALAR instead."
+# endif
+# undef XXH_VECTOR
+# define XXH_VECTOR XXH_SCALAR
+#endif
+
+/*
+ * Controls the alignment of the accumulator,
+ * for compatibility with aligned vector loads, which are usually faster.
+ */
+#ifndef XXH_ACC_ALIGN
+# if defined(XXH_X86DISPATCH)
+# define XXH_ACC_ALIGN 64 /* for compatibility with avx512 */
+# elif XXH_VECTOR == XXH_SCALAR /* scalar */
+# define XXH_ACC_ALIGN 8
+# elif XXH_VECTOR == XXH_SSE2 /* sse2 */
+# define XXH_ACC_ALIGN 16
+# elif XXH_VECTOR == XXH_AVX2 /* avx2 */
+# define XXH_ACC_ALIGN 32
+# elif XXH_VECTOR == XXH_NEON /* neon */
+# define XXH_ACC_ALIGN 16
+# elif XXH_VECTOR == XXH_VSX /* vsx */
+# define XXH_ACC_ALIGN 16
+# elif XXH_VECTOR == XXH_AVX512 /* avx512 */
+# define XXH_ACC_ALIGN 64
+# elif XXH_VECTOR == XXH_SVE /* sve */
+# define XXH_ACC_ALIGN 64
+# endif
+#endif
+
+#if defined(XXH_X86DISPATCH) || XXH_VECTOR == XXH_SSE2 \
+ || XXH_VECTOR == XXH_AVX2 || XXH_VECTOR == XXH_AVX512
+# define XXH_SEC_ALIGN XXH_ACC_ALIGN
+#elif XXH_VECTOR == XXH_SVE
+# define XXH_SEC_ALIGN XXH_ACC_ALIGN
+#else
+# define XXH_SEC_ALIGN 8
+#endif
+
+#if defined(__GNUC__) || defined(__clang__)
+# define XXH_ALIASING __attribute__((may_alias))
+#else
+# define XXH_ALIASING /* nothing */
+#endif
+
+/*
+ * UGLY HACK:
+ * GCC usually generates the best code with -O3 for xxHash.
+ *
+ * However, when targeting AVX2, it is overzealous in its unrolling resulting
+ * in code roughly 3/4 the speed of Clang.
+ *
+ * There are other issues, such as GCC splitting _mm256_loadu_si256 into
+ * _mm_loadu_si128 + _mm256_inserti128_si256. This is an optimization which
+ * only applies to Sandy and Ivy Bridge... which don't even support AVX2.
+ *
+ * That is why when compiling the AVX2 version, it is recommended to use either
+ * -O2 -mavx2 -march=haswell
+ * or
+ * -O2 -mavx2 -mno-avx256-split-unaligned-load
+ * for decent performance, or to use Clang instead.
+ *
+ * Fortunately, we can control the first one with a pragma that forces GCC into
+ * -O2, but the other one we can't control without "failed to inline always
+ * inline function due to target mismatch" warnings.
+ */
+#if XXH_VECTOR == XXH_AVX2 /* AVX2 */ \
+ && defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \
+ && defined(__OPTIMIZE__) && XXH_SIZE_OPT <= 0 /* respect -O0 and -Os */
+# pragma GCC push_options
+# pragma GCC optimize("-O2")
+#endif
+
+#if XXH_VECTOR == XXH_NEON
+
+/*
+ * UGLY HACK: While AArch64 GCC on Linux does not seem to care, on macOS, GCC -O3
+ * optimizes out the entire hashLong loop because of the aliasing violation.
+ *
+ * However, GCC is also inefficient at load-store optimization with vld1q/vst1q,
+ * so the only option is to mark it as aliasing.
+ */
+typedef uint64x2_t xxh_aliasing_uint64x2_t XXH_ALIASING;
+
+/*!
+ * @internal
+ * @brief `vld1q_u64` but faster and alignment-safe.
+ *
+ * On AArch64, unaligned access is always safe, but on ARMv7-a, it is only
+ * *conditionally* safe (`vld1` has an alignment bit like `movdq[ua]` in x86).
+ *
+ * GCC for AArch64 sees `vld1q_u8` as an intrinsic instead of a load, so it
+ * prohibits load-store optimizations. Therefore, a direct dereference is used.
+ *
+ * Otherwise, `vld1q_u8` is used with `vreinterpretq_u8_u64` to do a safe
+ * unaligned load.
+ */
+#if defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__)
+XXH_FORCE_INLINE uint64x2_t XXH_vld1q_u64(void const* ptr) /* silence -Wcast-align */
+{
+ return *(xxh_aliasing_uint64x2_t const *)ptr;
+}
+#else
+XXH_FORCE_INLINE uint64x2_t XXH_vld1q_u64(void const* ptr)
+{
+ return vreinterpretq_u64_u8(vld1q_u8((uint8_t const*)ptr));
+}
+#endif
+
+/*!
+ * @internal
+ * @brief `vmlal_u32` on low and high halves of a vector.
+ *
+ * This is a workaround for AArch64 GCC < 11 which implemented arm_neon.h with
+ * inline assembly and were therefore incapable of merging the `vget_{low, high}_u32`
+ * with `vmlal_u32`.
+ */
+#if defined(__aarch64__) && defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 11
+XXH_FORCE_INLINE uint64x2_t
+XXH_vmlal_low_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs)
+{
+ /* Inline assembly is the only way */
+ __asm__("umlal %0.2d, %1.2s, %2.2s" : "+w" (acc) : "w" (lhs), "w" (rhs));
+ return acc;
+}
+XXH_FORCE_INLINE uint64x2_t
+XXH_vmlal_high_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs)
+{
+ /* This intrinsic works as expected */
+ return vmlal_high_u32(acc, lhs, rhs);
+}
+#else
+/* Portable intrinsic versions */
+XXH_FORCE_INLINE uint64x2_t
+XXH_vmlal_low_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs)
+{
+ return vmlal_u32(acc, vget_low_u32(lhs), vget_low_u32(rhs));
+}
+/*! @copydoc XXH_vmlal_low_u32
+ * Assume the compiler converts this to vmlal_high_u32 on aarch64 */
+XXH_FORCE_INLINE uint64x2_t
+XXH_vmlal_high_u32(uint64x2_t acc, uint32x4_t lhs, uint32x4_t rhs)
+{
+ return vmlal_u32(acc, vget_high_u32(lhs), vget_high_u32(rhs));
+}
+#endif
+
+/*!
+ * @ingroup tuning
+ * @brief Controls the NEON to scalar ratio for XXH3
+ *
+ * This can be set to 2, 4, 6, or 8.
+ *
+ * ARM Cortex CPUs are _very_ sensitive to how their pipelines are used.
+ *
+ * For example, the Cortex-A73 can dispatch 3 micro-ops per cycle, but only 2 of those
+ * can be NEON. If you are only using NEON instructions, you are only using 2/3 of the CPU
+ * bandwidth.
+ *
+ * This is even more noticeable on the more advanced cores like the Cortex-A76 which
+ * can dispatch 8 micro-ops per cycle, but still only 2 NEON micro-ops at once.
+ *
+ * Therefore, to make the most out of the pipeline, it is beneficial to run 6 NEON lanes
+ * and 2 scalar lanes, which is chosen by default.
+ *
+ * This does not apply to Apple processors or 32-bit processors, which run better with
+ * full NEON. These will default to 8. Additionally, size-optimized builds run 8 lanes.
+ *
+ * This change benefits CPUs with large micro-op buffers without negatively affecting
+ * most other CPUs:
+ *
+ * | Chipset | Dispatch type | NEON only | 6:2 hybrid | Diff. |
+ * |:----------------------|:--------------------|----------:|-----------:|------:|
+ * | Snapdragon 730 (A76) | 2 NEON/8 micro-ops | 8.8 GB/s | 10.1 GB/s | ~16% |
+ * | Snapdragon 835 (A73) | 2 NEON/3 micro-ops | 5.1 GB/s | 5.3 GB/s | ~5% |
+ * | Marvell PXA1928 (A53) | In-order dual-issue | 1.9 GB/s | 1.9 GB/s | 0% |
+ * | Apple M1 | 4 NEON/8 micro-ops | 37.3 GB/s | 36.1 GB/s | ~-3% |
+ *
+ * It also seems to fix some bad codegen on GCC, making it almost as fast as clang.
+ *
+ * When using WASM SIMD128, if this is 2 or 6, SIMDe will scalarize 2 of the lanes meaning
+ * it effectively becomes worse 4.
+ *
+ * @see XXH3_accumulate_512_neon()
+ */
+# ifndef XXH3_NEON_LANES
+# if (defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64) || defined(_M_ARM64EC)) \
+ && !defined(__APPLE__) && XXH_SIZE_OPT <= 0
+# define XXH3_NEON_LANES 6
+# else
+# define XXH3_NEON_LANES XXH_ACC_NB
+# endif
+# endif
+#endif /* XXH_VECTOR == XXH_NEON */
+
+/*
+ * VSX and Z Vector helpers.
+ *
+ * This is very messy, and any pull requests to clean this up are welcome.
+ *
+ * There are a lot of problems with supporting VSX and s390x, due to
+ * inconsistent intrinsics, spotty coverage, and multiple endiannesses.
+ */
+#if XXH_VECTOR == XXH_VSX
+/* Annoyingly, these headers _may_ define three macros: `bool`, `vector`,
+ * and `pixel`. This is a problem for obvious reasons.
+ *
+ * These keywords are unnecessary; the spec literally says they are
+ * equivalent to `__bool`, `__vector`, and `__pixel` and may be undef'd
+ * after including the header.
+ *
+ * We use pragma push_macro/pop_macro to keep the namespace clean. */
+# pragma push_macro("bool")
+# pragma push_macro("vector")
+# pragma push_macro("pixel")
+/* silence potential macro redefined warnings */
+# undef bool
+# undef vector
+# undef pixel
+
+# if defined(__s390x__)
+# include <s390intrin.h>
+# else
+# include <altivec.h>
+# endif
+
+/* Restore the original macro values, if applicable. */
+# pragma pop_macro("pixel")
+# pragma pop_macro("vector")
+# pragma pop_macro("bool")
+
+typedef __vector unsigned long long xxh_u64x2;
+typedef __vector unsigned char xxh_u8x16;
+typedef __vector unsigned xxh_u32x4;
+
+/*
+ * UGLY HACK: Similar to aarch64 macOS GCC, s390x GCC has the same aliasing issue.
+ */
+typedef xxh_u64x2 xxh_aliasing_u64x2 XXH_ALIASING;
+
+# ifndef XXH_VSX_BE
+# if defined(__BIG_ENDIAN__) \
+ || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+# define XXH_VSX_BE 1
+# elif defined(__VEC_ELEMENT_REG_ORDER__) && __VEC_ELEMENT_REG_ORDER__ == __ORDER_BIG_ENDIAN__
+# warning "-maltivec=be is not recommended. Please use native endianness."
+# define XXH_VSX_BE 1
+# else
+# define XXH_VSX_BE 0
+# endif
+# endif /* !defined(XXH_VSX_BE) */
+
+# if XXH_VSX_BE
+# if defined(__POWER9_VECTOR__) || (defined(__clang__) && defined(__s390x__))
+# define XXH_vec_revb vec_revb
+# else
+/*!
+ * A polyfill for POWER9's vec_revb().
+ */
+XXH_FORCE_INLINE xxh_u64x2 XXH_vec_revb(xxh_u64x2 val)
+{
+ xxh_u8x16 const vByteSwap = { 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
+ 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08 };
+ return vec_perm(val, val, vByteSwap);
+}
+# endif
+# endif /* XXH_VSX_BE */
+
+/*!
+ * Performs an unaligned vector load and byte swaps it on big endian.
+ */
+XXH_FORCE_INLINE xxh_u64x2 XXH_vec_loadu(const void *ptr)
+{
+ xxh_u64x2 ret;
+ XXH_memcpy(&ret, ptr, sizeof(xxh_u64x2));
+# if XXH_VSX_BE
+ ret = XXH_vec_revb(ret);
+# endif
+ return ret;
+}
+
+/*
+ * vec_mulo and vec_mule are very problematic intrinsics on PowerPC
+ *
+ * These intrinsics weren't added until GCC 8, despite existing for a while,
+ * and they are endian dependent. Also, their meaning swap depending on version.
+ * */
+# if defined(__s390x__)
+ /* s390x is always big endian, no issue on this platform */
+# define XXH_vec_mulo vec_mulo
+# define XXH_vec_mule vec_mule
+# elif defined(__clang__) && XXH_HAS_BUILTIN(__builtin_altivec_vmuleuw) && !defined(__ibmxl__)
+/* Clang has a better way to control this, we can just use the builtin which doesn't swap. */
+ /* The IBM XL Compiler (which defined __clang__) only implements the vec_* operations */
+# define XXH_vec_mulo __builtin_altivec_vmulouw
+# define XXH_vec_mule __builtin_altivec_vmuleuw
+# else
+/* gcc needs inline assembly */
+/* Adapted from https://github.com/google/highwayhash/blob/master/highwayhash/hh_vsx.h. */
+XXH_FORCE_INLINE xxh_u64x2 XXH_vec_mulo(xxh_u32x4 a, xxh_u32x4 b)
+{
+ xxh_u64x2 result;
+ __asm__("vmulouw %0, %1, %2" : "=v" (result) : "v" (a), "v" (b));
+ return result;
+}
+XXH_FORCE_INLINE xxh_u64x2 XXH_vec_mule(xxh_u32x4 a, xxh_u32x4 b)
+{
+ xxh_u64x2 result;
+ __asm__("vmuleuw %0, %1, %2" : "=v" (result) : "v" (a), "v" (b));
+ return result;
+}
+# endif /* XXH_vec_mulo, XXH_vec_mule */
+#endif /* XXH_VECTOR == XXH_VSX */
+
+#if XXH_VECTOR == XXH_SVE
+#define ACCRND(acc, offset) \
+do { \
+ svuint64_t input_vec = svld1_u64(mask, xinput + offset); \
+ svuint64_t secret_vec = svld1_u64(mask, xsecret + offset); \
+ svuint64_t mixed = sveor_u64_x(mask, secret_vec, input_vec); \
+ svuint64_t swapped = svtbl_u64(input_vec, kSwap); \
+ svuint64_t mixed_lo = svextw_u64_x(mask, mixed); \
+ svuint64_t mixed_hi = svlsr_n_u64_x(mask, mixed, 32); \
+ svuint64_t mul = svmad_u64_x(mask, mixed_lo, mixed_hi, swapped); \
+ acc = svadd_u64_x(mask, acc, mul); \
+} while (0)
+#endif /* XXH_VECTOR == XXH_SVE */
+
+/* prefetch
+ * can be disabled, by declaring XXH_NO_PREFETCH build macro */
+#if defined(XXH_NO_PREFETCH)
+# define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */
+#else
+# if XXH_SIZE_OPT >= 1
+# define XXH_PREFETCH(ptr) (void)(ptr)
+# elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) /* _mm_prefetch() not defined outside of x86/x64 */
+# include <mmintrin.h> /* https://msdn.microsoft.com/fr-fr/library/84szxsww(v=vs.90).aspx */
+# define XXH_PREFETCH(ptr) _mm_prefetch((const char*)(ptr), _MM_HINT_T0)
+# elif defined(__GNUC__) && ( (__GNUC__ >= 4) || ( (__GNUC__ == 3) && (__GNUC_MINOR__ >= 1) ) )
+# define XXH_PREFETCH(ptr) __builtin_prefetch((ptr), 0 /* rw==read */, 3 /* locality */)
+# else
+# define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */
+# endif
+#endif /* XXH_NO_PREFETCH */
+
+
+/* ==========================================
+ * XXH3 default settings
+ * ========================================== */
+
+#define XXH_SECRET_DEFAULT_SIZE 192 /* minimum XXH3_SECRET_SIZE_MIN */
+
+#if (XXH_SECRET_DEFAULT_SIZE < XXH3_SECRET_SIZE_MIN)
+# error "default keyset is not large enough"
+#endif
+
+/*! Pseudorandom secret taken directly from FARSH. */
+XXH_ALIGN(64) static const xxh_u8 XXH3_kSecret[XXH_SECRET_DEFAULT_SIZE] = {
+ 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
+ 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
+ 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
+ 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,
+ 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,
+ 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
+ 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
+ 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,
+ 0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
+ 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,
+ 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
+ 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
+};
+
+static const xxh_u64 PRIME_MX1 = 0x165667919E3779F9ULL; /*!< 0b0001011001010110011001111001000110011110001101110111100111111001 */
+static const xxh_u64 PRIME_MX2 = 0x9FB21C651E98DF25ULL; /*!< 0b1001111110110010000111000110010100011110100110001101111100100101 */
+
+#ifdef XXH_OLD_NAMES
+# define kSecret XXH3_kSecret
+#endif
+
+#ifdef XXH_DOXYGEN
+/*!
+ * @brief Calculates a 32-bit to 64-bit long multiply.
+ *
+ * Implemented as a macro.
+ *
+ * Wraps `__emulu` on MSVC x86 because it tends to call `__allmul` when it doesn't
+ * need to (but it shouldn't need to anyways, it is about 7 instructions to do
+ * a 64x64 multiply...). Since we know that this will _always_ emit `MULL`, we
+ * use that instead of the normal method.
+ *
+ * If you are compiling for platforms like Thumb-1 and don't have a better option,
+ * you may also want to write your own long multiply routine here.
+ *
+ * @param x, y Numbers to be multiplied
+ * @return 64-bit product of the low 32 bits of @p x and @p y.
+ */
+XXH_FORCE_INLINE xxh_u64
+XXH_mult32to64(xxh_u64 x, xxh_u64 y)
+{
+ return (x & 0xFFFFFFFF) * (y & 0xFFFFFFFF);
+}
+#elif defined(_MSC_VER) && defined(_M_IX86)
+# define XXH_mult32to64(x, y) __emulu((unsigned)(x), (unsigned)(y))
+#else
+/*
+ * Downcast + upcast is usually better than masking on older compilers like
+ * GCC 4.2 (especially 32-bit ones), all without affecting newer compilers.
+ *
+ * The other method, (x & 0xFFFFFFFF) * (y & 0xFFFFFFFF), will AND both operands
+ * and perform a full 64x64 multiply -- entirely redundant on 32-bit.
+ */
+# define XXH_mult32to64(x, y) ((xxh_u64)(xxh_u32)(x) * (xxh_u64)(xxh_u32)(y))
+#endif
+
+/*!
+ * @brief Calculates a 64->128-bit long multiply.
+ *
+ * Uses `__uint128_t` and `_umul128` if available, otherwise uses a scalar
+ * version.
+ *
+ * @param lhs , rhs The 64-bit integers to be multiplied
+ * @return The 128-bit result represented in an @ref XXH128_hash_t.
+ */
+static XXH128_hash_t
+XXH_mult64to128(xxh_u64 lhs, xxh_u64 rhs)
+{
+ /*
+ * GCC/Clang __uint128_t method.
+ *
+ * On most 64-bit targets, GCC and Clang define a __uint128_t type.
+ * This is usually the best way as it usually uses a native long 64-bit
+ * multiply, such as MULQ on x86_64 or MUL + UMULH on aarch64.
+ *
+ * Usually.
+ *
+ * Despite being a 32-bit platform, Clang (and emscripten) define this type
+ * despite not having the arithmetic for it. This results in a laggy
+ * compiler builtin call which calculates a full 128-bit multiply.
+ * In that case it is best to use the portable one.
+ * https://github.com/Cyan4973/xxHash/issues/211#issuecomment-515575677
+ */
+#if (defined(__GNUC__) || defined(__clang__)) && !defined(__wasm__) \
+ && defined(__SIZEOF_INT128__) \
+ || (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128)
+
+ __uint128_t const product = (__uint128_t)lhs * (__uint128_t)rhs;
+ XXH128_hash_t r128;
+ r128.low64 = (xxh_u64)(product);
+ r128.high64 = (xxh_u64)(product >> 64);
+ return r128;
+
+ /*
+ * MSVC for x64's _umul128 method.
+ *
+ * xxh_u64 _umul128(xxh_u64 Multiplier, xxh_u64 Multiplicand, xxh_u64 *HighProduct);
+ *
+ * This compiles to single operand MUL on x64.
+ */
+#elif (defined(_M_X64) || defined(_M_IA64)) && !defined(_M_ARM64EC)
+
+#ifndef _MSC_VER
+# pragma intrinsic(_umul128)
+#endif
+ xxh_u64 product_high;
+ xxh_u64 const product_low = _umul128(lhs, rhs, &product_high);
+ XXH128_hash_t r128;
+ r128.low64 = product_low;
+ r128.high64 = product_high;
+ return r128;
+
+ /*
+ * MSVC for ARM64's __umulh method.
+ *
+ * This compiles to the same MUL + UMULH as GCC/Clang's __uint128_t method.
+ */
+#elif defined(_M_ARM64) || defined(_M_ARM64EC)
+
+#ifndef _MSC_VER
+# pragma intrinsic(__umulh)
+#endif
+ XXH128_hash_t r128;
+ r128.low64 = lhs * rhs;
+ r128.high64 = __umulh(lhs, rhs);
+ return r128;
+
+#else
+ /*
+ * Portable scalar method. Optimized for 32-bit and 64-bit ALUs.
+ *
+ * This is a fast and simple grade school multiply, which is shown below
+ * with base 10 arithmetic instead of base 0x100000000.
+ *
+ * 9 3 // D2 lhs = 93
+ * x 7 5 // D2 rhs = 75
+ * ----------
+ * 1 5 // D2 lo_lo = (93 % 10) * (75 % 10) = 15
+ * 4 5 | // D2 hi_lo = (93 / 10) * (75 % 10) = 45
+ * 2 1 | // D2 lo_hi = (93 % 10) * (75 / 10) = 21
+ * + 6 3 | | // D2 hi_hi = (93 / 10) * (75 / 10) = 63
+ * ---------
+ * 2 7 | // D2 cross = (15 / 10) + (45 % 10) + 21 = 27
+ * + 6 7 | | // D2 upper = (27 / 10) + (45 / 10) + 63 = 67
+ * ---------
+ * 6 9 7 5 // D4 res = (27 * 10) + (15 % 10) + (67 * 100) = 6975
+ *
+ * The reasons for adding the products like this are:
+ * 1. It avoids manual carry tracking. Just like how
+ * (9 * 9) + 9 + 9 = 99, the same applies with this for UINT64_MAX.
+ * This avoids a lot of complexity.
+ *
+ * 2. It hints for, and on Clang, compiles to, the powerful UMAAL
+ * instruction available in ARM's Digital Signal Processing extension
+ * in 32-bit ARMv6 and later, which is shown below:
+ *
+ * void UMAAL(xxh_u32 *RdLo, xxh_u32 *RdHi, xxh_u32 Rn, xxh_u32 Rm)
+ * {
+ * xxh_u64 product = (xxh_u64)*RdLo * (xxh_u64)*RdHi + Rn + Rm;
+ * *RdLo = (xxh_u32)(product & 0xFFFFFFFF);
+ * *RdHi = (xxh_u32)(product >> 32);
+ * }
+ *
+ * This instruction was designed for efficient long multiplication, and
+ * allows this to be calculated in only 4 instructions at speeds
+ * comparable to some 64-bit ALUs.
+ *
+ * 3. It isn't terrible on other platforms. Usually this will be a couple
+ * of 32-bit ADD/ADCs.
+ */
+
+ /* First calculate all of the cross products. */
+ xxh_u64 const lo_lo = XXH_mult32to64(lhs & 0xFFFFFFFF, rhs & 0xFFFFFFFF);
+ xxh_u64 const hi_lo = XXH_mult32to64(lhs >> 32, rhs & 0xFFFFFFFF);
+ xxh_u64 const lo_hi = XXH_mult32to64(lhs & 0xFFFFFFFF, rhs >> 32);
+ xxh_u64 const hi_hi = XXH_mult32to64(lhs >> 32, rhs >> 32);
+
+ /* Now add the products together. These will never overflow. */
+ xxh_u64 const cross = (lo_lo >> 32) + (hi_lo & 0xFFFFFFFF) + lo_hi;
+ xxh_u64 const upper = (hi_lo >> 32) + (cross >> 32) + hi_hi;
+ xxh_u64 const lower = (cross << 32) | (lo_lo & 0xFFFFFFFF);
+
+ XXH128_hash_t r128;
+ r128.low64 = lower;
+ r128.high64 = upper;
+ return r128;
+#endif
+}
+
+/*!
+ * @brief Calculates a 64-bit to 128-bit multiply, then XOR folds it.
+ *
+ * The reason for the separate function is to prevent passing too many structs
+ * around by value. This will hopefully inline the multiply, but we don't force it.
+ *
+ * @param lhs , rhs The 64-bit integers to multiply
+ * @return The low 64 bits of the product XOR'd by the high 64 bits.
+ * @see XXH_mult64to128()
+ */
+static xxh_u64
+XXH3_mul128_fold64(xxh_u64 lhs, xxh_u64 rhs)
+{
+ XXH128_hash_t product = XXH_mult64to128(lhs, rhs);
+ return product.low64 ^ product.high64;
+}
+
+/*! Seems to produce slightly better code on GCC for some reason. */
+XXH_FORCE_INLINE XXH_CONSTF xxh_u64 XXH_xorshift64(xxh_u64 v64, int shift)
+{
+ XXH_ASSERT(0 <= shift && shift < 64);
+ return v64 ^ (v64 >> shift);
+}
+
+/*
+ * This is a fast avalanche stage,
+ * suitable when input bits are already partially mixed
+ */
+static XXH64_hash_t XXH3_avalanche(xxh_u64 h64)
+{
+ h64 = XXH_xorshift64(h64, 37);
+ h64 *= PRIME_MX1;
+ h64 = XXH_xorshift64(h64, 32);
+ return h64;
+}
+
+/*
+ * This is a stronger avalanche,
+ * inspired by Pelle Evensen's rrmxmx
+ * preferable when input has not been previously mixed
+ */
+static XXH64_hash_t XXH3_rrmxmx(xxh_u64 h64, xxh_u64 len)
+{
+ /* this mix is inspired by Pelle Evensen's rrmxmx */
+ h64 ^= XXH_rotl64(h64, 49) ^ XXH_rotl64(h64, 24);
+ h64 *= PRIME_MX2;
+ h64 ^= (h64 >> 35) + len ;
+ h64 *= PRIME_MX2;
+ return XXH_xorshift64(h64, 28);
+}
+
+
+/* ==========================================
+ * Short keys
+ * ==========================================
+ * One of the shortcomings of XXH32 and XXH64 was that their performance was
+ * sub-optimal on short lengths. It used an iterative algorithm which strongly
+ * favored lengths that were a multiple of 4 or 8.
+ *
+ * Instead of iterating over individual inputs, we use a set of single shot
+ * functions which piece together a range of lengths and operate in constant time.
+ *
+ * Additionally, the number of multiplies has been significantly reduced. This
+ * reduces latency, especially when emulating 64-bit multiplies on 32-bit.
+ *
+ * Depending on the platform, this may or may not be faster than XXH32, but it
+ * is almost guaranteed to be faster than XXH64.
+ */
+
+/*
+ * At very short lengths, there isn't enough input to fully hide secrets, or use
+ * the entire secret.
+ *
+ * There is also only a limited amount of mixing we can do before significantly
+ * impacting performance.
+ *
+ * Therefore, we use different sections of the secret and always mix two secret
+ * samples with an XOR. This should have no effect on performance on the
+ * seedless or withSeed variants because everything _should_ be constant folded
+ * by modern compilers.
+ *
+ * The XOR mixing hides individual parts of the secret and increases entropy.
+ *
+ * This adds an extra layer of strength for custom secrets.
+ */
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
+XXH3_len_1to3_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(1 <= len && len <= 3);
+ XXH_ASSERT(secret != NULL);
+ /*
+ * len = 1: combined = { input[0], 0x01, input[0], input[0] }
+ * len = 2: combined = { input[1], 0x02, input[0], input[1] }
+ * len = 3: combined = { input[2], 0x03, input[0], input[1] }
+ */
+ { xxh_u8 const c1 = input[0];
+ xxh_u8 const c2 = input[len >> 1];
+ xxh_u8 const c3 = input[len - 1];
+ xxh_u32 const combined = ((xxh_u32)c1 << 16) | ((xxh_u32)c2 << 24)
+ | ((xxh_u32)c3 << 0) | ((xxh_u32)len << 8);
+ xxh_u64 const bitflip = (XXH_readLE32(secret) ^ XXH_readLE32(secret+4)) + seed;
+ xxh_u64 const keyed = (xxh_u64)combined ^ bitflip;
+ return XXH64_avalanche(keyed);
+ }
+}
+
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
+XXH3_len_4to8_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(secret != NULL);
+ XXH_ASSERT(4 <= len && len <= 8);
+ seed ^= (xxh_u64)XXH_swap32((xxh_u32)seed) << 32;
+ { xxh_u32 const input1 = XXH_readLE32(input);
+ xxh_u32 const input2 = XXH_readLE32(input + len - 4);
+ xxh_u64 const bitflip = (XXH_readLE64(secret+8) ^ XXH_readLE64(secret+16)) - seed;
+ xxh_u64 const input64 = input2 + (((xxh_u64)input1) << 32);
+ xxh_u64 const keyed = input64 ^ bitflip;
+ return XXH3_rrmxmx(keyed, len);
+ }
+}
+
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
+XXH3_len_9to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(secret != NULL);
+ XXH_ASSERT(9 <= len && len <= 16);
+ { xxh_u64 const bitflip1 = (XXH_readLE64(secret+24) ^ XXH_readLE64(secret+32)) + seed;
+ xxh_u64 const bitflip2 = (XXH_readLE64(secret+40) ^ XXH_readLE64(secret+48)) - seed;
+ xxh_u64 const input_lo = XXH_readLE64(input) ^ bitflip1;
+ xxh_u64 const input_hi = XXH_readLE64(input + len - 8) ^ bitflip2;
+ xxh_u64 const acc = len
+ + XXH_swap64(input_lo) + input_hi
+ + XXH3_mul128_fold64(input_lo, input_hi);
+ return XXH3_avalanche(acc);
+ }
+}
+
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
+XXH3_len_0to16_64b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(len <= 16);
+ { if (XXH_likely(len > 8)) return XXH3_len_9to16_64b(input, len, secret, seed);
+ if (XXH_likely(len >= 4)) return XXH3_len_4to8_64b(input, len, secret, seed);
+ if (len) return XXH3_len_1to3_64b(input, len, secret, seed);
+ return XXH64_avalanche(seed ^ (XXH_readLE64(secret+56) ^ XXH_readLE64(secret+64)));
+ }
+}
+
+/*
+ * DISCLAIMER: There are known *seed-dependent* multicollisions here due to
+ * multiplication by zero, affecting hashes of lengths 17 to 240.
+ *
+ * However, they are very unlikely.
+ *
+ * Keep this in mind when using the unseeded XXH3_64bits() variant: As with all
+ * unseeded non-cryptographic hashes, it does not attempt to defend itself
+ * against specially crafted inputs, only random inputs.
+ *
+ * Compared to classic UMAC where a 1 in 2^31 chance of 4 consecutive bytes
+ * cancelling out the secret is taken an arbitrary number of times (addressed
+ * in XXH3_accumulate_512), this collision is very unlikely with random inputs
+ * and/or proper seeding:
+ *
+ * This only has a 1 in 2^63 chance of 8 consecutive bytes cancelling out, in a
+ * function that is only called up to 16 times per hash with up to 240 bytes of
+ * input.
+ *
+ * This is not too bad for a non-cryptographic hash function, especially with
+ * only 64 bit outputs.
+ *
+ * The 128-bit variant (which trades some speed for strength) is NOT affected
+ * by this, although it is always a good idea to use a proper seed if you care
+ * about strength.
+ */
+XXH_FORCE_INLINE xxh_u64 XXH3_mix16B(const xxh_u8* XXH_RESTRICT input,
+ const xxh_u8* XXH_RESTRICT secret, xxh_u64 seed64)
+{
+#if defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \
+ && defined(__i386__) && defined(__SSE2__) /* x86 + SSE2 */ \
+ && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable like XXH32 hack */
+ /*
+ * UGLY HACK:
+ * GCC for x86 tends to autovectorize the 128-bit multiply, resulting in
+ * slower code.
+ *
+ * By forcing seed64 into a register, we disrupt the cost model and
+ * cause it to scalarize. See `XXH32_round()`
+ *
+ * FIXME: Clang's output is still _much_ faster -- On an AMD Ryzen 3600,
+ * XXH3_64bits @ len=240 runs at 4.6 GB/s with Clang 9, but 3.3 GB/s on
+ * GCC 9.2, despite both emitting scalar code.
+ *
+ * GCC generates much better scalar code than Clang for the rest of XXH3,
+ * which is why finding a more optimal codepath is an interest.
+ */
+ XXH_COMPILER_GUARD(seed64);
+#endif
+ { xxh_u64 const input_lo = XXH_readLE64(input);
+ xxh_u64 const input_hi = XXH_readLE64(input+8);
+ return XXH3_mul128_fold64(
+ input_lo ^ (XXH_readLE64(secret) + seed64),
+ input_hi ^ (XXH_readLE64(secret+8) - seed64)
+ );
+ }
+}
+
+/* For mid range keys, XXH3 uses a Mum-hash variant. */
+XXH_FORCE_INLINE XXH_PUREF XXH64_hash_t
+XXH3_len_17to128_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH64_hash_t seed)
+{
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize;
+ XXH_ASSERT(16 < len && len <= 128);
+
+ { xxh_u64 acc = len * XXH_PRIME64_1;
+#if XXH_SIZE_OPT >= 1
+ /* Smaller and cleaner, but slightly slower. */
+ unsigned int i = (unsigned int)(len - 1) / 32;
+ do {
+ acc += XXH3_mix16B(input+16 * i, secret+32*i, seed);
+ acc += XXH3_mix16B(input+len-16*(i+1), secret+32*i+16, seed);
+ } while (i-- != 0);
+#else
+ if (len > 32) {
+ if (len > 64) {
+ if (len > 96) {
+ acc += XXH3_mix16B(input+48, secret+96, seed);
+ acc += XXH3_mix16B(input+len-64, secret+112, seed);
+ }
+ acc += XXH3_mix16B(input+32, secret+64, seed);
+ acc += XXH3_mix16B(input+len-48, secret+80, seed);
+ }
+ acc += XXH3_mix16B(input+16, secret+32, seed);
+ acc += XXH3_mix16B(input+len-32, secret+48, seed);
+ }
+ acc += XXH3_mix16B(input+0, secret+0, seed);
+ acc += XXH3_mix16B(input+len-16, secret+16, seed);
+#endif
+ return XXH3_avalanche(acc);
+ }
+}
+
+/*!
+ * @brief Maximum size of "short" key in bytes.
+ */
+#define XXH3_MIDSIZE_MAX 240
+
+XXH_NO_INLINE XXH_PUREF XXH64_hash_t
+XXH3_len_129to240_64b(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH64_hash_t seed)
+{
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize;
+ XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX);
+
+ #define XXH3_MIDSIZE_STARTOFFSET 3
+ #define XXH3_MIDSIZE_LASTOFFSET 17
+
+ { xxh_u64 acc = len * XXH_PRIME64_1;
+ xxh_u64 acc_end;
+ unsigned int const nbRounds = (unsigned int)len / 16;
+ unsigned int i;
+ XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX);
+ for (i=0; i<8; i++) {
+ acc += XXH3_mix16B(input+(16*i), secret+(16*i), seed);
+ }
+ /* last bytes */
+ acc_end = XXH3_mix16B(input + len - 16, secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET, seed);
+ XXH_ASSERT(nbRounds >= 8);
+ acc = XXH3_avalanche(acc);
+#if defined(__clang__) /* Clang */ \
+ && (defined(__ARM_NEON) || defined(__ARM_NEON__)) /* NEON */ \
+ && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable */
+ /*
+ * UGLY HACK:
+ * Clang for ARMv7-A tries to vectorize this loop, similar to GCC x86.
+ * In everywhere else, it uses scalar code.
+ *
+ * For 64->128-bit multiplies, even if the NEON was 100% optimal, it
+ * would still be slower than UMAAL (see XXH_mult64to128).
+ *
+ * Unfortunately, Clang doesn't handle the long multiplies properly and
+ * converts them to the nonexistent "vmulq_u64" intrinsic, which is then
+ * scalarized into an ugly mess of VMOV.32 instructions.
+ *
+ * This mess is difficult to avoid without turning autovectorization
+ * off completely, but they are usually relatively minor and/or not
+ * worth it to fix.
+ *
+ * This loop is the easiest to fix, as unlike XXH32, this pragma
+ * _actually works_ because it is a loop vectorization instead of an
+ * SLP vectorization.
+ */
+ #pragma clang loop vectorize(disable)
+#endif
+ for (i=8 ; i < nbRounds; i++) {
+ /*
+ * Prevents clang for unrolling the acc loop and interleaving with this one.
+ */
+ XXH_COMPILER_GUARD(acc);
+ acc_end += XXH3_mix16B(input+(16*i), secret+(16*(i-8)) + XXH3_MIDSIZE_STARTOFFSET, seed);
+ }
+ return XXH3_avalanche(acc + acc_end);
+ }
+}
+
+
+/* ======= Long Keys ======= */
+
+#define XXH_STRIPE_LEN 64
+#define XXH_SECRET_CONSUME_RATE 8 /* nb of secret bytes consumed at each accumulation */
+#define XXH_ACC_NB (XXH_STRIPE_LEN / sizeof(xxh_u64))
+
+#ifdef XXH_OLD_NAMES
+# define STRIPE_LEN XXH_STRIPE_LEN
+# define ACC_NB XXH_ACC_NB
+#endif
+
+#ifndef XXH_PREFETCH_DIST
+# ifdef __clang__
+# define XXH_PREFETCH_DIST 320
+# else
+# if (XXH_VECTOR == XXH_AVX512)
+# define XXH_PREFETCH_DIST 512
+# else
+# define XXH_PREFETCH_DIST 384
+# endif
+# endif /* __clang__ */
+#endif /* XXH_PREFETCH_DIST */
+
+/*
+ * These macros are to generate an XXH3_accumulate() function.
+ * The two arguments select the name suffix and target attribute.
+ *
+ * The name of this symbol is XXH3_accumulate_<name>() and it calls
+ * XXH3_accumulate_512_<name>().
+ *
+ * It may be useful to hand implement this function if the compiler fails to
+ * optimize the inline function.
+ */
+#define XXH3_ACCUMULATE_TEMPLATE(name) \
+void \
+XXH3_accumulate_##name(xxh_u64* XXH_RESTRICT acc, \
+ const xxh_u8* XXH_RESTRICT input, \
+ const xxh_u8* XXH_RESTRICT secret, \
+ size_t nbStripes) \
+{ \
+ size_t n; \
+ for (n = 0; n < nbStripes; n++ ) { \
+ const xxh_u8* const in = input + n*XXH_STRIPE_LEN; \
+ XXH_PREFETCH(in + XXH_PREFETCH_DIST); \
+ XXH3_accumulate_512_##name( \
+ acc, \
+ in, \
+ secret + n*XXH_SECRET_CONSUME_RATE); \
+ } \
+}
+
+
+XXH_FORCE_INLINE void XXH_writeLE64(void* dst, xxh_u64 v64)
+{
+ if (!XXH_CPU_LITTLE_ENDIAN) v64 = XXH_swap64(v64);
+ XXH_memcpy(dst, &v64, sizeof(v64));
+}
+
+/* Several intrinsic functions below are supposed to accept __int64 as argument,
+ * as documented in https://software.intel.com/sites/landingpage/IntrinsicsGuide/ .
+ * However, several environments do not define __int64 type,
+ * requiring a workaround.
+ */
+#if !defined (__VMS) \
+ && (defined (__cplusplus) \
+ || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) )
+ typedef int64_t xxh_i64;
+#else
+ /* the following type must have a width of 64-bit */
+ typedef long long xxh_i64;
+#endif
+
+
+/*
+ * XXH3_accumulate_512 is the tightest loop for long inputs, and it is the most optimized.
+ *
+ * It is a hardened version of UMAC, based off of FARSH's implementation.
+ *
+ * This was chosen because it adapts quite well to 32-bit, 64-bit, and SIMD
+ * implementations, and it is ridiculously fast.
+ *
+ * We harden it by mixing the original input to the accumulators as well as the product.
+ *
+ * This means that in the (relatively likely) case of a multiply by zero, the
+ * original input is preserved.
+ *
+ * On 128-bit inputs, we swap 64-bit pairs when we add the input to improve
+ * cross-pollination, as otherwise the upper and lower halves would be
+ * essentially independent.
+ *
+ * This doesn't matter on 64-bit hashes since they all get merged together in
+ * the end, so we skip the extra step.
+ *
+ * Both XXH3_64bits and XXH3_128bits use this subroutine.
+ */
+
+#if (XXH_VECTOR == XXH_AVX512) \
+ || (defined(XXH_DISPATCH_AVX512) && XXH_DISPATCH_AVX512 != 0)
+
+#ifndef XXH_TARGET_AVX512
+# define XXH_TARGET_AVX512 /* disable attribute target */
+#endif
+
+XXH_FORCE_INLINE XXH_TARGET_AVX512 void
+XXH3_accumulate_512_avx512(void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ __m512i* const xacc = (__m512i *) acc;
+ XXH_ASSERT((((size_t)acc) & 63) == 0);
+ XXH_STATIC_ASSERT(XXH_STRIPE_LEN == sizeof(__m512i));
+
+ {
+ /* data_vec = input[0]; */
+ __m512i const data_vec = _mm512_loadu_si512 (input);
+ /* key_vec = secret[0]; */
+ __m512i const key_vec = _mm512_loadu_si512 (secret);
+ /* data_key = data_vec ^ key_vec; */
+ __m512i const data_key = _mm512_xor_si512 (data_vec, key_vec);
+ /* data_key_lo = data_key >> 32; */
+ __m512i const data_key_lo = _mm512_srli_epi64 (data_key, 32);
+ /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */
+ __m512i const product = _mm512_mul_epu32 (data_key, data_key_lo);
+ /* xacc[0] += swap(data_vec); */
+ __m512i const data_swap = _mm512_shuffle_epi32(data_vec, (_MM_PERM_ENUM)_MM_SHUFFLE(1, 0, 3, 2));
+ __m512i const sum = _mm512_add_epi64(*xacc, data_swap);
+ /* xacc[0] += product; */
+ *xacc = _mm512_add_epi64(product, sum);
+ }
+}
+XXH_FORCE_INLINE XXH_TARGET_AVX512 XXH3_ACCUMULATE_TEMPLATE(avx512)
+
+/*
+ * XXH3_scrambleAcc: Scrambles the accumulators to improve mixing.
+ *
+ * Multiplication isn't perfect, as explained by Google in HighwayHash:
+ *
+ * // Multiplication mixes/scrambles bytes 0-7 of the 64-bit result to
+ * // varying degrees. In descending order of goodness, bytes
+ * // 3 4 2 5 1 6 0 7 have quality 228 224 164 160 100 96 36 32.
+ * // As expected, the upper and lower bytes are much worse.
+ *
+ * Source: https://github.com/google/highwayhash/blob/0aaf66b/highwayhash/hh_avx2.h#L291
+ *
+ * Since our algorithm uses a pseudorandom secret to add some variance into the
+ * mix, we don't need to (or want to) mix as often or as much as HighwayHash does.
+ *
+ * This isn't as tight as XXH3_accumulate, but still written in SIMD to avoid
+ * extraction.
+ *
+ * Both XXH3_64bits and XXH3_128bits use this subroutine.
+ */
+
+XXH_FORCE_INLINE XXH_TARGET_AVX512 void
+XXH3_scrambleAcc_avx512(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 63) == 0);
+ XXH_STATIC_ASSERT(XXH_STRIPE_LEN == sizeof(__m512i));
+ { __m512i* const xacc = (__m512i*) acc;
+ const __m512i prime32 = _mm512_set1_epi32((int)XXH_PRIME32_1);
+
+ /* xacc[0] ^= (xacc[0] >> 47) */
+ __m512i const acc_vec = *xacc;
+ __m512i const shifted = _mm512_srli_epi64 (acc_vec, 47);
+ /* xacc[0] ^= secret; */
+ __m512i const key_vec = _mm512_loadu_si512 (secret);
+ __m512i const data_key = _mm512_ternarylogic_epi32(key_vec, acc_vec, shifted, 0x96 /* key_vec ^ acc_vec ^ shifted */);
+
+ /* xacc[0] *= XXH_PRIME32_1; */
+ __m512i const data_key_hi = _mm512_srli_epi64 (data_key, 32);
+ __m512i const prod_lo = _mm512_mul_epu32 (data_key, prime32);
+ __m512i const prod_hi = _mm512_mul_epu32 (data_key_hi, prime32);
+ *xacc = _mm512_add_epi64(prod_lo, _mm512_slli_epi64(prod_hi, 32));
+ }
+}
+
+XXH_FORCE_INLINE XXH_TARGET_AVX512 void
+XXH3_initCustomSecret_avx512(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
+{
+ XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 63) == 0);
+ XXH_STATIC_ASSERT(XXH_SEC_ALIGN == 64);
+ XXH_ASSERT(((size_t)customSecret & 63) == 0);
+ (void)(&XXH_writeLE64);
+ { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / sizeof(__m512i);
+ __m512i const seed_pos = _mm512_set1_epi64((xxh_i64)seed64);
+ __m512i const seed = _mm512_mask_sub_epi64(seed_pos, 0xAA, _mm512_set1_epi8(0), seed_pos);
+
+ const __m512i* const src = (const __m512i*) ((const void*) XXH3_kSecret);
+ __m512i* const dest = ( __m512i*) customSecret;
+ int i;
+ XXH_ASSERT(((size_t)src & 63) == 0); /* control alignment */
+ XXH_ASSERT(((size_t)dest & 63) == 0);
+ for (i=0; i < nbRounds; ++i) {
+ dest[i] = _mm512_add_epi64(_mm512_load_si512(src + i), seed);
+ } }
+}
+
+#endif
+
+#if (XXH_VECTOR == XXH_AVX2) \
+ || (defined(XXH_DISPATCH_AVX2) && XXH_DISPATCH_AVX2 != 0)
+
+#ifndef XXH_TARGET_AVX2
+# define XXH_TARGET_AVX2 /* disable attribute target */
+#endif
+
+XXH_FORCE_INLINE XXH_TARGET_AVX2 void
+XXH3_accumulate_512_avx2( void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 31) == 0);
+ { __m256i* const xacc = (__m256i *) acc;
+ /* Unaligned. This is mainly for pointer arithmetic, and because
+ * _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */
+ const __m256i* const xinput = (const __m256i *) input;
+ /* Unaligned. This is mainly for pointer arithmetic, and because
+ * _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */
+ const __m256i* const xsecret = (const __m256i *) secret;
+
+ size_t i;
+ for (i=0; i < XXH_STRIPE_LEN/sizeof(__m256i); i++) {
+ /* data_vec = xinput[i]; */
+ __m256i const data_vec = _mm256_loadu_si256 (xinput+i);
+ /* key_vec = xsecret[i]; */
+ __m256i const key_vec = _mm256_loadu_si256 (xsecret+i);
+ /* data_key = data_vec ^ key_vec; */
+ __m256i const data_key = _mm256_xor_si256 (data_vec, key_vec);
+ /* data_key_lo = data_key >> 32; */
+ __m256i const data_key_lo = _mm256_srli_epi64 (data_key, 32);
+ /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */
+ __m256i const product = _mm256_mul_epu32 (data_key, data_key_lo);
+ /* xacc[i] += swap(data_vec); */
+ __m256i const data_swap = _mm256_shuffle_epi32(data_vec, _MM_SHUFFLE(1, 0, 3, 2));
+ __m256i const sum = _mm256_add_epi64(xacc[i], data_swap);
+ /* xacc[i] += product; */
+ xacc[i] = _mm256_add_epi64(product, sum);
+ } }
+}
+XXH_FORCE_INLINE XXH_TARGET_AVX2 XXH3_ACCUMULATE_TEMPLATE(avx2)
+
+XXH_FORCE_INLINE XXH_TARGET_AVX2 void
+XXH3_scrambleAcc_avx2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 31) == 0);
+ { __m256i* const xacc = (__m256i*) acc;
+ /* Unaligned. This is mainly for pointer arithmetic, and because
+ * _mm256_loadu_si256 requires a const __m256i * pointer for some reason. */
+ const __m256i* const xsecret = (const __m256i *) secret;
+ const __m256i prime32 = _mm256_set1_epi32((int)XXH_PRIME32_1);
+
+ size_t i;
+ for (i=0; i < XXH_STRIPE_LEN/sizeof(__m256i); i++) {
+ /* xacc[i] ^= (xacc[i] >> 47) */
+ __m256i const acc_vec = xacc[i];
+ __m256i const shifted = _mm256_srli_epi64 (acc_vec, 47);
+ __m256i const data_vec = _mm256_xor_si256 (acc_vec, shifted);
+ /* xacc[i] ^= xsecret; */
+ __m256i const key_vec = _mm256_loadu_si256 (xsecret+i);
+ __m256i const data_key = _mm256_xor_si256 (data_vec, key_vec);
+
+ /* xacc[i] *= XXH_PRIME32_1; */
+ __m256i const data_key_hi = _mm256_srli_epi64 (data_key, 32);
+ __m256i const prod_lo = _mm256_mul_epu32 (data_key, prime32);
+ __m256i const prod_hi = _mm256_mul_epu32 (data_key_hi, prime32);
+ xacc[i] = _mm256_add_epi64(prod_lo, _mm256_slli_epi64(prod_hi, 32));
+ }
+ }
+}
+
+XXH_FORCE_INLINE XXH_TARGET_AVX2 void XXH3_initCustomSecret_avx2(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
+{
+ XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 31) == 0);
+ XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE / sizeof(__m256i)) == 6);
+ XXH_STATIC_ASSERT(XXH_SEC_ALIGN <= 64);
+ (void)(&XXH_writeLE64);
+ XXH_PREFETCH(customSecret);
+ { __m256i const seed = _mm256_set_epi64x((xxh_i64)(0U - seed64), (xxh_i64)seed64, (xxh_i64)(0U - seed64), (xxh_i64)seed64);
+
+ const __m256i* const src = (const __m256i*) ((const void*) XXH3_kSecret);
+ __m256i* dest = ( __m256i*) customSecret;
+
+# if defined(__GNUC__) || defined(__clang__)
+ /*
+ * On GCC & Clang, marking 'dest' as modified will cause the compiler:
+ * - do not extract the secret from sse registers in the internal loop
+ * - use less common registers, and avoid pushing these reg into stack
+ */
+ XXH_COMPILER_GUARD(dest);
+# endif
+ XXH_ASSERT(((size_t)src & 31) == 0); /* control alignment */
+ XXH_ASSERT(((size_t)dest & 31) == 0);
+
+ /* GCC -O2 need unroll loop manually */
+ dest[0] = _mm256_add_epi64(_mm256_load_si256(src+0), seed);
+ dest[1] = _mm256_add_epi64(_mm256_load_si256(src+1), seed);
+ dest[2] = _mm256_add_epi64(_mm256_load_si256(src+2), seed);
+ dest[3] = _mm256_add_epi64(_mm256_load_si256(src+3), seed);
+ dest[4] = _mm256_add_epi64(_mm256_load_si256(src+4), seed);
+ dest[5] = _mm256_add_epi64(_mm256_load_si256(src+5), seed);
+ }
+}
+
+#endif
+
+/* x86dispatch always generates SSE2 */
+#if (XXH_VECTOR == XXH_SSE2) || defined(XXH_X86DISPATCH)
+
+#ifndef XXH_TARGET_SSE2
+# define XXH_TARGET_SSE2 /* disable attribute target */
+#endif
+
+XXH_FORCE_INLINE XXH_TARGET_SSE2 void
+XXH3_accumulate_512_sse2( void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ /* SSE2 is just a half-scale version of the AVX2 version. */
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+ { __m128i* const xacc = (__m128i *) acc;
+ /* Unaligned. This is mainly for pointer arithmetic, and because
+ * _mm_loadu_si128 requires a const __m128i * pointer for some reason. */
+ const __m128i* const xinput = (const __m128i *) input;
+ /* Unaligned. This is mainly for pointer arithmetic, and because
+ * _mm_loadu_si128 requires a const __m128i * pointer for some reason. */
+ const __m128i* const xsecret = (const __m128i *) secret;
+
+ size_t i;
+ for (i=0; i < XXH_STRIPE_LEN/sizeof(__m128i); i++) {
+ /* data_vec = xinput[i]; */
+ __m128i const data_vec = _mm_loadu_si128 (xinput+i);
+ /* key_vec = xsecret[i]; */
+ __m128i const key_vec = _mm_loadu_si128 (xsecret+i);
+ /* data_key = data_vec ^ key_vec; */
+ __m128i const data_key = _mm_xor_si128 (data_vec, key_vec);
+ /* data_key_lo = data_key >> 32; */
+ __m128i const data_key_lo = _mm_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1));
+ /* product = (data_key & 0xffffffff) * (data_key_lo & 0xffffffff); */
+ __m128i const product = _mm_mul_epu32 (data_key, data_key_lo);
+ /* xacc[i] += swap(data_vec); */
+ __m128i const data_swap = _mm_shuffle_epi32(data_vec, _MM_SHUFFLE(1,0,3,2));
+ __m128i const sum = _mm_add_epi64(xacc[i], data_swap);
+ /* xacc[i] += product; */
+ xacc[i] = _mm_add_epi64(product, sum);
+ } }
+}
+XXH_FORCE_INLINE XXH_TARGET_SSE2 XXH3_ACCUMULATE_TEMPLATE(sse2)
+
+XXH_FORCE_INLINE XXH_TARGET_SSE2 void
+XXH3_scrambleAcc_sse2(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+ { __m128i* const xacc = (__m128i*) acc;
+ /* Unaligned. This is mainly for pointer arithmetic, and because
+ * _mm_loadu_si128 requires a const __m128i * pointer for some reason. */
+ const __m128i* const xsecret = (const __m128i *) secret;
+ const __m128i prime32 = _mm_set1_epi32((int)XXH_PRIME32_1);
+
+ size_t i;
+ for (i=0; i < XXH_STRIPE_LEN/sizeof(__m128i); i++) {
+ /* xacc[i] ^= (xacc[i] >> 47) */
+ __m128i const acc_vec = xacc[i];
+ __m128i const shifted = _mm_srli_epi64 (acc_vec, 47);
+ __m128i const data_vec = _mm_xor_si128 (acc_vec, shifted);
+ /* xacc[i] ^= xsecret[i]; */
+ __m128i const key_vec = _mm_loadu_si128 (xsecret+i);
+ __m128i const data_key = _mm_xor_si128 (data_vec, key_vec);
+
+ /* xacc[i] *= XXH_PRIME32_1; */
+ __m128i const data_key_hi = _mm_shuffle_epi32 (data_key, _MM_SHUFFLE(0, 3, 0, 1));
+ __m128i const prod_lo = _mm_mul_epu32 (data_key, prime32);
+ __m128i const prod_hi = _mm_mul_epu32 (data_key_hi, prime32);
+ xacc[i] = _mm_add_epi64(prod_lo, _mm_slli_epi64(prod_hi, 32));
+ }
+ }
+}
+
+XXH_FORCE_INLINE XXH_TARGET_SSE2 void XXH3_initCustomSecret_sse2(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
+{
+ XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 15) == 0);
+ (void)(&XXH_writeLE64);
+ { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / sizeof(__m128i);
+
+# if defined(_MSC_VER) && defined(_M_IX86) && _MSC_VER < 1900
+ /* MSVC 32bit mode does not support _mm_set_epi64x before 2015 */
+ XXH_ALIGN(16) const xxh_i64 seed64x2[2] = { (xxh_i64)seed64, (xxh_i64)(0U - seed64) };
+ __m128i const seed = _mm_load_si128((__m128i const*)seed64x2);
+# else
+ __m128i const seed = _mm_set_epi64x((xxh_i64)(0U - seed64), (xxh_i64)seed64);
+# endif
+ int i;
+
+ const void* const src16 = XXH3_kSecret;
+ __m128i* dst16 = (__m128i*) customSecret;
+# if defined(__GNUC__) || defined(__clang__)
+ /*
+ * On GCC & Clang, marking 'dest' as modified will cause the compiler:
+ * - do not extract the secret from sse registers in the internal loop
+ * - use less common registers, and avoid pushing these reg into stack
+ */
+ XXH_COMPILER_GUARD(dst16);
+# endif
+ XXH_ASSERT(((size_t)src16 & 15) == 0); /* control alignment */
+ XXH_ASSERT(((size_t)dst16 & 15) == 0);
+
+ for (i=0; i < nbRounds; ++i) {
+ dst16[i] = _mm_add_epi64(_mm_load_si128((const __m128i *)src16+i), seed);
+ } }
+}
+
+#endif
+
+#if (XXH_VECTOR == XXH_NEON)
+
+/* forward declarations for the scalar routines */
+XXH_FORCE_INLINE void
+XXH3_scalarRound(void* XXH_RESTRICT acc, void const* XXH_RESTRICT input,
+ void const* XXH_RESTRICT secret, size_t lane);
+
+XXH_FORCE_INLINE void
+XXH3_scalarScrambleRound(void* XXH_RESTRICT acc,
+ void const* XXH_RESTRICT secret, size_t lane);
+
+/*!
+ * @internal
+ * @brief The bulk processing loop for NEON and WASM SIMD128.
+ *
+ * The NEON code path is actually partially scalar when running on AArch64. This
+ * is to optimize the pipelining and can have up to 15% speedup depending on the
+ * CPU, and it also mitigates some GCC codegen issues.
+ *
+ * @see XXH3_NEON_LANES for configuring this and details about this optimization.
+ *
+ * NEON's 32-bit to 64-bit long multiply takes a half vector of 32-bit
+ * integers instead of the other platforms which mask full 64-bit vectors,
+ * so the setup is more complicated than just shifting right.
+ *
+ * Additionally, there is an optimization for 4 lanes at once noted below.
+ *
+ * Since, as stated, the most optimal amount of lanes for Cortexes is 6,
+ * there needs to be *three* versions of the accumulate operation used
+ * for the remaining 2 lanes.
+ *
+ * WASM's SIMD128 uses SIMDe's arm_neon.h polyfill because the intrinsics overlap
+ * nearly perfectly.
+ */
+
+XXH_FORCE_INLINE void
+XXH3_accumulate_512_neon( void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+ XXH_STATIC_ASSERT(XXH3_NEON_LANES > 0 && XXH3_NEON_LANES <= XXH_ACC_NB && XXH3_NEON_LANES % 2 == 0);
+ { /* GCC for darwin arm64 does not like aliasing here */
+ xxh_aliasing_uint64x2_t* const xacc = (xxh_aliasing_uint64x2_t*) acc;
+ /* We don't use a uint32x4_t pointer because it causes bus errors on ARMv7. */
+ uint8_t const* xinput = (const uint8_t *) input;
+ uint8_t const* xsecret = (const uint8_t *) secret;
+
+ size_t i;
+#ifdef __wasm_simd128__
+ /*
+ * On WASM SIMD128, Clang emits direct address loads when XXH3_kSecret
+ * is constant propagated, which results in it converting it to this
+ * inside the loop:
+ *
+ * a = v128.load(XXH3_kSecret + 0 + $secret_offset, offset = 0)
+ * b = v128.load(XXH3_kSecret + 16 + $secret_offset, offset = 0)
+ * ...
+ *
+ * This requires a full 32-bit address immediate (and therefore a 6 byte
+ * instruction) as well as an add for each offset.
+ *
+ * Putting an asm guard prevents it from folding (at the cost of losing
+ * the alignment hint), and uses the free offset in `v128.load` instead
+ * of adding secret_offset each time which overall reduces code size by
+ * about a kilobyte and improves performance.
+ */
+ XXH_COMPILER_GUARD(xsecret);
+#endif
+ /* Scalar lanes use the normal scalarRound routine */
+ for (i = XXH3_NEON_LANES; i < XXH_ACC_NB; i++) {
+ XXH3_scalarRound(acc, input, secret, i);
+ }
+ i = 0;
+ /* 4 NEON lanes at a time. */
+ for (; i+1 < XXH3_NEON_LANES / 2; i+=2) {
+ /* data_vec = xinput[i]; */
+ uint64x2_t data_vec_1 = XXH_vld1q_u64(xinput + (i * 16));
+ uint64x2_t data_vec_2 = XXH_vld1q_u64(xinput + ((i+1) * 16));
+ /* key_vec = xsecret[i]; */
+ uint64x2_t key_vec_1 = XXH_vld1q_u64(xsecret + (i * 16));
+ uint64x2_t key_vec_2 = XXH_vld1q_u64(xsecret + ((i+1) * 16));
+ /* data_swap = swap(data_vec) */
+ uint64x2_t data_swap_1 = vextq_u64(data_vec_1, data_vec_1, 1);
+ uint64x2_t data_swap_2 = vextq_u64(data_vec_2, data_vec_2, 1);
+ /* data_key = data_vec ^ key_vec; */
+ uint64x2_t data_key_1 = veorq_u64(data_vec_1, key_vec_1);
+ uint64x2_t data_key_2 = veorq_u64(data_vec_2, key_vec_2);
+
+ /*
+ * If we reinterpret the 64x2 vectors as 32x4 vectors, we can use a
+ * de-interleave operation for 4 lanes in 1 step with `vuzpq_u32` to
+ * get one vector with the low 32 bits of each lane, and one vector
+ * with the high 32 bits of each lane.
+ *
+ * The intrinsic returns a double vector because the original ARMv7-a
+ * instruction modified both arguments in place. AArch64 and SIMD128 emit
+ * two instructions from this intrinsic.
+ *
+ * [ dk11L | dk11H | dk12L | dk12H ] -> [ dk11L | dk12L | dk21L | dk22L ]
+ * [ dk21L | dk21H | dk22L | dk22H ] -> [ dk11H | dk12H | dk21H | dk22H ]
+ */
+ uint32x4x2_t unzipped = vuzpq_u32(
+ vreinterpretq_u32_u64(data_key_1),
+ vreinterpretq_u32_u64(data_key_2)
+ );
+ /* data_key_lo = data_key & 0xFFFFFFFF */
+ uint32x4_t data_key_lo = unzipped.val[0];
+ /* data_key_hi = data_key >> 32 */
+ uint32x4_t data_key_hi = unzipped.val[1];
+ /*
+ * Then, we can split the vectors horizontally and multiply which, as for most
+ * widening intrinsics, have a variant that works on both high half vectors
+ * for free on AArch64. A similar instruction is available on SIMD128.
+ *
+ * sum = data_swap + (u64x2) data_key_lo * (u64x2) data_key_hi
+ */
+ uint64x2_t sum_1 = XXH_vmlal_low_u32(data_swap_1, data_key_lo, data_key_hi);
+ uint64x2_t sum_2 = XXH_vmlal_high_u32(data_swap_2, data_key_lo, data_key_hi);
+ /*
+ * Clang reorders
+ * a += b * c; // umlal swap.2d, dkl.2s, dkh.2s
+ * c += a; // add acc.2d, acc.2d, swap.2d
+ * to
+ * c += a; // add acc.2d, acc.2d, swap.2d
+ * c += b * c; // umlal acc.2d, dkl.2s, dkh.2s
+ *
+ * While it would make sense in theory since the addition is faster,
+ * for reasons likely related to umlal being limited to certain NEON
+ * pipelines, this is worse. A compiler guard fixes this.
+ */
+ XXH_COMPILER_GUARD_CLANG_NEON(sum_1);
+ XXH_COMPILER_GUARD_CLANG_NEON(sum_2);
+ /* xacc[i] = acc_vec + sum; */
+ xacc[i] = vaddq_u64(xacc[i], sum_1);
+ xacc[i+1] = vaddq_u64(xacc[i+1], sum_2);
+ }
+ /* Operate on the remaining NEON lanes 2 at a time. */
+ for (; i < XXH3_NEON_LANES / 2; i++) {
+ /* data_vec = xinput[i]; */
+ uint64x2_t data_vec = XXH_vld1q_u64(xinput + (i * 16));
+ /* key_vec = xsecret[i]; */
+ uint64x2_t key_vec = XXH_vld1q_u64(xsecret + (i * 16));
+ /* acc_vec_2 = swap(data_vec) */
+ uint64x2_t data_swap = vextq_u64(data_vec, data_vec, 1);
+ /* data_key = data_vec ^ key_vec; */
+ uint64x2_t data_key = veorq_u64(data_vec, key_vec);
+ /* For two lanes, just use VMOVN and VSHRN. */
+ /* data_key_lo = data_key & 0xFFFFFFFF; */
+ uint32x2_t data_key_lo = vmovn_u64(data_key);
+ /* data_key_hi = data_key >> 32; */
+ uint32x2_t data_key_hi = vshrn_n_u64(data_key, 32);
+ /* sum = data_swap + (u64x2) data_key_lo * (u64x2) data_key_hi; */
+ uint64x2_t sum = vmlal_u32(data_swap, data_key_lo, data_key_hi);
+ /* Same Clang workaround as before */
+ XXH_COMPILER_GUARD_CLANG_NEON(sum);
+ /* xacc[i] = acc_vec + sum; */
+ xacc[i] = vaddq_u64 (xacc[i], sum);
+ }
+ }
+}
+XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(neon)
+
+XXH_FORCE_INLINE void
+XXH3_scrambleAcc_neon(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+
+ { xxh_aliasing_uint64x2_t* xacc = (xxh_aliasing_uint64x2_t*) acc;
+ uint8_t const* xsecret = (uint8_t const*) secret;
+
+ size_t i;
+ /* WASM uses operator overloads and doesn't need these. */
+#ifndef __wasm_simd128__
+ /* { prime32_1, prime32_1 } */
+ uint32x2_t const kPrimeLo = vdup_n_u32(XXH_PRIME32_1);
+ /* { 0, prime32_1, 0, prime32_1 } */
+ uint32x4_t const kPrimeHi = vreinterpretq_u32_u64(vdupq_n_u64((xxh_u64)XXH_PRIME32_1 << 32));
+#endif
+
+ /* AArch64 uses both scalar and neon at the same time */
+ for (i = XXH3_NEON_LANES; i < XXH_ACC_NB; i++) {
+ XXH3_scalarScrambleRound(acc, secret, i);
+ }
+ for (i=0; i < XXH3_NEON_LANES / 2; i++) {
+ /* xacc[i] ^= (xacc[i] >> 47); */
+ uint64x2_t acc_vec = xacc[i];
+ uint64x2_t shifted = vshrq_n_u64(acc_vec, 47);
+ uint64x2_t data_vec = veorq_u64(acc_vec, shifted);
+
+ /* xacc[i] ^= xsecret[i]; */
+ uint64x2_t key_vec = XXH_vld1q_u64(xsecret + (i * 16));
+ uint64x2_t data_key = veorq_u64(data_vec, key_vec);
+ /* xacc[i] *= XXH_PRIME32_1 */
+#ifdef __wasm_simd128__
+ /* SIMD128 has multiply by u64x2, use it instead of expanding and scalarizing */
+ xacc[i] = data_key * XXH_PRIME32_1;
+#else
+ /*
+ * Expanded version with portable NEON intrinsics
+ *
+ * lo(x) * lo(y) + (hi(x) * lo(y) << 32)
+ *
+ * prod_hi = hi(data_key) * lo(prime) << 32
+ *
+ * Since we only need 32 bits of this multiply a trick can be used, reinterpreting the vector
+ * as a uint32x4_t and multiplying by { 0, prime, 0, prime } to cancel out the unwanted bits
+ * and avoid the shift.
+ */
+ uint32x4_t prod_hi = vmulq_u32 (vreinterpretq_u32_u64(data_key), kPrimeHi);
+ /* Extract low bits for vmlal_u32 */
+ uint32x2_t data_key_lo = vmovn_u64(data_key);
+ /* xacc[i] = prod_hi + lo(data_key) * XXH_PRIME32_1; */
+ xacc[i] = vmlal_u32(vreinterpretq_u64_u32(prod_hi), data_key_lo, kPrimeLo);
+#endif
+ }
+ }
+}
+#endif
+
+#if (XXH_VECTOR == XXH_VSX)
+
+XXH_FORCE_INLINE void
+XXH3_accumulate_512_vsx( void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ /* presumed aligned */
+ xxh_aliasing_u64x2* const xacc = (xxh_aliasing_u64x2*) acc;
+ xxh_u8 const* const xinput = (xxh_u8 const*) input; /* no alignment restriction */
+ xxh_u8 const* const xsecret = (xxh_u8 const*) secret; /* no alignment restriction */
+ xxh_u64x2 const v32 = { 32, 32 };
+ size_t i;
+ for (i = 0; i < XXH_STRIPE_LEN / sizeof(xxh_u64x2); i++) {
+ /* data_vec = xinput[i]; */
+ xxh_u64x2 const data_vec = XXH_vec_loadu(xinput + 16*i);
+ /* key_vec = xsecret[i]; */
+ xxh_u64x2 const key_vec = XXH_vec_loadu(xsecret + 16*i);
+ xxh_u64x2 const data_key = data_vec ^ key_vec;
+ /* shuffled = (data_key << 32) | (data_key >> 32); */
+ xxh_u32x4 const shuffled = (xxh_u32x4)vec_rl(data_key, v32);
+ /* product = ((xxh_u64x2)data_key & 0xFFFFFFFF) * ((xxh_u64x2)shuffled & 0xFFFFFFFF); */
+ xxh_u64x2 const product = XXH_vec_mulo((xxh_u32x4)data_key, shuffled);
+ /* acc_vec = xacc[i]; */
+ xxh_u64x2 acc_vec = xacc[i];
+ acc_vec += product;
+
+ /* swap high and low halves */
+#ifdef __s390x__
+ acc_vec += vec_permi(data_vec, data_vec, 2);
+#else
+ acc_vec += vec_xxpermdi(data_vec, data_vec, 2);
+#endif
+ xacc[i] = acc_vec;
+ }
+}
+XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(vsx)
+
+XXH_FORCE_INLINE void
+XXH3_scrambleAcc_vsx(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+ XXH_ASSERT((((size_t)acc) & 15) == 0);
+
+ { xxh_aliasing_u64x2* const xacc = (xxh_aliasing_u64x2*) acc;
+ const xxh_u8* const xsecret = (const xxh_u8*) secret;
+ /* constants */
+ xxh_u64x2 const v32 = { 32, 32 };
+ xxh_u64x2 const v47 = { 47, 47 };
+ xxh_u32x4 const prime = { XXH_PRIME32_1, XXH_PRIME32_1, XXH_PRIME32_1, XXH_PRIME32_1 };
+ size_t i;
+ for (i = 0; i < XXH_STRIPE_LEN / sizeof(xxh_u64x2); i++) {
+ /* xacc[i] ^= (xacc[i] >> 47); */
+ xxh_u64x2 const acc_vec = xacc[i];
+ xxh_u64x2 const data_vec = acc_vec ^ (acc_vec >> v47);
+
+ /* xacc[i] ^= xsecret[i]; */
+ xxh_u64x2 const key_vec = XXH_vec_loadu(xsecret + 16*i);
+ xxh_u64x2 const data_key = data_vec ^ key_vec;
+
+ /* xacc[i] *= XXH_PRIME32_1 */
+ /* prod_lo = ((xxh_u64x2)data_key & 0xFFFFFFFF) * ((xxh_u64x2)prime & 0xFFFFFFFF); */
+ xxh_u64x2 const prod_even = XXH_vec_mule((xxh_u32x4)data_key, prime);
+ /* prod_hi = ((xxh_u64x2)data_key >> 32) * ((xxh_u64x2)prime >> 32); */
+ xxh_u64x2 const prod_odd = XXH_vec_mulo((xxh_u32x4)data_key, prime);
+ xacc[i] = prod_odd + (prod_even << v32);
+ } }
+}
+
+#endif
+
+#if (XXH_VECTOR == XXH_SVE)
+
+XXH_FORCE_INLINE void
+XXH3_accumulate_512_sve( void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ uint64_t *xacc = (uint64_t *)acc;
+ const uint64_t *xinput = (const uint64_t *)(const void *)input;
+ const uint64_t *xsecret = (const uint64_t *)(const void *)secret;
+ svuint64_t kSwap = sveor_n_u64_z(svptrue_b64(), svindex_u64(0, 1), 1);
+ uint64_t element_count = svcntd();
+ if (element_count >= 8) {
+ svbool_t mask = svptrue_pat_b64(SV_VL8);
+ svuint64_t vacc = svld1_u64(mask, xacc);
+ ACCRND(vacc, 0);
+ svst1_u64(mask, xacc, vacc);
+ } else if (element_count == 2) { /* sve128 */
+ svbool_t mask = svptrue_pat_b64(SV_VL2);
+ svuint64_t acc0 = svld1_u64(mask, xacc + 0);
+ svuint64_t acc1 = svld1_u64(mask, xacc + 2);
+ svuint64_t acc2 = svld1_u64(mask, xacc + 4);
+ svuint64_t acc3 = svld1_u64(mask, xacc + 6);
+ ACCRND(acc0, 0);
+ ACCRND(acc1, 2);
+ ACCRND(acc2, 4);
+ ACCRND(acc3, 6);
+ svst1_u64(mask, xacc + 0, acc0);
+ svst1_u64(mask, xacc + 2, acc1);
+ svst1_u64(mask, xacc + 4, acc2);
+ svst1_u64(mask, xacc + 6, acc3);
+ } else {
+ svbool_t mask = svptrue_pat_b64(SV_VL4);
+ svuint64_t acc0 = svld1_u64(mask, xacc + 0);
+ svuint64_t acc1 = svld1_u64(mask, xacc + 4);
+ ACCRND(acc0, 0);
+ ACCRND(acc1, 4);
+ svst1_u64(mask, xacc + 0, acc0);
+ svst1_u64(mask, xacc + 4, acc1);
+ }
+}
+
+XXH_FORCE_INLINE void
+XXH3_accumulate_sve(xxh_u64* XXH_RESTRICT acc,
+ const xxh_u8* XXH_RESTRICT input,
+ const xxh_u8* XXH_RESTRICT secret,
+ size_t nbStripes)
+{
+ if (nbStripes != 0) {
+ uint64_t *xacc = (uint64_t *)acc;
+ const uint64_t *xinput = (const uint64_t *)(const void *)input;
+ const uint64_t *xsecret = (const uint64_t *)(const void *)secret;
+ svuint64_t kSwap = sveor_n_u64_z(svptrue_b64(), svindex_u64(0, 1), 1);
+ uint64_t element_count = svcntd();
+ if (element_count >= 8) {
+ svbool_t mask = svptrue_pat_b64(SV_VL8);
+ svuint64_t vacc = svld1_u64(mask, xacc + 0);
+ do {
+ /* svprfd(svbool_t, void *, enum svfprop); */
+ svprfd(mask, xinput + 128, SV_PLDL1STRM);
+ ACCRND(vacc, 0);
+ xinput += 8;
+ xsecret += 1;
+ nbStripes--;
+ } while (nbStripes != 0);
+
+ svst1_u64(mask, xacc + 0, vacc);
+ } else if (element_count == 2) { /* sve128 */
+ svbool_t mask = svptrue_pat_b64(SV_VL2);
+ svuint64_t acc0 = svld1_u64(mask, xacc + 0);
+ svuint64_t acc1 = svld1_u64(mask, xacc + 2);
+ svuint64_t acc2 = svld1_u64(mask, xacc + 4);
+ svuint64_t acc3 = svld1_u64(mask, xacc + 6);
+ do {
+ svprfd(mask, xinput + 128, SV_PLDL1STRM);
+ ACCRND(acc0, 0);
+ ACCRND(acc1, 2);
+ ACCRND(acc2, 4);
+ ACCRND(acc3, 6);
+ xinput += 8;
+ xsecret += 1;
+ nbStripes--;
+ } while (nbStripes != 0);
+
+ svst1_u64(mask, xacc + 0, acc0);
+ svst1_u64(mask, xacc + 2, acc1);
+ svst1_u64(mask, xacc + 4, acc2);
+ svst1_u64(mask, xacc + 6, acc3);
+ } else {
+ svbool_t mask = svptrue_pat_b64(SV_VL4);
+ svuint64_t acc0 = svld1_u64(mask, xacc + 0);
+ svuint64_t acc1 = svld1_u64(mask, xacc + 4);
+ do {
+ svprfd(mask, xinput + 128, SV_PLDL1STRM);
+ ACCRND(acc0, 0);
+ ACCRND(acc1, 4);
+ xinput += 8;
+ xsecret += 1;
+ nbStripes--;
+ } while (nbStripes != 0);
+
+ svst1_u64(mask, xacc + 0, acc0);
+ svst1_u64(mask, xacc + 4, acc1);
+ }
+ }
+}
+
+#endif
+
+/* scalar variants - universal */
+
+#if defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__))
+/*
+ * In XXH3_scalarRound(), GCC and Clang have a similar codegen issue, where they
+ * emit an excess mask and a full 64-bit multiply-add (MADD X-form).
+ *
+ * While this might not seem like much, as AArch64 is a 64-bit architecture, only
+ * big Cortex designs have a full 64-bit multiplier.
+ *
+ * On the little cores, the smaller 32-bit multiplier is used, and full 64-bit
+ * multiplies expand to 2-3 multiplies in microcode. This has a major penalty
+ * of up to 4 latency cycles and 2 stall cycles in the multiply pipeline.
+ *
+ * Thankfully, AArch64 still provides the 32-bit long multiply-add (UMADDL) which does
+ * not have this penalty and does the mask automatically.
+ */
+XXH_FORCE_INLINE xxh_u64
+XXH_mult32to64_add64(xxh_u64 lhs, xxh_u64 rhs, xxh_u64 acc)
+{
+ xxh_u64 ret;
+ /* note: %x = 64-bit register, %w = 32-bit register */
+ __asm__("umaddl %x0, %w1, %w2, %x3" : "=r" (ret) : "r" (lhs), "r" (rhs), "r" (acc));
+ return ret;
+}
+#else
+XXH_FORCE_INLINE xxh_u64
+XXH_mult32to64_add64(xxh_u64 lhs, xxh_u64 rhs, xxh_u64 acc)
+{
+ return XXH_mult32to64((xxh_u32)lhs, (xxh_u32)rhs) + acc;
+}
+#endif
+
+/*!
+ * @internal
+ * @brief Scalar round for @ref XXH3_accumulate_512_scalar().
+ *
+ * This is extracted to its own function because the NEON path uses a combination
+ * of NEON and scalar.
+ */
+XXH_FORCE_INLINE void
+XXH3_scalarRound(void* XXH_RESTRICT acc,
+ void const* XXH_RESTRICT input,
+ void const* XXH_RESTRICT secret,
+ size_t lane)
+{
+ xxh_u64* xacc = (xxh_u64*) acc;
+ xxh_u8 const* xinput = (xxh_u8 const*) input;
+ xxh_u8 const* xsecret = (xxh_u8 const*) secret;
+ XXH_ASSERT(lane < XXH_ACC_NB);
+ XXH_ASSERT(((size_t)acc & (XXH_ACC_ALIGN-1)) == 0);
+ {
+ xxh_u64 const data_val = XXH_readLE64(xinput + lane * 8);
+ xxh_u64 const data_key = data_val ^ XXH_readLE64(xsecret + lane * 8);
+ xacc[lane ^ 1] += data_val; /* swap adjacent lanes */
+ xacc[lane] = XXH_mult32to64_add64(data_key /* & 0xFFFFFFFF */, data_key >> 32, xacc[lane]);
+ }
+}
+
+/*!
+ * @internal
+ * @brief Processes a 64 byte block of data using the scalar path.
+ */
+XXH_FORCE_INLINE void
+XXH3_accumulate_512_scalar(void* XXH_RESTRICT acc,
+ const void* XXH_RESTRICT input,
+ const void* XXH_RESTRICT secret)
+{
+ size_t i;
+ /* ARM GCC refuses to unroll this loop, resulting in a 24% slowdown on ARMv6. */
+#if defined(__GNUC__) && !defined(__clang__) \
+ && (defined(__arm__) || defined(__thumb2__)) \
+ && defined(__ARM_FEATURE_UNALIGNED) /* no unaligned access just wastes bytes */ \
+ && XXH_SIZE_OPT <= 0
+# pragma GCC unroll 8
+#endif
+ for (i=0; i < XXH_ACC_NB; i++) {
+ XXH3_scalarRound(acc, input, secret, i);
+ }
+}
+XXH_FORCE_INLINE XXH3_ACCUMULATE_TEMPLATE(scalar)
+
+/*!
+ * @internal
+ * @brief Scalar scramble step for @ref XXH3_scrambleAcc_scalar().
+ *
+ * This is extracted to its own function because the NEON path uses a combination
+ * of NEON and scalar.
+ */
+XXH_FORCE_INLINE void
+XXH3_scalarScrambleRound(void* XXH_RESTRICT acc,
+ void const* XXH_RESTRICT secret,
+ size_t lane)
+{
+ xxh_u64* const xacc = (xxh_u64*) acc; /* presumed aligned */
+ const xxh_u8* const xsecret = (const xxh_u8*) secret; /* no alignment restriction */
+ XXH_ASSERT((((size_t)acc) & (XXH_ACC_ALIGN-1)) == 0);
+ XXH_ASSERT(lane < XXH_ACC_NB);
+ {
+ xxh_u64 const key64 = XXH_readLE64(xsecret + lane * 8);
+ xxh_u64 acc64 = xacc[lane];
+ acc64 = XXH_xorshift64(acc64, 47);
+ acc64 ^= key64;
+ acc64 *= XXH_PRIME32_1;
+ xacc[lane] = acc64;
+ }
+}
+
+/*!
+ * @internal
+ * @brief Scrambles the accumulators after a large chunk has been read
+ */
+XXH_FORCE_INLINE void
+XXH3_scrambleAcc_scalar(void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret)
+{
+ size_t i;
+ for (i=0; i < XXH_ACC_NB; i++) {
+ XXH3_scalarScrambleRound(acc, secret, i);
+ }
+}
+
+XXH_FORCE_INLINE void
+XXH3_initCustomSecret_scalar(void* XXH_RESTRICT customSecret, xxh_u64 seed64)
+{
+ /*
+ * We need a separate pointer for the hack below,
+ * which requires a non-const pointer.
+ * Any decent compiler will optimize this out otherwise.
+ */
+ const xxh_u8* kSecretPtr = XXH3_kSecret;
+ XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 15) == 0);
+
+#if defined(__GNUC__) && defined(__aarch64__)
+ /*
+ * UGLY HACK:
+ * GCC and Clang generate a bunch of MOV/MOVK pairs for aarch64, and they are
+ * placed sequentially, in order, at the top of the unrolled loop.
+ *
+ * While MOVK is great for generating constants (2 cycles for a 64-bit
+ * constant compared to 4 cycles for LDR), it fights for bandwidth with
+ * the arithmetic instructions.
+ *
+ * I L S
+ * MOVK
+ * MOVK
+ * MOVK
+ * MOVK
+ * ADD
+ * SUB STR
+ * STR
+ * By forcing loads from memory (as the asm line causes the compiler to assume
+ * that XXH3_kSecretPtr has been changed), the pipelines are used more
+ * efficiently:
+ * I L S
+ * LDR
+ * ADD LDR
+ * SUB STR
+ * STR
+ *
+ * See XXH3_NEON_LANES for details on the pipsline.
+ *
+ * XXH3_64bits_withSeed, len == 256, Snapdragon 835
+ * without hack: 2654.4 MB/s
+ * with hack: 3202.9 MB/s
+ */
+ XXH_COMPILER_GUARD(kSecretPtr);
+#endif
+ { int const nbRounds = XXH_SECRET_DEFAULT_SIZE / 16;
+ int i;
+ for (i=0; i < nbRounds; i++) {
+ /*
+ * The asm hack causes the compiler to assume that kSecretPtr aliases with
+ * customSecret, and on aarch64, this prevented LDP from merging two
+ * loads together for free. Putting the loads together before the stores
+ * properly generates LDP.
+ */
+ xxh_u64 lo = XXH_readLE64(kSecretPtr + 16*i) + seed64;
+ xxh_u64 hi = XXH_readLE64(kSecretPtr + 16*i + 8) - seed64;
+ XXH_writeLE64((xxh_u8*)customSecret + 16*i, lo);
+ XXH_writeLE64((xxh_u8*)customSecret + 16*i + 8, hi);
+ } }
+}
+
+
+typedef void (*XXH3_f_accumulate)(xxh_u64* XXH_RESTRICT, const xxh_u8* XXH_RESTRICT, const xxh_u8* XXH_RESTRICT, size_t);
+typedef void (*XXH3_f_scrambleAcc)(void* XXH_RESTRICT, const void*);
+typedef void (*XXH3_f_initCustomSecret)(void* XXH_RESTRICT, xxh_u64);
+
+
+#if (XXH_VECTOR == XXH_AVX512)
+
+#define XXH3_accumulate_512 XXH3_accumulate_512_avx512
+#define XXH3_accumulate XXH3_accumulate_avx512
+#define XXH3_scrambleAcc XXH3_scrambleAcc_avx512
+#define XXH3_initCustomSecret XXH3_initCustomSecret_avx512
+
+#elif (XXH_VECTOR == XXH_AVX2)
+
+#define XXH3_accumulate_512 XXH3_accumulate_512_avx2
+#define XXH3_accumulate XXH3_accumulate_avx2
+#define XXH3_scrambleAcc XXH3_scrambleAcc_avx2
+#define XXH3_initCustomSecret XXH3_initCustomSecret_avx2
+
+#elif (XXH_VECTOR == XXH_SSE2)
+
+#define XXH3_accumulate_512 XXH3_accumulate_512_sse2
+#define XXH3_accumulate XXH3_accumulate_sse2
+#define XXH3_scrambleAcc XXH3_scrambleAcc_sse2
+#define XXH3_initCustomSecret XXH3_initCustomSecret_sse2
+
+#elif (XXH_VECTOR == XXH_NEON)
+
+#define XXH3_accumulate_512 XXH3_accumulate_512_neon
+#define XXH3_accumulate XXH3_accumulate_neon
+#define XXH3_scrambleAcc XXH3_scrambleAcc_neon
+#define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
+
+#elif (XXH_VECTOR == XXH_VSX)
+
+#define XXH3_accumulate_512 XXH3_accumulate_512_vsx
+#define XXH3_accumulate XXH3_accumulate_vsx
+#define XXH3_scrambleAcc XXH3_scrambleAcc_vsx
+#define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
+
+#elif (XXH_VECTOR == XXH_SVE)
+#define XXH3_accumulate_512 XXH3_accumulate_512_sve
+#define XXH3_accumulate XXH3_accumulate_sve
+#define XXH3_scrambleAcc XXH3_scrambleAcc_scalar
+#define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
+
+#else /* scalar */
+
+#define XXH3_accumulate_512 XXH3_accumulate_512_scalar
+#define XXH3_accumulate XXH3_accumulate_scalar
+#define XXH3_scrambleAcc XXH3_scrambleAcc_scalar
+#define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
+
+#endif
+
+#if XXH_SIZE_OPT >= 1 /* don't do SIMD for initialization */
+# undef XXH3_initCustomSecret
+# define XXH3_initCustomSecret XXH3_initCustomSecret_scalar
+#endif
+
+XXH_FORCE_INLINE void
+XXH3_hashLong_internal_loop(xxh_u64* XXH_RESTRICT acc,
+ const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH3_f_accumulate f_acc,
+ XXH3_f_scrambleAcc f_scramble)
+{
+ size_t const nbStripesPerBlock = (secretSize - XXH_STRIPE_LEN) / XXH_SECRET_CONSUME_RATE;
+ size_t const block_len = XXH_STRIPE_LEN * nbStripesPerBlock;
+ size_t const nb_blocks = (len - 1) / block_len;
+
+ size_t n;
+
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
+
+ for (n = 0; n < nb_blocks; n++) {
+ f_acc(acc, input + n*block_len, secret, nbStripesPerBlock);
+ f_scramble(acc, secret + secretSize - XXH_STRIPE_LEN);
+ }
+
+ /* last partial block */
+ XXH_ASSERT(len > XXH_STRIPE_LEN);
+ { size_t const nbStripes = ((len - 1) - (block_len * nb_blocks)) / XXH_STRIPE_LEN;
+ XXH_ASSERT(nbStripes <= (secretSize / XXH_SECRET_CONSUME_RATE));
+ f_acc(acc, input + nb_blocks*block_len, secret, nbStripes);
+
+ /* last stripe */
+ { const xxh_u8* const p = input + len - XXH_STRIPE_LEN;
+#define XXH_SECRET_LASTACC_START 7 /* not aligned on 8, last secret is different from acc & scrambler */
+ XXH3_accumulate_512(acc, p, secret + secretSize - XXH_STRIPE_LEN - XXH_SECRET_LASTACC_START);
+ } }
+}
+
+XXH_FORCE_INLINE xxh_u64
+XXH3_mix2Accs(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secret)
+{
+ return XXH3_mul128_fold64(
+ acc[0] ^ XXH_readLE64(secret),
+ acc[1] ^ XXH_readLE64(secret+8) );
+}
+
+static XXH64_hash_t
+XXH3_mergeAccs(const xxh_u64* XXH_RESTRICT acc, const xxh_u8* XXH_RESTRICT secret, xxh_u64 start)
+{
+ xxh_u64 result64 = start;
+ size_t i = 0;
+
+ for (i = 0; i < 4; i++) {
+ result64 += XXH3_mix2Accs(acc+2*i, secret + 16*i);
+#if defined(__clang__) /* Clang */ \
+ && (defined(__arm__) || defined(__thumb__)) /* ARMv7 */ \
+ && (defined(__ARM_NEON) || defined(__ARM_NEON__)) /* NEON */ \
+ && !defined(XXH_ENABLE_AUTOVECTORIZE) /* Define to disable */
+ /*
+ * UGLY HACK:
+ * Prevent autovectorization on Clang ARMv7-a. Exact same problem as
+ * the one in XXH3_len_129to240_64b. Speeds up shorter keys > 240b.
+ * XXH3_64bits, len == 256, Snapdragon 835:
+ * without hack: 2063.7 MB/s
+ * with hack: 2560.7 MB/s
+ */
+ XXH_COMPILER_GUARD(result64);
+#endif
+ }
+
+ return XXH3_avalanche(result64);
+}
+
+#define XXH3_INIT_ACC { XXH_PRIME32_3, XXH_PRIME64_1, XXH_PRIME64_2, XXH_PRIME64_3, \
+ XXH_PRIME64_4, XXH_PRIME32_2, XXH_PRIME64_5, XXH_PRIME32_1 }
+
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3_hashLong_64b_internal(const void* XXH_RESTRICT input, size_t len,
+ const void* XXH_RESTRICT secret, size_t secretSize,
+ XXH3_f_accumulate f_acc,
+ XXH3_f_scrambleAcc f_scramble)
+{
+ XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC;
+
+ XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, (const xxh_u8*)secret, secretSize, f_acc, f_scramble);
+
+ /* converge into final hash */
+ XXH_STATIC_ASSERT(sizeof(acc) == 64);
+ /* do not align on 8, so that the secret is different from the accumulator */
+#define XXH_SECRET_MERGEACCS_START 11
+ XXH_ASSERT(secretSize >= sizeof(acc) + XXH_SECRET_MERGEACCS_START);
+ return XXH3_mergeAccs(acc, (const xxh_u8*)secret + XXH_SECRET_MERGEACCS_START, (xxh_u64)len * XXH_PRIME64_1);
+}
+
+/*
+ * It's important for performance to transmit secret's size (when it's static)
+ * so that the compiler can properly optimize the vectorized loop.
+ * This makes a big performance difference for "medium" keys (<1 KB) when using AVX instruction set.
+ * When the secret size is unknown, or on GCC 12 where the mix of NO_INLINE and FORCE_INLINE
+ * breaks -Og, this is XXH_NO_INLINE.
+ */
+XXH3_WITH_SECRET_INLINE XXH64_hash_t
+XXH3_hashLong_64b_withSecret(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen)
+{
+ (void)seed64;
+ return XXH3_hashLong_64b_internal(input, len, secret, secretLen, XXH3_accumulate, XXH3_scrambleAcc);
+}
+
+/*
+ * It's preferable for performance that XXH3_hashLong is not inlined,
+ * as it results in a smaller function for small data, easier to the instruction cache.
+ * Note that inside this no_inline function, we do inline the internal loop,
+ * and provide a statically defined secret size to allow optimization of vector loop.
+ */
+XXH_NO_INLINE XXH_PUREF XXH64_hash_t
+XXH3_hashLong_64b_default(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed64, const xxh_u8* XXH_RESTRICT secret, size_t secretLen)
+{
+ (void)seed64; (void)secret; (void)secretLen;
+ return XXH3_hashLong_64b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_accumulate, XXH3_scrambleAcc);
+}
+
+/*
+ * XXH3_hashLong_64b_withSeed():
+ * Generate a custom key based on alteration of default XXH3_kSecret with the seed,
+ * and then use this key for long mode hashing.
+ *
+ * This operation is decently fast but nonetheless costs a little bit of time.
+ * Try to avoid it whenever possible (typically when seed==0).
+ *
+ * It's important for performance that XXH3_hashLong is not inlined. Not sure
+ * why (uop cache maybe?), but the difference is large and easily measurable.
+ */
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3_hashLong_64b_withSeed_internal(const void* input, size_t len,
+ XXH64_hash_t seed,
+ XXH3_f_accumulate f_acc,
+ XXH3_f_scrambleAcc f_scramble,
+ XXH3_f_initCustomSecret f_initSec)
+{
+#if XXH_SIZE_OPT <= 0
+ if (seed == 0)
+ return XXH3_hashLong_64b_internal(input, len,
+ XXH3_kSecret, sizeof(XXH3_kSecret),
+ f_acc, f_scramble);
+#endif
+ { XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
+ f_initSec(secret, seed);
+ return XXH3_hashLong_64b_internal(input, len, secret, sizeof(secret),
+ f_acc, f_scramble);
+ }
+}
+
+/*
+ * It's important for performance that XXH3_hashLong is not inlined.
+ */
+XXH_NO_INLINE XXH64_hash_t
+XXH3_hashLong_64b_withSeed(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed, const xxh_u8* XXH_RESTRICT secret, size_t secretLen)
+{
+ (void)secret; (void)secretLen;
+ return XXH3_hashLong_64b_withSeed_internal(input, len, seed,
+ XXH3_accumulate, XXH3_scrambleAcc, XXH3_initCustomSecret);
+}
+
+
+typedef XXH64_hash_t (*XXH3_hashLong64_f)(const void* XXH_RESTRICT, size_t,
+ XXH64_hash_t, const xxh_u8* XXH_RESTRICT, size_t);
+
+XXH_FORCE_INLINE XXH64_hash_t
+XXH3_64bits_internal(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen,
+ XXH3_hashLong64_f f_hashLong)
+{
+ XXH_ASSERT(secretLen >= XXH3_SECRET_SIZE_MIN);
+ /*
+ * If an action is to be taken if `secretLen` condition is not respected,
+ * it should be done here.
+ * For now, it's a contract pre-condition.
+ * Adding a check and a branch here would cost performance at every hash.
+ * Also, note that function signature doesn't offer room to return an error.
+ */
+ if (len <= 16)
+ return XXH3_len_0to16_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, seed64);
+ if (len <= 128)
+ return XXH3_len_17to128_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64);
+ if (len <= XXH3_MIDSIZE_MAX)
+ return XXH3_len_129to240_64b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64);
+ return f_hashLong(input, len, seed64, (const xxh_u8*)secret, secretLen);
+}
+
+
+/* === Public entry point === */
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH64_hash_t XXH3_64bits(XXH_NOESCAPE const void* input, size_t length)
+{
+ return XXH3_64bits_internal(input, length, 0, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_default);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH64_hash_t
+XXH3_64bits_withSecret(XXH_NOESCAPE const void* input, size_t length, XXH_NOESCAPE const void* secret, size_t secretSize)
+{
+ return XXH3_64bits_internal(input, length, 0, secret, secretSize, XXH3_hashLong_64b_withSecret);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH64_hash_t
+XXH3_64bits_withSeed(XXH_NOESCAPE const void* input, size_t length, XXH64_hash_t seed)
+{
+ return XXH3_64bits_internal(input, length, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed);
+}
+
+XXH_PUBLIC_API XXH64_hash_t
+XXH3_64bits_withSecretandSeed(XXH_NOESCAPE const void* input, size_t length, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed)
+{
+ if (length <= XXH3_MIDSIZE_MAX)
+ return XXH3_64bits_internal(input, length, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL);
+ return XXH3_hashLong_64b_withSecret(input, length, seed, (const xxh_u8*)secret, secretSize);
+}
+
+
+/* === XXH3 streaming === */
+#ifndef XXH_NO_STREAM
+/*
+ * Malloc's a pointer that is always aligned to align.
+ *
+ * This must be freed with `XXH_alignedFree()`.
+ *
+ * malloc typically guarantees 16 byte alignment on 64-bit systems and 8 byte
+ * alignment on 32-bit. This isn't enough for the 32 byte aligned loads in AVX2
+ * or on 32-bit, the 16 byte aligned loads in SSE2 and NEON.
+ *
+ * This underalignment previously caused a rather obvious crash which went
+ * completely unnoticed due to XXH3_createState() not actually being tested.
+ * Credit to RedSpah for noticing this bug.
+ *
+ * The alignment is done manually: Functions like posix_memalign or _mm_malloc
+ * are avoided: To maintain portability, we would have to write a fallback
+ * like this anyways, and besides, testing for the existence of library
+ * functions without relying on external build tools is impossible.
+ *
+ * The method is simple: Overallocate, manually align, and store the offset
+ * to the original behind the returned pointer.
+ *
+ * Align must be a power of 2 and 8 <= align <= 128.
+ */
+static XXH_MALLOCF void* XXH_alignedMalloc(size_t s, size_t align)
+{
+ XXH_ASSERT(align <= 128 && align >= 8); /* range check */
+ XXH_ASSERT((align & (align-1)) == 0); /* power of 2 */
+ XXH_ASSERT(s != 0 && s < (s + align)); /* empty/overflow */
+ { /* Overallocate to make room for manual realignment and an offset byte */
+ xxh_u8* base = (xxh_u8*)XXH_malloc(s + align);
+ if (base != NULL) {
+ /*
+ * Get the offset needed to align this pointer.
+ *
+ * Even if the returned pointer is aligned, there will always be
+ * at least one byte to store the offset to the original pointer.
+ */
+ size_t offset = align - ((size_t)base & (align - 1)); /* base % align */
+ /* Add the offset for the now-aligned pointer */
+ xxh_u8* ptr = base + offset;
+
+ XXH_ASSERT((size_t)ptr % align == 0);
+
+ /* Store the offset immediately before the returned pointer. */
+ ptr[-1] = (xxh_u8)offset;
+ return ptr;
+ }
+ return NULL;
+ }
+}
+/*
+ * Frees an aligned pointer allocated by XXH_alignedMalloc(). Don't pass
+ * normal malloc'd pointers, XXH_alignedMalloc has a specific data layout.
+ */
+static void XXH_alignedFree(void* p)
+{
+ if (p != NULL) {
+ xxh_u8* ptr = (xxh_u8*)p;
+ /* Get the offset byte we added in XXH_malloc. */
+ xxh_u8 offset = ptr[-1];
+ /* Free the original malloc'd pointer */
+ xxh_u8* base = ptr - offset;
+ XXH_free(base);
+ }
+}
+/*! @ingroup XXH3_family */
+/*!
+ * @brief Allocate an @ref XXH3_state_t.
+ *
+ * @return An allocated pointer of @ref XXH3_state_t on success.
+ * @return `NULL` on failure.
+ *
+ * @note Must be freed with XXH3_freeState().
+ */
+XXH_PUBLIC_API XXH3_state_t* XXH3_createState(void)
+{
+ XXH3_state_t* const state = (XXH3_state_t*)XXH_alignedMalloc(sizeof(XXH3_state_t), 64);
+ if (state==NULL) return NULL;
+ XXH3_INITSTATE(state);
+ return state;
+}
+
+/*! @ingroup XXH3_family */
+/*!
+ * @brief Frees an @ref XXH3_state_t.
+ *
+ * @param statePtr A pointer to an @ref XXH3_state_t allocated with @ref XXH3_createState().
+ *
+ * @return @ref XXH_OK.
+ *
+ * @note Must be allocated with XXH3_createState().
+ */
+XXH_PUBLIC_API XXH_errorcode XXH3_freeState(XXH3_state_t* statePtr)
+{
+ XXH_alignedFree(statePtr);
+ return XXH_OK;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API void
+XXH3_copyState(XXH_NOESCAPE XXH3_state_t* dst_state, XXH_NOESCAPE const XXH3_state_t* src_state)
+{
+ XXH_memcpy(dst_state, src_state, sizeof(*dst_state));
+}
+
+static void
+XXH3_reset_internal(XXH3_state_t* statePtr,
+ XXH64_hash_t seed,
+ const void* secret, size_t secretSize)
+{
+ size_t const initStart = offsetof(XXH3_state_t, bufferedSize);
+ size_t const initLength = offsetof(XXH3_state_t, nbStripesPerBlock) - initStart;
+ XXH_ASSERT(offsetof(XXH3_state_t, nbStripesPerBlock) > initStart);
+ XXH_ASSERT(statePtr != NULL);
+ /* set members from bufferedSize to nbStripesPerBlock (excluded) to 0 */
+ memset((char*)statePtr + initStart, 0, initLength);
+ statePtr->acc[0] = XXH_PRIME32_3;
+ statePtr->acc[1] = XXH_PRIME64_1;
+ statePtr->acc[2] = XXH_PRIME64_2;
+ statePtr->acc[3] = XXH_PRIME64_3;
+ statePtr->acc[4] = XXH_PRIME64_4;
+ statePtr->acc[5] = XXH_PRIME32_2;
+ statePtr->acc[6] = XXH_PRIME64_5;
+ statePtr->acc[7] = XXH_PRIME32_1;
+ statePtr->seed = seed;
+ statePtr->useSeed = (seed != 0);
+ statePtr->extSecret = (const unsigned char*)secret;
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
+ statePtr->secretLimit = secretSize - XXH_STRIPE_LEN;
+ statePtr->nbStripesPerBlock = statePtr->secretLimit / XXH_SECRET_CONSUME_RATE;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ XXH3_reset_internal(statePtr, 0, XXH3_kSecret, XXH_SECRET_DEFAULT_SIZE);
+ return XXH_OK;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ XXH3_reset_internal(statePtr, 0, secret, secretSize);
+ if (secret == NULL) return XXH_ERROR;
+ if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR;
+ return XXH_OK;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ if (seed==0) return XXH3_64bits_reset(statePtr);
+ if ((seed != statePtr->seed) || (statePtr->extSecret != NULL))
+ XXH3_initCustomSecret(statePtr->customSecret, seed);
+ XXH3_reset_internal(statePtr, seed, NULL, XXH_SECRET_DEFAULT_SIZE);
+ return XXH_OK;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed64)
+{
+ if (statePtr == NULL) return XXH_ERROR;
+ if (secret == NULL) return XXH_ERROR;
+ if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR;
+ XXH3_reset_internal(statePtr, seed64, secret, secretSize);
+ statePtr->useSeed = 1; /* always, even if seed64==0 */
+ return XXH_OK;
+}
+
+/*!
+ * @internal
+ * @brief Processes a large input for XXH3_update() and XXH3_digest_long().
+ *
+ * Unlike XXH3_hashLong_internal_loop(), this can process data that overlaps a block.
+ *
+ * @param acc Pointer to the 8 accumulator lanes
+ * @param nbStripesSoFarPtr In/out pointer to the number of leftover stripes in the block*
+ * @param nbStripesPerBlock Number of stripes in a block
+ * @param input Input pointer
+ * @param nbStripes Number of stripes to process
+ * @param secret Secret pointer
+ * @param secretLimit Offset of the last block in @p secret
+ * @param f_acc Pointer to an XXH3_accumulate implementation
+ * @param f_scramble Pointer to an XXH3_scrambleAcc implementation
+ * @return Pointer past the end of @p input after processing
+ */
+XXH_FORCE_INLINE const xxh_u8 *
+XXH3_consumeStripes(xxh_u64* XXH_RESTRICT acc,
+ size_t* XXH_RESTRICT nbStripesSoFarPtr, size_t nbStripesPerBlock,
+ const xxh_u8* XXH_RESTRICT input, size_t nbStripes,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretLimit,
+ XXH3_f_accumulate f_acc,
+ XXH3_f_scrambleAcc f_scramble)
+{
+ const xxh_u8* initialSecret = secret + *nbStripesSoFarPtr * XXH_SECRET_CONSUME_RATE;
+ /* Process full blocks */
+ if (nbStripes >= (nbStripesPerBlock - *nbStripesSoFarPtr)) {
+ /* Process the initial partial block... */
+ size_t nbStripesThisIter = nbStripesPerBlock - *nbStripesSoFarPtr;
+
+ do {
+ /* Accumulate and scramble */
+ f_acc(acc, input, initialSecret, nbStripesThisIter);
+ f_scramble(acc, secret + secretLimit);
+ input += nbStripesThisIter * XXH_STRIPE_LEN;
+ nbStripes -= nbStripesThisIter;
+ /* Then continue the loop with the full block size */
+ nbStripesThisIter = nbStripesPerBlock;
+ initialSecret = secret;
+ } while (nbStripes >= nbStripesPerBlock);
+ *nbStripesSoFarPtr = 0;
+ }
+ /* Process a partial block */
+ if (nbStripes > 0) {
+ f_acc(acc, input, initialSecret, nbStripes);
+ input += nbStripes * XXH_STRIPE_LEN;
+ *nbStripesSoFarPtr += nbStripes;
+ }
+ /* Return end pointer */
+ return input;
+}
+
+#ifndef XXH3_STREAM_USE_STACK
+# if XXH_SIZE_OPT <= 0 && !defined(__clang__) /* clang doesn't need additional stack space */
+# define XXH3_STREAM_USE_STACK 1
+# endif
+#endif
+/*
+ * Both XXH3_64bits_update and XXH3_128bits_update use this routine.
+ */
+XXH_FORCE_INLINE XXH_errorcode
+XXH3_update(XXH3_state_t* XXH_RESTRICT const state,
+ const xxh_u8* XXH_RESTRICT input, size_t len,
+ XXH3_f_accumulate f_acc,
+ XXH3_f_scrambleAcc f_scramble)
+{
+ if (input==NULL) {
+ XXH_ASSERT(len == 0);
+ return XXH_OK;
+ }
+
+ XXH_ASSERT(state != NULL);
+ { const xxh_u8* const bEnd = input + len;
+ const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret;
+#if defined(XXH3_STREAM_USE_STACK) && XXH3_STREAM_USE_STACK >= 1
+ /* For some reason, gcc and MSVC seem to suffer greatly
+ * when operating accumulators directly into state.
+ * Operating into stack space seems to enable proper optimization.
+ * clang, on the other hand, doesn't seem to need this trick */
+ XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[8];
+ XXH_memcpy(acc, state->acc, sizeof(acc));
+#else
+ xxh_u64* XXH_RESTRICT const acc = state->acc;
+#endif
+ state->totalLen += len;
+ XXH_ASSERT(state->bufferedSize <= XXH3_INTERNALBUFFER_SIZE);
+
+ /* small input : just fill in tmp buffer */
+ if (len <= XXH3_INTERNALBUFFER_SIZE - state->bufferedSize) {
+ XXH_memcpy(state->buffer + state->bufferedSize, input, len);
+ state->bufferedSize += (XXH32_hash_t)len;
+ return XXH_OK;
+ }
+
+ /* total input is now > XXH3_INTERNALBUFFER_SIZE */
+ #define XXH3_INTERNALBUFFER_STRIPES (XXH3_INTERNALBUFFER_SIZE / XXH_STRIPE_LEN)
+ XXH_STATIC_ASSERT(XXH3_INTERNALBUFFER_SIZE % XXH_STRIPE_LEN == 0); /* clean multiple */
+
+ /*
+ * Internal buffer is partially filled (always, except at beginning)
+ * Complete it, then consume it.
+ */
+ if (state->bufferedSize) {
+ size_t const loadSize = XXH3_INTERNALBUFFER_SIZE - state->bufferedSize;
+ XXH_memcpy(state->buffer + state->bufferedSize, input, loadSize);
+ input += loadSize;
+ XXH3_consumeStripes(acc,
+ &state->nbStripesSoFar, state->nbStripesPerBlock,
+ state->buffer, XXH3_INTERNALBUFFER_STRIPES,
+ secret, state->secretLimit,
+ f_acc, f_scramble);
+ state->bufferedSize = 0;
+ }
+ XXH_ASSERT(input < bEnd);
+ if (bEnd - input > XXH3_INTERNALBUFFER_SIZE) {
+ size_t nbStripes = (size_t)(bEnd - 1 - input) / XXH_STRIPE_LEN;
+ input = XXH3_consumeStripes(acc,
+ &state->nbStripesSoFar, state->nbStripesPerBlock,
+ input, nbStripes,
+ secret, state->secretLimit,
+ f_acc, f_scramble);
+ XXH_memcpy(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN, input - XXH_STRIPE_LEN, XXH_STRIPE_LEN);
+
+ }
+ /* Some remaining input (always) : buffer it */
+ XXH_ASSERT(input < bEnd);
+ XXH_ASSERT(bEnd - input <= XXH3_INTERNALBUFFER_SIZE);
+ XXH_ASSERT(state->bufferedSize == 0);
+ XXH_memcpy(state->buffer, input, (size_t)(bEnd-input));
+ state->bufferedSize = (XXH32_hash_t)(bEnd-input);
+#if defined(XXH3_STREAM_USE_STACK) && XXH3_STREAM_USE_STACK >= 1
+ /* save stack accumulators into state */
+ XXH_memcpy(state->acc, acc, sizeof(acc));
+#endif
+ }
+
+ return XXH_OK;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_64bits_update(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len)
+{
+ return XXH3_update(state, (const xxh_u8*)input, len,
+ XXH3_accumulate, XXH3_scrambleAcc);
+}
+
+
+XXH_FORCE_INLINE void
+XXH3_digest_long (XXH64_hash_t* acc,
+ const XXH3_state_t* state,
+ const unsigned char* secret)
+{
+ xxh_u8 lastStripe[XXH_STRIPE_LEN];
+ const xxh_u8* lastStripePtr;
+
+ /*
+ * Digest on a local copy. This way, the state remains unaltered, and it can
+ * continue ingesting more input afterwards.
+ */
+ XXH_memcpy(acc, state->acc, sizeof(state->acc));
+ if (state->bufferedSize >= XXH_STRIPE_LEN) {
+ /* Consume remaining stripes then point to remaining data in buffer */
+ size_t const nbStripes = (state->bufferedSize - 1) / XXH_STRIPE_LEN;
+ size_t nbStripesSoFar = state->nbStripesSoFar;
+ XXH3_consumeStripes(acc,
+ &nbStripesSoFar, state->nbStripesPerBlock,
+ state->buffer, nbStripes,
+ secret, state->secretLimit,
+ XXH3_accumulate, XXH3_scrambleAcc);
+ lastStripePtr = state->buffer + state->bufferedSize - XXH_STRIPE_LEN;
+ } else { /* bufferedSize < XXH_STRIPE_LEN */
+ /* Copy to temp buffer */
+ size_t const catchupSize = XXH_STRIPE_LEN - state->bufferedSize;
+ XXH_ASSERT(state->bufferedSize > 0); /* there is always some input buffered */
+ XXH_memcpy(lastStripe, state->buffer + sizeof(state->buffer) - catchupSize, catchupSize);
+ XXH_memcpy(lastStripe + catchupSize, state->buffer, state->bufferedSize);
+ lastStripePtr = lastStripe;
+ }
+ /* Last stripe */
+ XXH3_accumulate_512(acc,
+ lastStripePtr,
+ secret + state->secretLimit - XXH_SECRET_LASTACC_START);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH64_hash_t XXH3_64bits_digest (XXH_NOESCAPE const XXH3_state_t* state)
+{
+ const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret;
+ if (state->totalLen > XXH3_MIDSIZE_MAX) {
+ XXH_ALIGN(XXH_ACC_ALIGN) XXH64_hash_t acc[XXH_ACC_NB];
+ XXH3_digest_long(acc, state, secret);
+ return XXH3_mergeAccs(acc,
+ secret + XXH_SECRET_MERGEACCS_START,
+ (xxh_u64)state->totalLen * XXH_PRIME64_1);
+ }
+ /* totalLen <= XXH3_MIDSIZE_MAX: digesting a short input */
+ if (state->useSeed)
+ return XXH3_64bits_withSeed(state->buffer, (size_t)state->totalLen, state->seed);
+ return XXH3_64bits_withSecret(state->buffer, (size_t)(state->totalLen),
+ secret, state->secretLimit + XXH_STRIPE_LEN);
+}
+#endif /* !XXH_NO_STREAM */
+
+
+/* ==========================================
+ * XXH3 128 bits (a.k.a XXH128)
+ * ==========================================
+ * XXH3's 128-bit variant has better mixing and strength than the 64-bit variant,
+ * even without counting the significantly larger output size.
+ *
+ * For example, extra steps are taken to avoid the seed-dependent collisions
+ * in 17-240 byte inputs (See XXH3_mix16B and XXH128_mix32B).
+ *
+ * This strength naturally comes at the cost of some speed, especially on short
+ * lengths. Note that longer hashes are about as fast as the 64-bit version
+ * due to it using only a slight modification of the 64-bit loop.
+ *
+ * XXH128 is also more oriented towards 64-bit machines. It is still extremely
+ * fast for a _128-bit_ hash on 32-bit (it usually clears XXH64).
+ */
+
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
+XXH3_len_1to3_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ /* A doubled version of 1to3_64b with different constants. */
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(1 <= len && len <= 3);
+ XXH_ASSERT(secret != NULL);
+ /*
+ * len = 1: combinedl = { input[0], 0x01, input[0], input[0] }
+ * len = 2: combinedl = { input[1], 0x02, input[0], input[1] }
+ * len = 3: combinedl = { input[2], 0x03, input[0], input[1] }
+ */
+ { xxh_u8 const c1 = input[0];
+ xxh_u8 const c2 = input[len >> 1];
+ xxh_u8 const c3 = input[len - 1];
+ xxh_u32 const combinedl = ((xxh_u32)c1 <<16) | ((xxh_u32)c2 << 24)
+ | ((xxh_u32)c3 << 0) | ((xxh_u32)len << 8);
+ xxh_u32 const combinedh = XXH_rotl32(XXH_swap32(combinedl), 13);
+ xxh_u64 const bitflipl = (XXH_readLE32(secret) ^ XXH_readLE32(secret+4)) + seed;
+ xxh_u64 const bitfliph = (XXH_readLE32(secret+8) ^ XXH_readLE32(secret+12)) - seed;
+ xxh_u64 const keyed_lo = (xxh_u64)combinedl ^ bitflipl;
+ xxh_u64 const keyed_hi = (xxh_u64)combinedh ^ bitfliph;
+ XXH128_hash_t h128;
+ h128.low64 = XXH64_avalanche(keyed_lo);
+ h128.high64 = XXH64_avalanche(keyed_hi);
+ return h128;
+ }
+}
+
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
+XXH3_len_4to8_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(secret != NULL);
+ XXH_ASSERT(4 <= len && len <= 8);
+ seed ^= (xxh_u64)XXH_swap32((xxh_u32)seed) << 32;
+ { xxh_u32 const input_lo = XXH_readLE32(input);
+ xxh_u32 const input_hi = XXH_readLE32(input + len - 4);
+ xxh_u64 const input_64 = input_lo + ((xxh_u64)input_hi << 32);
+ xxh_u64 const bitflip = (XXH_readLE64(secret+16) ^ XXH_readLE64(secret+24)) + seed;
+ xxh_u64 const keyed = input_64 ^ bitflip;
+
+ /* Shift len to the left to ensure it is even, this avoids even multiplies. */
+ XXH128_hash_t m128 = XXH_mult64to128(keyed, XXH_PRIME64_1 + (len << 2));
+
+ m128.high64 += (m128.low64 << 1);
+ m128.low64 ^= (m128.high64 >> 3);
+
+ m128.low64 = XXH_xorshift64(m128.low64, 35);
+ m128.low64 *= PRIME_MX2;
+ m128.low64 = XXH_xorshift64(m128.low64, 28);
+ m128.high64 = XXH3_avalanche(m128.high64);
+ return m128;
+ }
+}
+
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
+XXH3_len_9to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(input != NULL);
+ XXH_ASSERT(secret != NULL);
+ XXH_ASSERT(9 <= len && len <= 16);
+ { xxh_u64 const bitflipl = (XXH_readLE64(secret+32) ^ XXH_readLE64(secret+40)) - seed;
+ xxh_u64 const bitfliph = (XXH_readLE64(secret+48) ^ XXH_readLE64(secret+56)) + seed;
+ xxh_u64 const input_lo = XXH_readLE64(input);
+ xxh_u64 input_hi = XXH_readLE64(input + len - 8);
+ XXH128_hash_t m128 = XXH_mult64to128(input_lo ^ input_hi ^ bitflipl, XXH_PRIME64_1);
+ /*
+ * Put len in the middle of m128 to ensure that the length gets mixed to
+ * both the low and high bits in the 128x64 multiply below.
+ */
+ m128.low64 += (xxh_u64)(len - 1) << 54;
+ input_hi ^= bitfliph;
+ /*
+ * Add the high 32 bits of input_hi to the high 32 bits of m128, then
+ * add the long product of the low 32 bits of input_hi and XXH_PRIME32_2 to
+ * the high 64 bits of m128.
+ *
+ * The best approach to this operation is different on 32-bit and 64-bit.
+ */
+ if (sizeof(void *) < sizeof(xxh_u64)) { /* 32-bit */
+ /*
+ * 32-bit optimized version, which is more readable.
+ *
+ * On 32-bit, it removes an ADC and delays a dependency between the two
+ * halves of m128.high64, but it generates an extra mask on 64-bit.
+ */
+ m128.high64 += (input_hi & 0xFFFFFFFF00000000ULL) + XXH_mult32to64((xxh_u32)input_hi, XXH_PRIME32_2);
+ } else {
+ /*
+ * 64-bit optimized (albeit more confusing) version.
+ *
+ * Uses some properties of addition and multiplication to remove the mask:
+ *
+ * Let:
+ * a = input_hi.lo = (input_hi & 0x00000000FFFFFFFF)
+ * b = input_hi.hi = (input_hi & 0xFFFFFFFF00000000)
+ * c = XXH_PRIME32_2
+ *
+ * a + (b * c)
+ * Inverse Property: x + y - x == y
+ * a + (b * (1 + c - 1))
+ * Distributive Property: x * (y + z) == (x * y) + (x * z)
+ * a + (b * 1) + (b * (c - 1))
+ * Identity Property: x * 1 == x
+ * a + b + (b * (c - 1))
+ *
+ * Substitute a, b, and c:
+ * input_hi.hi + input_hi.lo + ((xxh_u64)input_hi.lo * (XXH_PRIME32_2 - 1))
+ *
+ * Since input_hi.hi + input_hi.lo == input_hi, we get this:
+ * input_hi + ((xxh_u64)input_hi.lo * (XXH_PRIME32_2 - 1))
+ */
+ m128.high64 += input_hi + XXH_mult32to64((xxh_u32)input_hi, XXH_PRIME32_2 - 1);
+ }
+ /* m128 ^= XXH_swap64(m128 >> 64); */
+ m128.low64 ^= XXH_swap64(m128.high64);
+
+ { /* 128x64 multiply: h128 = m128 * XXH_PRIME64_2; */
+ XXH128_hash_t h128 = XXH_mult64to128(m128.low64, XXH_PRIME64_2);
+ h128.high64 += m128.high64 * XXH_PRIME64_2;
+
+ h128.low64 = XXH3_avalanche(h128.low64);
+ h128.high64 = XXH3_avalanche(h128.high64);
+ return h128;
+ } }
+}
+
+/*
+ * Assumption: `secret` size is >= XXH3_SECRET_SIZE_MIN
+ */
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
+XXH3_len_0to16_128b(const xxh_u8* input, size_t len, const xxh_u8* secret, XXH64_hash_t seed)
+{
+ XXH_ASSERT(len <= 16);
+ { if (len > 8) return XXH3_len_9to16_128b(input, len, secret, seed);
+ if (len >= 4) return XXH3_len_4to8_128b(input, len, secret, seed);
+ if (len) return XXH3_len_1to3_128b(input, len, secret, seed);
+ { XXH128_hash_t h128;
+ xxh_u64 const bitflipl = XXH_readLE64(secret+64) ^ XXH_readLE64(secret+72);
+ xxh_u64 const bitfliph = XXH_readLE64(secret+80) ^ XXH_readLE64(secret+88);
+ h128.low64 = XXH64_avalanche(seed ^ bitflipl);
+ h128.high64 = XXH64_avalanche( seed ^ bitfliph);
+ return h128;
+ } }
+}
+
+/*
+ * A bit slower than XXH3_mix16B, but handles multiply by zero better.
+ */
+XXH_FORCE_INLINE XXH128_hash_t
+XXH128_mix32B(XXH128_hash_t acc, const xxh_u8* input_1, const xxh_u8* input_2,
+ const xxh_u8* secret, XXH64_hash_t seed)
+{
+ acc.low64 += XXH3_mix16B (input_1, secret+0, seed);
+ acc.low64 ^= XXH_readLE64(input_2) + XXH_readLE64(input_2 + 8);
+ acc.high64 += XXH3_mix16B (input_2, secret+16, seed);
+ acc.high64 ^= XXH_readLE64(input_1) + XXH_readLE64(input_1 + 8);
+ return acc;
+}
+
+
+XXH_FORCE_INLINE XXH_PUREF XXH128_hash_t
+XXH3_len_17to128_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH64_hash_t seed)
+{
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize;
+ XXH_ASSERT(16 < len && len <= 128);
+
+ { XXH128_hash_t acc;
+ acc.low64 = len * XXH_PRIME64_1;
+ acc.high64 = 0;
+
+#if XXH_SIZE_OPT >= 1
+ {
+ /* Smaller, but slightly slower. */
+ unsigned int i = (unsigned int)(len - 1) / 32;
+ do {
+ acc = XXH128_mix32B(acc, input+16*i, input+len-16*(i+1), secret+32*i, seed);
+ } while (i-- != 0);
+ }
+#else
+ if (len > 32) {
+ if (len > 64) {
+ if (len > 96) {
+ acc = XXH128_mix32B(acc, input+48, input+len-64, secret+96, seed);
+ }
+ acc = XXH128_mix32B(acc, input+32, input+len-48, secret+64, seed);
+ }
+ acc = XXH128_mix32B(acc, input+16, input+len-32, secret+32, seed);
+ }
+ acc = XXH128_mix32B(acc, input, input+len-16, secret, seed);
+#endif
+ { XXH128_hash_t h128;
+ h128.low64 = acc.low64 + acc.high64;
+ h128.high64 = (acc.low64 * XXH_PRIME64_1)
+ + (acc.high64 * XXH_PRIME64_4)
+ + ((len - seed) * XXH_PRIME64_2);
+ h128.low64 = XXH3_avalanche(h128.low64);
+ h128.high64 = (XXH64_hash_t)0 - XXH3_avalanche(h128.high64);
+ return h128;
+ }
+ }
+}
+
+XXH_NO_INLINE XXH_PUREF XXH128_hash_t
+XXH3_len_129to240_128b(const xxh_u8* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH64_hash_t seed)
+{
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN); (void)secretSize;
+ XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX);
+
+ { XXH128_hash_t acc;
+ unsigned i;
+ acc.low64 = len * XXH_PRIME64_1;
+ acc.high64 = 0;
+ /*
+ * We set as `i` as offset + 32. We do this so that unchanged
+ * `len` can be used as upper bound. This reaches a sweet spot
+ * where both x86 and aarch64 get simple agen and good codegen
+ * for the loop.
+ */
+ for (i = 32; i < 160; i += 32) {
+ acc = XXH128_mix32B(acc,
+ input + i - 32,
+ input + i - 16,
+ secret + i - 32,
+ seed);
+ }
+ acc.low64 = XXH3_avalanche(acc.low64);
+ acc.high64 = XXH3_avalanche(acc.high64);
+ /*
+ * NB: `i <= len` will duplicate the last 32-bytes if
+ * len % 32 was zero. This is an unfortunate necessity to keep
+ * the hash result stable.
+ */
+ for (i=160; i <= len; i += 32) {
+ acc = XXH128_mix32B(acc,
+ input + i - 32,
+ input + i - 16,
+ secret + XXH3_MIDSIZE_STARTOFFSET + i - 160,
+ seed);
+ }
+ /* last bytes */
+ acc = XXH128_mix32B(acc,
+ input + len - 16,
+ input + len - 32,
+ secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET - 16,
+ (XXH64_hash_t)0 - seed);
+
+ { XXH128_hash_t h128;
+ h128.low64 = acc.low64 + acc.high64;
+ h128.high64 = (acc.low64 * XXH_PRIME64_1)
+ + (acc.high64 * XXH_PRIME64_4)
+ + ((len - seed) * XXH_PRIME64_2);
+ h128.low64 = XXH3_avalanche(h128.low64);
+ h128.high64 = (XXH64_hash_t)0 - XXH3_avalanche(h128.high64);
+ return h128;
+ }
+ }
+}
+
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3_hashLong_128b_internal(const void* XXH_RESTRICT input, size_t len,
+ const xxh_u8* XXH_RESTRICT secret, size_t secretSize,
+ XXH3_f_accumulate f_acc,
+ XXH3_f_scrambleAcc f_scramble)
+{
+ XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC;
+
+ XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len, secret, secretSize, f_acc, f_scramble);
+
+ /* converge into final hash */
+ XXH_STATIC_ASSERT(sizeof(acc) == 64);
+ XXH_ASSERT(secretSize >= sizeof(acc) + XXH_SECRET_MERGEACCS_START);
+ { XXH128_hash_t h128;
+ h128.low64 = XXH3_mergeAccs(acc,
+ secret + XXH_SECRET_MERGEACCS_START,
+ (xxh_u64)len * XXH_PRIME64_1);
+ h128.high64 = XXH3_mergeAccs(acc,
+ secret + secretSize
+ - sizeof(acc) - XXH_SECRET_MERGEACCS_START,
+ ~((xxh_u64)len * XXH_PRIME64_2));
+ return h128;
+ }
+}
+
+/*
+ * It's important for performance that XXH3_hashLong() is not inlined.
+ */
+XXH_NO_INLINE XXH_PUREF XXH128_hash_t
+XXH3_hashLong_128b_default(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed64,
+ const void* XXH_RESTRICT secret, size_t secretLen)
+{
+ (void)seed64; (void)secret; (void)secretLen;
+ return XXH3_hashLong_128b_internal(input, len, XXH3_kSecret, sizeof(XXH3_kSecret),
+ XXH3_accumulate, XXH3_scrambleAcc);
+}
+
+/*
+ * It's important for performance to pass @p secretLen (when it's static)
+ * to the compiler, so that it can properly optimize the vectorized loop.
+ *
+ * When the secret size is unknown, or on GCC 12 where the mix of NO_INLINE and FORCE_INLINE
+ * breaks -Og, this is XXH_NO_INLINE.
+ */
+XXH3_WITH_SECRET_INLINE XXH128_hash_t
+XXH3_hashLong_128b_withSecret(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed64,
+ const void* XXH_RESTRICT secret, size_t secretLen)
+{
+ (void)seed64;
+ return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, secretLen,
+ XXH3_accumulate, XXH3_scrambleAcc);
+}
+
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3_hashLong_128b_withSeed_internal(const void* XXH_RESTRICT input, size_t len,
+ XXH64_hash_t seed64,
+ XXH3_f_accumulate f_acc,
+ XXH3_f_scrambleAcc f_scramble,
+ XXH3_f_initCustomSecret f_initSec)
+{
+ if (seed64 == 0)
+ return XXH3_hashLong_128b_internal(input, len,
+ XXH3_kSecret, sizeof(XXH3_kSecret),
+ f_acc, f_scramble);
+ { XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
+ f_initSec(secret, seed64);
+ return XXH3_hashLong_128b_internal(input, len, (const xxh_u8*)secret, sizeof(secret),
+ f_acc, f_scramble);
+ }
+}
+
+/*
+ * It's important for performance that XXH3_hashLong is not inlined.
+ */
+XXH_NO_INLINE XXH128_hash_t
+XXH3_hashLong_128b_withSeed(const void* input, size_t len,
+ XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen)
+{
+ (void)secret; (void)secretLen;
+ return XXH3_hashLong_128b_withSeed_internal(input, len, seed64,
+ XXH3_accumulate, XXH3_scrambleAcc, XXH3_initCustomSecret);
+}
+
+typedef XXH128_hash_t (*XXH3_hashLong128_f)(const void* XXH_RESTRICT, size_t,
+ XXH64_hash_t, const void* XXH_RESTRICT, size_t);
+
+XXH_FORCE_INLINE XXH128_hash_t
+XXH3_128bits_internal(const void* input, size_t len,
+ XXH64_hash_t seed64, const void* XXH_RESTRICT secret, size_t secretLen,
+ XXH3_hashLong128_f f_hl128)
+{
+ XXH_ASSERT(secretLen >= XXH3_SECRET_SIZE_MIN);
+ /*
+ * If an action is to be taken if `secret` conditions are not respected,
+ * it should be done here.
+ * For now, it's a contract pre-condition.
+ * Adding a check and a branch here would cost performance at every hash.
+ */
+ if (len <= 16)
+ return XXH3_len_0to16_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, seed64);
+ if (len <= 128)
+ return XXH3_len_17to128_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64);
+ if (len <= XXH3_MIDSIZE_MAX)
+ return XXH3_len_129to240_128b((const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64);
+ return f_hl128(input, len, seed64, secret, secretLen);
+}
+
+
+/* === Public XXH128 API === */
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t XXH3_128bits(XXH_NOESCAPE const void* input, size_t len)
+{
+ return XXH3_128bits_internal(input, len, 0,
+ XXH3_kSecret, sizeof(XXH3_kSecret),
+ XXH3_hashLong_128b_default);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t
+XXH3_128bits_withSecret(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize)
+{
+ return XXH3_128bits_internal(input, len, 0,
+ (const xxh_u8*)secret, secretSize,
+ XXH3_hashLong_128b_withSecret);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t
+XXH3_128bits_withSeed(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed)
+{
+ return XXH3_128bits_internal(input, len, seed,
+ XXH3_kSecret, sizeof(XXH3_kSecret),
+ XXH3_hashLong_128b_withSeed);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t
+XXH3_128bits_withSecretandSeed(XXH_NOESCAPE const void* input, size_t len, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed)
+{
+ if (len <= XXH3_MIDSIZE_MAX)
+ return XXH3_128bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), NULL);
+ return XXH3_hashLong_128b_withSecret(input, len, seed, secret, secretSize);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t
+XXH128(XXH_NOESCAPE const void* input, size_t len, XXH64_hash_t seed)
+{
+ return XXH3_128bits_withSeed(input, len, seed);
+}
+
+
+/* === XXH3 128-bit streaming === */
+#ifndef XXH_NO_STREAM
+/*
+ * All initialization and update functions are identical to 64-bit streaming variant.
+ * The only difference is the finalization routine.
+ */
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_reset(XXH_NOESCAPE XXH3_state_t* statePtr)
+{
+ return XXH3_64bits_reset(statePtr);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_reset_withSecret(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize)
+{
+ return XXH3_64bits_reset_withSecret(statePtr, secret, secretSize);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_reset_withSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH64_hash_t seed)
+{
+ return XXH3_64bits_reset_withSeed(statePtr, seed);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_reset_withSecretandSeed(XXH_NOESCAPE XXH3_state_t* statePtr, XXH_NOESCAPE const void* secret, size_t secretSize, XXH64_hash_t seed)
+{
+ return XXH3_64bits_reset_withSecretandSeed(statePtr, secret, secretSize, seed);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_128bits_update(XXH_NOESCAPE XXH3_state_t* state, XXH_NOESCAPE const void* input, size_t len)
+{
+ return XXH3_64bits_update(state, input, len);
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t XXH3_128bits_digest (XXH_NOESCAPE const XXH3_state_t* state)
+{
+ const unsigned char* const secret = (state->extSecret == NULL) ? state->customSecret : state->extSecret;
+ if (state->totalLen > XXH3_MIDSIZE_MAX) {
+ XXH_ALIGN(XXH_ACC_ALIGN) XXH64_hash_t acc[XXH_ACC_NB];
+ XXH3_digest_long(acc, state, secret);
+ XXH_ASSERT(state->secretLimit + XXH_STRIPE_LEN >= sizeof(acc) + XXH_SECRET_MERGEACCS_START);
+ { XXH128_hash_t h128;
+ h128.low64 = XXH3_mergeAccs(acc,
+ secret + XXH_SECRET_MERGEACCS_START,
+ (xxh_u64)state->totalLen * XXH_PRIME64_1);
+ h128.high64 = XXH3_mergeAccs(acc,
+ secret + state->secretLimit + XXH_STRIPE_LEN
+ - sizeof(acc) - XXH_SECRET_MERGEACCS_START,
+ ~((xxh_u64)state->totalLen * XXH_PRIME64_2));
+ return h128;
+ }
+ }
+ /* len <= XXH3_MIDSIZE_MAX : short code */
+ if (state->seed)
+ return XXH3_128bits_withSeed(state->buffer, (size_t)state->totalLen, state->seed);
+ return XXH3_128bits_withSecret(state->buffer, (size_t)(state->totalLen),
+ secret, state->secretLimit + XXH_STRIPE_LEN);
+}
+#endif /* !XXH_NO_STREAM */
+/* 128-bit utility functions */
+
+#include <string.h> /* memcmp, memcpy */
+
+/* return : 1 is equal, 0 if different */
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API int XXH128_isEqual(XXH128_hash_t h1, XXH128_hash_t h2)
+{
+ /* note : XXH128_hash_t is compact, it has no padding byte */
+ return !(memcmp(&h1, &h2, sizeof(h1)));
+}
+
+/* This prototype is compatible with stdlib's qsort().
+ * @return : >0 if *h128_1 > *h128_2
+ * <0 if *h128_1 < *h128_2
+ * =0 if *h128_1 == *h128_2 */
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API int XXH128_cmp(XXH_NOESCAPE const void* h128_1, XXH_NOESCAPE const void* h128_2)
+{
+ XXH128_hash_t const h1 = *(const XXH128_hash_t*)h128_1;
+ XXH128_hash_t const h2 = *(const XXH128_hash_t*)h128_2;
+ int const hcmp = (h1.high64 > h2.high64) - (h2.high64 > h1.high64);
+ /* note : bets that, in most cases, hash values are different */
+ if (hcmp) return hcmp;
+ return (h1.low64 > h2.low64) - (h2.low64 > h1.low64);
+}
+
+
+/*====== Canonical representation ======*/
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API void
+XXH128_canonicalFromHash(XXH_NOESCAPE XXH128_canonical_t* dst, XXH128_hash_t hash)
+{
+ XXH_STATIC_ASSERT(sizeof(XXH128_canonical_t) == sizeof(XXH128_hash_t));
+ if (XXH_CPU_LITTLE_ENDIAN) {
+ hash.high64 = XXH_swap64(hash.high64);
+ hash.low64 = XXH_swap64(hash.low64);
+ }
+ XXH_memcpy(dst, &hash.high64, sizeof(hash.high64));
+ XXH_memcpy((char*)dst + sizeof(hash.high64), &hash.low64, sizeof(hash.low64));
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH128_hash_t
+XXH128_hashFromCanonical(XXH_NOESCAPE const XXH128_canonical_t* src)
+{
+ XXH128_hash_t h;
+ h.high64 = XXH_readBE64(src);
+ h.low64 = XXH_readBE64(src->digest + 8);
+ return h;
+}
+
+
+
+/* ==========================================
+ * Secret generators
+ * ==========================================
+ */
+#define XXH_MIN(x, y) (((x) > (y)) ? (y) : (x))
+
+XXH_FORCE_INLINE void XXH3_combine16(void* dst, XXH128_hash_t h128)
+{
+ XXH_writeLE64( dst, XXH_readLE64(dst) ^ h128.low64 );
+ XXH_writeLE64( (char*)dst+8, XXH_readLE64((char*)dst+8) ^ h128.high64 );
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API XXH_errorcode
+XXH3_generateSecret(XXH_NOESCAPE void* secretBuffer, size_t secretSize, XXH_NOESCAPE const void* customSeed, size_t customSeedSize)
+{
+#if (XXH_DEBUGLEVEL >= 1)
+ XXH_ASSERT(secretBuffer != NULL);
+ XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
+#else
+ /* production mode, assert() are disabled */
+ if (secretBuffer == NULL) return XXH_ERROR;
+ if (secretSize < XXH3_SECRET_SIZE_MIN) return XXH_ERROR;
+#endif
+
+ if (customSeedSize == 0) {
+ customSeed = XXH3_kSecret;
+ customSeedSize = XXH_SECRET_DEFAULT_SIZE;
+ }
+#if (XXH_DEBUGLEVEL >= 1)
+ XXH_ASSERT(customSeed != NULL);
+#else
+ if (customSeed == NULL) return XXH_ERROR;
+#endif
+
+ /* Fill secretBuffer with a copy of customSeed - repeat as needed */
+ { size_t pos = 0;
+ while (pos < secretSize) {
+ size_t const toCopy = XXH_MIN((secretSize - pos), customSeedSize);
+ memcpy((char*)secretBuffer + pos, customSeed, toCopy);
+ pos += toCopy;
+ } }
+
+ { size_t const nbSeg16 = secretSize / 16;
+ size_t n;
+ XXH128_canonical_t scrambler;
+ XXH128_canonicalFromHash(&scrambler, XXH128(customSeed, customSeedSize, 0));
+ for (n=0; n<nbSeg16; n++) {
+ XXH128_hash_t const h128 = XXH128(&scrambler, sizeof(scrambler), n);
+ XXH3_combine16((char*)secretBuffer + n*16, h128);
+ }
+ /* last segment */
+ XXH3_combine16((char*)secretBuffer + secretSize - 16, XXH128_hashFromCanonical(&scrambler));
+ }
+ return XXH_OK;
+}
+
+/*! @ingroup XXH3_family */
+XXH_PUBLIC_API void
+XXH3_generateSecret_fromSeed(XXH_NOESCAPE void* secretBuffer, XXH64_hash_t seed)
+{
+ XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
+ XXH3_initCustomSecret(secret, seed);
+ XXH_ASSERT(secretBuffer != NULL);
+ memcpy(secretBuffer, secret, XXH_SECRET_DEFAULT_SIZE);
+}
+
+
+
+/* Pop our optimization override from above */
+#if XXH_VECTOR == XXH_AVX2 /* AVX2 */ \
+ && defined(__GNUC__) && !defined(__clang__) /* GCC, not Clang */ \
+ && defined(__OPTIMIZE__) && XXH_SIZE_OPT <= 0 /* respect -O0 and -Os */
+# pragma GCC pop_options
+#endif
+
+#endif /* XXH_NO_LONG_LONG */
+
+#endif /* XXH_NO_XXH3 */
+
+/*!
+ * @}
+ */
+#endif /* XXH_IMPLEMENTATION */
+
+
+#if defined (__cplusplus)
+} /* extern "C" */
+#endif
diff --git a/third_party/zstd/lib/common/zstd_common.c b/third_party/zstd/lib/common/zstd_common.c
new file mode 100644
index 0000000000..3f04c22abf
--- /dev/null
+++ b/third_party/zstd/lib/common/zstd_common.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+
+
+/*-*************************************
+* Dependencies
+***************************************/
+#define ZSTD_DEPS_NEED_MALLOC
+#include "error_private.h"
+#include "zstd_internal.h"
+
+
+/*-****************************************
+* Version
+******************************************/
+unsigned ZSTD_versionNumber(void) { return ZSTD_VERSION_NUMBER; }
+
+const char* ZSTD_versionString(void) { return ZSTD_VERSION_STRING; }
+
+
+/*-****************************************
+* ZSTD Error Management
+******************************************/
+#undef ZSTD_isError /* defined within zstd_internal.h */
+/*! ZSTD_isError() :
+ * tells if a return value is an error code
+ * symbol is required for external callers */
+unsigned ZSTD_isError(size_t code) { return ERR_isError(code); }
+
+/*! ZSTD_getErrorName() :
+ * provides error code string from function result (useful for debugging) */
+const char* ZSTD_getErrorName(size_t code) { return ERR_getErrorName(code); }
+
+/*! ZSTD_getError() :
+ * convert a `size_t` function result into a proper ZSTD_errorCode enum */
+ZSTD_ErrorCode ZSTD_getErrorCode(size_t code) { return ERR_getErrorCode(code); }
+
+/*! ZSTD_getErrorString() :
+ * provides error code string from enum */
+const char* ZSTD_getErrorString(ZSTD_ErrorCode code) { return ERR_getErrorString(code); }
diff --git a/third_party/zstd/lib/common/zstd_deps.h b/third_party/zstd/lib/common/zstd_deps.h
new file mode 100644
index 0000000000..4d767ae9b0
--- /dev/null
+++ b/third_party/zstd/lib/common/zstd_deps.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+/* This file provides common libc dependencies that zstd requires.
+ * The purpose is to allow replacing this file with a custom implementation
+ * to compile zstd without libc support.
+ */
+
+/* Need:
+ * NULL
+ * INT_MAX
+ * UINT_MAX
+ * ZSTD_memcpy()
+ * ZSTD_memset()
+ * ZSTD_memmove()
+ */
+#ifndef ZSTD_DEPS_COMMON
+#define ZSTD_DEPS_COMMON
+
+#include <limits.h>
+#include <stddef.h>
+#include <string.h>
+
+#if defined(__GNUC__) && __GNUC__ >= 4
+# define ZSTD_memcpy(d,s,l) __builtin_memcpy((d),(s),(l))
+# define ZSTD_memmove(d,s,l) __builtin_memmove((d),(s),(l))
+# define ZSTD_memset(p,v,l) __builtin_memset((p),(v),(l))
+#else
+# define ZSTD_memcpy(d,s,l) memcpy((d),(s),(l))
+# define ZSTD_memmove(d,s,l) memmove((d),(s),(l))
+# define ZSTD_memset(p,v,l) memset((p),(v),(l))
+#endif
+
+#endif /* ZSTD_DEPS_COMMON */
+
+/* Need:
+ * ZSTD_malloc()
+ * ZSTD_free()
+ * ZSTD_calloc()
+ */
+#ifdef ZSTD_DEPS_NEED_MALLOC
+#ifndef ZSTD_DEPS_MALLOC
+#define ZSTD_DEPS_MALLOC
+
+#include <stdlib.h>
+
+#define ZSTD_malloc(s) malloc(s)
+#define ZSTD_calloc(n,s) calloc((n), (s))
+#define ZSTD_free(p) free((p))
+
+#endif /* ZSTD_DEPS_MALLOC */
+#endif /* ZSTD_DEPS_NEED_MALLOC */
+
+/*
+ * Provides 64-bit math support.
+ * Need:
+ * U64 ZSTD_div64(U64 dividend, U32 divisor)
+ */
+#ifdef ZSTD_DEPS_NEED_MATH64
+#ifndef ZSTD_DEPS_MATH64
+#define ZSTD_DEPS_MATH64
+
+#define ZSTD_div64(dividend, divisor) ((dividend) / (divisor))
+
+#endif /* ZSTD_DEPS_MATH64 */
+#endif /* ZSTD_DEPS_NEED_MATH64 */
+
+/* Need:
+ * assert()
+ */
+#ifdef ZSTD_DEPS_NEED_ASSERT
+#ifndef ZSTD_DEPS_ASSERT
+#define ZSTD_DEPS_ASSERT
+
+#include <assert.h>
+
+#endif /* ZSTD_DEPS_ASSERT */
+#endif /* ZSTD_DEPS_NEED_ASSERT */
+
+/* Need:
+ * ZSTD_DEBUG_PRINT()
+ */
+#ifdef ZSTD_DEPS_NEED_IO
+#ifndef ZSTD_DEPS_IO
+#define ZSTD_DEPS_IO
+
+#include <stdio.h>
+#define ZSTD_DEBUG_PRINT(...) fprintf(stderr, __VA_ARGS__)
+
+#endif /* ZSTD_DEPS_IO */
+#endif /* ZSTD_DEPS_NEED_IO */
+
+/* Only requested when <stdint.h> is known to be present.
+ * Need:
+ * intptr_t
+ */
+#ifdef ZSTD_DEPS_NEED_STDINT
+#ifndef ZSTD_DEPS_STDINT
+#define ZSTD_DEPS_STDINT
+
+#include <stdint.h>
+
+#endif /* ZSTD_DEPS_STDINT */
+#endif /* ZSTD_DEPS_NEED_STDINT */
diff --git a/third_party/zstd/lib/common/zstd_internal.h b/third_party/zstd/lib/common/zstd_internal.h
new file mode 100644
index 0000000000..ecb9cfba87
--- /dev/null
+++ b/third_party/zstd/lib/common/zstd_internal.h
@@ -0,0 +1,392 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#ifndef ZSTD_CCOMMON_H_MODULE
+#define ZSTD_CCOMMON_H_MODULE
+
+/* this module contains definitions which must be identical
+ * across compression, decompression and dictBuilder.
+ * It also contains a few functions useful to at least 2 of them
+ * and which benefit from being inlined */
+
+/*-*************************************
+* Dependencies
+***************************************/
+#include "compiler.h"
+#include "cpu.h"
+#include "mem.h"
+#include "debug.h" /* assert, DEBUGLOG, RAWLOG, g_debuglevel */
+#include "error_private.h"
+#define ZSTD_STATIC_LINKING_ONLY
+#include "../zstd.h"
+#define FSE_STATIC_LINKING_ONLY
+#include "fse.h"
+#include "huf.h"
+#ifndef XXH_STATIC_LINKING_ONLY
+# define XXH_STATIC_LINKING_ONLY /* XXH64_state_t */
+#endif
+#include "xxhash.h" /* XXH_reset, update, digest */
+#ifndef ZSTD_NO_TRACE
+# include "zstd_trace.h"
+#else
+# define ZSTD_TRACE 0
+#endif
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+/* ---- static assert (debug) --- */
+#define ZSTD_STATIC_ASSERT(c) DEBUG_STATIC_ASSERT(c)
+#define ZSTD_isError ERR_isError /* for inlining */
+#define FSE_isError ERR_isError
+#define HUF_isError ERR_isError
+
+
+/*-*************************************
+* shared macros
+***************************************/
+#undef MIN
+#undef MAX
+#define MIN(a,b) ((a)<(b) ? (a) : (b))
+#define MAX(a,b) ((a)>(b) ? (a) : (b))
+#define BOUNDED(min,val,max) (MAX(min,MIN(val,max)))
+
+
+/*-*************************************
+* Common constants
+***************************************/
+#define ZSTD_OPT_NUM (1<<12)
+
+#define ZSTD_REP_NUM 3 /* number of repcodes */
+static UNUSED_ATTR const U32 repStartValue[ZSTD_REP_NUM] = { 1, 4, 8 };
+
+#define KB *(1 <<10)
+#define MB *(1 <<20)
+#define GB *(1U<<30)
+
+#define BIT7 128
+#define BIT6 64
+#define BIT5 32
+#define BIT4 16
+#define BIT1 2
+#define BIT0 1
+
+#define ZSTD_WINDOWLOG_ABSOLUTEMIN 10
+static UNUSED_ATTR const size_t ZSTD_fcs_fieldSize[4] = { 0, 2, 4, 8 };
+static UNUSED_ATTR const size_t ZSTD_did_fieldSize[4] = { 0, 1, 2, 4 };
+
+#define ZSTD_FRAMEIDSIZE 4 /* magic number size */
+
+#define ZSTD_BLOCKHEADERSIZE 3 /* C standard doesn't allow `static const` variable to be init using another `static const` variable */
+static UNUSED_ATTR const size_t ZSTD_blockHeaderSize = ZSTD_BLOCKHEADERSIZE;
+typedef enum { bt_raw, bt_rle, bt_compressed, bt_reserved } blockType_e;
+
+#define ZSTD_FRAMECHECKSUMSIZE 4
+
+#define MIN_SEQUENCES_SIZE 1 /* nbSeq==0 */
+#define MIN_CBLOCK_SIZE (1 /*litCSize*/ + 1 /* RLE or RAW */) /* for a non-null block */
+#define MIN_LITERALS_FOR_4_STREAMS 6
+
+typedef enum { set_basic, set_rle, set_compressed, set_repeat } symbolEncodingType_e;
+
+#define LONGNBSEQ 0x7F00
+
+#define MINMATCH 3
+
+#define Litbits 8
+#define LitHufLog 11
+#define MaxLit ((1<<Litbits) - 1)
+#define MaxML 52
+#define MaxLL 35
+#define DefaultMaxOff 28
+#define MaxOff 31
+#define MaxSeq MAX(MaxLL, MaxML) /* Assumption : MaxOff < MaxLL,MaxML */
+#define MLFSELog 9
+#define LLFSELog 9
+#define OffFSELog 8
+#define MaxFSELog MAX(MAX(MLFSELog, LLFSELog), OffFSELog)
+#define MaxMLBits 16
+#define MaxLLBits 16
+
+#define ZSTD_MAX_HUF_HEADER_SIZE 128 /* header + <= 127 byte tree description */
+/* Each table cannot take more than #symbols * FSELog bits */
+#define ZSTD_MAX_FSE_HEADERS_SIZE (((MaxML + 1) * MLFSELog + (MaxLL + 1) * LLFSELog + (MaxOff + 1) * OffFSELog + 7) / 8)
+
+static UNUSED_ATTR const U8 LL_bits[MaxLL+1] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 2, 2, 3, 3,
+ 4, 6, 7, 8, 9,10,11,12,
+ 13,14,15,16
+};
+static UNUSED_ATTR const S16 LL_defaultNorm[MaxLL+1] = {
+ 4, 3, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 1, 1, 1,
+ 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 3, 2, 1, 1, 1, 1, 1,
+ -1,-1,-1,-1
+};
+#define LL_DEFAULTNORMLOG 6 /* for static allocation */
+static UNUSED_ATTR const U32 LL_defaultNormLog = LL_DEFAULTNORMLOG;
+
+static UNUSED_ATTR const U8 ML_bits[MaxML+1] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 2, 2, 3, 3,
+ 4, 4, 5, 7, 8, 9,10,11,
+ 12,13,14,15,16
+};
+static UNUSED_ATTR const S16 ML_defaultNorm[MaxML+1] = {
+ 1, 4, 3, 2, 2, 2, 2, 2,
+ 2, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1,-1,-1,
+ -1,-1,-1,-1,-1
+};
+#define ML_DEFAULTNORMLOG 6 /* for static allocation */
+static UNUSED_ATTR const U32 ML_defaultNormLog = ML_DEFAULTNORMLOG;
+
+static UNUSED_ATTR const S16 OF_defaultNorm[DefaultMaxOff+1] = {
+ 1, 1, 1, 1, 1, 1, 2, 2,
+ 2, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ -1,-1,-1,-1,-1
+};
+#define OF_DEFAULTNORMLOG 5 /* for static allocation */
+static UNUSED_ATTR const U32 OF_defaultNormLog = OF_DEFAULTNORMLOG;
+
+
+/*-*******************************************
+* Shared functions to include for inlining
+*********************************************/
+static void ZSTD_copy8(void* dst, const void* src) {
+#if defined(ZSTD_ARCH_ARM_NEON)
+ vst1_u8((uint8_t*)dst, vld1_u8((const uint8_t*)src));
+#else
+ ZSTD_memcpy(dst, src, 8);
+#endif
+}
+#define COPY8(d,s) do { ZSTD_copy8(d,s); d+=8; s+=8; } while (0)
+
+/* Need to use memmove here since the literal buffer can now be located within
+ the dst buffer. In circumstances where the op "catches up" to where the
+ literal buffer is, there can be partial overlaps in this call on the final
+ copy if the literal is being shifted by less than 16 bytes. */
+static void ZSTD_copy16(void* dst, const void* src) {
+#if defined(ZSTD_ARCH_ARM_NEON)
+ vst1q_u8((uint8_t*)dst, vld1q_u8((const uint8_t*)src));
+#elif defined(ZSTD_ARCH_X86_SSE2)
+ _mm_storeu_si128((__m128i*)dst, _mm_loadu_si128((const __m128i*)src));
+#elif defined(__clang__)
+ ZSTD_memmove(dst, src, 16);
+#else
+ /* ZSTD_memmove is not inlined properly by gcc */
+ BYTE copy16_buf[16];
+ ZSTD_memcpy(copy16_buf, src, 16);
+ ZSTD_memcpy(dst, copy16_buf, 16);
+#endif
+}
+#define COPY16(d,s) do { ZSTD_copy16(d,s); d+=16; s+=16; } while (0)
+
+#define WILDCOPY_OVERLENGTH 32
+#define WILDCOPY_VECLEN 16
+
+typedef enum {
+ ZSTD_no_overlap,
+ ZSTD_overlap_src_before_dst
+ /* ZSTD_overlap_dst_before_src, */
+} ZSTD_overlap_e;
+
+/*! ZSTD_wildcopy() :
+ * Custom version of ZSTD_memcpy(), can over read/write up to WILDCOPY_OVERLENGTH bytes (if length==0)
+ * @param ovtype controls the overlap detection
+ * - ZSTD_no_overlap: The source and destination are guaranteed to be at least WILDCOPY_VECLEN bytes apart.
+ * - ZSTD_overlap_src_before_dst: The src and dst may overlap, but they MUST be at least 8 bytes apart.
+ * The src buffer must be before the dst buffer.
+ */
+MEM_STATIC FORCE_INLINE_ATTR
+void ZSTD_wildcopy(void* dst, const void* src, ptrdiff_t length, ZSTD_overlap_e const ovtype)
+{
+ ptrdiff_t diff = (BYTE*)dst - (const BYTE*)src;
+ const BYTE* ip = (const BYTE*)src;
+ BYTE* op = (BYTE*)dst;
+ BYTE* const oend = op + length;
+
+ if (ovtype == ZSTD_overlap_src_before_dst && diff < WILDCOPY_VECLEN) {
+ /* Handle short offset copies. */
+ do {
+ COPY8(op, ip);
+ } while (op < oend);
+ } else {
+ assert(diff >= WILDCOPY_VECLEN || diff <= -WILDCOPY_VECLEN);
+ /* Separate out the first COPY16() call because the copy length is
+ * almost certain to be short, so the branches have different
+ * probabilities. Since it is almost certain to be short, only do
+ * one COPY16() in the first call. Then, do two calls per loop since
+ * at that point it is more likely to have a high trip count.
+ */
+ ZSTD_copy16(op, ip);
+ if (16 >= length) return;
+ op += 16;
+ ip += 16;
+ do {
+ COPY16(op, ip);
+ COPY16(op, ip);
+ }
+ while (op < oend);
+ }
+}
+
+MEM_STATIC size_t ZSTD_limitCopy(void* dst, size_t dstCapacity, const void* src, size_t srcSize)
+{
+ size_t const length = MIN(dstCapacity, srcSize);
+ if (length > 0) {
+ ZSTD_memcpy(dst, src, length);
+ }
+ return length;
+}
+
+/* define "workspace is too large" as this number of times larger than needed */
+#define ZSTD_WORKSPACETOOLARGE_FACTOR 3
+
+/* when workspace is continuously too large
+ * during at least this number of times,
+ * context's memory usage is considered wasteful,
+ * because it's sized to handle a worst case scenario which rarely happens.
+ * In which case, resize it down to free some memory */
+#define ZSTD_WORKSPACETOOLARGE_MAXDURATION 128
+
+/* Controls whether the input/output buffer is buffered or stable. */
+typedef enum {
+ ZSTD_bm_buffered = 0, /* Buffer the input/output */
+ ZSTD_bm_stable = 1 /* ZSTD_inBuffer/ZSTD_outBuffer is stable */
+} ZSTD_bufferMode_e;
+
+
+/*-*******************************************
+* Private declarations
+*********************************************/
+typedef struct seqDef_s {
+ U32 offBase; /* offBase == Offset + ZSTD_REP_NUM, or repcode 1,2,3 */
+ U16 litLength;
+ U16 mlBase; /* mlBase == matchLength - MINMATCH */
+} seqDef;
+
+/* Controls whether seqStore has a single "long" litLength or matchLength. See seqStore_t. */
+typedef enum {
+ ZSTD_llt_none = 0, /* no longLengthType */
+ ZSTD_llt_literalLength = 1, /* represents a long literal */
+ ZSTD_llt_matchLength = 2 /* represents a long match */
+} ZSTD_longLengthType_e;
+
+typedef struct {
+ seqDef* sequencesStart;
+ seqDef* sequences; /* ptr to end of sequences */
+ BYTE* litStart;
+ BYTE* lit; /* ptr to end of literals */
+ BYTE* llCode;
+ BYTE* mlCode;
+ BYTE* ofCode;
+ size_t maxNbSeq;
+ size_t maxNbLit;
+
+ /* longLengthPos and longLengthType to allow us to represent either a single litLength or matchLength
+ * in the seqStore that has a value larger than U16 (if it exists). To do so, we increment
+ * the existing value of the litLength or matchLength by 0x10000.
+ */
+ ZSTD_longLengthType_e longLengthType;
+ U32 longLengthPos; /* Index of the sequence to apply long length modification to */
+} seqStore_t;
+
+typedef struct {
+ U32 litLength;
+ U32 matchLength;
+} ZSTD_sequenceLength;
+
+/**
+ * Returns the ZSTD_sequenceLength for the given sequences. It handles the decoding of long sequences
+ * indicated by longLengthPos and longLengthType, and adds MINMATCH back to matchLength.
+ */
+MEM_STATIC ZSTD_sequenceLength ZSTD_getSequenceLength(seqStore_t const* seqStore, seqDef const* seq)
+{
+ ZSTD_sequenceLength seqLen;
+ seqLen.litLength = seq->litLength;
+ seqLen.matchLength = seq->mlBase + MINMATCH;
+ if (seqStore->longLengthPos == (U32)(seq - seqStore->sequencesStart)) {
+ if (seqStore->longLengthType == ZSTD_llt_literalLength) {
+ seqLen.litLength += 0x10000;
+ }
+ if (seqStore->longLengthType == ZSTD_llt_matchLength) {
+ seqLen.matchLength += 0x10000;
+ }
+ }
+ return seqLen;
+}
+
+/**
+ * Contains the compressed frame size and an upper-bound for the decompressed frame size.
+ * Note: before using `compressedSize`, check for errors using ZSTD_isError().
+ * similarly, before using `decompressedBound`, check for errors using:
+ * `decompressedBound != ZSTD_CONTENTSIZE_ERROR`
+ */
+typedef struct {
+ size_t nbBlocks;
+ size_t compressedSize;
+ unsigned long long decompressedBound;
+} ZSTD_frameSizeInfo; /* decompress & legacy */
+
+const seqStore_t* ZSTD_getSeqStore(const ZSTD_CCtx* ctx); /* compress & dictBuilder */
+int ZSTD_seqToCodes(const seqStore_t* seqStorePtr); /* compress, dictBuilder, decodeCorpus (shouldn't get its definition from here) */
+
+
+/* ZSTD_invalidateRepCodes() :
+ * ensures next compression will not use repcodes from previous block.
+ * Note : only works with regular variant;
+ * do not use with extDict variant ! */
+void ZSTD_invalidateRepCodes(ZSTD_CCtx* cctx); /* zstdmt, adaptive_compression (shouldn't get this definition from here) */
+
+
+typedef struct {
+ blockType_e blockType;
+ U32 lastBlock;
+ U32 origSize;
+} blockProperties_t; /* declared here for decompress and fullbench */
+
+/*! ZSTD_getcBlockSize() :
+ * Provides the size of compressed block from block header `src` */
+/* Used by: decompress, fullbench */
+size_t ZSTD_getcBlockSize(const void* src, size_t srcSize,
+ blockProperties_t* bpPtr);
+
+/*! ZSTD_decodeSeqHeaders() :
+ * decode sequence header from src */
+/* Used by: zstd_decompress_block, fullbench */
+size_t ZSTD_decodeSeqHeaders(ZSTD_DCtx* dctx, int* nbSeqPtr,
+ const void* src, size_t srcSize);
+
+/**
+ * @returns true iff the CPU supports dynamic BMI2 dispatch.
+ */
+MEM_STATIC int ZSTD_cpuSupportsBmi2(void)
+{
+ ZSTD_cpuid_t cpuid = ZSTD_cpuid();
+ return ZSTD_cpuid_bmi1(cpuid) && ZSTD_cpuid_bmi2(cpuid);
+}
+
+#if defined (__cplusplus)
+}
+#endif
+
+#endif /* ZSTD_CCOMMON_H_MODULE */
diff --git a/third_party/zstd/lib/common/zstd_trace.h b/third_party/zstd/lib/common/zstd_trace.h
new file mode 100644
index 0000000000..da20534ebd
--- /dev/null
+++ b/third_party/zstd/lib/common/zstd_trace.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#ifndef ZSTD_TRACE_H
+#define ZSTD_TRACE_H
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+#include <stddef.h>
+
+/* weak symbol support
+ * For now, enable conservatively:
+ * - Only GNUC
+ * - Only ELF
+ * - Only x86-64, i386 and aarch64
+ * Also, explicitly disable on platforms known not to work so they aren't
+ * forgotten in the future.
+ */
+#if !defined(ZSTD_HAVE_WEAK_SYMBOLS) && \
+ defined(__GNUC__) && defined(__ELF__) && \
+ (defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) || defined(__aarch64__)) && \
+ !defined(__APPLE__) && !defined(_WIN32) && !defined(__MINGW32__) && \
+ !defined(__CYGWIN__) && !defined(_AIX)
+# define ZSTD_HAVE_WEAK_SYMBOLS 1
+#else
+# define ZSTD_HAVE_WEAK_SYMBOLS 0
+#endif
+#if ZSTD_HAVE_WEAK_SYMBOLS
+# define ZSTD_WEAK_ATTR __attribute__((__weak__))
+#else
+# define ZSTD_WEAK_ATTR
+#endif
+
+/* Only enable tracing when weak symbols are available. */
+#ifndef ZSTD_TRACE
+# define ZSTD_TRACE ZSTD_HAVE_WEAK_SYMBOLS
+#endif
+
+#if ZSTD_TRACE
+
+struct ZSTD_CCtx_s;
+struct ZSTD_DCtx_s;
+struct ZSTD_CCtx_params_s;
+
+typedef struct {
+ /**
+ * ZSTD_VERSION_NUMBER
+ *
+ * This is guaranteed to be the first member of ZSTD_trace.
+ * Otherwise, this struct is not stable between versions. If
+ * the version number does not match your expectation, you
+ * should not interpret the rest of the struct.
+ */
+ unsigned version;
+ /**
+ * Non-zero if streaming (de)compression is used.
+ */
+ unsigned streaming;
+ /**
+ * The dictionary ID.
+ */
+ unsigned dictionaryID;
+ /**
+ * Is the dictionary cold?
+ * Only set on decompression.
+ */
+ unsigned dictionaryIsCold;
+ /**
+ * The dictionary size or zero if no dictionary.
+ */
+ size_t dictionarySize;
+ /**
+ * The uncompressed size of the data.
+ */
+ size_t uncompressedSize;
+ /**
+ * The compressed size of the data.
+ */
+ size_t compressedSize;
+ /**
+ * The fully resolved CCtx parameters (NULL on decompression).
+ */
+ struct ZSTD_CCtx_params_s const* params;
+ /**
+ * The ZSTD_CCtx pointer (NULL on decompression).
+ */
+ struct ZSTD_CCtx_s const* cctx;
+ /**
+ * The ZSTD_DCtx pointer (NULL on compression).
+ */
+ struct ZSTD_DCtx_s const* dctx;
+} ZSTD_Trace;
+
+/**
+ * A tracing context. It must be 0 when tracing is disabled.
+ * Otherwise, any non-zero value returned by a tracing begin()
+ * function is presented to any subsequent calls to end().
+ *
+ * Any non-zero value is treated as tracing is enabled and not
+ * interpreted by the library.
+ *
+ * Two possible uses are:
+ * * A timestamp for when the begin() function was called.
+ * * A unique key identifying the (de)compression, like the
+ * address of the [dc]ctx pointer if you need to track
+ * more information than just a timestamp.
+ */
+typedef unsigned long long ZSTD_TraceCtx;
+
+/**
+ * Trace the beginning of a compression call.
+ * @param cctx The dctx pointer for the compression.
+ * It can be used as a key to map begin() to end().
+ * @returns Non-zero if tracing is enabled. The return value is
+ * passed to ZSTD_trace_compress_end().
+ */
+ZSTD_WEAK_ATTR ZSTD_TraceCtx ZSTD_trace_compress_begin(
+ struct ZSTD_CCtx_s const* cctx);
+
+/**
+ * Trace the end of a compression call.
+ * @param ctx The return value of ZSTD_trace_compress_begin().
+ * @param trace The zstd tracing info.
+ */
+ZSTD_WEAK_ATTR void ZSTD_trace_compress_end(
+ ZSTD_TraceCtx ctx,
+ ZSTD_Trace const* trace);
+
+/**
+ * Trace the beginning of a decompression call.
+ * @param dctx The dctx pointer for the decompression.
+ * It can be used as a key to map begin() to end().
+ * @returns Non-zero if tracing is enabled. The return value is
+ * passed to ZSTD_trace_compress_end().
+ */
+ZSTD_WEAK_ATTR ZSTD_TraceCtx ZSTD_trace_decompress_begin(
+ struct ZSTD_DCtx_s const* dctx);
+
+/**
+ * Trace the end of a decompression call.
+ * @param ctx The return value of ZSTD_trace_decompress_begin().
+ * @param trace The zstd tracing info.
+ */
+ZSTD_WEAK_ATTR void ZSTD_trace_decompress_end(
+ ZSTD_TraceCtx ctx,
+ ZSTD_Trace const* trace);
+
+#endif /* ZSTD_TRACE */
+
+#if defined (__cplusplus)
+}
+#endif
+
+#endif /* ZSTD_TRACE_H */
diff --git a/third_party/zstd/lib/decompress/huf_decompress.c b/third_party/zstd/lib/decompress/huf_decompress.c
new file mode 100644
index 0000000000..f85dd0beea
--- /dev/null
+++ b/third_party/zstd/lib/decompress/huf_decompress.c
@@ -0,0 +1,1944 @@
+/* ******************************************************************
+ * huff0 huffman decoder,
+ * part of Finite State Entropy library
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * You can contact the author at :
+ * - FSE+HUF source repository : https://github.com/Cyan4973/FiniteStateEntropy
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+****************************************************************** */
+
+/* **************************************************************
+* Dependencies
+****************************************************************/
+#include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memset */
+#include "../common/compiler.h"
+#include "../common/bitstream.h" /* BIT_* */
+#include "../common/fse.h" /* to compress headers */
+#include "../common/huf.h"
+#include "../common/error_private.h"
+#include "../common/zstd_internal.h"
+#include "../common/bits.h" /* ZSTD_highbit32, ZSTD_countTrailingZeros64 */
+
+/* **************************************************************
+* Constants
+****************************************************************/
+
+#define HUF_DECODER_FAST_TABLELOG 11
+
+/* **************************************************************
+* Macros
+****************************************************************/
+
+#ifdef HUF_DISABLE_FAST_DECODE
+# define HUF_ENABLE_FAST_DECODE 0
+#else
+# define HUF_ENABLE_FAST_DECODE 1
+#endif
+
+/* These two optional macros force the use one way or another of the two
+ * Huffman decompression implementations. You can't force in both directions
+ * at the same time.
+ */
+#if defined(HUF_FORCE_DECOMPRESS_X1) && \
+ defined(HUF_FORCE_DECOMPRESS_X2)
+#error "Cannot force the use of the X1 and X2 decoders at the same time!"
+#endif
+
+/* When DYNAMIC_BMI2 is enabled, fast decoders are only called when bmi2 is
+ * supported at runtime, so we can add the BMI2 target attribute.
+ * When it is disabled, we will still get BMI2 if it is enabled statically.
+ */
+#if DYNAMIC_BMI2
+# define HUF_FAST_BMI2_ATTRS BMI2_TARGET_ATTRIBUTE
+#else
+# define HUF_FAST_BMI2_ATTRS
+#endif
+
+#ifdef __cplusplus
+# define HUF_EXTERN_C extern "C"
+#else
+# define HUF_EXTERN_C
+#endif
+#define HUF_ASM_DECL HUF_EXTERN_C
+
+#if DYNAMIC_BMI2
+# define HUF_NEED_BMI2_FUNCTION 1
+#else
+# define HUF_NEED_BMI2_FUNCTION 0
+#endif
+
+/* **************************************************************
+* Error Management
+****************************************************************/
+#define HUF_isError ERR_isError
+
+
+/* **************************************************************
+* Byte alignment for workSpace management
+****************************************************************/
+#define HUF_ALIGN(x, a) HUF_ALIGN_MASK((x), (a) - 1)
+#define HUF_ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask))
+
+
+/* **************************************************************
+* BMI2 Variant Wrappers
+****************************************************************/
+typedef size_t (*HUF_DecompressUsingDTableFn)(void *dst, size_t dstSize,
+ const void *cSrc,
+ size_t cSrcSize,
+ const HUF_DTable *DTable);
+
+#if DYNAMIC_BMI2
+
+#define HUF_DGEN(fn) \
+ \
+ static size_t fn##_default( \
+ void* dst, size_t dstSize, \
+ const void* cSrc, size_t cSrcSize, \
+ const HUF_DTable* DTable) \
+ { \
+ return fn##_body(dst, dstSize, cSrc, cSrcSize, DTable); \
+ } \
+ \
+ static BMI2_TARGET_ATTRIBUTE size_t fn##_bmi2( \
+ void* dst, size_t dstSize, \
+ const void* cSrc, size_t cSrcSize, \
+ const HUF_DTable* DTable) \
+ { \
+ return fn##_body(dst, dstSize, cSrc, cSrcSize, DTable); \
+ } \
+ \
+ static size_t fn(void* dst, size_t dstSize, void const* cSrc, \
+ size_t cSrcSize, HUF_DTable const* DTable, int flags) \
+ { \
+ if (flags & HUF_flags_bmi2) { \
+ return fn##_bmi2(dst, dstSize, cSrc, cSrcSize, DTable); \
+ } \
+ return fn##_default(dst, dstSize, cSrc, cSrcSize, DTable); \
+ }
+
+#else
+
+#define HUF_DGEN(fn) \
+ static size_t fn(void* dst, size_t dstSize, void const* cSrc, \
+ size_t cSrcSize, HUF_DTable const* DTable, int flags) \
+ { \
+ (void)flags; \
+ return fn##_body(dst, dstSize, cSrc, cSrcSize, DTable); \
+ }
+
+#endif
+
+
+/*-***************************/
+/* generic DTableDesc */
+/*-***************************/
+typedef struct { BYTE maxTableLog; BYTE tableType; BYTE tableLog; BYTE reserved; } DTableDesc;
+
+static DTableDesc HUF_getDTableDesc(const HUF_DTable* table)
+{
+ DTableDesc dtd;
+ ZSTD_memcpy(&dtd, table, sizeof(dtd));
+ return dtd;
+}
+
+static size_t HUF_initFastDStream(BYTE const* ip) {
+ BYTE const lastByte = ip[7];
+ size_t const bitsConsumed = lastByte ? 8 - ZSTD_highbit32(lastByte) : 0;
+ size_t const value = MEM_readLEST(ip) | 1;
+ assert(bitsConsumed <= 8);
+ assert(sizeof(size_t) == 8);
+ return value << bitsConsumed;
+}
+
+
+/**
+ * The input/output arguments to the Huffman fast decoding loop:
+ *
+ * ip [in/out] - The input pointers, must be updated to reflect what is consumed.
+ * op [in/out] - The output pointers, must be updated to reflect what is written.
+ * bits [in/out] - The bitstream containers, must be updated to reflect the current state.
+ * dt [in] - The decoding table.
+ * ilowest [in] - The beginning of the valid range of the input. Decoders may read
+ * down to this pointer. It may be below iend[0].
+ * oend [in] - The end of the output stream. op[3] must not cross oend.
+ * iend [in] - The end of each input stream. ip[i] may cross iend[i],
+ * as long as it is above ilowest, but that indicates corruption.
+ */
+typedef struct {
+ BYTE const* ip[4];
+ BYTE* op[4];
+ U64 bits[4];
+ void const* dt;
+ BYTE const* ilowest;
+ BYTE* oend;
+ BYTE const* iend[4];
+} HUF_DecompressFastArgs;
+
+typedef void (*HUF_DecompressFastLoopFn)(HUF_DecompressFastArgs*);
+
+/**
+ * Initializes args for the fast decoding loop.
+ * @returns 1 on success
+ * 0 if the fallback implementation should be used.
+ * Or an error code on failure.
+ */
+static size_t HUF_DecompressFastArgs_init(HUF_DecompressFastArgs* args, void* dst, size_t dstSize, void const* src, size_t srcSize, const HUF_DTable* DTable)
+{
+ void const* dt = DTable + 1;
+ U32 const dtLog = HUF_getDTableDesc(DTable).tableLog;
+
+ const BYTE* const istart = (const BYTE*)src;
+
+ BYTE* const oend = ZSTD_maybeNullPtrAdd((BYTE*)dst, dstSize);
+
+ /* The fast decoding loop assumes 64-bit little-endian.
+ * This condition is false on x32.
+ */
+ if (!MEM_isLittleEndian() || MEM_32bits())
+ return 0;
+
+ /* Avoid nullptr addition */
+ if (dstSize == 0)
+ return 0;
+ assert(dst != NULL);
+
+ /* strict minimum : jump table + 1 byte per stream */
+ if (srcSize < 10)
+ return ERROR(corruption_detected);
+
+ /* Must have at least 8 bytes per stream because we don't handle initializing smaller bit containers.
+ * If table log is not correct at this point, fallback to the old decoder.
+ * On small inputs we don't have enough data to trigger the fast loop, so use the old decoder.
+ */
+ if (dtLog != HUF_DECODER_FAST_TABLELOG)
+ return 0;
+
+ /* Read the jump table. */
+ {
+ size_t const length1 = MEM_readLE16(istart);
+ size_t const length2 = MEM_readLE16(istart+2);
+ size_t const length3 = MEM_readLE16(istart+4);
+ size_t const length4 = srcSize - (length1 + length2 + length3 + 6);
+ args->iend[0] = istart + 6; /* jumpTable */
+ args->iend[1] = args->iend[0] + length1;
+ args->iend[2] = args->iend[1] + length2;
+ args->iend[3] = args->iend[2] + length3;
+
+ /* HUF_initFastDStream() requires this, and this small of an input
+ * won't benefit from the ASM loop anyways.
+ */
+ if (length1 < 8 || length2 < 8 || length3 < 8 || length4 < 8)
+ return 0;
+ if (length4 > srcSize) return ERROR(corruption_detected); /* overflow */
+ }
+ /* ip[] contains the position that is currently loaded into bits[]. */
+ args->ip[0] = args->iend[1] - sizeof(U64);
+ args->ip[1] = args->iend[2] - sizeof(U64);
+ args->ip[2] = args->iend[3] - sizeof(U64);
+ args->ip[3] = (BYTE const*)src + srcSize - sizeof(U64);
+
+ /* op[] contains the output pointers. */
+ args->op[0] = (BYTE*)dst;
+ args->op[1] = args->op[0] + (dstSize+3)/4;
+ args->op[2] = args->op[1] + (dstSize+3)/4;
+ args->op[3] = args->op[2] + (dstSize+3)/4;
+
+ /* No point to call the ASM loop for tiny outputs. */
+ if (args->op[3] >= oend)
+ return 0;
+
+ /* bits[] is the bit container.
+ * It is read from the MSB down to the LSB.
+ * It is shifted left as it is read, and zeros are
+ * shifted in. After the lowest valid bit a 1 is
+ * set, so that CountTrailingZeros(bits[]) can be used
+ * to count how many bits we've consumed.
+ */
+ args->bits[0] = HUF_initFastDStream(args->ip[0]);
+ args->bits[1] = HUF_initFastDStream(args->ip[1]);
+ args->bits[2] = HUF_initFastDStream(args->ip[2]);
+ args->bits[3] = HUF_initFastDStream(args->ip[3]);
+
+ /* The decoders must be sure to never read beyond ilowest.
+ * This is lower than iend[0], but allowing decoders to read
+ * down to ilowest can allow an extra iteration or two in the
+ * fast loop.
+ */
+ args->ilowest = istart;
+
+ args->oend = oend;
+ args->dt = dt;
+
+ return 1;
+}
+
+static size_t HUF_initRemainingDStream(BIT_DStream_t* bit, HUF_DecompressFastArgs const* args, int stream, BYTE* segmentEnd)
+{
+ /* Validate that we haven't overwritten. */
+ if (args->op[stream] > segmentEnd)
+ return ERROR(corruption_detected);
+ /* Validate that we haven't read beyond iend[].
+ * Note that ip[] may be < iend[] because the MSB is
+ * the next bit to read, and we may have consumed 100%
+ * of the stream, so down to iend[i] - 8 is valid.
+ */
+ if (args->ip[stream] < args->iend[stream] - 8)
+ return ERROR(corruption_detected);
+
+ /* Construct the BIT_DStream_t. */
+ assert(sizeof(size_t) == 8);
+ bit->bitContainer = MEM_readLEST(args->ip[stream]);
+ bit->bitsConsumed = ZSTD_countTrailingZeros64(args->bits[stream]);
+ bit->start = (const char*)args->ilowest;
+ bit->limitPtr = bit->start + sizeof(size_t);
+ bit->ptr = (const char*)args->ip[stream];
+
+ return 0;
+}
+
+/* Calls X(N) for each stream 0, 1, 2, 3. */
+#define HUF_4X_FOR_EACH_STREAM(X) \
+ do { \
+ X(0); \
+ X(1); \
+ X(2); \
+ X(3); \
+ } while (0)
+
+/* Calls X(N, var) for each stream 0, 1, 2, 3. */
+#define HUF_4X_FOR_EACH_STREAM_WITH_VAR(X, var) \
+ do { \
+ X(0, (var)); \
+ X(1, (var)); \
+ X(2, (var)); \
+ X(3, (var)); \
+ } while (0)
+
+
+#ifndef HUF_FORCE_DECOMPRESS_X2
+
+/*-***************************/
+/* single-symbol decoding */
+/*-***************************/
+typedef struct { BYTE nbBits; BYTE byte; } HUF_DEltX1; /* single-symbol decoding */
+
+/**
+ * Packs 4 HUF_DEltX1 structs into a U64. This is used to lay down 4 entries at
+ * a time.
+ */
+static U64 HUF_DEltX1_set4(BYTE symbol, BYTE nbBits) {
+ U64 D4;
+ if (MEM_isLittleEndian()) {
+ D4 = (U64)((symbol << 8) + nbBits);
+ } else {
+ D4 = (U64)(symbol + (nbBits << 8));
+ }
+ assert(D4 < (1U << 16));
+ D4 *= 0x0001000100010001ULL;
+ return D4;
+}
+
+/**
+ * Increase the tableLog to targetTableLog and rescales the stats.
+ * If tableLog > targetTableLog this is a no-op.
+ * @returns New tableLog
+ */
+static U32 HUF_rescaleStats(BYTE* huffWeight, U32* rankVal, U32 nbSymbols, U32 tableLog, U32 targetTableLog)
+{
+ if (tableLog > targetTableLog)
+ return tableLog;
+ if (tableLog < targetTableLog) {
+ U32 const scale = targetTableLog - tableLog;
+ U32 s;
+ /* Increase the weight for all non-zero probability symbols by scale. */
+ for (s = 0; s < nbSymbols; ++s) {
+ huffWeight[s] += (BYTE)((huffWeight[s] == 0) ? 0 : scale);
+ }
+ /* Update rankVal to reflect the new weights.
+ * All weights except 0 get moved to weight + scale.
+ * Weights [1, scale] are empty.
+ */
+ for (s = targetTableLog; s > scale; --s) {
+ rankVal[s] = rankVal[s - scale];
+ }
+ for (s = scale; s > 0; --s) {
+ rankVal[s] = 0;
+ }
+ }
+ return targetTableLog;
+}
+
+typedef struct {
+ U32 rankVal[HUF_TABLELOG_ABSOLUTEMAX + 1];
+ U32 rankStart[HUF_TABLELOG_ABSOLUTEMAX + 1];
+ U32 statsWksp[HUF_READ_STATS_WORKSPACE_SIZE_U32];
+ BYTE symbols[HUF_SYMBOLVALUE_MAX + 1];
+ BYTE huffWeight[HUF_SYMBOLVALUE_MAX + 1];
+} HUF_ReadDTableX1_Workspace;
+
+size_t HUF_readDTableX1_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int flags)
+{
+ U32 tableLog = 0;
+ U32 nbSymbols = 0;
+ size_t iSize;
+ void* const dtPtr = DTable + 1;
+ HUF_DEltX1* const dt = (HUF_DEltX1*)dtPtr;
+ HUF_ReadDTableX1_Workspace* wksp = (HUF_ReadDTableX1_Workspace*)workSpace;
+
+ DEBUG_STATIC_ASSERT(HUF_DECOMPRESS_WORKSPACE_SIZE >= sizeof(*wksp));
+ if (sizeof(*wksp) > wkspSize) return ERROR(tableLog_tooLarge);
+
+ DEBUG_STATIC_ASSERT(sizeof(DTableDesc) == sizeof(HUF_DTable));
+ /* ZSTD_memset(huffWeight, 0, sizeof(huffWeight)); */ /* is not necessary, even though some analyzer complain ... */
+
+ iSize = HUF_readStats_wksp(wksp->huffWeight, HUF_SYMBOLVALUE_MAX + 1, wksp->rankVal, &nbSymbols, &tableLog, src, srcSize, wksp->statsWksp, sizeof(wksp->statsWksp), flags);
+ if (HUF_isError(iSize)) return iSize;
+
+
+ /* Table header */
+ { DTableDesc dtd = HUF_getDTableDesc(DTable);
+ U32 const maxTableLog = dtd.maxTableLog + 1;
+ U32 const targetTableLog = MIN(maxTableLog, HUF_DECODER_FAST_TABLELOG);
+ tableLog = HUF_rescaleStats(wksp->huffWeight, wksp->rankVal, nbSymbols, tableLog, targetTableLog);
+ if (tableLog > (U32)(dtd.maxTableLog+1)) return ERROR(tableLog_tooLarge); /* DTable too small, Huffman tree cannot fit in */
+ dtd.tableType = 0;
+ dtd.tableLog = (BYTE)tableLog;
+ ZSTD_memcpy(DTable, &dtd, sizeof(dtd));
+ }
+
+ /* Compute symbols and rankStart given rankVal:
+ *
+ * rankVal already contains the number of values of each weight.
+ *
+ * symbols contains the symbols ordered by weight. First are the rankVal[0]
+ * weight 0 symbols, followed by the rankVal[1] weight 1 symbols, and so on.
+ * symbols[0] is filled (but unused) to avoid a branch.
+ *
+ * rankStart contains the offset where each rank belongs in the DTable.
+ * rankStart[0] is not filled because there are no entries in the table for
+ * weight 0.
+ */
+ { int n;
+ U32 nextRankStart = 0;
+ int const unroll = 4;
+ int const nLimit = (int)nbSymbols - unroll + 1;
+ for (n=0; n<(int)tableLog+1; n++) {
+ U32 const curr = nextRankStart;
+ nextRankStart += wksp->rankVal[n];
+ wksp->rankStart[n] = curr;
+ }
+ for (n=0; n < nLimit; n += unroll) {
+ int u;
+ for (u=0; u < unroll; ++u) {
+ size_t const w = wksp->huffWeight[n+u];
+ wksp->symbols[wksp->rankStart[w]++] = (BYTE)(n+u);
+ }
+ }
+ for (; n < (int)nbSymbols; ++n) {
+ size_t const w = wksp->huffWeight[n];
+ wksp->symbols[wksp->rankStart[w]++] = (BYTE)n;
+ }
+ }
+
+ /* fill DTable
+ * We fill all entries of each weight in order.
+ * That way length is a constant for each iteration of the outer loop.
+ * We can switch based on the length to a different inner loop which is
+ * optimized for that particular case.
+ */
+ { U32 w;
+ int symbol = wksp->rankVal[0];
+ int rankStart = 0;
+ for (w=1; w<tableLog+1; ++w) {
+ int const symbolCount = wksp->rankVal[w];
+ int const length = (1 << w) >> 1;
+ int uStart = rankStart;
+ BYTE const nbBits = (BYTE)(tableLog + 1 - w);
+ int s;
+ int u;
+ switch (length) {
+ case 1:
+ for (s=0; s<symbolCount; ++s) {
+ HUF_DEltX1 D;
+ D.byte = wksp->symbols[symbol + s];
+ D.nbBits = nbBits;
+ dt[uStart] = D;
+ uStart += 1;
+ }
+ break;
+ case 2:
+ for (s=0; s<symbolCount; ++s) {
+ HUF_DEltX1 D;
+ D.byte = wksp->symbols[symbol + s];
+ D.nbBits = nbBits;
+ dt[uStart+0] = D;
+ dt[uStart+1] = D;
+ uStart += 2;
+ }
+ break;
+ case 4:
+ for (s=0; s<symbolCount; ++s) {
+ U64 const D4 = HUF_DEltX1_set4(wksp->symbols[symbol + s], nbBits);
+ MEM_write64(dt + uStart, D4);
+ uStart += 4;
+ }
+ break;
+ case 8:
+ for (s=0; s<symbolCount; ++s) {
+ U64 const D4 = HUF_DEltX1_set4(wksp->symbols[symbol + s], nbBits);
+ MEM_write64(dt + uStart, D4);
+ MEM_write64(dt + uStart + 4, D4);
+ uStart += 8;
+ }
+ break;
+ default:
+ for (s=0; s<symbolCount; ++s) {
+ U64 const D4 = HUF_DEltX1_set4(wksp->symbols[symbol + s], nbBits);
+ for (u=0; u < length; u += 16) {
+ MEM_write64(dt + uStart + u + 0, D4);
+ MEM_write64(dt + uStart + u + 4, D4);
+ MEM_write64(dt + uStart + u + 8, D4);
+ MEM_write64(dt + uStart + u + 12, D4);
+ }
+ assert(u == length);
+ uStart += length;
+ }
+ break;
+ }
+ symbol += symbolCount;
+ rankStart += symbolCount * length;
+ }
+ }
+ return iSize;
+}
+
+FORCE_INLINE_TEMPLATE BYTE
+HUF_decodeSymbolX1(BIT_DStream_t* Dstream, const HUF_DEltX1* dt, const U32 dtLog)
+{
+ size_t const val = BIT_lookBitsFast(Dstream, dtLog); /* note : dtLog >= 1 */
+ BYTE const c = dt[val].byte;
+ BIT_skipBits(Dstream, dt[val].nbBits);
+ return c;
+}
+
+#define HUF_DECODE_SYMBOLX1_0(ptr, DStreamPtr) \
+ do { *ptr++ = HUF_decodeSymbolX1(DStreamPtr, dt, dtLog); } while (0)
+
+#define HUF_DECODE_SYMBOLX1_1(ptr, DStreamPtr) \
+ do { \
+ if (MEM_64bits() || (HUF_TABLELOG_MAX<=12)) \
+ HUF_DECODE_SYMBOLX1_0(ptr, DStreamPtr); \
+ } while (0)
+
+#define HUF_DECODE_SYMBOLX1_2(ptr, DStreamPtr) \
+ do { \
+ if (MEM_64bits()) \
+ HUF_DECODE_SYMBOLX1_0(ptr, DStreamPtr); \
+ } while (0)
+
+HINT_INLINE size_t
+HUF_decodeStreamX1(BYTE* p, BIT_DStream_t* const bitDPtr, BYTE* const pEnd, const HUF_DEltX1* const dt, const U32 dtLog)
+{
+ BYTE* const pStart = p;
+
+ /* up to 4 symbols at a time */
+ if ((pEnd - p) > 3) {
+ while ((BIT_reloadDStream(bitDPtr) == BIT_DStream_unfinished) & (p < pEnd-3)) {
+ HUF_DECODE_SYMBOLX1_2(p, bitDPtr);
+ HUF_DECODE_SYMBOLX1_1(p, bitDPtr);
+ HUF_DECODE_SYMBOLX1_2(p, bitDPtr);
+ HUF_DECODE_SYMBOLX1_0(p, bitDPtr);
+ }
+ } else {
+ BIT_reloadDStream(bitDPtr);
+ }
+
+ /* [0-3] symbols remaining */
+ if (MEM_32bits())
+ while ((BIT_reloadDStream(bitDPtr) == BIT_DStream_unfinished) & (p < pEnd))
+ HUF_DECODE_SYMBOLX1_0(p, bitDPtr);
+
+ /* no more data to retrieve from bitstream, no need to reload */
+ while (p < pEnd)
+ HUF_DECODE_SYMBOLX1_0(p, bitDPtr);
+
+ return (size_t)(pEnd-pStart);
+}
+
+FORCE_INLINE_TEMPLATE size_t
+HUF_decompress1X1_usingDTable_internal_body(
+ void* dst, size_t dstSize,
+ const void* cSrc, size_t cSrcSize,
+ const HUF_DTable* DTable)
+{
+ BYTE* op = (BYTE*)dst;
+ BYTE* const oend = ZSTD_maybeNullPtrAdd(op, dstSize);
+ const void* dtPtr = DTable + 1;
+ const HUF_DEltX1* const dt = (const HUF_DEltX1*)dtPtr;
+ BIT_DStream_t bitD;
+ DTableDesc const dtd = HUF_getDTableDesc(DTable);
+ U32 const dtLog = dtd.tableLog;
+
+ CHECK_F( BIT_initDStream(&bitD, cSrc, cSrcSize) );
+
+ HUF_decodeStreamX1(op, &bitD, oend, dt, dtLog);
+
+ if (!BIT_endOfDStream(&bitD)) return ERROR(corruption_detected);
+
+ return dstSize;
+}
+
+/* HUF_decompress4X1_usingDTable_internal_body():
+ * Conditions :
+ * @dstSize >= 6
+ */
+FORCE_INLINE_TEMPLATE size_t
+HUF_decompress4X1_usingDTable_internal_body(
+ void* dst, size_t dstSize,
+ const void* cSrc, size_t cSrcSize,
+ const HUF_DTable* DTable)
+{
+ /* Check */
+ if (cSrcSize < 10) return ERROR(corruption_detected); /* strict minimum : jump table + 1 byte per stream */
+ if (dstSize < 6) return ERROR(corruption_detected); /* stream 4-split doesn't work */
+
+ { const BYTE* const istart = (const BYTE*) cSrc;
+ BYTE* const ostart = (BYTE*) dst;
+ BYTE* const oend = ostart + dstSize;
+ BYTE* const olimit = oend - 3;
+ const void* const dtPtr = DTable + 1;
+ const HUF_DEltX1* const dt = (const HUF_DEltX1*)dtPtr;
+
+ /* Init */
+ BIT_DStream_t bitD1;
+ BIT_DStream_t bitD2;
+ BIT_DStream_t bitD3;
+ BIT_DStream_t bitD4;
+ size_t const length1 = MEM_readLE16(istart);
+ size_t const length2 = MEM_readLE16(istart+2);
+ size_t const length3 = MEM_readLE16(istart+4);
+ size_t const length4 = cSrcSize - (length1 + length2 + length3 + 6);
+ const BYTE* const istart1 = istart + 6; /* jumpTable */
+ const BYTE* const istart2 = istart1 + length1;
+ const BYTE* const istart3 = istart2 + length2;
+ const BYTE* const istart4 = istart3 + length3;
+ const size_t segmentSize = (dstSize+3) / 4;
+ BYTE* const opStart2 = ostart + segmentSize;
+ BYTE* const opStart3 = opStart2 + segmentSize;
+ BYTE* const opStart4 = opStart3 + segmentSize;
+ BYTE* op1 = ostart;
+ BYTE* op2 = opStart2;
+ BYTE* op3 = opStart3;
+ BYTE* op4 = opStart4;
+ DTableDesc const dtd = HUF_getDTableDesc(DTable);
+ U32 const dtLog = dtd.tableLog;
+ U32 endSignal = 1;
+
+ if (length4 > cSrcSize) return ERROR(corruption_detected); /* overflow */
+ if (opStart4 > oend) return ERROR(corruption_detected); /* overflow */
+ assert(dstSize >= 6); /* validated above */
+ CHECK_F( BIT_initDStream(&bitD1, istart1, length1) );
+ CHECK_F( BIT_initDStream(&bitD2, istart2, length2) );
+ CHECK_F( BIT_initDStream(&bitD3, istart3, length3) );
+ CHECK_F( BIT_initDStream(&bitD4, istart4, length4) );
+
+ /* up to 16 symbols per loop (4 symbols per stream) in 64-bit mode */
+ if ((size_t)(oend - op4) >= sizeof(size_t)) {
+ for ( ; (endSignal) & (op4 < olimit) ; ) {
+ HUF_DECODE_SYMBOLX1_2(op1, &bitD1);
+ HUF_DECODE_SYMBOLX1_2(op2, &bitD2);
+ HUF_DECODE_SYMBOLX1_2(op3, &bitD3);
+ HUF_DECODE_SYMBOLX1_2(op4, &bitD4);
+ HUF_DECODE_SYMBOLX1_1(op1, &bitD1);
+ HUF_DECODE_SYMBOLX1_1(op2, &bitD2);
+ HUF_DECODE_SYMBOLX1_1(op3, &bitD3);
+ HUF_DECODE_SYMBOLX1_1(op4, &bitD4);
+ HUF_DECODE_SYMBOLX1_2(op1, &bitD1);
+ HUF_DECODE_SYMBOLX1_2(op2, &bitD2);
+ HUF_DECODE_SYMBOLX1_2(op3, &bitD3);
+ HUF_DECODE_SYMBOLX1_2(op4, &bitD4);
+ HUF_DECODE_SYMBOLX1_0(op1, &bitD1);
+ HUF_DECODE_SYMBOLX1_0(op2, &bitD2);
+ HUF_DECODE_SYMBOLX1_0(op3, &bitD3);
+ HUF_DECODE_SYMBOLX1_0(op4, &bitD4);
+ endSignal &= BIT_reloadDStreamFast(&bitD1) == BIT_DStream_unfinished;
+ endSignal &= BIT_reloadDStreamFast(&bitD2) == BIT_DStream_unfinished;
+ endSignal &= BIT_reloadDStreamFast(&bitD3) == BIT_DStream_unfinished;
+ endSignal &= BIT_reloadDStreamFast(&bitD4) == BIT_DStream_unfinished;
+ }
+ }
+
+ /* check corruption */
+ /* note : should not be necessary : op# advance in lock step, and we control op4.
+ * but curiously, binary generated by gcc 7.2 & 7.3 with -mbmi2 runs faster when >=1 test is present */
+ if (op1 > opStart2) return ERROR(corruption_detected);
+ if (op2 > opStart3) return ERROR(corruption_detected);
+ if (op3 > opStart4) return ERROR(corruption_detected);
+ /* note : op4 supposed already verified within main loop */
+
+ /* finish bitStreams one by one */
+ HUF_decodeStreamX1(op1, &bitD1, opStart2, dt, dtLog);
+ HUF_decodeStreamX1(op2, &bitD2, opStart3, dt, dtLog);
+ HUF_decodeStreamX1(op3, &bitD3, opStart4, dt, dtLog);
+ HUF_decodeStreamX1(op4, &bitD4, oend, dt, dtLog);
+
+ /* check */
+ { U32 const endCheck = BIT_endOfDStream(&bitD1) & BIT_endOfDStream(&bitD2) & BIT_endOfDStream(&bitD3) & BIT_endOfDStream(&bitD4);
+ if (!endCheck) return ERROR(corruption_detected); }
+
+ /* decoded size */
+ return dstSize;
+ }
+}
+
+#if HUF_NEED_BMI2_FUNCTION
+static BMI2_TARGET_ATTRIBUTE
+size_t HUF_decompress4X1_usingDTable_internal_bmi2(void* dst, size_t dstSize, void const* cSrc,
+ size_t cSrcSize, HUF_DTable const* DTable) {
+ return HUF_decompress4X1_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable);
+}
+#endif
+
+static
+size_t HUF_decompress4X1_usingDTable_internal_default(void* dst, size_t dstSize, void const* cSrc,
+ size_t cSrcSize, HUF_DTable const* DTable) {
+ return HUF_decompress4X1_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable);
+}
+
+#if ZSTD_ENABLE_ASM_X86_64_BMI2
+
+HUF_ASM_DECL void HUF_decompress4X1_usingDTable_internal_fast_asm_loop(HUF_DecompressFastArgs* args) ZSTDLIB_HIDDEN;
+
+#endif
+
+static HUF_FAST_BMI2_ATTRS
+void HUF_decompress4X1_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* args)
+{
+ U64 bits[4];
+ BYTE const* ip[4];
+ BYTE* op[4];
+ U16 const* const dtable = (U16 const*)args->dt;
+ BYTE* const oend = args->oend;
+ BYTE const* const ilowest = args->ilowest;
+
+ /* Copy the arguments to local variables */
+ ZSTD_memcpy(&bits, &args->bits, sizeof(bits));
+ ZSTD_memcpy((void*)(&ip), &args->ip, sizeof(ip));
+ ZSTD_memcpy(&op, &args->op, sizeof(op));
+
+ assert(MEM_isLittleEndian());
+ assert(!MEM_32bits());
+
+ for (;;) {
+ BYTE* olimit;
+ int stream;
+
+ /* Assert loop preconditions */
+#ifndef NDEBUG
+ for (stream = 0; stream < 4; ++stream) {
+ assert(op[stream] <= (stream == 3 ? oend : op[stream + 1]));
+ assert(ip[stream] >= ilowest);
+ }
+#endif
+ /* Compute olimit */
+ {
+ /* Each iteration produces 5 output symbols per stream */
+ size_t const oiters = (size_t)(oend - op[3]) / 5;
+ /* Each iteration consumes up to 11 bits * 5 = 55 bits < 7 bytes
+ * per stream.
+ */
+ size_t const iiters = (size_t)(ip[0] - ilowest) / 7;
+ /* We can safely run iters iterations before running bounds checks */
+ size_t const iters = MIN(oiters, iiters);
+ size_t const symbols = iters * 5;
+
+ /* We can simply check that op[3] < olimit, instead of checking all
+ * of our bounds, since we can't hit the other bounds until we've run
+ * iters iterations, which only happens when op[3] == olimit.
+ */
+ olimit = op[3] + symbols;
+
+ /* Exit fast decoding loop once we reach the end. */
+ if (op[3] == olimit)
+ break;
+
+ /* Exit the decoding loop if any input pointer has crossed the
+ * previous one. This indicates corruption, and a precondition
+ * to our loop is that ip[i] >= ip[0].
+ */
+ for (stream = 1; stream < 4; ++stream) {
+ if (ip[stream] < ip[stream - 1])
+ goto _out;
+ }
+ }
+
+#ifndef NDEBUG
+ for (stream = 1; stream < 4; ++stream) {
+ assert(ip[stream] >= ip[stream - 1]);
+ }
+#endif
+
+#define HUF_4X1_DECODE_SYMBOL(_stream, _symbol) \
+ do { \
+ int const index = (int)(bits[(_stream)] >> 53); \
+ int const entry = (int)dtable[index]; \
+ bits[(_stream)] <<= (entry & 0x3F); \
+ op[(_stream)][(_symbol)] = (BYTE)((entry >> 8) & 0xFF); \
+ } while (0)
+
+#define HUF_4X1_RELOAD_STREAM(_stream) \
+ do { \
+ int const ctz = ZSTD_countTrailingZeros64(bits[(_stream)]); \
+ int const nbBits = ctz & 7; \
+ int const nbBytes = ctz >> 3; \
+ op[(_stream)] += 5; \
+ ip[(_stream)] -= nbBytes; \
+ bits[(_stream)] = MEM_read64(ip[(_stream)]) | 1; \
+ bits[(_stream)] <<= nbBits; \
+ } while (0)
+
+ /* Manually unroll the loop because compilers don't consistently
+ * unroll the inner loops, which destroys performance.
+ */
+ do {
+ /* Decode 5 symbols in each of the 4 streams */
+ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 0);
+ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 1);
+ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 2);
+ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 3);
+ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 4);
+
+ /* Reload each of the 4 the bitstreams */
+ HUF_4X_FOR_EACH_STREAM(HUF_4X1_RELOAD_STREAM);
+ } while (op[3] < olimit);
+
+#undef HUF_4X1_DECODE_SYMBOL
+#undef HUF_4X1_RELOAD_STREAM
+ }
+
+_out:
+
+ /* Save the final values of each of the state variables back to args. */
+ ZSTD_memcpy(&args->bits, &bits, sizeof(bits));
+ ZSTD_memcpy((void*)(&args->ip), &ip, sizeof(ip));
+ ZSTD_memcpy(&args->op, &op, sizeof(op));
+}
+
+/**
+ * @returns @p dstSize on success (>= 6)
+ * 0 if the fallback implementation should be used
+ * An error if an error occurred
+ */
+static HUF_FAST_BMI2_ATTRS
+size_t
+HUF_decompress4X1_usingDTable_internal_fast(
+ void* dst, size_t dstSize,
+ const void* cSrc, size_t cSrcSize,
+ const HUF_DTable* DTable,
+ HUF_DecompressFastLoopFn loopFn)
+{
+ void const* dt = DTable + 1;
+ BYTE const* const ilowest = (BYTE const*)cSrc;
+ BYTE* const oend = ZSTD_maybeNullPtrAdd((BYTE*)dst, dstSize);
+ HUF_DecompressFastArgs args;
+ { size_t const ret = HUF_DecompressFastArgs_init(&args, dst, dstSize, cSrc, cSrcSize, DTable);
+ FORWARD_IF_ERROR(ret, "Failed to init fast loop args");
+ if (ret == 0)
+ return 0;
+ }
+
+ assert(args.ip[0] >= args.ilowest);
+ loopFn(&args);
+
+ /* Our loop guarantees that ip[] >= ilowest and that we haven't
+ * overwritten any op[].
+ */
+ assert(args.ip[0] >= ilowest);
+ assert(args.ip[0] >= ilowest);
+ assert(args.ip[1] >= ilowest);
+ assert(args.ip[2] >= ilowest);
+ assert(args.ip[3] >= ilowest);
+ assert(args.op[3] <= oend);
+
+ assert(ilowest == args.ilowest);
+ assert(ilowest + 6 == args.iend[0]);
+ (void)ilowest;
+
+ /* finish bit streams one by one. */
+ { size_t const segmentSize = (dstSize+3) / 4;
+ BYTE* segmentEnd = (BYTE*)dst;
+ int i;
+ for (i = 0; i < 4; ++i) {
+ BIT_DStream_t bit;
+ if (segmentSize <= (size_t)(oend - segmentEnd))
+ segmentEnd += segmentSize;
+ else
+ segmentEnd = oend;
+ FORWARD_IF_ERROR(HUF_initRemainingDStream(&bit, &args, i, segmentEnd), "corruption");
+ /* Decompress and validate that we've produced exactly the expected length. */
+ args.op[i] += HUF_decodeStreamX1(args.op[i], &bit, segmentEnd, (HUF_DEltX1 const*)dt, HUF_DECODER_FAST_TABLELOG);
+ if (args.op[i] != segmentEnd) return ERROR(corruption_detected);
+ }
+ }
+
+ /* decoded size */
+ assert(dstSize != 0);
+ return dstSize;
+}
+
+HUF_DGEN(HUF_decompress1X1_usingDTable_internal)
+
+static size_t HUF_decompress4X1_usingDTable_internal(void* dst, size_t dstSize, void const* cSrc,
+ size_t cSrcSize, HUF_DTable const* DTable, int flags)
+{
+ HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X1_usingDTable_internal_default;
+ HUF_DecompressFastLoopFn loopFn = HUF_decompress4X1_usingDTable_internal_fast_c_loop;
+
+#if DYNAMIC_BMI2
+ if (flags & HUF_flags_bmi2) {
+ fallbackFn = HUF_decompress4X1_usingDTable_internal_bmi2;
+# if ZSTD_ENABLE_ASM_X86_64_BMI2
+ if (!(flags & HUF_flags_disableAsm)) {
+ loopFn = HUF_decompress4X1_usingDTable_internal_fast_asm_loop;
+ }
+# endif
+ } else {
+ return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable);
+ }
+#endif
+
+#if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__)
+ if (!(flags & HUF_flags_disableAsm)) {
+ loopFn = HUF_decompress4X1_usingDTable_internal_fast_asm_loop;
+ }
+#endif
+
+ if (HUF_ENABLE_FAST_DECODE && !(flags & HUF_flags_disableFast)) {
+ size_t const ret = HUF_decompress4X1_usingDTable_internal_fast(dst, dstSize, cSrc, cSrcSize, DTable, loopFn);
+ if (ret != 0)
+ return ret;
+ }
+ return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable);
+}
+
+static size_t HUF_decompress4X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize,
+ const void* cSrc, size_t cSrcSize,
+ void* workSpace, size_t wkspSize, int flags)
+{
+ const BYTE* ip = (const BYTE*) cSrc;
+
+ size_t const hSize = HUF_readDTableX1_wksp(dctx, cSrc, cSrcSize, workSpace, wkspSize, flags);
+ if (HUF_isError(hSize)) return hSize;
+ if (hSize >= cSrcSize) return ERROR(srcSize_wrong);
+ ip += hSize; cSrcSize -= hSize;
+
+ return HUF_decompress4X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, flags);
+}
+
+#endif /* HUF_FORCE_DECOMPRESS_X2 */
+
+
+#ifndef HUF_FORCE_DECOMPRESS_X1
+
+/* *************************/
+/* double-symbols decoding */
+/* *************************/
+
+typedef struct { U16 sequence; BYTE nbBits; BYTE length; } HUF_DEltX2; /* double-symbols decoding */
+typedef struct { BYTE symbol; } sortedSymbol_t;
+typedef U32 rankValCol_t[HUF_TABLELOG_MAX + 1];
+typedef rankValCol_t rankVal_t[HUF_TABLELOG_MAX];
+
+/**
+ * Constructs a HUF_DEltX2 in a U32.
+ */
+static U32 HUF_buildDEltX2U32(U32 symbol, U32 nbBits, U32 baseSeq, int level)
+{
+ U32 seq;
+ DEBUG_STATIC_ASSERT(offsetof(HUF_DEltX2, sequence) == 0);
+ DEBUG_STATIC_ASSERT(offsetof(HUF_DEltX2, nbBits) == 2);
+ DEBUG_STATIC_ASSERT(offsetof(HUF_DEltX2, length) == 3);
+ DEBUG_STATIC_ASSERT(sizeof(HUF_DEltX2) == sizeof(U32));
+ if (MEM_isLittleEndian()) {
+ seq = level == 1 ? symbol : (baseSeq + (symbol << 8));
+ return seq + (nbBits << 16) + ((U32)level << 24);
+ } else {
+ seq = level == 1 ? (symbol << 8) : ((baseSeq << 8) + symbol);
+ return (seq << 16) + (nbBits << 8) + (U32)level;
+ }
+}
+
+/**
+ * Constructs a HUF_DEltX2.
+ */
+static HUF_DEltX2 HUF_buildDEltX2(U32 symbol, U32 nbBits, U32 baseSeq, int level)
+{
+ HUF_DEltX2 DElt;
+ U32 const val = HUF_buildDEltX2U32(symbol, nbBits, baseSeq, level);
+ DEBUG_STATIC_ASSERT(sizeof(DElt) == sizeof(val));
+ ZSTD_memcpy(&DElt, &val, sizeof(val));
+ return DElt;
+}
+
+/**
+ * Constructs 2 HUF_DEltX2s and packs them into a U64.
+ */
+static U64 HUF_buildDEltX2U64(U32 symbol, U32 nbBits, U16 baseSeq, int level)
+{
+ U32 DElt = HUF_buildDEltX2U32(symbol, nbBits, baseSeq, level);
+ return (U64)DElt + ((U64)DElt << 32);
+}
+
+/**
+ * Fills the DTable rank with all the symbols from [begin, end) that are each
+ * nbBits long.
+ *
+ * @param DTableRank The start of the rank in the DTable.
+ * @param begin The first symbol to fill (inclusive).
+ * @param end The last symbol to fill (exclusive).
+ * @param nbBits Each symbol is nbBits long.
+ * @param tableLog The table log.
+ * @param baseSeq If level == 1 { 0 } else { the first level symbol }
+ * @param level The level in the table. Must be 1 or 2.
+ */
+static void HUF_fillDTableX2ForWeight(
+ HUF_DEltX2* DTableRank,
+ sortedSymbol_t const* begin, sortedSymbol_t const* end,
+ U32 nbBits, U32 tableLog,
+ U16 baseSeq, int const level)
+{
+ U32 const length = 1U << ((tableLog - nbBits) & 0x1F /* quiet static-analyzer */);
+ const sortedSymbol_t* ptr;
+ assert(level >= 1 && level <= 2);
+ switch (length) {
+ case 1:
+ for (ptr = begin; ptr != end; ++ptr) {
+ HUF_DEltX2 const DElt = HUF_buildDEltX2(ptr->symbol, nbBits, baseSeq, level);
+ *DTableRank++ = DElt;
+ }
+ break;
+ case 2:
+ for (ptr = begin; ptr != end; ++ptr) {
+ HUF_DEltX2 const DElt = HUF_buildDEltX2(ptr->symbol, nbBits, baseSeq, level);
+ DTableRank[0] = DElt;
+ DTableRank[1] = DElt;
+ DTableRank += 2;
+ }
+ break;
+ case 4:
+ for (ptr = begin; ptr != end; ++ptr) {
+ U64 const DEltX2 = HUF_buildDEltX2U64(ptr->symbol, nbBits, baseSeq, level);
+ ZSTD_memcpy(DTableRank + 0, &DEltX2, sizeof(DEltX2));
+ ZSTD_memcpy(DTableRank + 2, &DEltX2, sizeof(DEltX2));
+ DTableRank += 4;
+ }
+ break;
+ case 8:
+ for (ptr = begin; ptr != end; ++ptr) {
+ U64 const DEltX2 = HUF_buildDEltX2U64(ptr->symbol, nbBits, baseSeq, level);
+ ZSTD_memcpy(DTableRank + 0, &DEltX2, sizeof(DEltX2));
+ ZSTD_memcpy(DTableRank + 2, &DEltX2, sizeof(DEltX2));
+ ZSTD_memcpy(DTableRank + 4, &DEltX2, sizeof(DEltX2));
+ ZSTD_memcpy(DTableRank + 6, &DEltX2, sizeof(DEltX2));
+ DTableRank += 8;
+ }
+ break;
+ default:
+ for (ptr = begin; ptr != end; ++ptr) {
+ U64 const DEltX2 = HUF_buildDEltX2U64(ptr->symbol, nbBits, baseSeq, level);
+ HUF_DEltX2* const DTableRankEnd = DTableRank + length;
+ for (; DTableRank != DTableRankEnd; DTableRank += 8) {
+ ZSTD_memcpy(DTableRank + 0, &DEltX2, sizeof(DEltX2));
+ ZSTD_memcpy(DTableRank + 2, &DEltX2, sizeof(DEltX2));
+ ZSTD_memcpy(DTableRank + 4, &DEltX2, sizeof(DEltX2));
+ ZSTD_memcpy(DTableRank + 6, &DEltX2, sizeof(DEltX2));
+ }
+ }
+ break;
+ }
+}
+
+/* HUF_fillDTableX2Level2() :
+ * `rankValOrigin` must be a table of at least (HUF_TABLELOG_MAX + 1) U32 */
+static void HUF_fillDTableX2Level2(HUF_DEltX2* DTable, U32 targetLog, const U32 consumedBits,
+ const U32* rankVal, const int minWeight, const int maxWeight1,
+ const sortedSymbol_t* sortedSymbols, U32 const* rankStart,
+ U32 nbBitsBaseline, U16 baseSeq)
+{
+ /* Fill skipped values (all positions up to rankVal[minWeight]).
+ * These are positions only get a single symbol because the combined weight
+ * is too large.
+ */
+ if (minWeight>1) {
+ U32 const length = 1U << ((targetLog - consumedBits) & 0x1F /* quiet static-analyzer */);
+ U64 const DEltX2 = HUF_buildDEltX2U64(baseSeq, consumedBits, /* baseSeq */ 0, /* level */ 1);
+ int const skipSize = rankVal[minWeight];
+ assert(length > 1);
+ assert((U32)skipSize < length);
+ switch (length) {
+ case 2:
+ assert(skipSize == 1);
+ ZSTD_memcpy(DTable, &DEltX2, sizeof(DEltX2));
+ break;
+ case 4:
+ assert(skipSize <= 4);
+ ZSTD_memcpy(DTable + 0, &DEltX2, sizeof(DEltX2));
+ ZSTD_memcpy(DTable + 2, &DEltX2, sizeof(DEltX2));
+ break;
+ default:
+ {
+ int i;
+ for (i = 0; i < skipSize; i += 8) {
+ ZSTD_memcpy(DTable + i + 0, &DEltX2, sizeof(DEltX2));
+ ZSTD_memcpy(DTable + i + 2, &DEltX2, sizeof(DEltX2));
+ ZSTD_memcpy(DTable + i + 4, &DEltX2, sizeof(DEltX2));
+ ZSTD_memcpy(DTable + i + 6, &DEltX2, sizeof(DEltX2));
+ }
+ }
+ }
+ }
+
+ /* Fill each of the second level symbols by weight. */
+ {
+ int w;
+ for (w = minWeight; w < maxWeight1; ++w) {
+ int const begin = rankStart[w];
+ int const end = rankStart[w+1];
+ U32 const nbBits = nbBitsBaseline - w;
+ U32 const totalBits = nbBits + consumedBits;
+ HUF_fillDTableX2ForWeight(
+ DTable + rankVal[w],
+ sortedSymbols + begin, sortedSymbols + end,
+ totalBits, targetLog,
+ baseSeq, /* level */ 2);
+ }
+ }
+}
+
+static void HUF_fillDTableX2(HUF_DEltX2* DTable, const U32 targetLog,
+ const sortedSymbol_t* sortedList,
+ const U32* rankStart, rankValCol_t* rankValOrigin, const U32 maxWeight,
+ const U32 nbBitsBaseline)
+{
+ U32* const rankVal = rankValOrigin[0];
+ const int scaleLog = nbBitsBaseline - targetLog; /* note : targetLog >= srcLog, hence scaleLog <= 1 */
+ const U32 minBits = nbBitsBaseline - maxWeight;
+ int w;
+ int const wEnd = (int)maxWeight + 1;
+
+ /* Fill DTable in order of weight. */
+ for (w = 1; w < wEnd; ++w) {
+ int const begin = (int)rankStart[w];
+ int const end = (int)rankStart[w+1];
+ U32 const nbBits = nbBitsBaseline - w;
+
+ if (targetLog-nbBits >= minBits) {
+ /* Enough room for a second symbol. */
+ int start = rankVal[w];
+ U32 const length = 1U << ((targetLog - nbBits) & 0x1F /* quiet static-analyzer */);
+ int minWeight = nbBits + scaleLog;
+ int s;
+ if (minWeight < 1) minWeight = 1;
+ /* Fill the DTable for every symbol of weight w.
+ * These symbols get at least 1 second symbol.
+ */
+ for (s = begin; s != end; ++s) {
+ HUF_fillDTableX2Level2(
+ DTable + start, targetLog, nbBits,
+ rankValOrigin[nbBits], minWeight, wEnd,
+ sortedList, rankStart,
+ nbBitsBaseline, sortedList[s].symbol);
+ start += length;
+ }
+ } else {
+ /* Only a single symbol. */
+ HUF_fillDTableX2ForWeight(
+ DTable + rankVal[w],
+ sortedList + begin, sortedList + end,
+ nbBits, targetLog,
+ /* baseSeq */ 0, /* level */ 1);
+ }
+ }
+}
+
+typedef struct {
+ rankValCol_t rankVal[HUF_TABLELOG_MAX];
+ U32 rankStats[HUF_TABLELOG_MAX + 1];
+ U32 rankStart0[HUF_TABLELOG_MAX + 3];
+ sortedSymbol_t sortedSymbol[HUF_SYMBOLVALUE_MAX + 1];
+ BYTE weightList[HUF_SYMBOLVALUE_MAX + 1];
+ U32 calleeWksp[HUF_READ_STATS_WORKSPACE_SIZE_U32];
+} HUF_ReadDTableX2_Workspace;
+
+size_t HUF_readDTableX2_wksp(HUF_DTable* DTable,
+ const void* src, size_t srcSize,
+ void* workSpace, size_t wkspSize, int flags)
+{
+ U32 tableLog, maxW, nbSymbols;
+ DTableDesc dtd = HUF_getDTableDesc(DTable);
+ U32 maxTableLog = dtd.maxTableLog;
+ size_t iSize;
+ void* dtPtr = DTable+1; /* force compiler to avoid strict-aliasing */
+ HUF_DEltX2* const dt = (HUF_DEltX2*)dtPtr;
+ U32 *rankStart;
+
+ HUF_ReadDTableX2_Workspace* const wksp = (HUF_ReadDTableX2_Workspace*)workSpace;
+
+ if (sizeof(*wksp) > wkspSize) return ERROR(GENERIC);
+
+ rankStart = wksp->rankStart0 + 1;
+ ZSTD_memset(wksp->rankStats, 0, sizeof(wksp->rankStats));
+ ZSTD_memset(wksp->rankStart0, 0, sizeof(wksp->rankStart0));
+
+ DEBUG_STATIC_ASSERT(sizeof(HUF_DEltX2) == sizeof(HUF_DTable)); /* if compiler fails here, assertion is wrong */
+ if (maxTableLog > HUF_TABLELOG_MAX) return ERROR(tableLog_tooLarge);
+ /* ZSTD_memset(weightList, 0, sizeof(weightList)); */ /* is not necessary, even though some analyzer complain ... */
+
+ iSize = HUF_readStats_wksp(wksp->weightList, HUF_SYMBOLVALUE_MAX + 1, wksp->rankStats, &nbSymbols, &tableLog, src, srcSize, wksp->calleeWksp, sizeof(wksp->calleeWksp), flags);
+ if (HUF_isError(iSize)) return iSize;
+
+ /* check result */
+ if (tableLog > maxTableLog) return ERROR(tableLog_tooLarge); /* DTable can't fit code depth */
+ if (tableLog <= HUF_DECODER_FAST_TABLELOG && maxTableLog > HUF_DECODER_FAST_TABLELOG) maxTableLog = HUF_DECODER_FAST_TABLELOG;
+
+ /* find maxWeight */
+ for (maxW = tableLog; wksp->rankStats[maxW]==0; maxW--) {} /* necessarily finds a solution before 0 */
+
+ /* Get start index of each weight */
+ { U32 w, nextRankStart = 0;
+ for (w=1; w<maxW+1; w++) {
+ U32 curr = nextRankStart;
+ nextRankStart += wksp->rankStats[w];
+ rankStart[w] = curr;
+ }
+ rankStart[0] = nextRankStart; /* put all 0w symbols at the end of sorted list*/
+ rankStart[maxW+1] = nextRankStart;
+ }
+
+ /* sort symbols by weight */
+ { U32 s;
+ for (s=0; s<nbSymbols; s++) {
+ U32 const w = wksp->weightList[s];
+ U32 const r = rankStart[w]++;
+ wksp->sortedSymbol[r].symbol = (BYTE)s;
+ }
+ rankStart[0] = 0; /* forget 0w symbols; this is beginning of weight(1) */
+ }
+
+ /* Build rankVal */
+ { U32* const rankVal0 = wksp->rankVal[0];
+ { int const rescale = (maxTableLog-tableLog) - 1; /* tableLog <= maxTableLog */
+ U32 nextRankVal = 0;
+ U32 w;
+ for (w=1; w<maxW+1; w++) {
+ U32 curr = nextRankVal;
+ nextRankVal += wksp->rankStats[w] << (w+rescale);
+ rankVal0[w] = curr;
+ } }
+ { U32 const minBits = tableLog+1 - maxW;
+ U32 consumed;
+ for (consumed = minBits; consumed < maxTableLog - minBits + 1; consumed++) {
+ U32* const rankValPtr = wksp->rankVal[consumed];
+ U32 w;
+ for (w = 1; w < maxW+1; w++) {
+ rankValPtr[w] = rankVal0[w] >> consumed;
+ } } } }
+
+ HUF_fillDTableX2(dt, maxTableLog,
+ wksp->sortedSymbol,
+ wksp->rankStart0, wksp->rankVal, maxW,
+ tableLog+1);
+
+ dtd.tableLog = (BYTE)maxTableLog;
+ dtd.tableType = 1;
+ ZSTD_memcpy(DTable, &dtd, sizeof(dtd));
+ return iSize;
+}
+
+
+FORCE_INLINE_TEMPLATE U32
+HUF_decodeSymbolX2(void* op, BIT_DStream_t* DStream, const HUF_DEltX2* dt, const U32 dtLog)
+{
+ size_t const val = BIT_lookBitsFast(DStream, dtLog); /* note : dtLog >= 1 */
+ ZSTD_memcpy(op, &dt[val].sequence, 2);
+ BIT_skipBits(DStream, dt[val].nbBits);
+ return dt[val].length;
+}
+
+FORCE_INLINE_TEMPLATE U32
+HUF_decodeLastSymbolX2(void* op, BIT_DStream_t* DStream, const HUF_DEltX2* dt, const U32 dtLog)
+{
+ size_t const val = BIT_lookBitsFast(DStream, dtLog); /* note : dtLog >= 1 */
+ ZSTD_memcpy(op, &dt[val].sequence, 1);
+ if (dt[val].length==1) {
+ BIT_skipBits(DStream, dt[val].nbBits);
+ } else {
+ if (DStream->bitsConsumed < (sizeof(DStream->bitContainer)*8)) {
+ BIT_skipBits(DStream, dt[val].nbBits);
+ if (DStream->bitsConsumed > (sizeof(DStream->bitContainer)*8))
+ /* ugly hack; works only because it's the last symbol. Note : can't easily extract nbBits from just this symbol */
+ DStream->bitsConsumed = (sizeof(DStream->bitContainer)*8);
+ }
+ }
+ return 1;
+}
+
+#define HUF_DECODE_SYMBOLX2_0(ptr, DStreamPtr) \
+ do { ptr += HUF_decodeSymbolX2(ptr, DStreamPtr, dt, dtLog); } while (0)
+
+#define HUF_DECODE_SYMBOLX2_1(ptr, DStreamPtr) \
+ do { \
+ if (MEM_64bits() || (HUF_TABLELOG_MAX<=12)) \
+ ptr += HUF_decodeSymbolX2(ptr, DStreamPtr, dt, dtLog); \
+ } while (0)
+
+#define HUF_DECODE_SYMBOLX2_2(ptr, DStreamPtr) \
+ do { \
+ if (MEM_64bits()) \
+ ptr += HUF_decodeSymbolX2(ptr, DStreamPtr, dt, dtLog); \
+ } while (0)
+
+HINT_INLINE size_t
+HUF_decodeStreamX2(BYTE* p, BIT_DStream_t* bitDPtr, BYTE* const pEnd,
+ const HUF_DEltX2* const dt, const U32 dtLog)
+{
+ BYTE* const pStart = p;
+
+ /* up to 8 symbols at a time */
+ if ((size_t)(pEnd - p) >= sizeof(bitDPtr->bitContainer)) {
+ if (dtLog <= 11 && MEM_64bits()) {
+ /* up to 10 symbols at a time */
+ while ((BIT_reloadDStream(bitDPtr) == BIT_DStream_unfinished) & (p < pEnd-9)) {
+ HUF_DECODE_SYMBOLX2_0(p, bitDPtr);
+ HUF_DECODE_SYMBOLX2_0(p, bitDPtr);
+ HUF_DECODE_SYMBOLX2_0(p, bitDPtr);
+ HUF_DECODE_SYMBOLX2_0(p, bitDPtr);
+ HUF_DECODE_SYMBOLX2_0(p, bitDPtr);
+ }
+ } else {
+ /* up to 8 symbols at a time */
+ while ((BIT_reloadDStream(bitDPtr) == BIT_DStream_unfinished) & (p < pEnd-(sizeof(bitDPtr->bitContainer)-1))) {
+ HUF_DECODE_SYMBOLX2_2(p, bitDPtr);
+ HUF_DECODE_SYMBOLX2_1(p, bitDPtr);
+ HUF_DECODE_SYMBOLX2_2(p, bitDPtr);
+ HUF_DECODE_SYMBOLX2_0(p, bitDPtr);
+ }
+ }
+ } else {
+ BIT_reloadDStream(bitDPtr);
+ }
+
+ /* closer to end : up to 2 symbols at a time */
+ if ((size_t)(pEnd - p) >= 2) {
+ while ((BIT_reloadDStream(bitDPtr) == BIT_DStream_unfinished) & (p <= pEnd-2))
+ HUF_DECODE_SYMBOLX2_0(p, bitDPtr);
+
+ while (p <= pEnd-2)
+ HUF_DECODE_SYMBOLX2_0(p, bitDPtr); /* no need to reload : reached the end of DStream */
+ }
+
+ if (p < pEnd)
+ p += HUF_decodeLastSymbolX2(p, bitDPtr, dt, dtLog);
+
+ return p-pStart;
+}
+
+FORCE_INLINE_TEMPLATE size_t
+HUF_decompress1X2_usingDTable_internal_body(
+ void* dst, size_t dstSize,
+ const void* cSrc, size_t cSrcSize,
+ const HUF_DTable* DTable)
+{
+ BIT_DStream_t bitD;
+
+ /* Init */
+ CHECK_F( BIT_initDStream(&bitD, cSrc, cSrcSize) );
+
+ /* decode */
+ { BYTE* const ostart = (BYTE*) dst;
+ BYTE* const oend = ZSTD_maybeNullPtrAdd(ostart, dstSize);
+ const void* const dtPtr = DTable+1; /* force compiler to not use strict-aliasing */
+ const HUF_DEltX2* const dt = (const HUF_DEltX2*)dtPtr;
+ DTableDesc const dtd = HUF_getDTableDesc(DTable);
+ HUF_decodeStreamX2(ostart, &bitD, oend, dt, dtd.tableLog);
+ }
+
+ /* check */
+ if (!BIT_endOfDStream(&bitD)) return ERROR(corruption_detected);
+
+ /* decoded size */
+ return dstSize;
+}
+
+/* HUF_decompress4X2_usingDTable_internal_body():
+ * Conditions:
+ * @dstSize >= 6
+ */
+FORCE_INLINE_TEMPLATE size_t
+HUF_decompress4X2_usingDTable_internal_body(
+ void* dst, size_t dstSize,
+ const void* cSrc, size_t cSrcSize,
+ const HUF_DTable* DTable)
+{
+ if (cSrcSize < 10) return ERROR(corruption_detected); /* strict minimum : jump table + 1 byte per stream */
+ if (dstSize < 6) return ERROR(corruption_detected); /* stream 4-split doesn't work */
+
+ { const BYTE* const istart = (const BYTE*) cSrc;
+ BYTE* const ostart = (BYTE*) dst;
+ BYTE* const oend = ostart + dstSize;
+ BYTE* const olimit = oend - (sizeof(size_t)-1);
+ const void* const dtPtr = DTable+1;
+ const HUF_DEltX2* const dt = (const HUF_DEltX2*)dtPtr;
+
+ /* Init */
+ BIT_DStream_t bitD1;
+ BIT_DStream_t bitD2;
+ BIT_DStream_t bitD3;
+ BIT_DStream_t bitD4;
+ size_t const length1 = MEM_readLE16(istart);
+ size_t const length2 = MEM_readLE16(istart+2);
+ size_t const length3 = MEM_readLE16(istart+4);
+ size_t const length4 = cSrcSize - (length1 + length2 + length3 + 6);
+ const BYTE* const istart1 = istart + 6; /* jumpTable */
+ const BYTE* const istart2 = istart1 + length1;
+ const BYTE* const istart3 = istart2 + length2;
+ const BYTE* const istart4 = istart3 + length3;
+ size_t const segmentSize = (dstSize+3) / 4;
+ BYTE* const opStart2 = ostart + segmentSize;
+ BYTE* const opStart3 = opStart2 + segmentSize;
+ BYTE* const opStart4 = opStart3 + segmentSize;
+ BYTE* op1 = ostart;
+ BYTE* op2 = opStart2;
+ BYTE* op3 = opStart3;
+ BYTE* op4 = opStart4;
+ U32 endSignal = 1;
+ DTableDesc const dtd = HUF_getDTableDesc(DTable);
+ U32 const dtLog = dtd.tableLog;
+
+ if (length4 > cSrcSize) return ERROR(corruption_detected); /* overflow */
+ if (opStart4 > oend) return ERROR(corruption_detected); /* overflow */
+ assert(dstSize >= 6 /* validated above */);
+ CHECK_F( BIT_initDStream(&bitD1, istart1, length1) );
+ CHECK_F( BIT_initDStream(&bitD2, istart2, length2) );
+ CHECK_F( BIT_initDStream(&bitD3, istart3, length3) );
+ CHECK_F( BIT_initDStream(&bitD4, istart4, length4) );
+
+ /* 16-32 symbols per loop (4-8 symbols per stream) */
+ if ((size_t)(oend - op4) >= sizeof(size_t)) {
+ for ( ; (endSignal) & (op4 < olimit); ) {
+#if defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
+ HUF_DECODE_SYMBOLX2_2(op1, &bitD1);
+ HUF_DECODE_SYMBOLX2_1(op1, &bitD1);
+ HUF_DECODE_SYMBOLX2_2(op1, &bitD1);
+ HUF_DECODE_SYMBOLX2_0(op1, &bitD1);
+ HUF_DECODE_SYMBOLX2_2(op2, &bitD2);
+ HUF_DECODE_SYMBOLX2_1(op2, &bitD2);
+ HUF_DECODE_SYMBOLX2_2(op2, &bitD2);
+ HUF_DECODE_SYMBOLX2_0(op2, &bitD2);
+ endSignal &= BIT_reloadDStreamFast(&bitD1) == BIT_DStream_unfinished;
+ endSignal &= BIT_reloadDStreamFast(&bitD2) == BIT_DStream_unfinished;
+ HUF_DECODE_SYMBOLX2_2(op3, &bitD3);
+ HUF_DECODE_SYMBOLX2_1(op3, &bitD3);
+ HUF_DECODE_SYMBOLX2_2(op3, &bitD3);
+ HUF_DECODE_SYMBOLX2_0(op3, &bitD3);
+ HUF_DECODE_SYMBOLX2_2(op4, &bitD4);
+ HUF_DECODE_SYMBOLX2_1(op4, &bitD4);
+ HUF_DECODE_SYMBOLX2_2(op4, &bitD4);
+ HUF_DECODE_SYMBOLX2_0(op4, &bitD4);
+ endSignal &= BIT_reloadDStreamFast(&bitD3) == BIT_DStream_unfinished;
+ endSignal &= BIT_reloadDStreamFast(&bitD4) == BIT_DStream_unfinished;
+#else
+ HUF_DECODE_SYMBOLX2_2(op1, &bitD1);
+ HUF_DECODE_SYMBOLX2_2(op2, &bitD2);
+ HUF_DECODE_SYMBOLX2_2(op3, &bitD3);
+ HUF_DECODE_SYMBOLX2_2(op4, &bitD4);
+ HUF_DECODE_SYMBOLX2_1(op1, &bitD1);
+ HUF_DECODE_SYMBOLX2_1(op2, &bitD2);
+ HUF_DECODE_SYMBOLX2_1(op3, &bitD3);
+ HUF_DECODE_SYMBOLX2_1(op4, &bitD4);
+ HUF_DECODE_SYMBOLX2_2(op1, &bitD1);
+ HUF_DECODE_SYMBOLX2_2(op2, &bitD2);
+ HUF_DECODE_SYMBOLX2_2(op3, &bitD3);
+ HUF_DECODE_SYMBOLX2_2(op4, &bitD4);
+ HUF_DECODE_SYMBOLX2_0(op1, &bitD1);
+ HUF_DECODE_SYMBOLX2_0(op2, &bitD2);
+ HUF_DECODE_SYMBOLX2_0(op3, &bitD3);
+ HUF_DECODE_SYMBOLX2_0(op4, &bitD4);
+ endSignal = (U32)LIKELY((U32)
+ (BIT_reloadDStreamFast(&bitD1) == BIT_DStream_unfinished)
+ & (BIT_reloadDStreamFast(&bitD2) == BIT_DStream_unfinished)
+ & (BIT_reloadDStreamFast(&bitD3) == BIT_DStream_unfinished)
+ & (BIT_reloadDStreamFast(&bitD4) == BIT_DStream_unfinished));
+#endif
+ }
+ }
+
+ /* check corruption */
+ if (op1 > opStart2) return ERROR(corruption_detected);
+ if (op2 > opStart3) return ERROR(corruption_detected);
+ if (op3 > opStart4) return ERROR(corruption_detected);
+ /* note : op4 already verified within main loop */
+
+ /* finish bitStreams one by one */
+ HUF_decodeStreamX2(op1, &bitD1, opStart2, dt, dtLog);
+ HUF_decodeStreamX2(op2, &bitD2, opStart3, dt, dtLog);
+ HUF_decodeStreamX2(op3, &bitD3, opStart4, dt, dtLog);
+ HUF_decodeStreamX2(op4, &bitD4, oend, dt, dtLog);
+
+ /* check */
+ { U32 const endCheck = BIT_endOfDStream(&bitD1) & BIT_endOfDStream(&bitD2) & BIT_endOfDStream(&bitD3) & BIT_endOfDStream(&bitD4);
+ if (!endCheck) return ERROR(corruption_detected); }
+
+ /* decoded size */
+ return dstSize;
+ }
+}
+
+#if HUF_NEED_BMI2_FUNCTION
+static BMI2_TARGET_ATTRIBUTE
+size_t HUF_decompress4X2_usingDTable_internal_bmi2(void* dst, size_t dstSize, void const* cSrc,
+ size_t cSrcSize, HUF_DTable const* DTable) {
+ return HUF_decompress4X2_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable);
+}
+#endif
+
+static
+size_t HUF_decompress4X2_usingDTable_internal_default(void* dst, size_t dstSize, void const* cSrc,
+ size_t cSrcSize, HUF_DTable const* DTable) {
+ return HUF_decompress4X2_usingDTable_internal_body(dst, dstSize, cSrc, cSrcSize, DTable);
+}
+
+#if ZSTD_ENABLE_ASM_X86_64_BMI2
+
+HUF_ASM_DECL void HUF_decompress4X2_usingDTable_internal_fast_asm_loop(HUF_DecompressFastArgs* args) ZSTDLIB_HIDDEN;
+
+#endif
+
+static HUF_FAST_BMI2_ATTRS
+void HUF_decompress4X2_usingDTable_internal_fast_c_loop(HUF_DecompressFastArgs* args)
+{
+ U64 bits[4];
+ BYTE const* ip[4];
+ BYTE* op[4];
+ BYTE* oend[4];
+ HUF_DEltX2 const* const dtable = (HUF_DEltX2 const*)args->dt;
+ BYTE const* const ilowest = args->ilowest;
+
+ /* Copy the arguments to local registers. */
+ ZSTD_memcpy(&bits, &args->bits, sizeof(bits));
+ ZSTD_memcpy((void*)(&ip), &args->ip, sizeof(ip));
+ ZSTD_memcpy(&op, &args->op, sizeof(op));
+
+ oend[0] = op[1];
+ oend[1] = op[2];
+ oend[2] = op[3];
+ oend[3] = args->oend;
+
+ assert(MEM_isLittleEndian());
+ assert(!MEM_32bits());
+
+ for (;;) {
+ BYTE* olimit;
+ int stream;
+
+ /* Assert loop preconditions */
+#ifndef NDEBUG
+ for (stream = 0; stream < 4; ++stream) {
+ assert(op[stream] <= oend[stream]);
+ assert(ip[stream] >= ilowest);
+ }
+#endif
+ /* Compute olimit */
+ {
+ /* Each loop does 5 table lookups for each of the 4 streams.
+ * Each table lookup consumes up to 11 bits of input, and produces
+ * up to 2 bytes of output.
+ */
+ /* We can consume up to 7 bytes of input per iteration per stream.
+ * We also know that each input pointer is >= ip[0]. So we can run
+ * iters loops before running out of input.
+ */
+ size_t iters = (size_t)(ip[0] - ilowest) / 7;
+ /* Each iteration can produce up to 10 bytes of output per stream.
+ * Each output stream my advance at different rates. So take the
+ * minimum number of safe iterations among all the output streams.
+ */
+ for (stream = 0; stream < 4; ++stream) {
+ size_t const oiters = (size_t)(oend[stream] - op[stream]) / 10;
+ iters = MIN(iters, oiters);
+ }
+
+ /* Each iteration produces at least 5 output symbols. So until
+ * op[3] crosses olimit, we know we haven't executed iters
+ * iterations yet. This saves us maintaining an iters counter,
+ * at the expense of computing the remaining # of iterations
+ * more frequently.
+ */
+ olimit = op[3] + (iters * 5);
+
+ /* Exit the fast decoding loop once we reach the end. */
+ if (op[3] == olimit)
+ break;
+
+ /* Exit the decoding loop if any input pointer has crossed the
+ * previous one. This indicates corruption, and a precondition
+ * to our loop is that ip[i] >= ip[0].
+ */
+ for (stream = 1; stream < 4; ++stream) {
+ if (ip[stream] < ip[stream - 1])
+ goto _out;
+ }
+ }
+
+#ifndef NDEBUG
+ for (stream = 1; stream < 4; ++stream) {
+ assert(ip[stream] >= ip[stream - 1]);
+ }
+#endif
+
+#define HUF_4X2_DECODE_SYMBOL(_stream, _decode3) \
+ do { \
+ if ((_decode3) || (_stream) != 3) { \
+ int const index = (int)(bits[(_stream)] >> 53); \
+ HUF_DEltX2 const entry = dtable[index]; \
+ MEM_write16(op[(_stream)], entry.sequence); \
+ bits[(_stream)] <<= (entry.nbBits) & 0x3F; \
+ op[(_stream)] += (entry.length); \
+ } \
+ } while (0)
+
+#define HUF_4X2_RELOAD_STREAM(_stream) \
+ do { \
+ HUF_4X2_DECODE_SYMBOL(3, 1); \
+ { \
+ int const ctz = ZSTD_countTrailingZeros64(bits[(_stream)]); \
+ int const nbBits = ctz & 7; \
+ int const nbBytes = ctz >> 3; \
+ ip[(_stream)] -= nbBytes; \
+ bits[(_stream)] = MEM_read64(ip[(_stream)]) | 1; \
+ bits[(_stream)] <<= nbBits; \
+ } \
+ } while (0)
+
+ /* Manually unroll the loop because compilers don't consistently
+ * unroll the inner loops, which destroys performance.
+ */
+ do {
+ /* Decode 5 symbols from each of the first 3 streams.
+ * The final stream will be decoded during the reload phase
+ * to reduce register pressure.
+ */
+ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0);
+ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0);
+ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0);
+ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0);
+ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0);
+
+ /* Decode one symbol from the final stream */
+ HUF_4X2_DECODE_SYMBOL(3, 1);
+
+ /* Decode 4 symbols from the final stream & reload bitstreams.
+ * The final stream is reloaded last, meaning that all 5 symbols
+ * are decoded from the final stream before it is reloaded.
+ */
+ HUF_4X_FOR_EACH_STREAM(HUF_4X2_RELOAD_STREAM);
+ } while (op[3] < olimit);
+ }
+
+#undef HUF_4X2_DECODE_SYMBOL
+#undef HUF_4X2_RELOAD_STREAM
+
+_out:
+
+ /* Save the final values of each of the state variables back to args. */
+ ZSTD_memcpy(&args->bits, &bits, sizeof(bits));
+ ZSTD_memcpy((void*)(&args->ip), &ip, sizeof(ip));
+ ZSTD_memcpy(&args->op, &op, sizeof(op));
+}
+
+
+static HUF_FAST_BMI2_ATTRS size_t
+HUF_decompress4X2_usingDTable_internal_fast(
+ void* dst, size_t dstSize,
+ const void* cSrc, size_t cSrcSize,
+ const HUF_DTable* DTable,
+ HUF_DecompressFastLoopFn loopFn) {
+ void const* dt = DTable + 1;
+ const BYTE* const ilowest = (const BYTE*)cSrc;
+ BYTE* const oend = ZSTD_maybeNullPtrAdd((BYTE*)dst, dstSize);
+ HUF_DecompressFastArgs args;
+ {
+ size_t const ret = HUF_DecompressFastArgs_init(&args, dst, dstSize, cSrc, cSrcSize, DTable);
+ FORWARD_IF_ERROR(ret, "Failed to init asm args");
+ if (ret == 0)
+ return 0;
+ }
+
+ assert(args.ip[0] >= args.ilowest);
+ loopFn(&args);
+
+ /* note : op4 already verified within main loop */
+ assert(args.ip[0] >= ilowest);
+ assert(args.ip[1] >= ilowest);
+ assert(args.ip[2] >= ilowest);
+ assert(args.ip[3] >= ilowest);
+ assert(args.op[3] <= oend);
+
+ assert(ilowest == args.ilowest);
+ assert(ilowest + 6 == args.iend[0]);
+ (void)ilowest;
+
+ /* finish bitStreams one by one */
+ {
+ size_t const segmentSize = (dstSize+3) / 4;
+ BYTE* segmentEnd = (BYTE*)dst;
+ int i;
+ for (i = 0; i < 4; ++i) {
+ BIT_DStream_t bit;
+ if (segmentSize <= (size_t)(oend - segmentEnd))
+ segmentEnd += segmentSize;
+ else
+ segmentEnd = oend;
+ FORWARD_IF_ERROR(HUF_initRemainingDStream(&bit, &args, i, segmentEnd), "corruption");
+ args.op[i] += HUF_decodeStreamX2(args.op[i], &bit, segmentEnd, (HUF_DEltX2 const*)dt, HUF_DECODER_FAST_TABLELOG);
+ if (args.op[i] != segmentEnd)
+ return ERROR(corruption_detected);
+ }
+ }
+
+ /* decoded size */
+ return dstSize;
+}
+
+static size_t HUF_decompress4X2_usingDTable_internal(void* dst, size_t dstSize, void const* cSrc,
+ size_t cSrcSize, HUF_DTable const* DTable, int flags)
+{
+ HUF_DecompressUsingDTableFn fallbackFn = HUF_decompress4X2_usingDTable_internal_default;
+ HUF_DecompressFastLoopFn loopFn = HUF_decompress4X2_usingDTable_internal_fast_c_loop;
+
+#if DYNAMIC_BMI2
+ if (flags & HUF_flags_bmi2) {
+ fallbackFn = HUF_decompress4X2_usingDTable_internal_bmi2;
+# if ZSTD_ENABLE_ASM_X86_64_BMI2
+ if (!(flags & HUF_flags_disableAsm)) {
+ loopFn = HUF_decompress4X2_usingDTable_internal_fast_asm_loop;
+ }
+# endif
+ } else {
+ return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable);
+ }
+#endif
+
+#if ZSTD_ENABLE_ASM_X86_64_BMI2 && defined(__BMI2__)
+ if (!(flags & HUF_flags_disableAsm)) {
+ loopFn = HUF_decompress4X2_usingDTable_internal_fast_asm_loop;
+ }
+#endif
+
+ if (HUF_ENABLE_FAST_DECODE && !(flags & HUF_flags_disableFast)) {
+ size_t const ret = HUF_decompress4X2_usingDTable_internal_fast(dst, dstSize, cSrc, cSrcSize, DTable, loopFn);
+ if (ret != 0)
+ return ret;
+ }
+ return fallbackFn(dst, dstSize, cSrc, cSrcSize, DTable);
+}
+
+HUF_DGEN(HUF_decompress1X2_usingDTable_internal)
+
+size_t HUF_decompress1X2_DCtx_wksp(HUF_DTable* DCtx, void* dst, size_t dstSize,
+ const void* cSrc, size_t cSrcSize,
+ void* workSpace, size_t wkspSize, int flags)
+{
+ const BYTE* ip = (const BYTE*) cSrc;
+
+ size_t const hSize = HUF_readDTableX2_wksp(DCtx, cSrc, cSrcSize,
+ workSpace, wkspSize, flags);
+ if (HUF_isError(hSize)) return hSize;
+ if (hSize >= cSrcSize) return ERROR(srcSize_wrong);
+ ip += hSize; cSrcSize -= hSize;
+
+ return HUF_decompress1X2_usingDTable_internal(dst, dstSize, ip, cSrcSize, DCtx, flags);
+}
+
+static size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize,
+ const void* cSrc, size_t cSrcSize,
+ void* workSpace, size_t wkspSize, int flags)
+{
+ const BYTE* ip = (const BYTE*) cSrc;
+
+ size_t hSize = HUF_readDTableX2_wksp(dctx, cSrc, cSrcSize,
+ workSpace, wkspSize, flags);
+ if (HUF_isError(hSize)) return hSize;
+ if (hSize >= cSrcSize) return ERROR(srcSize_wrong);
+ ip += hSize; cSrcSize -= hSize;
+
+ return HUF_decompress4X2_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, flags);
+}
+
+#endif /* HUF_FORCE_DECOMPRESS_X1 */
+
+
+/* ***********************************/
+/* Universal decompression selectors */
+/* ***********************************/
+
+
+#if !defined(HUF_FORCE_DECOMPRESS_X1) && !defined(HUF_FORCE_DECOMPRESS_X2)
+typedef struct { U32 tableTime; U32 decode256Time; } algo_time_t;
+static const algo_time_t algoTime[16 /* Quantization */][2 /* single, double */] =
+{
+ /* single, double, quad */
+ {{0,0}, {1,1}}, /* Q==0 : impossible */
+ {{0,0}, {1,1}}, /* Q==1 : impossible */
+ {{ 150,216}, { 381,119}}, /* Q == 2 : 12-18% */
+ {{ 170,205}, { 514,112}}, /* Q == 3 : 18-25% */
+ {{ 177,199}, { 539,110}}, /* Q == 4 : 25-32% */
+ {{ 197,194}, { 644,107}}, /* Q == 5 : 32-38% */
+ {{ 221,192}, { 735,107}}, /* Q == 6 : 38-44% */
+ {{ 256,189}, { 881,106}}, /* Q == 7 : 44-50% */
+ {{ 359,188}, {1167,109}}, /* Q == 8 : 50-56% */
+ {{ 582,187}, {1570,114}}, /* Q == 9 : 56-62% */
+ {{ 688,187}, {1712,122}}, /* Q ==10 : 62-69% */
+ {{ 825,186}, {1965,136}}, /* Q ==11 : 69-75% */
+ {{ 976,185}, {2131,150}}, /* Q ==12 : 75-81% */
+ {{1180,186}, {2070,175}}, /* Q ==13 : 81-87% */
+ {{1377,185}, {1731,202}}, /* Q ==14 : 87-93% */
+ {{1412,185}, {1695,202}}, /* Q ==15 : 93-99% */
+};
+#endif
+
+/** HUF_selectDecoder() :
+ * Tells which decoder is likely to decode faster,
+ * based on a set of pre-computed metrics.
+ * @return : 0==HUF_decompress4X1, 1==HUF_decompress4X2 .
+ * Assumption : 0 < dstSize <= 128 KB */
+U32 HUF_selectDecoder (size_t dstSize, size_t cSrcSize)
+{
+ assert(dstSize > 0);
+ assert(dstSize <= 128*1024);
+#if defined(HUF_FORCE_DECOMPRESS_X1)
+ (void)dstSize;
+ (void)cSrcSize;
+ return 0;
+#elif defined(HUF_FORCE_DECOMPRESS_X2)
+ (void)dstSize;
+ (void)cSrcSize;
+ return 1;
+#else
+ /* decoder timing evaluation */
+ { U32 const Q = (cSrcSize >= dstSize) ? 15 : (U32)(cSrcSize * 16 / dstSize); /* Q < 16 */
+ U32 const D256 = (U32)(dstSize >> 8);
+ U32 const DTime0 = algoTime[Q][0].tableTime + (algoTime[Q][0].decode256Time * D256);
+ U32 DTime1 = algoTime[Q][1].tableTime + (algoTime[Q][1].decode256Time * D256);
+ DTime1 += DTime1 >> 5; /* small advantage to algorithm using less memory, to reduce cache eviction */
+ return DTime1 < DTime0;
+ }
+#endif
+}
+
+size_t HUF_decompress1X_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize,
+ const void* cSrc, size_t cSrcSize,
+ void* workSpace, size_t wkspSize, int flags)
+{
+ /* validation checks */
+ if (dstSize == 0) return ERROR(dstSize_tooSmall);
+ if (cSrcSize > dstSize) return ERROR(corruption_detected); /* invalid */
+ if (cSrcSize == dstSize) { ZSTD_memcpy(dst, cSrc, dstSize); return dstSize; } /* not compressed */
+ if (cSrcSize == 1) { ZSTD_memset(dst, *(const BYTE*)cSrc, dstSize); return dstSize; } /* RLE */
+
+ { U32 const algoNb = HUF_selectDecoder(dstSize, cSrcSize);
+#if defined(HUF_FORCE_DECOMPRESS_X1)
+ (void)algoNb;
+ assert(algoNb == 0);
+ return HUF_decompress1X1_DCtx_wksp(dctx, dst, dstSize, cSrc,
+ cSrcSize, workSpace, wkspSize, flags);
+#elif defined(HUF_FORCE_DECOMPRESS_X2)
+ (void)algoNb;
+ assert(algoNb == 1);
+ return HUF_decompress1X2_DCtx_wksp(dctx, dst, dstSize, cSrc,
+ cSrcSize, workSpace, wkspSize, flags);
+#else
+ return algoNb ? HUF_decompress1X2_DCtx_wksp(dctx, dst, dstSize, cSrc,
+ cSrcSize, workSpace, wkspSize, flags):
+ HUF_decompress1X1_DCtx_wksp(dctx, dst, dstSize, cSrc,
+ cSrcSize, workSpace, wkspSize, flags);
+#endif
+ }
+}
+
+
+size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags)
+{
+ DTableDesc const dtd = HUF_getDTableDesc(DTable);
+#if defined(HUF_FORCE_DECOMPRESS_X1)
+ (void)dtd;
+ assert(dtd.tableType == 0);
+ return HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags);
+#elif defined(HUF_FORCE_DECOMPRESS_X2)
+ (void)dtd;
+ assert(dtd.tableType == 1);
+ return HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags);
+#else
+ return dtd.tableType ? HUF_decompress1X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags) :
+ HUF_decompress1X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags);
+#endif
+}
+
+#ifndef HUF_FORCE_DECOMPRESS_X2
+size_t HUF_decompress1X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags)
+{
+ const BYTE* ip = (const BYTE*) cSrc;
+
+ size_t const hSize = HUF_readDTableX1_wksp(dctx, cSrc, cSrcSize, workSpace, wkspSize, flags);
+ if (HUF_isError(hSize)) return hSize;
+ if (hSize >= cSrcSize) return ERROR(srcSize_wrong);
+ ip += hSize; cSrcSize -= hSize;
+
+ return HUF_decompress1X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, flags);
+}
+#endif
+
+size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags)
+{
+ DTableDesc const dtd = HUF_getDTableDesc(DTable);
+#if defined(HUF_FORCE_DECOMPRESS_X1)
+ (void)dtd;
+ assert(dtd.tableType == 0);
+ return HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags);
+#elif defined(HUF_FORCE_DECOMPRESS_X2)
+ (void)dtd;
+ assert(dtd.tableType == 1);
+ return HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags);
+#else
+ return dtd.tableType ? HUF_decompress4X2_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags) :
+ HUF_decompress4X1_usingDTable_internal(dst, maxDstSize, cSrc, cSrcSize, DTable, flags);
+#endif
+}
+
+size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags)
+{
+ /* validation checks */
+ if (dstSize == 0) return ERROR(dstSize_tooSmall);
+ if (cSrcSize == 0) return ERROR(corruption_detected);
+
+ { U32 const algoNb = HUF_selectDecoder(dstSize, cSrcSize);
+#if defined(HUF_FORCE_DECOMPRESS_X1)
+ (void)algoNb;
+ assert(algoNb == 0);
+ return HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, flags);
+#elif defined(HUF_FORCE_DECOMPRESS_X2)
+ (void)algoNb;
+ assert(algoNb == 1);
+ return HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, flags);
+#else
+ return algoNb ? HUF_decompress4X2_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, flags) :
+ HUF_decompress4X1_DCtx_wksp(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, flags);
+#endif
+ }
+}
diff --git a/third_party/zstd/lib/decompress/huf_decompress_amd64.S b/third_party/zstd/lib/decompress/huf_decompress_amd64.S
new file mode 100644
index 0000000000..78da291ee3
--- /dev/null
+++ b/third_party/zstd/lib/decompress/huf_decompress_amd64.S
@@ -0,0 +1,595 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#include "../common/portability_macros.h"
+
+#if defined(__ELF__) && defined(__GNUC__)
+/* Stack marking
+ * ref: https://wiki.gentoo.org/wiki/Hardened/GNU_stack_quickstart
+ */
+.section .note.GNU-stack,"",%progbits
+
+#if defined(__aarch64__)
+/* Mark that this assembly supports BTI & PAC, because it is empty for aarch64.
+ * See: https://github.com/facebook/zstd/issues/3841
+ * See: https://gcc.godbolt.org/z/sqr5T4ffK
+ * See: https://lore.kernel.org/linux-arm-kernel/20200429211641.9279-8-broonie@kernel.org/
+ * See: https://reviews.llvm.org/D62609
+ */
+.pushsection .note.gnu.property, "a"
+.p2align 3
+.long 4 /* size of the name - "GNU\0" */
+.long 0x10 /* size of descriptor */
+.long 0x5 /* NT_GNU_PROPERTY_TYPE_0 */
+.asciz "GNU"
+.long 0xc0000000 /* pr_type - GNU_PROPERTY_AARCH64_FEATURE_1_AND */
+.long 4 /* pr_datasz - 4 bytes */
+.long 3 /* pr_data - GNU_PROPERTY_AARCH64_FEATURE_1_BTI | GNU_PROPERTY_AARCH64_FEATURE_1_PAC */
+.p2align 3 /* pr_padding - bring everything to 8 byte alignment */
+.popsection
+#endif
+
+#endif
+
+#if ZSTD_ENABLE_ASM_X86_64_BMI2
+
+/* Calling convention:
+ *
+ * %rdi contains the first argument: HUF_DecompressAsmArgs*.
+ * %rbp isn't maintained (no frame pointer).
+ * %rsp contains the stack pointer that grows down.
+ * No red-zone is assumed, only addresses >= %rsp are used.
+ * All register contents are preserved.
+ *
+ * TODO: Support Windows calling convention.
+ */
+
+ZSTD_HIDE_ASM_FUNCTION(HUF_decompress4X1_usingDTable_internal_fast_asm_loop)
+ZSTD_HIDE_ASM_FUNCTION(HUF_decompress4X2_usingDTable_internal_fast_asm_loop)
+ZSTD_HIDE_ASM_FUNCTION(_HUF_decompress4X2_usingDTable_internal_fast_asm_loop)
+ZSTD_HIDE_ASM_FUNCTION(_HUF_decompress4X1_usingDTable_internal_fast_asm_loop)
+.global HUF_decompress4X1_usingDTable_internal_fast_asm_loop
+.global HUF_decompress4X2_usingDTable_internal_fast_asm_loop
+.global _HUF_decompress4X1_usingDTable_internal_fast_asm_loop
+.global _HUF_decompress4X2_usingDTable_internal_fast_asm_loop
+.text
+
+/* Sets up register mappings for clarity.
+ * op[], bits[], dtable & ip[0] each get their own register.
+ * ip[1,2,3] & olimit alias var[].
+ * %rax is a scratch register.
+ */
+
+#define op0 rsi
+#define op1 rbx
+#define op2 rcx
+#define op3 rdi
+
+#define ip0 r8
+#define ip1 r9
+#define ip2 r10
+#define ip3 r11
+
+#define bits0 rbp
+#define bits1 rdx
+#define bits2 r12
+#define bits3 r13
+#define dtable r14
+#define olimit r15
+
+/* var[] aliases ip[1,2,3] & olimit
+ * ip[1,2,3] are saved every iteration.
+ * olimit is only used in compute_olimit.
+ */
+#define var0 r15
+#define var1 r9
+#define var2 r10
+#define var3 r11
+
+/* 32-bit var registers */
+#define vard0 r15d
+#define vard1 r9d
+#define vard2 r10d
+#define vard3 r11d
+
+/* Calls X(N) for each stream 0, 1, 2, 3. */
+#define FOR_EACH_STREAM(X) \
+ X(0); \
+ X(1); \
+ X(2); \
+ X(3)
+
+/* Calls X(N, idx) for each stream 0, 1, 2, 3. */
+#define FOR_EACH_STREAM_WITH_INDEX(X, idx) \
+ X(0, idx); \
+ X(1, idx); \
+ X(2, idx); \
+ X(3, idx)
+
+/* Define both _HUF_* & HUF_* symbols because MacOS
+ * C symbols are prefixed with '_' & Linux symbols aren't.
+ */
+_HUF_decompress4X1_usingDTable_internal_fast_asm_loop:
+HUF_decompress4X1_usingDTable_internal_fast_asm_loop:
+ ZSTD_CET_ENDBRANCH
+ /* Save all registers - even if they are callee saved for simplicity. */
+ push %rax
+ push %rbx
+ push %rcx
+ push %rdx
+ push %rbp
+ push %rsi
+ push %rdi
+ push %r8
+ push %r9
+ push %r10
+ push %r11
+ push %r12
+ push %r13
+ push %r14
+ push %r15
+
+ /* Read HUF_DecompressAsmArgs* args from %rax */
+ movq %rdi, %rax
+ movq 0(%rax), %ip0
+ movq 8(%rax), %ip1
+ movq 16(%rax), %ip2
+ movq 24(%rax), %ip3
+ movq 32(%rax), %op0
+ movq 40(%rax), %op1
+ movq 48(%rax), %op2
+ movq 56(%rax), %op3
+ movq 64(%rax), %bits0
+ movq 72(%rax), %bits1
+ movq 80(%rax), %bits2
+ movq 88(%rax), %bits3
+ movq 96(%rax), %dtable
+ push %rax /* argument */
+ push 104(%rax) /* ilowest */
+ push 112(%rax) /* oend */
+ push %olimit /* olimit space */
+
+ subq $24, %rsp
+
+.L_4X1_compute_olimit:
+ /* Computes how many iterations we can do safely
+ * %r15, %rax may be clobbered
+ * rbx, rdx must be saved
+ * op3 & ip0 mustn't be clobbered
+ */
+ movq %rbx, 0(%rsp)
+ movq %rdx, 8(%rsp)
+
+ movq 32(%rsp), %rax /* rax = oend */
+ subq %op3, %rax /* rax = oend - op3 */
+
+ /* r15 = (oend - op3) / 5 */
+ movabsq $-3689348814741910323, %rdx
+ mulq %rdx
+ movq %rdx, %r15
+ shrq $2, %r15
+
+ movq %ip0, %rax /* rax = ip0 */
+ movq 40(%rsp), %rdx /* rdx = ilowest */
+ subq %rdx, %rax /* rax = ip0 - ilowest */
+ movq %rax, %rbx /* rbx = ip0 - ilowest */
+
+ /* rdx = (ip0 - ilowest) / 7 */
+ movabsq $2635249153387078803, %rdx
+ mulq %rdx
+ subq %rdx, %rbx
+ shrq %rbx
+ addq %rbx, %rdx
+ shrq $2, %rdx
+
+ /* r15 = min(%rdx, %r15) */
+ cmpq %rdx, %r15
+ cmova %rdx, %r15
+
+ /* r15 = r15 * 5 */
+ leaq (%r15, %r15, 4), %r15
+
+ /* olimit = op3 + r15 */
+ addq %op3, %olimit
+
+ movq 8(%rsp), %rdx
+ movq 0(%rsp), %rbx
+
+ /* If (op3 + 20 > olimit) */
+ movq %op3, %rax /* rax = op3 */
+ cmpq %rax, %olimit /* op3 == olimit */
+ je .L_4X1_exit
+
+ /* If (ip1 < ip0) go to exit */
+ cmpq %ip0, %ip1
+ jb .L_4X1_exit
+
+ /* If (ip2 < ip1) go to exit */
+ cmpq %ip1, %ip2
+ jb .L_4X1_exit
+
+ /* If (ip3 < ip2) go to exit */
+ cmpq %ip2, %ip3
+ jb .L_4X1_exit
+
+/* Reads top 11 bits from bits[n]
+ * Loads dt[bits[n]] into var[n]
+ */
+#define GET_NEXT_DELT(n) \
+ movq $53, %var##n; \
+ shrxq %var##n, %bits##n, %var##n; \
+ movzwl (%dtable,%var##n,2),%vard##n
+
+/* var[n] must contain the DTable entry computed with GET_NEXT_DELT
+ * Moves var[n] to %rax
+ * bits[n] <<= var[n] & 63
+ * op[n][idx] = %rax >> 8
+ * %ah is a way to access bits [8, 16) of %rax
+ */
+#define DECODE_FROM_DELT(n, idx) \
+ movq %var##n, %rax; \
+ shlxq %var##n, %bits##n, %bits##n; \
+ movb %ah, idx(%op##n)
+
+/* Assumes GET_NEXT_DELT has been called.
+ * Calls DECODE_FROM_DELT then GET_NEXT_DELT
+ */
+#define DECODE_AND_GET_NEXT(n, idx) \
+ DECODE_FROM_DELT(n, idx); \
+ GET_NEXT_DELT(n) \
+
+/* // ctz & nbBytes is stored in bits[n]
+ * // nbBits is stored in %rax
+ * ctz = CTZ[bits[n]]
+ * nbBits = ctz & 7
+ * nbBytes = ctz >> 3
+ * op[n] += 5
+ * ip[n] -= nbBytes
+ * // Note: x86-64 is little-endian ==> no bswap
+ * bits[n] = MEM_readST(ip[n]) | 1
+ * bits[n] <<= nbBits
+ */
+#define RELOAD_BITS(n) \
+ bsfq %bits##n, %bits##n; \
+ movq %bits##n, %rax; \
+ andq $7, %rax; \
+ shrq $3, %bits##n; \
+ leaq 5(%op##n), %op##n; \
+ subq %bits##n, %ip##n; \
+ movq (%ip##n), %bits##n; \
+ orq $1, %bits##n; \
+ shlx %rax, %bits##n, %bits##n
+
+ /* Store clobbered variables on the stack */
+ movq %olimit, 24(%rsp)
+ movq %ip1, 0(%rsp)
+ movq %ip2, 8(%rsp)
+ movq %ip3, 16(%rsp)
+
+ /* Call GET_NEXT_DELT for each stream */
+ FOR_EACH_STREAM(GET_NEXT_DELT)
+
+ .p2align 6
+
+.L_4X1_loop_body:
+ /* Decode 5 symbols in each of the 4 streams (20 total)
+ * Must have called GET_NEXT_DELT for each stream
+ */
+ FOR_EACH_STREAM_WITH_INDEX(DECODE_AND_GET_NEXT, 0)
+ FOR_EACH_STREAM_WITH_INDEX(DECODE_AND_GET_NEXT, 1)
+ FOR_EACH_STREAM_WITH_INDEX(DECODE_AND_GET_NEXT, 2)
+ FOR_EACH_STREAM_WITH_INDEX(DECODE_AND_GET_NEXT, 3)
+ FOR_EACH_STREAM_WITH_INDEX(DECODE_FROM_DELT, 4)
+
+ /* Load ip[1,2,3] from stack (var[] aliases them)
+ * ip[] is needed for RELOAD_BITS
+ * Each will be stored back to the stack after RELOAD
+ */
+ movq 0(%rsp), %ip1
+ movq 8(%rsp), %ip2
+ movq 16(%rsp), %ip3
+
+ /* Reload each stream & fetch the next table entry
+ * to prepare for the next iteration
+ */
+ RELOAD_BITS(0)
+ GET_NEXT_DELT(0)
+
+ RELOAD_BITS(1)
+ movq %ip1, 0(%rsp)
+ GET_NEXT_DELT(1)
+
+ RELOAD_BITS(2)
+ movq %ip2, 8(%rsp)
+ GET_NEXT_DELT(2)
+
+ RELOAD_BITS(3)
+ movq %ip3, 16(%rsp)
+ GET_NEXT_DELT(3)
+
+ /* If op3 < olimit: continue the loop */
+ cmp %op3, 24(%rsp)
+ ja .L_4X1_loop_body
+
+ /* Reload ip[1,2,3] from stack */
+ movq 0(%rsp), %ip1
+ movq 8(%rsp), %ip2
+ movq 16(%rsp), %ip3
+
+ /* Re-compute olimit */
+ jmp .L_4X1_compute_olimit
+
+#undef GET_NEXT_DELT
+#undef DECODE_FROM_DELT
+#undef DECODE
+#undef RELOAD_BITS
+.L_4X1_exit:
+ addq $24, %rsp
+
+ /* Restore stack (oend & olimit) */
+ pop %rax /* olimit */
+ pop %rax /* oend */
+ pop %rax /* ilowest */
+ pop %rax /* arg */
+
+ /* Save ip / op / bits */
+ movq %ip0, 0(%rax)
+ movq %ip1, 8(%rax)
+ movq %ip2, 16(%rax)
+ movq %ip3, 24(%rax)
+ movq %op0, 32(%rax)
+ movq %op1, 40(%rax)
+ movq %op2, 48(%rax)
+ movq %op3, 56(%rax)
+ movq %bits0, 64(%rax)
+ movq %bits1, 72(%rax)
+ movq %bits2, 80(%rax)
+ movq %bits3, 88(%rax)
+
+ /* Restore registers */
+ pop %r15
+ pop %r14
+ pop %r13
+ pop %r12
+ pop %r11
+ pop %r10
+ pop %r9
+ pop %r8
+ pop %rdi
+ pop %rsi
+ pop %rbp
+ pop %rdx
+ pop %rcx
+ pop %rbx
+ pop %rax
+ ret
+
+_HUF_decompress4X2_usingDTable_internal_fast_asm_loop:
+HUF_decompress4X2_usingDTable_internal_fast_asm_loop:
+ ZSTD_CET_ENDBRANCH
+ /* Save all registers - even if they are callee saved for simplicity. */
+ push %rax
+ push %rbx
+ push %rcx
+ push %rdx
+ push %rbp
+ push %rsi
+ push %rdi
+ push %r8
+ push %r9
+ push %r10
+ push %r11
+ push %r12
+ push %r13
+ push %r14
+ push %r15
+
+ movq %rdi, %rax
+ movq 0(%rax), %ip0
+ movq 8(%rax), %ip1
+ movq 16(%rax), %ip2
+ movq 24(%rax), %ip3
+ movq 32(%rax), %op0
+ movq 40(%rax), %op1
+ movq 48(%rax), %op2
+ movq 56(%rax), %op3
+ movq 64(%rax), %bits0
+ movq 72(%rax), %bits1
+ movq 80(%rax), %bits2
+ movq 88(%rax), %bits3
+ movq 96(%rax), %dtable
+ push %rax /* argument */
+ push %rax /* olimit */
+ push 104(%rax) /* ilowest */
+
+ movq 112(%rax), %rax
+ push %rax /* oend3 */
+
+ movq %op3, %rax
+ push %rax /* oend2 */
+
+ movq %op2, %rax
+ push %rax /* oend1 */
+
+ movq %op1, %rax
+ push %rax /* oend0 */
+
+ /* Scratch space */
+ subq $8, %rsp
+
+.L_4X2_compute_olimit:
+ /* Computes how many iterations we can do safely
+ * %r15, %rax may be clobbered
+ * rdx must be saved
+ * op[1,2,3,4] & ip0 mustn't be clobbered
+ */
+ movq %rdx, 0(%rsp)
+
+ /* We can consume up to 7 input bytes each iteration. */
+ movq %ip0, %rax /* rax = ip0 */
+ movq 40(%rsp), %rdx /* rdx = ilowest */
+ subq %rdx, %rax /* rax = ip0 - ilowest */
+ movq %rax, %r15 /* r15 = ip0 - ilowest */
+
+ /* rdx = rax / 7 */
+ movabsq $2635249153387078803, %rdx
+ mulq %rdx
+ subq %rdx, %r15
+ shrq %r15
+ addq %r15, %rdx
+ shrq $2, %rdx
+
+ /* r15 = (ip0 - ilowest) / 7 */
+ movq %rdx, %r15
+
+ /* r15 = min(r15, min(oend0 - op0, oend1 - op1, oend2 - op2, oend3 - op3) / 10) */
+ movq 8(%rsp), %rax /* rax = oend0 */
+ subq %op0, %rax /* rax = oend0 - op0 */
+ movq 16(%rsp), %rdx /* rdx = oend1 */
+ subq %op1, %rdx /* rdx = oend1 - op1 */
+
+ cmpq %rax, %rdx
+ cmova %rax, %rdx /* rdx = min(%rdx, %rax) */
+
+ movq 24(%rsp), %rax /* rax = oend2 */
+ subq %op2, %rax /* rax = oend2 - op2 */
+
+ cmpq %rax, %rdx
+ cmova %rax, %rdx /* rdx = min(%rdx, %rax) */
+
+ movq 32(%rsp), %rax /* rax = oend3 */
+ subq %op3, %rax /* rax = oend3 - op3 */
+
+ cmpq %rax, %rdx
+ cmova %rax, %rdx /* rdx = min(%rdx, %rax) */
+
+ movabsq $-3689348814741910323, %rax
+ mulq %rdx
+ shrq $3, %rdx /* rdx = rdx / 10 */
+
+ /* r15 = min(%rdx, %r15) */
+ cmpq %rdx, %r15
+ cmova %rdx, %r15
+
+ /* olimit = op3 + 5 * r15 */
+ movq %r15, %rax
+ leaq (%op3, %rax, 4), %olimit
+ addq %rax, %olimit
+
+ movq 0(%rsp), %rdx
+
+ /* If (op3 + 10 > olimit) */
+ movq %op3, %rax /* rax = op3 */
+ cmpq %rax, %olimit /* op3 == olimit */
+ je .L_4X2_exit
+
+ /* If (ip1 < ip0) go to exit */
+ cmpq %ip0, %ip1
+ jb .L_4X2_exit
+
+ /* If (ip2 < ip1) go to exit */
+ cmpq %ip1, %ip2
+ jb .L_4X2_exit
+
+ /* If (ip3 < ip2) go to exit */
+ cmpq %ip2, %ip3
+ jb .L_4X2_exit
+
+#define DECODE(n, idx) \
+ movq %bits##n, %rax; \
+ shrq $53, %rax; \
+ movzwl 0(%dtable,%rax,4),%r8d; \
+ movzbl 2(%dtable,%rax,4),%r15d; \
+ movzbl 3(%dtable,%rax,4),%eax; \
+ movw %r8w, (%op##n); \
+ shlxq %r15, %bits##n, %bits##n; \
+ addq %rax, %op##n
+
+#define RELOAD_BITS(n) \
+ bsfq %bits##n, %bits##n; \
+ movq %bits##n, %rax; \
+ shrq $3, %bits##n; \
+ andq $7, %rax; \
+ subq %bits##n, %ip##n; \
+ movq (%ip##n), %bits##n; \
+ orq $1, %bits##n; \
+ shlxq %rax, %bits##n, %bits##n
+
+
+ movq %olimit, 48(%rsp)
+
+ .p2align 6
+
+.L_4X2_loop_body:
+ /* We clobber r8, so store it on the stack */
+ movq %r8, 0(%rsp)
+
+ /* Decode 5 symbols from each of the 4 streams (20 symbols total). */
+ FOR_EACH_STREAM_WITH_INDEX(DECODE, 0)
+ FOR_EACH_STREAM_WITH_INDEX(DECODE, 1)
+ FOR_EACH_STREAM_WITH_INDEX(DECODE, 2)
+ FOR_EACH_STREAM_WITH_INDEX(DECODE, 3)
+ FOR_EACH_STREAM_WITH_INDEX(DECODE, 4)
+
+ /* Reload r8 */
+ movq 0(%rsp), %r8
+
+ FOR_EACH_STREAM(RELOAD_BITS)
+
+ cmp %op3, 48(%rsp)
+ ja .L_4X2_loop_body
+ jmp .L_4X2_compute_olimit
+
+#undef DECODE
+#undef RELOAD_BITS
+.L_4X2_exit:
+ addq $8, %rsp
+ /* Restore stack (oend & olimit) */
+ pop %rax /* oend0 */
+ pop %rax /* oend1 */
+ pop %rax /* oend2 */
+ pop %rax /* oend3 */
+ pop %rax /* ilowest */
+ pop %rax /* olimit */
+ pop %rax /* arg */
+
+ /* Save ip / op / bits */
+ movq %ip0, 0(%rax)
+ movq %ip1, 8(%rax)
+ movq %ip2, 16(%rax)
+ movq %ip3, 24(%rax)
+ movq %op0, 32(%rax)
+ movq %op1, 40(%rax)
+ movq %op2, 48(%rax)
+ movq %op3, 56(%rax)
+ movq %bits0, 64(%rax)
+ movq %bits1, 72(%rax)
+ movq %bits2, 80(%rax)
+ movq %bits3, 88(%rax)
+
+ /* Restore registers */
+ pop %r15
+ pop %r14
+ pop %r13
+ pop %r12
+ pop %r11
+ pop %r10
+ pop %r9
+ pop %r8
+ pop %rdi
+ pop %rsi
+ pop %rbp
+ pop %rdx
+ pop %rcx
+ pop %rbx
+ pop %rax
+ ret
+
+#endif
diff --git a/third_party/zstd/lib/decompress/zstd_ddict.c b/third_party/zstd/lib/decompress/zstd_ddict.c
new file mode 100644
index 0000000000..309ec0d036
--- /dev/null
+++ b/third_party/zstd/lib/decompress/zstd_ddict.c
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+/* zstd_ddict.c :
+ * concentrates all logic that needs to know the internals of ZSTD_DDict object */
+
+/*-*******************************************************
+* Dependencies
+*********************************************************/
+#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customFree */
+#include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */
+#include "../common/cpu.h" /* bmi2 */
+#include "../common/mem.h" /* low level memory routines */
+#define FSE_STATIC_LINKING_ONLY
+#include "../common/fse.h"
+#include "../common/huf.h"
+#include "zstd_decompress_internal.h"
+#include "zstd_ddict.h"
+
+#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1)
+# include "../legacy/zstd_legacy.h"
+#endif
+
+
+
+/*-*******************************************************
+* Types
+*********************************************************/
+struct ZSTD_DDict_s {
+ void* dictBuffer;
+ const void* dictContent;
+ size_t dictSize;
+ ZSTD_entropyDTables_t entropy;
+ U32 dictID;
+ U32 entropyPresent;
+ ZSTD_customMem cMem;
+}; /* typedef'd to ZSTD_DDict within "zstd.h" */
+
+const void* ZSTD_DDict_dictContent(const ZSTD_DDict* ddict)
+{
+ assert(ddict != NULL);
+ return ddict->dictContent;
+}
+
+size_t ZSTD_DDict_dictSize(const ZSTD_DDict* ddict)
+{
+ assert(ddict != NULL);
+ return ddict->dictSize;
+}
+
+void ZSTD_copyDDictParameters(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict)
+{
+ DEBUGLOG(4, "ZSTD_copyDDictParameters");
+ assert(dctx != NULL);
+ assert(ddict != NULL);
+ dctx->dictID = ddict->dictID;
+ dctx->prefixStart = ddict->dictContent;
+ dctx->virtualStart = ddict->dictContent;
+ dctx->dictEnd = (const BYTE*)ddict->dictContent + ddict->dictSize;
+ dctx->previousDstEnd = dctx->dictEnd;
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ dctx->dictContentBeginForFuzzing = dctx->prefixStart;
+ dctx->dictContentEndForFuzzing = dctx->previousDstEnd;
+#endif
+ if (ddict->entropyPresent) {
+ dctx->litEntropy = 1;
+ dctx->fseEntropy = 1;
+ dctx->LLTptr = ddict->entropy.LLTable;
+ dctx->MLTptr = ddict->entropy.MLTable;
+ dctx->OFTptr = ddict->entropy.OFTable;
+ dctx->HUFptr = ddict->entropy.hufTable;
+ dctx->entropy.rep[0] = ddict->entropy.rep[0];
+ dctx->entropy.rep[1] = ddict->entropy.rep[1];
+ dctx->entropy.rep[2] = ddict->entropy.rep[2];
+ } else {
+ dctx->litEntropy = 0;
+ dctx->fseEntropy = 0;
+ }
+}
+
+
+static size_t
+ZSTD_loadEntropy_intoDDict(ZSTD_DDict* ddict,
+ ZSTD_dictContentType_e dictContentType)
+{
+ ddict->dictID = 0;
+ ddict->entropyPresent = 0;
+ if (dictContentType == ZSTD_dct_rawContent) return 0;
+
+ if (ddict->dictSize < 8) {
+ if (dictContentType == ZSTD_dct_fullDict)
+ return ERROR(dictionary_corrupted); /* only accept specified dictionaries */
+ return 0; /* pure content mode */
+ }
+ { U32 const magic = MEM_readLE32(ddict->dictContent);
+ if (magic != ZSTD_MAGIC_DICTIONARY) {
+ if (dictContentType == ZSTD_dct_fullDict)
+ return ERROR(dictionary_corrupted); /* only accept specified dictionaries */
+ return 0; /* pure content mode */
+ }
+ }
+ ddict->dictID = MEM_readLE32((const char*)ddict->dictContent + ZSTD_FRAMEIDSIZE);
+
+ /* load entropy tables */
+ RETURN_ERROR_IF(ZSTD_isError(ZSTD_loadDEntropy(
+ &ddict->entropy, ddict->dictContent, ddict->dictSize)),
+ dictionary_corrupted, "");
+ ddict->entropyPresent = 1;
+ return 0;
+}
+
+
+static size_t ZSTD_initDDict_internal(ZSTD_DDict* ddict,
+ const void* dict, size_t dictSize,
+ ZSTD_dictLoadMethod_e dictLoadMethod,
+ ZSTD_dictContentType_e dictContentType)
+{
+ if ((dictLoadMethod == ZSTD_dlm_byRef) || (!dict) || (!dictSize)) {
+ ddict->dictBuffer = NULL;
+ ddict->dictContent = dict;
+ if (!dict) dictSize = 0;
+ } else {
+ void* const internalBuffer = ZSTD_customMalloc(dictSize, ddict->cMem);
+ ddict->dictBuffer = internalBuffer;
+ ddict->dictContent = internalBuffer;
+ if (!internalBuffer) return ERROR(memory_allocation);
+ ZSTD_memcpy(internalBuffer, dict, dictSize);
+ }
+ ddict->dictSize = dictSize;
+ ddict->entropy.hufTable[0] = (HUF_DTable)((ZSTD_HUFFDTABLE_CAPACITY_LOG)*0x1000001); /* cover both little and big endian */
+
+ /* parse dictionary content */
+ FORWARD_IF_ERROR( ZSTD_loadEntropy_intoDDict(ddict, dictContentType) , "");
+
+ return 0;
+}
+
+ZSTD_DDict* ZSTD_createDDict_advanced(const void* dict, size_t dictSize,
+ ZSTD_dictLoadMethod_e dictLoadMethod,
+ ZSTD_dictContentType_e dictContentType,
+ ZSTD_customMem customMem)
+{
+ if ((!customMem.customAlloc) ^ (!customMem.customFree)) return NULL;
+
+ { ZSTD_DDict* const ddict = (ZSTD_DDict*) ZSTD_customMalloc(sizeof(ZSTD_DDict), customMem);
+ if (ddict == NULL) return NULL;
+ ddict->cMem = customMem;
+ { size_t const initResult = ZSTD_initDDict_internal(ddict,
+ dict, dictSize,
+ dictLoadMethod, dictContentType);
+ if (ZSTD_isError(initResult)) {
+ ZSTD_freeDDict(ddict);
+ return NULL;
+ } }
+ return ddict;
+ }
+}
+
+/*! ZSTD_createDDict() :
+* Create a digested dictionary, to start decompression without startup delay.
+* `dict` content is copied inside DDict.
+* Consequently, `dict` can be released after `ZSTD_DDict` creation */
+ZSTD_DDict* ZSTD_createDDict(const void* dict, size_t dictSize)
+{
+ ZSTD_customMem const allocator = { NULL, NULL, NULL };
+ return ZSTD_createDDict_advanced(dict, dictSize, ZSTD_dlm_byCopy, ZSTD_dct_auto, allocator);
+}
+
+/*! ZSTD_createDDict_byReference() :
+ * Create a digested dictionary, to start decompression without startup delay.
+ * Dictionary content is simply referenced, it will be accessed during decompression.
+ * Warning : dictBuffer must outlive DDict (DDict must be freed before dictBuffer) */
+ZSTD_DDict* ZSTD_createDDict_byReference(const void* dictBuffer, size_t dictSize)
+{
+ ZSTD_customMem const allocator = { NULL, NULL, NULL };
+ return ZSTD_createDDict_advanced(dictBuffer, dictSize, ZSTD_dlm_byRef, ZSTD_dct_auto, allocator);
+}
+
+
+const ZSTD_DDict* ZSTD_initStaticDDict(
+ void* sBuffer, size_t sBufferSize,
+ const void* dict, size_t dictSize,
+ ZSTD_dictLoadMethod_e dictLoadMethod,
+ ZSTD_dictContentType_e dictContentType)
+{
+ size_t const neededSpace = sizeof(ZSTD_DDict)
+ + (dictLoadMethod == ZSTD_dlm_byRef ? 0 : dictSize);
+ ZSTD_DDict* const ddict = (ZSTD_DDict*)sBuffer;
+ assert(sBuffer != NULL);
+ assert(dict != NULL);
+ if ((size_t)sBuffer & 7) return NULL; /* 8-aligned */
+ if (sBufferSize < neededSpace) return NULL;
+ if (dictLoadMethod == ZSTD_dlm_byCopy) {
+ ZSTD_memcpy(ddict+1, dict, dictSize); /* local copy */
+ dict = ddict+1;
+ }
+ if (ZSTD_isError( ZSTD_initDDict_internal(ddict,
+ dict, dictSize,
+ ZSTD_dlm_byRef, dictContentType) ))
+ return NULL;
+ return ddict;
+}
+
+
+size_t ZSTD_freeDDict(ZSTD_DDict* ddict)
+{
+ if (ddict==NULL) return 0; /* support free on NULL */
+ { ZSTD_customMem const cMem = ddict->cMem;
+ ZSTD_customFree(ddict->dictBuffer, cMem);
+ ZSTD_customFree(ddict, cMem);
+ return 0;
+ }
+}
+
+/*! ZSTD_estimateDDictSize() :
+ * Estimate amount of memory that will be needed to create a dictionary for decompression.
+ * Note : dictionary created by reference using ZSTD_dlm_byRef are smaller */
+size_t ZSTD_estimateDDictSize(size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod)
+{
+ return sizeof(ZSTD_DDict) + (dictLoadMethod == ZSTD_dlm_byRef ? 0 : dictSize);
+}
+
+size_t ZSTD_sizeof_DDict(const ZSTD_DDict* ddict)
+{
+ if (ddict==NULL) return 0; /* support sizeof on NULL */
+ return sizeof(*ddict) + (ddict->dictBuffer ? ddict->dictSize : 0) ;
+}
+
+/*! ZSTD_getDictID_fromDDict() :
+ * Provides the dictID of the dictionary loaded into `ddict`.
+ * If @return == 0, the dictionary is not conformant to Zstandard specification, or empty.
+ * Non-conformant dictionaries can still be loaded, but as content-only dictionaries. */
+unsigned ZSTD_getDictID_fromDDict(const ZSTD_DDict* ddict)
+{
+ if (ddict==NULL) return 0;
+ return ddict->dictID;
+}
diff --git a/third_party/zstd/lib/decompress/zstd_ddict.h b/third_party/zstd/lib/decompress/zstd_ddict.h
new file mode 100644
index 0000000000..c4ca8877a0
--- /dev/null
+++ b/third_party/zstd/lib/decompress/zstd_ddict.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+
+#ifndef ZSTD_DDICT_H
+#define ZSTD_DDICT_H
+
+/*-*******************************************************
+ * Dependencies
+ *********************************************************/
+#include "../common/zstd_deps.h" /* size_t */
+#include "../zstd.h" /* ZSTD_DDict, and several public functions */
+
+
+/*-*******************************************************
+ * Interface
+ *********************************************************/
+
+/* note: several prototypes are already published in `zstd.h` :
+ * ZSTD_createDDict()
+ * ZSTD_createDDict_byReference()
+ * ZSTD_createDDict_advanced()
+ * ZSTD_freeDDict()
+ * ZSTD_initStaticDDict()
+ * ZSTD_sizeof_DDict()
+ * ZSTD_estimateDDictSize()
+ * ZSTD_getDictID_fromDict()
+ */
+
+const void* ZSTD_DDict_dictContent(const ZSTD_DDict* ddict);
+size_t ZSTD_DDict_dictSize(const ZSTD_DDict* ddict);
+
+void ZSTD_copyDDictParameters(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict);
+
+
+
+#endif /* ZSTD_DDICT_H */
diff --git a/third_party/zstd/lib/decompress/zstd_decompress.c b/third_party/zstd/lib/decompress/zstd_decompress.c
new file mode 100644
index 0000000000..2f03cf7b0c
--- /dev/null
+++ b/third_party/zstd/lib/decompress/zstd_decompress.c
@@ -0,0 +1,2407 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+
+/* ***************************************************************
+* Tuning parameters
+*****************************************************************/
+/*!
+ * HEAPMODE :
+ * Select how default decompression function ZSTD_decompress() allocates its context,
+ * on stack (0), or into heap (1, default; requires malloc()).
+ * Note that functions with explicit context such as ZSTD_decompressDCtx() are unaffected.
+ */
+#ifndef ZSTD_HEAPMODE
+# define ZSTD_HEAPMODE 1
+#endif
+
+/*!
+* LEGACY_SUPPORT :
+* if set to 1+, ZSTD_decompress() can decode older formats (v0.1+)
+*/
+#ifndef ZSTD_LEGACY_SUPPORT
+# define ZSTD_LEGACY_SUPPORT 0
+#endif
+
+/*!
+ * MAXWINDOWSIZE_DEFAULT :
+ * maximum window size accepted by DStream __by default__.
+ * Frames requiring more memory will be rejected.
+ * It's possible to set a different limit using ZSTD_DCtx_setMaxWindowSize().
+ */
+#ifndef ZSTD_MAXWINDOWSIZE_DEFAULT
+# define ZSTD_MAXWINDOWSIZE_DEFAULT (((U32)1 << ZSTD_WINDOWLOG_LIMIT_DEFAULT) + 1)
+#endif
+
+/*!
+ * NO_FORWARD_PROGRESS_MAX :
+ * maximum allowed nb of calls to ZSTD_decompressStream()
+ * without any forward progress
+ * (defined as: no byte read from input, and no byte flushed to output)
+ * before triggering an error.
+ */
+#ifndef ZSTD_NO_FORWARD_PROGRESS_MAX
+# define ZSTD_NO_FORWARD_PROGRESS_MAX 16
+#endif
+
+
+/*-*******************************************************
+* Dependencies
+*********************************************************/
+#include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */
+#include "../common/allocations.h" /* ZSTD_customMalloc, ZSTD_customCalloc, ZSTD_customFree */
+#include "../common/error_private.h"
+#include "../common/zstd_internal.h" /* blockProperties_t */
+#include "../common/mem.h" /* low level memory routines */
+#include "../common/bits.h" /* ZSTD_highbit32 */
+#define FSE_STATIC_LINKING_ONLY
+#include "../common/fse.h"
+#include "../common/huf.h"
+#include "../common/xxhash.h" /* XXH64_reset, XXH64_update, XXH64_digest, XXH64 */
+#include "zstd_decompress_internal.h" /* ZSTD_DCtx */
+#include "zstd_ddict.h" /* ZSTD_DDictDictContent */
+#include "zstd_decompress_block.h" /* ZSTD_decompressBlock_internal */
+
+#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1)
+# include "../legacy/zstd_legacy.h"
+#endif
+
+
+
+/*************************************
+ * Multiple DDicts Hashset internals *
+ *************************************/
+
+#define DDICT_HASHSET_MAX_LOAD_FACTOR_COUNT_MULT 4
+#define DDICT_HASHSET_MAX_LOAD_FACTOR_SIZE_MULT 3 /* These two constants represent SIZE_MULT/COUNT_MULT load factor without using a float.
+ * Currently, that means a 0.75 load factor.
+ * So, if count * COUNT_MULT / size * SIZE_MULT != 0, then we've exceeded
+ * the load factor of the ddict hash set.
+ */
+
+#define DDICT_HASHSET_TABLE_BASE_SIZE 64
+#define DDICT_HASHSET_RESIZE_FACTOR 2
+
+/* Hash function to determine starting position of dict insertion within the table
+ * Returns an index between [0, hashSet->ddictPtrTableSize]
+ */
+static size_t ZSTD_DDictHashSet_getIndex(const ZSTD_DDictHashSet* hashSet, U32 dictID) {
+ const U64 hash = XXH64(&dictID, sizeof(U32), 0);
+ /* DDict ptr table size is a multiple of 2, use size - 1 as mask to get index within [0, hashSet->ddictPtrTableSize) */
+ return hash & (hashSet->ddictPtrTableSize - 1);
+}
+
+/* Adds DDict to a hashset without resizing it.
+ * If inserting a DDict with a dictID that already exists in the set, replaces the one in the set.
+ * Returns 0 if successful, or a zstd error code if something went wrong.
+ */
+static size_t ZSTD_DDictHashSet_emplaceDDict(ZSTD_DDictHashSet* hashSet, const ZSTD_DDict* ddict) {
+ const U32 dictID = ZSTD_getDictID_fromDDict(ddict);
+ size_t idx = ZSTD_DDictHashSet_getIndex(hashSet, dictID);
+ const size_t idxRangeMask = hashSet->ddictPtrTableSize - 1;
+ RETURN_ERROR_IF(hashSet->ddictPtrCount == hashSet->ddictPtrTableSize, GENERIC, "Hash set is full!");
+ DEBUGLOG(4, "Hashed index: for dictID: %u is %zu", dictID, idx);
+ while (hashSet->ddictPtrTable[idx] != NULL) {
+ /* Replace existing ddict if inserting ddict with same dictID */
+ if (ZSTD_getDictID_fromDDict(hashSet->ddictPtrTable[idx]) == dictID) {
+ DEBUGLOG(4, "DictID already exists, replacing rather than adding");
+ hashSet->ddictPtrTable[idx] = ddict;
+ return 0;
+ }
+ idx &= idxRangeMask;
+ idx++;
+ }
+ DEBUGLOG(4, "Final idx after probing for dictID %u is: %zu", dictID, idx);
+ hashSet->ddictPtrTable[idx] = ddict;
+ hashSet->ddictPtrCount++;
+ return 0;
+}
+
+/* Expands hash table by factor of DDICT_HASHSET_RESIZE_FACTOR and
+ * rehashes all values, allocates new table, frees old table.
+ * Returns 0 on success, otherwise a zstd error code.
+ */
+static size_t ZSTD_DDictHashSet_expand(ZSTD_DDictHashSet* hashSet, ZSTD_customMem customMem) {
+ size_t newTableSize = hashSet->ddictPtrTableSize * DDICT_HASHSET_RESIZE_FACTOR;
+ const ZSTD_DDict** newTable = (const ZSTD_DDict**)ZSTD_customCalloc(sizeof(ZSTD_DDict*) * newTableSize, customMem);
+ const ZSTD_DDict** oldTable = hashSet->ddictPtrTable;
+ size_t oldTableSize = hashSet->ddictPtrTableSize;
+ size_t i;
+
+ DEBUGLOG(4, "Expanding DDict hash table! Old size: %zu new size: %zu", oldTableSize, newTableSize);
+ RETURN_ERROR_IF(!newTable, memory_allocation, "Expanded hashset allocation failed!");
+ hashSet->ddictPtrTable = newTable;
+ hashSet->ddictPtrTableSize = newTableSize;
+ hashSet->ddictPtrCount = 0;
+ for (i = 0; i < oldTableSize; ++i) {
+ if (oldTable[i] != NULL) {
+ FORWARD_IF_ERROR(ZSTD_DDictHashSet_emplaceDDict(hashSet, oldTable[i]), "");
+ }
+ }
+ ZSTD_customFree((void*)oldTable, customMem);
+ DEBUGLOG(4, "Finished re-hash");
+ return 0;
+}
+
+/* Fetches a DDict with the given dictID
+ * Returns the ZSTD_DDict* with the requested dictID. If it doesn't exist, then returns NULL.
+ */
+static const ZSTD_DDict* ZSTD_DDictHashSet_getDDict(ZSTD_DDictHashSet* hashSet, U32 dictID) {
+ size_t idx = ZSTD_DDictHashSet_getIndex(hashSet, dictID);
+ const size_t idxRangeMask = hashSet->ddictPtrTableSize - 1;
+ DEBUGLOG(4, "Hashed index: for dictID: %u is %zu", dictID, idx);
+ for (;;) {
+ size_t currDictID = ZSTD_getDictID_fromDDict(hashSet->ddictPtrTable[idx]);
+ if (currDictID == dictID || currDictID == 0) {
+ /* currDictID == 0 implies a NULL ddict entry */
+ break;
+ } else {
+ idx &= idxRangeMask; /* Goes to start of table when we reach the end */
+ idx++;
+ }
+ }
+ DEBUGLOG(4, "Final idx after probing for dictID %u is: %zu", dictID, idx);
+ return hashSet->ddictPtrTable[idx];
+}
+
+/* Allocates space for and returns a ddict hash set
+ * The hash set's ZSTD_DDict* table has all values automatically set to NULL to begin with.
+ * Returns NULL if allocation failed.
+ */
+static ZSTD_DDictHashSet* ZSTD_createDDictHashSet(ZSTD_customMem customMem) {
+ ZSTD_DDictHashSet* ret = (ZSTD_DDictHashSet*)ZSTD_customMalloc(sizeof(ZSTD_DDictHashSet), customMem);
+ DEBUGLOG(4, "Allocating new hash set");
+ if (!ret)
+ return NULL;
+ ret->ddictPtrTable = (const ZSTD_DDict**)ZSTD_customCalloc(DDICT_HASHSET_TABLE_BASE_SIZE * sizeof(ZSTD_DDict*), customMem);
+ if (!ret->ddictPtrTable) {
+ ZSTD_customFree(ret, customMem);
+ return NULL;
+ }
+ ret->ddictPtrTableSize = DDICT_HASHSET_TABLE_BASE_SIZE;
+ ret->ddictPtrCount = 0;
+ return ret;
+}
+
+/* Frees the table of ZSTD_DDict* within a hashset, then frees the hashset itself.
+ * Note: The ZSTD_DDict* within the table are NOT freed.
+ */
+static void ZSTD_freeDDictHashSet(ZSTD_DDictHashSet* hashSet, ZSTD_customMem customMem) {
+ DEBUGLOG(4, "Freeing ddict hash set");
+ if (hashSet && hashSet->ddictPtrTable) {
+ ZSTD_customFree((void*)hashSet->ddictPtrTable, customMem);
+ }
+ if (hashSet) {
+ ZSTD_customFree(hashSet, customMem);
+ }
+}
+
+/* Public function: Adds a DDict into the ZSTD_DDictHashSet, possibly triggering a resize of the hash set.
+ * Returns 0 on success, or a ZSTD error.
+ */
+static size_t ZSTD_DDictHashSet_addDDict(ZSTD_DDictHashSet* hashSet, const ZSTD_DDict* ddict, ZSTD_customMem customMem) {
+ DEBUGLOG(4, "Adding dict ID: %u to hashset with - Count: %zu Tablesize: %zu", ZSTD_getDictID_fromDDict(ddict), hashSet->ddictPtrCount, hashSet->ddictPtrTableSize);
+ if (hashSet->ddictPtrCount * DDICT_HASHSET_MAX_LOAD_FACTOR_COUNT_MULT / hashSet->ddictPtrTableSize * DDICT_HASHSET_MAX_LOAD_FACTOR_SIZE_MULT != 0) {
+ FORWARD_IF_ERROR(ZSTD_DDictHashSet_expand(hashSet, customMem), "");
+ }
+ FORWARD_IF_ERROR(ZSTD_DDictHashSet_emplaceDDict(hashSet, ddict), "");
+ return 0;
+}
+
+/*-*************************************************************
+* Context management
+***************************************************************/
+size_t ZSTD_sizeof_DCtx (const ZSTD_DCtx* dctx)
+{
+ if (dctx==NULL) return 0; /* support sizeof NULL */
+ return sizeof(*dctx)
+ + ZSTD_sizeof_DDict(dctx->ddictLocal)
+ + dctx->inBuffSize + dctx->outBuffSize;
+}
+
+size_t ZSTD_estimateDCtxSize(void) { return sizeof(ZSTD_DCtx); }
+
+
+static size_t ZSTD_startingInputLength(ZSTD_format_e format)
+{
+ size_t const startingInputLength = ZSTD_FRAMEHEADERSIZE_PREFIX(format);
+ /* only supports formats ZSTD_f_zstd1 and ZSTD_f_zstd1_magicless */
+ assert( (format == ZSTD_f_zstd1) || (format == ZSTD_f_zstd1_magicless) );
+ return startingInputLength;
+}
+
+static void ZSTD_DCtx_resetParameters(ZSTD_DCtx* dctx)
+{
+ assert(dctx->streamStage == zdss_init);
+ dctx->format = ZSTD_f_zstd1;
+ dctx->maxWindowSize = ZSTD_MAXWINDOWSIZE_DEFAULT;
+ dctx->outBufferMode = ZSTD_bm_buffered;
+ dctx->forceIgnoreChecksum = ZSTD_d_validateChecksum;
+ dctx->refMultipleDDicts = ZSTD_rmd_refSingleDDict;
+ dctx->disableHufAsm = 0;
+ dctx->maxBlockSizeParam = 0;
+}
+
+static void ZSTD_initDCtx_internal(ZSTD_DCtx* dctx)
+{
+ dctx->staticSize = 0;
+ dctx->ddict = NULL;
+ dctx->ddictLocal = NULL;
+ dctx->dictEnd = NULL;
+ dctx->ddictIsCold = 0;
+ dctx->dictUses = ZSTD_dont_use;
+ dctx->inBuff = NULL;
+ dctx->inBuffSize = 0;
+ dctx->outBuffSize = 0;
+ dctx->streamStage = zdss_init;
+#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1)
+ dctx->legacyContext = NULL;
+ dctx->previousLegacyVersion = 0;
+#endif
+ dctx->noForwardProgress = 0;
+ dctx->oversizedDuration = 0;
+ dctx->isFrameDecompression = 1;
+#if DYNAMIC_BMI2
+ dctx->bmi2 = ZSTD_cpuSupportsBmi2();
+#endif
+ dctx->ddictSet = NULL;
+ ZSTD_DCtx_resetParameters(dctx);
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ dctx->dictContentEndForFuzzing = NULL;
+#endif
+}
+
+ZSTD_DCtx* ZSTD_initStaticDCtx(void *workspace, size_t workspaceSize)
+{
+ ZSTD_DCtx* const dctx = (ZSTD_DCtx*) workspace;
+
+ if ((size_t)workspace & 7) return NULL; /* 8-aligned */
+ if (workspaceSize < sizeof(ZSTD_DCtx)) return NULL; /* minimum size */
+
+ ZSTD_initDCtx_internal(dctx);
+ dctx->staticSize = workspaceSize;
+ dctx->inBuff = (char*)(dctx+1);
+ return dctx;
+}
+
+static ZSTD_DCtx* ZSTD_createDCtx_internal(ZSTD_customMem customMem) {
+ if ((!customMem.customAlloc) ^ (!customMem.customFree)) return NULL;
+
+ { ZSTD_DCtx* const dctx = (ZSTD_DCtx*)ZSTD_customMalloc(sizeof(*dctx), customMem);
+ if (!dctx) return NULL;
+ dctx->customMem = customMem;
+ ZSTD_initDCtx_internal(dctx);
+ return dctx;
+ }
+}
+
+ZSTD_DCtx* ZSTD_createDCtx_advanced(ZSTD_customMem customMem)
+{
+ return ZSTD_createDCtx_internal(customMem);
+}
+
+ZSTD_DCtx* ZSTD_createDCtx(void)
+{
+ DEBUGLOG(3, "ZSTD_createDCtx");
+ return ZSTD_createDCtx_internal(ZSTD_defaultCMem);
+}
+
+static void ZSTD_clearDict(ZSTD_DCtx* dctx)
+{
+ ZSTD_freeDDict(dctx->ddictLocal);
+ dctx->ddictLocal = NULL;
+ dctx->ddict = NULL;
+ dctx->dictUses = ZSTD_dont_use;
+}
+
+size_t ZSTD_freeDCtx(ZSTD_DCtx* dctx)
+{
+ if (dctx==NULL) return 0; /* support free on NULL */
+ RETURN_ERROR_IF(dctx->staticSize, memory_allocation, "not compatible with static DCtx");
+ { ZSTD_customMem const cMem = dctx->customMem;
+ ZSTD_clearDict(dctx);
+ ZSTD_customFree(dctx->inBuff, cMem);
+ dctx->inBuff = NULL;
+#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
+ if (dctx->legacyContext)
+ ZSTD_freeLegacyStreamContext(dctx->legacyContext, dctx->previousLegacyVersion);
+#endif
+ if (dctx->ddictSet) {
+ ZSTD_freeDDictHashSet(dctx->ddictSet, cMem);
+ dctx->ddictSet = NULL;
+ }
+ ZSTD_customFree(dctx, cMem);
+ return 0;
+ }
+}
+
+/* no longer useful */
+void ZSTD_copyDCtx(ZSTD_DCtx* dstDCtx, const ZSTD_DCtx* srcDCtx)
+{
+ size_t const toCopy = (size_t)((char*)(&dstDCtx->inBuff) - (char*)dstDCtx);
+ ZSTD_memcpy(dstDCtx, srcDCtx, toCopy); /* no need to copy workspace */
+}
+
+/* Given a dctx with a digested frame params, re-selects the correct ZSTD_DDict based on
+ * the requested dict ID from the frame. If there exists a reference to the correct ZSTD_DDict, then
+ * accordingly sets the ddict to be used to decompress the frame.
+ *
+ * If no DDict is found, then no action is taken, and the ZSTD_DCtx::ddict remains as-is.
+ *
+ * ZSTD_d_refMultipleDDicts must be enabled for this function to be called.
+ */
+static void ZSTD_DCtx_selectFrameDDict(ZSTD_DCtx* dctx) {
+ assert(dctx->refMultipleDDicts && dctx->ddictSet);
+ DEBUGLOG(4, "Adjusting DDict based on requested dict ID from frame");
+ if (dctx->ddict) {
+ const ZSTD_DDict* frameDDict = ZSTD_DDictHashSet_getDDict(dctx->ddictSet, dctx->fParams.dictID);
+ if (frameDDict) {
+ DEBUGLOG(4, "DDict found!");
+ ZSTD_clearDict(dctx);
+ dctx->dictID = dctx->fParams.dictID;
+ dctx->ddict = frameDDict;
+ dctx->dictUses = ZSTD_use_indefinitely;
+ }
+ }
+}
+
+
+/*-*************************************************************
+ * Frame header decoding
+ ***************************************************************/
+
+/*! ZSTD_isFrame() :
+ * Tells if the content of `buffer` starts with a valid Frame Identifier.
+ * Note : Frame Identifier is 4 bytes. If `size < 4`, @return will always be 0.
+ * Note 2 : Legacy Frame Identifiers are considered valid only if Legacy Support is enabled.
+ * Note 3 : Skippable Frame Identifiers are considered valid. */
+unsigned ZSTD_isFrame(const void* buffer, size_t size)
+{
+ if (size < ZSTD_FRAMEIDSIZE) return 0;
+ { U32 const magic = MEM_readLE32(buffer);
+ if (magic == ZSTD_MAGICNUMBER) return 1;
+ if ((magic & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) return 1;
+ }
+#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
+ if (ZSTD_isLegacy(buffer, size)) return 1;
+#endif
+ return 0;
+}
+
+/*! ZSTD_isSkippableFrame() :
+ * Tells if the content of `buffer` starts with a valid Frame Identifier for a skippable frame.
+ * Note : Frame Identifier is 4 bytes. If `size < 4`, @return will always be 0.
+ */
+unsigned ZSTD_isSkippableFrame(const void* buffer, size_t size)
+{
+ if (size < ZSTD_FRAMEIDSIZE) return 0;
+ { U32 const magic = MEM_readLE32(buffer);
+ if ((magic & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) return 1;
+ }
+ return 0;
+}
+
+/** ZSTD_frameHeaderSize_internal() :
+ * srcSize must be large enough to reach header size fields.
+ * note : only works for formats ZSTD_f_zstd1 and ZSTD_f_zstd1_magicless.
+ * @return : size of the Frame Header
+ * or an error code, which can be tested with ZSTD_isError() */
+static size_t ZSTD_frameHeaderSize_internal(const void* src, size_t srcSize, ZSTD_format_e format)
+{
+ size_t const minInputSize = ZSTD_startingInputLength(format);
+ RETURN_ERROR_IF(srcSize < minInputSize, srcSize_wrong, "");
+
+ { BYTE const fhd = ((const BYTE*)src)[minInputSize-1];
+ U32 const dictID= fhd & 3;
+ U32 const singleSegment = (fhd >> 5) & 1;
+ U32 const fcsId = fhd >> 6;
+ return minInputSize + !singleSegment
+ + ZSTD_did_fieldSize[dictID] + ZSTD_fcs_fieldSize[fcsId]
+ + (singleSegment && !fcsId);
+ }
+}
+
+/** ZSTD_frameHeaderSize() :
+ * srcSize must be >= ZSTD_frameHeaderSize_prefix.
+ * @return : size of the Frame Header,
+ * or an error code (if srcSize is too small) */
+size_t ZSTD_frameHeaderSize(const void* src, size_t srcSize)
+{
+ return ZSTD_frameHeaderSize_internal(src, srcSize, ZSTD_f_zstd1);
+}
+
+
+/** ZSTD_getFrameHeader_advanced() :
+ * decode Frame Header, or require larger `srcSize`.
+ * note : only works for formats ZSTD_f_zstd1 and ZSTD_f_zstd1_magicless
+ * @return : 0, `zfhPtr` is correctly filled,
+ * >0, `srcSize` is too small, value is wanted `srcSize` amount,
+** or an error code, which can be tested using ZSTD_isError() */
+size_t ZSTD_getFrameHeader_advanced(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize, ZSTD_format_e format)
+{
+ const BYTE* ip = (const BYTE*)src;
+ size_t const minInputSize = ZSTD_startingInputLength(format);
+
+ DEBUGLOG(5, "ZSTD_getFrameHeader_advanced: minInputSize = %zu, srcSize = %zu", minInputSize, srcSize);
+
+ if (srcSize > 0) {
+ /* note : technically could be considered an assert(), since it's an invalid entry */
+ RETURN_ERROR_IF(src==NULL, GENERIC, "invalid parameter : src==NULL, but srcSize>0");
+ }
+ if (srcSize < minInputSize) {
+ if (srcSize > 0 && format != ZSTD_f_zstd1_magicless) {
+ /* when receiving less than @minInputSize bytes,
+ * control these bytes at least correspond to a supported magic number
+ * in order to error out early if they don't.
+ **/
+ size_t const toCopy = MIN(4, srcSize);
+ unsigned char hbuf[4]; MEM_writeLE32(hbuf, ZSTD_MAGICNUMBER);
+ assert(src != NULL);
+ ZSTD_memcpy(hbuf, src, toCopy);
+ if ( MEM_readLE32(hbuf) != ZSTD_MAGICNUMBER ) {
+ /* not a zstd frame : let's check if it's a skippable frame */
+ MEM_writeLE32(hbuf, ZSTD_MAGIC_SKIPPABLE_START);
+ ZSTD_memcpy(hbuf, src, toCopy);
+ if ((MEM_readLE32(hbuf) & ZSTD_MAGIC_SKIPPABLE_MASK) != ZSTD_MAGIC_SKIPPABLE_START) {
+ RETURN_ERROR(prefix_unknown,
+ "first bytes don't correspond to any supported magic number");
+ } } }
+ return minInputSize;
+ }
+
+ ZSTD_memset(zfhPtr, 0, sizeof(*zfhPtr)); /* not strictly necessary, but static analyzers may not understand that zfhPtr will be read only if return value is zero, since they are 2 different signals */
+ if ( (format != ZSTD_f_zstd1_magicless)
+ && (MEM_readLE32(src) != ZSTD_MAGICNUMBER) ) {
+ if ((MEM_readLE32(src) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) {
+ /* skippable frame */
+ if (srcSize < ZSTD_SKIPPABLEHEADERSIZE)
+ return ZSTD_SKIPPABLEHEADERSIZE; /* magic number + frame length */
+ ZSTD_memset(zfhPtr, 0, sizeof(*zfhPtr));
+ zfhPtr->frameContentSize = MEM_readLE32((const char *)src + ZSTD_FRAMEIDSIZE);
+ zfhPtr->frameType = ZSTD_skippableFrame;
+ return 0;
+ }
+ RETURN_ERROR(prefix_unknown, "");
+ }
+
+ /* ensure there is enough `srcSize` to fully read/decode frame header */
+ { size_t const fhsize = ZSTD_frameHeaderSize_internal(src, srcSize, format);
+ if (srcSize < fhsize) return fhsize;
+ zfhPtr->headerSize = (U32)fhsize;
+ }
+
+ { BYTE const fhdByte = ip[minInputSize-1];
+ size_t pos = minInputSize;
+ U32 const dictIDSizeCode = fhdByte&3;
+ U32 const checksumFlag = (fhdByte>>2)&1;
+ U32 const singleSegment = (fhdByte>>5)&1;
+ U32 const fcsID = fhdByte>>6;
+ U64 windowSize = 0;
+ U32 dictID = 0;
+ U64 frameContentSize = ZSTD_CONTENTSIZE_UNKNOWN;
+ RETURN_ERROR_IF((fhdByte & 0x08) != 0, frameParameter_unsupported,
+ "reserved bits, must be zero");
+
+ if (!singleSegment) {
+ BYTE const wlByte = ip[pos++];
+ U32 const windowLog = (wlByte >> 3) + ZSTD_WINDOWLOG_ABSOLUTEMIN;
+ RETURN_ERROR_IF(windowLog > ZSTD_WINDOWLOG_MAX, frameParameter_windowTooLarge, "");
+ windowSize = (1ULL << windowLog);
+ windowSize += (windowSize >> 3) * (wlByte&7);
+ }
+ switch(dictIDSizeCode)
+ {
+ default:
+ assert(0); /* impossible */
+ ZSTD_FALLTHROUGH;
+ case 0 : break;
+ case 1 : dictID = ip[pos]; pos++; break;
+ case 2 : dictID = MEM_readLE16(ip+pos); pos+=2; break;
+ case 3 : dictID = MEM_readLE32(ip+pos); pos+=4; break;
+ }
+ switch(fcsID)
+ {
+ default:
+ assert(0); /* impossible */
+ ZSTD_FALLTHROUGH;
+ case 0 : if (singleSegment) frameContentSize = ip[pos]; break;
+ case 1 : frameContentSize = MEM_readLE16(ip+pos)+256; break;
+ case 2 : frameContentSize = MEM_readLE32(ip+pos); break;
+ case 3 : frameContentSize = MEM_readLE64(ip+pos); break;
+ }
+ if (singleSegment) windowSize = frameContentSize;
+
+ zfhPtr->frameType = ZSTD_frame;
+ zfhPtr->frameContentSize = frameContentSize;
+ zfhPtr->windowSize = windowSize;
+ zfhPtr->blockSizeMax = (unsigned) MIN(windowSize, ZSTD_BLOCKSIZE_MAX);
+ zfhPtr->dictID = dictID;
+ zfhPtr->checksumFlag = checksumFlag;
+ }
+ return 0;
+}
+
+/** ZSTD_getFrameHeader() :
+ * decode Frame Header, or require larger `srcSize`.
+ * note : this function does not consume input, it only reads it.
+ * @return : 0, `zfhPtr` is correctly filled,
+ * >0, `srcSize` is too small, value is wanted `srcSize` amount,
+ * or an error code, which can be tested using ZSTD_isError() */
+size_t ZSTD_getFrameHeader(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize)
+{
+ return ZSTD_getFrameHeader_advanced(zfhPtr, src, srcSize, ZSTD_f_zstd1);
+}
+
+/** ZSTD_getFrameContentSize() :
+ * compatible with legacy mode
+ * @return : decompressed size of the single frame pointed to be `src` if known, otherwise
+ * - ZSTD_CONTENTSIZE_UNKNOWN if the size cannot be determined
+ * - ZSTD_CONTENTSIZE_ERROR if an error occurred (e.g. invalid magic number, srcSize too small) */
+unsigned long long ZSTD_getFrameContentSize(const void *src, size_t srcSize)
+{
+#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
+ if (ZSTD_isLegacy(src, srcSize)) {
+ unsigned long long const ret = ZSTD_getDecompressedSize_legacy(src, srcSize);
+ return ret == 0 ? ZSTD_CONTENTSIZE_UNKNOWN : ret;
+ }
+#endif
+ { ZSTD_frameHeader zfh;
+ if (ZSTD_getFrameHeader(&zfh, src, srcSize) != 0)
+ return ZSTD_CONTENTSIZE_ERROR;
+ if (zfh.frameType == ZSTD_skippableFrame) {
+ return 0;
+ } else {
+ return zfh.frameContentSize;
+ } }
+}
+
+static size_t readSkippableFrameSize(void const* src, size_t srcSize)
+{
+ size_t const skippableHeaderSize = ZSTD_SKIPPABLEHEADERSIZE;
+ U32 sizeU32;
+
+ RETURN_ERROR_IF(srcSize < ZSTD_SKIPPABLEHEADERSIZE, srcSize_wrong, "");
+
+ sizeU32 = MEM_readLE32((BYTE const*)src + ZSTD_FRAMEIDSIZE);
+ RETURN_ERROR_IF((U32)(sizeU32 + ZSTD_SKIPPABLEHEADERSIZE) < sizeU32,
+ frameParameter_unsupported, "");
+ { size_t const skippableSize = skippableHeaderSize + sizeU32;
+ RETURN_ERROR_IF(skippableSize > srcSize, srcSize_wrong, "");
+ return skippableSize;
+ }
+}
+
+/*! ZSTD_readSkippableFrame() :
+ * Retrieves content of a skippable frame, and writes it to dst buffer.
+ *
+ * The parameter magicVariant will receive the magicVariant that was supplied when the frame was written,
+ * i.e. magicNumber - ZSTD_MAGIC_SKIPPABLE_START. This can be NULL if the caller is not interested
+ * in the magicVariant.
+ *
+ * Returns an error if destination buffer is not large enough, or if this is not a valid skippable frame.
+ *
+ * @return : number of bytes written or a ZSTD error.
+ */
+size_t ZSTD_readSkippableFrame(void* dst, size_t dstCapacity,
+ unsigned* magicVariant, /* optional, can be NULL */
+ const void* src, size_t srcSize)
+{
+ RETURN_ERROR_IF(srcSize < ZSTD_SKIPPABLEHEADERSIZE, srcSize_wrong, "");
+
+ { U32 const magicNumber = MEM_readLE32(src);
+ size_t skippableFrameSize = readSkippableFrameSize(src, srcSize);
+ size_t skippableContentSize = skippableFrameSize - ZSTD_SKIPPABLEHEADERSIZE;
+
+ /* check input validity */
+ RETURN_ERROR_IF(!ZSTD_isSkippableFrame(src, srcSize), frameParameter_unsupported, "");
+ RETURN_ERROR_IF(skippableFrameSize < ZSTD_SKIPPABLEHEADERSIZE || skippableFrameSize > srcSize, srcSize_wrong, "");
+ RETURN_ERROR_IF(skippableContentSize > dstCapacity, dstSize_tooSmall, "");
+
+ /* deliver payload */
+ if (skippableContentSize > 0 && dst != NULL)
+ ZSTD_memcpy(dst, (const BYTE *)src + ZSTD_SKIPPABLEHEADERSIZE, skippableContentSize);
+ if (magicVariant != NULL)
+ *magicVariant = magicNumber - ZSTD_MAGIC_SKIPPABLE_START;
+ return skippableContentSize;
+ }
+}
+
+/** ZSTD_findDecompressedSize() :
+ * `srcSize` must be the exact length of some number of ZSTD compressed and/or
+ * skippable frames
+ * note: compatible with legacy mode
+ * @return : decompressed size of the frames contained */
+unsigned long long ZSTD_findDecompressedSize(const void* src, size_t srcSize)
+{
+ unsigned long long totalDstSize = 0;
+
+ while (srcSize >= ZSTD_startingInputLength(ZSTD_f_zstd1)) {
+ U32 const magicNumber = MEM_readLE32(src);
+
+ if ((magicNumber & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) {
+ size_t const skippableSize = readSkippableFrameSize(src, srcSize);
+ if (ZSTD_isError(skippableSize)) return ZSTD_CONTENTSIZE_ERROR;
+ assert(skippableSize <= srcSize);
+
+ src = (const BYTE *)src + skippableSize;
+ srcSize -= skippableSize;
+ continue;
+ }
+
+ { unsigned long long const fcs = ZSTD_getFrameContentSize(src, srcSize);
+ if (fcs >= ZSTD_CONTENTSIZE_ERROR) return fcs;
+
+ if (totalDstSize + fcs < totalDstSize)
+ return ZSTD_CONTENTSIZE_ERROR; /* check for overflow */
+ totalDstSize += fcs;
+ }
+ /* skip to next frame */
+ { size_t const frameSrcSize = ZSTD_findFrameCompressedSize(src, srcSize);
+ if (ZSTD_isError(frameSrcSize)) return ZSTD_CONTENTSIZE_ERROR;
+ assert(frameSrcSize <= srcSize);
+
+ src = (const BYTE *)src + frameSrcSize;
+ srcSize -= frameSrcSize;
+ }
+ } /* while (srcSize >= ZSTD_frameHeaderSize_prefix) */
+
+ if (srcSize) return ZSTD_CONTENTSIZE_ERROR;
+
+ return totalDstSize;
+}
+
+/** ZSTD_getDecompressedSize() :
+ * compatible with legacy mode
+ * @return : decompressed size if known, 0 otherwise
+ note : 0 can mean any of the following :
+ - frame content is empty
+ - decompressed size field is not present in frame header
+ - frame header unknown / not supported
+ - frame header not complete (`srcSize` too small) */
+unsigned long long ZSTD_getDecompressedSize(const void* src, size_t srcSize)
+{
+ unsigned long long const ret = ZSTD_getFrameContentSize(src, srcSize);
+ ZSTD_STATIC_ASSERT(ZSTD_CONTENTSIZE_ERROR < ZSTD_CONTENTSIZE_UNKNOWN);
+ return (ret >= ZSTD_CONTENTSIZE_ERROR) ? 0 : ret;
+}
+
+
+/** ZSTD_decodeFrameHeader() :
+ * `headerSize` must be the size provided by ZSTD_frameHeaderSize().
+ * If multiple DDict references are enabled, also will choose the correct DDict to use.
+ * @return : 0 if success, or an error code, which can be tested using ZSTD_isError() */
+static size_t ZSTD_decodeFrameHeader(ZSTD_DCtx* dctx, const void* src, size_t headerSize)
+{
+ size_t const result = ZSTD_getFrameHeader_advanced(&(dctx->fParams), src, headerSize, dctx->format);
+ if (ZSTD_isError(result)) return result; /* invalid header */
+ RETURN_ERROR_IF(result>0, srcSize_wrong, "headerSize too small");
+
+ /* Reference DDict requested by frame if dctx references multiple ddicts */
+ if (dctx->refMultipleDDicts == ZSTD_rmd_refMultipleDDicts && dctx->ddictSet) {
+ ZSTD_DCtx_selectFrameDDict(dctx);
+ }
+
+#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ /* Skip the dictID check in fuzzing mode, because it makes the search
+ * harder.
+ */
+ RETURN_ERROR_IF(dctx->fParams.dictID && (dctx->dictID != dctx->fParams.dictID),
+ dictionary_wrong, "");
+#endif
+ dctx->validateChecksum = (dctx->fParams.checksumFlag && !dctx->forceIgnoreChecksum) ? 1 : 0;
+ if (dctx->validateChecksum) XXH64_reset(&dctx->xxhState, 0);
+ dctx->processedCSize += headerSize;
+ return 0;
+}
+
+static ZSTD_frameSizeInfo ZSTD_errorFrameSizeInfo(size_t ret)
+{
+ ZSTD_frameSizeInfo frameSizeInfo;
+ frameSizeInfo.compressedSize = ret;
+ frameSizeInfo.decompressedBound = ZSTD_CONTENTSIZE_ERROR;
+ return frameSizeInfo;
+}
+
+static ZSTD_frameSizeInfo ZSTD_findFrameSizeInfo(const void* src, size_t srcSize, ZSTD_format_e format)
+{
+ ZSTD_frameSizeInfo frameSizeInfo;
+ ZSTD_memset(&frameSizeInfo, 0, sizeof(ZSTD_frameSizeInfo));
+
+#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
+ if (format == ZSTD_f_zstd1 && ZSTD_isLegacy(src, srcSize))
+ return ZSTD_findFrameSizeInfoLegacy(src, srcSize);
+#endif
+
+ if (format == ZSTD_f_zstd1 && (srcSize >= ZSTD_SKIPPABLEHEADERSIZE)
+ && (MEM_readLE32(src) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) {
+ frameSizeInfo.compressedSize = readSkippableFrameSize(src, srcSize);
+ assert(ZSTD_isError(frameSizeInfo.compressedSize) ||
+ frameSizeInfo.compressedSize <= srcSize);
+ return frameSizeInfo;
+ } else {
+ const BYTE* ip = (const BYTE*)src;
+ const BYTE* const ipstart = ip;
+ size_t remainingSize = srcSize;
+ size_t nbBlocks = 0;
+ ZSTD_frameHeader zfh;
+
+ /* Extract Frame Header */
+ { size_t const ret = ZSTD_getFrameHeader_advanced(&zfh, src, srcSize, format);
+ if (ZSTD_isError(ret))
+ return ZSTD_errorFrameSizeInfo(ret);
+ if (ret > 0)
+ return ZSTD_errorFrameSizeInfo(ERROR(srcSize_wrong));
+ }
+
+ ip += zfh.headerSize;
+ remainingSize -= zfh.headerSize;
+
+ /* Iterate over each block */
+ while (1) {
+ blockProperties_t blockProperties;
+ size_t const cBlockSize = ZSTD_getcBlockSize(ip, remainingSize, &blockProperties);
+ if (ZSTD_isError(cBlockSize))
+ return ZSTD_errorFrameSizeInfo(cBlockSize);
+
+ if (ZSTD_blockHeaderSize + cBlockSize > remainingSize)
+ return ZSTD_errorFrameSizeInfo(ERROR(srcSize_wrong));
+
+ ip += ZSTD_blockHeaderSize + cBlockSize;
+ remainingSize -= ZSTD_blockHeaderSize + cBlockSize;
+ nbBlocks++;
+
+ if (blockProperties.lastBlock) break;
+ }
+
+ /* Final frame content checksum */
+ if (zfh.checksumFlag) {
+ if (remainingSize < 4)
+ return ZSTD_errorFrameSizeInfo(ERROR(srcSize_wrong));
+ ip += 4;
+ }
+
+ frameSizeInfo.nbBlocks = nbBlocks;
+ frameSizeInfo.compressedSize = (size_t)(ip - ipstart);
+ frameSizeInfo.decompressedBound = (zfh.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN)
+ ? zfh.frameContentSize
+ : (unsigned long long)nbBlocks * zfh.blockSizeMax;
+ return frameSizeInfo;
+ }
+}
+
+static size_t ZSTD_findFrameCompressedSize_advanced(const void *src, size_t srcSize, ZSTD_format_e format) {
+ ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize, format);
+ return frameSizeInfo.compressedSize;
+}
+
+/** ZSTD_findFrameCompressedSize() :
+ * See docs in zstd.h
+ * Note: compatible with legacy mode */
+size_t ZSTD_findFrameCompressedSize(const void *src, size_t srcSize)
+{
+ return ZSTD_findFrameCompressedSize_advanced(src, srcSize, ZSTD_f_zstd1);
+}
+
+/** ZSTD_decompressBound() :
+ * compatible with legacy mode
+ * `src` must point to the start of a ZSTD frame or a skippeable frame
+ * `srcSize` must be at least as large as the frame contained
+ * @return : the maximum decompressed size of the compressed source
+ */
+unsigned long long ZSTD_decompressBound(const void* src, size_t srcSize)
+{
+ unsigned long long bound = 0;
+ /* Iterate over each frame */
+ while (srcSize > 0) {
+ ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize, ZSTD_f_zstd1);
+ size_t const compressedSize = frameSizeInfo.compressedSize;
+ unsigned long long const decompressedBound = frameSizeInfo.decompressedBound;
+ if (ZSTD_isError(compressedSize) || decompressedBound == ZSTD_CONTENTSIZE_ERROR)
+ return ZSTD_CONTENTSIZE_ERROR;
+ assert(srcSize >= compressedSize);
+ src = (const BYTE*)src + compressedSize;
+ srcSize -= compressedSize;
+ bound += decompressedBound;
+ }
+ return bound;
+}
+
+size_t ZSTD_decompressionMargin(void const* src, size_t srcSize)
+{
+ size_t margin = 0;
+ unsigned maxBlockSize = 0;
+
+ /* Iterate over each frame */
+ while (srcSize > 0) {
+ ZSTD_frameSizeInfo const frameSizeInfo = ZSTD_findFrameSizeInfo(src, srcSize, ZSTD_f_zstd1);
+ size_t const compressedSize = frameSizeInfo.compressedSize;
+ unsigned long long const decompressedBound = frameSizeInfo.decompressedBound;
+ ZSTD_frameHeader zfh;
+
+ FORWARD_IF_ERROR(ZSTD_getFrameHeader(&zfh, src, srcSize), "");
+ if (ZSTD_isError(compressedSize) || decompressedBound == ZSTD_CONTENTSIZE_ERROR)
+ return ERROR(corruption_detected);
+
+ if (zfh.frameType == ZSTD_frame) {
+ /* Add the frame header to our margin */
+ margin += zfh.headerSize;
+ /* Add the checksum to our margin */
+ margin += zfh.checksumFlag ? 4 : 0;
+ /* Add 3 bytes per block */
+ margin += 3 * frameSizeInfo.nbBlocks;
+
+ /* Compute the max block size */
+ maxBlockSize = MAX(maxBlockSize, zfh.blockSizeMax);
+ } else {
+ assert(zfh.frameType == ZSTD_skippableFrame);
+ /* Add the entire skippable frame size to our margin. */
+ margin += compressedSize;
+ }
+
+ assert(srcSize >= compressedSize);
+ src = (const BYTE*)src + compressedSize;
+ srcSize -= compressedSize;
+ }
+
+ /* Add the max block size back to the margin. */
+ margin += maxBlockSize;
+
+ return margin;
+}
+
+/*-*************************************************************
+ * Frame decoding
+ ***************************************************************/
+
+/** ZSTD_insertBlock() :
+ * insert `src` block into `dctx` history. Useful to track uncompressed blocks. */
+size_t ZSTD_insertBlock(ZSTD_DCtx* dctx, const void* blockStart, size_t blockSize)
+{
+ DEBUGLOG(5, "ZSTD_insertBlock: %u bytes", (unsigned)blockSize);
+ ZSTD_checkContinuity(dctx, blockStart, blockSize);
+ dctx->previousDstEnd = (const char*)blockStart + blockSize;
+ return blockSize;
+}
+
+
+static size_t ZSTD_copyRawBlock(void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize)
+{
+ DEBUGLOG(5, "ZSTD_copyRawBlock");
+ RETURN_ERROR_IF(srcSize > dstCapacity, dstSize_tooSmall, "");
+ if (dst == NULL) {
+ if (srcSize == 0) return 0;
+ RETURN_ERROR(dstBuffer_null, "");
+ }
+ ZSTD_memmove(dst, src, srcSize);
+ return srcSize;
+}
+
+static size_t ZSTD_setRleBlock(void* dst, size_t dstCapacity,
+ BYTE b,
+ size_t regenSize)
+{
+ RETURN_ERROR_IF(regenSize > dstCapacity, dstSize_tooSmall, "");
+ if (dst == NULL) {
+ if (regenSize == 0) return 0;
+ RETURN_ERROR(dstBuffer_null, "");
+ }
+ ZSTD_memset(dst, b, regenSize);
+ return regenSize;
+}
+
+static void ZSTD_DCtx_trace_end(ZSTD_DCtx const* dctx, U64 uncompressedSize, U64 compressedSize, unsigned streaming)
+{
+#if ZSTD_TRACE
+ if (dctx->traceCtx && ZSTD_trace_decompress_end != NULL) {
+ ZSTD_Trace trace;
+ ZSTD_memset(&trace, 0, sizeof(trace));
+ trace.version = ZSTD_VERSION_NUMBER;
+ trace.streaming = streaming;
+ if (dctx->ddict) {
+ trace.dictionaryID = ZSTD_getDictID_fromDDict(dctx->ddict);
+ trace.dictionarySize = ZSTD_DDict_dictSize(dctx->ddict);
+ trace.dictionaryIsCold = dctx->ddictIsCold;
+ }
+ trace.uncompressedSize = (size_t)uncompressedSize;
+ trace.compressedSize = (size_t)compressedSize;
+ trace.dctx = dctx;
+ ZSTD_trace_decompress_end(dctx->traceCtx, &trace);
+ }
+#else
+ (void)dctx;
+ (void)uncompressedSize;
+ (void)compressedSize;
+ (void)streaming;
+#endif
+}
+
+
+/*! ZSTD_decompressFrame() :
+ * @dctx must be properly initialized
+ * will update *srcPtr and *srcSizePtr,
+ * to make *srcPtr progress by one frame. */
+static size_t ZSTD_decompressFrame(ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity,
+ const void** srcPtr, size_t *srcSizePtr)
+{
+ const BYTE* const istart = (const BYTE*)(*srcPtr);
+ const BYTE* ip = istart;
+ BYTE* const ostart = (BYTE*)dst;
+ BYTE* const oend = dstCapacity != 0 ? ostart + dstCapacity : ostart;
+ BYTE* op = ostart;
+ size_t remainingSrcSize = *srcSizePtr;
+
+ DEBUGLOG(4, "ZSTD_decompressFrame (srcSize:%i)", (int)*srcSizePtr);
+
+ /* check */
+ RETURN_ERROR_IF(
+ remainingSrcSize < ZSTD_FRAMEHEADERSIZE_MIN(dctx->format)+ZSTD_blockHeaderSize,
+ srcSize_wrong, "");
+
+ /* Frame Header */
+ { size_t const frameHeaderSize = ZSTD_frameHeaderSize_internal(
+ ip, ZSTD_FRAMEHEADERSIZE_PREFIX(dctx->format), dctx->format);
+ if (ZSTD_isError(frameHeaderSize)) return frameHeaderSize;
+ RETURN_ERROR_IF(remainingSrcSize < frameHeaderSize+ZSTD_blockHeaderSize,
+ srcSize_wrong, "");
+ FORWARD_IF_ERROR( ZSTD_decodeFrameHeader(dctx, ip, frameHeaderSize) , "");
+ ip += frameHeaderSize; remainingSrcSize -= frameHeaderSize;
+ }
+
+ /* Shrink the blockSizeMax if enabled */
+ if (dctx->maxBlockSizeParam != 0)
+ dctx->fParams.blockSizeMax = MIN(dctx->fParams.blockSizeMax, (unsigned)dctx->maxBlockSizeParam);
+
+ /* Loop on each block */
+ while (1) {
+ BYTE* oBlockEnd = oend;
+ size_t decodedSize;
+ blockProperties_t blockProperties;
+ size_t const cBlockSize = ZSTD_getcBlockSize(ip, remainingSrcSize, &blockProperties);
+ if (ZSTD_isError(cBlockSize)) return cBlockSize;
+
+ ip += ZSTD_blockHeaderSize;
+ remainingSrcSize -= ZSTD_blockHeaderSize;
+ RETURN_ERROR_IF(cBlockSize > remainingSrcSize, srcSize_wrong, "");
+
+ if (ip >= op && ip < oBlockEnd) {
+ /* We are decompressing in-place. Limit the output pointer so that we
+ * don't overwrite the block that we are currently reading. This will
+ * fail decompression if the input & output pointers aren't spaced
+ * far enough apart.
+ *
+ * This is important to set, even when the pointers are far enough
+ * apart, because ZSTD_decompressBlock_internal() can decide to store
+ * literals in the output buffer, after the block it is decompressing.
+ * Since we don't want anything to overwrite our input, we have to tell
+ * ZSTD_decompressBlock_internal to never write past ip.
+ *
+ * See ZSTD_allocateLiteralsBuffer() for reference.
+ */
+ oBlockEnd = op + (ip - op);
+ }
+
+ switch(blockProperties.blockType)
+ {
+ case bt_compressed:
+ assert(dctx->isFrameDecompression == 1);
+ decodedSize = ZSTD_decompressBlock_internal(dctx, op, (size_t)(oBlockEnd-op), ip, cBlockSize, not_streaming);
+ break;
+ case bt_raw :
+ /* Use oend instead of oBlockEnd because this function is safe to overlap. It uses memmove. */
+ decodedSize = ZSTD_copyRawBlock(op, (size_t)(oend-op), ip, cBlockSize);
+ break;
+ case bt_rle :
+ decodedSize = ZSTD_setRleBlock(op, (size_t)(oBlockEnd-op), *ip, blockProperties.origSize);
+ break;
+ case bt_reserved :
+ default:
+ RETURN_ERROR(corruption_detected, "invalid block type");
+ }
+ FORWARD_IF_ERROR(decodedSize, "Block decompression failure");
+ DEBUGLOG(5, "Decompressed block of dSize = %u", (unsigned)decodedSize);
+ if (dctx->validateChecksum) {
+ XXH64_update(&dctx->xxhState, op, decodedSize);
+ }
+ if (decodedSize) /* support dst = NULL,0 */ {
+ op += decodedSize;
+ }
+ assert(ip != NULL);
+ ip += cBlockSize;
+ remainingSrcSize -= cBlockSize;
+ if (blockProperties.lastBlock) break;
+ }
+
+ if (dctx->fParams.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN) {
+ RETURN_ERROR_IF((U64)(op-ostart) != dctx->fParams.frameContentSize,
+ corruption_detected, "");
+ }
+ if (dctx->fParams.checksumFlag) { /* Frame content checksum verification */
+ RETURN_ERROR_IF(remainingSrcSize<4, checksum_wrong, "");
+ if (!dctx->forceIgnoreChecksum) {
+ U32 const checkCalc = (U32)XXH64_digest(&dctx->xxhState);
+ U32 checkRead;
+ checkRead = MEM_readLE32(ip);
+ RETURN_ERROR_IF(checkRead != checkCalc, checksum_wrong, "");
+ }
+ ip += 4;
+ remainingSrcSize -= 4;
+ }
+ ZSTD_DCtx_trace_end(dctx, (U64)(op-ostart), (U64)(ip-istart), /* streaming */ 0);
+ /* Allow caller to get size read */
+ DEBUGLOG(4, "ZSTD_decompressFrame: decompressed frame of size %zi, consuming %zi bytes of input", op-ostart, ip - (const BYTE*)*srcPtr);
+ *srcPtr = ip;
+ *srcSizePtr = remainingSrcSize;
+ return (size_t)(op-ostart);
+}
+
+static
+ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
+size_t ZSTD_decompressMultiFrame(ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize,
+ const void* dict, size_t dictSize,
+ const ZSTD_DDict* ddict)
+{
+ void* const dststart = dst;
+ int moreThan1Frame = 0;
+
+ DEBUGLOG(5, "ZSTD_decompressMultiFrame");
+ assert(dict==NULL || ddict==NULL); /* either dict or ddict set, not both */
+
+ if (ddict) {
+ dict = ZSTD_DDict_dictContent(ddict);
+ dictSize = ZSTD_DDict_dictSize(ddict);
+ }
+
+ while (srcSize >= ZSTD_startingInputLength(dctx->format)) {
+
+#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1)
+ if (dctx->format == ZSTD_f_zstd1 && ZSTD_isLegacy(src, srcSize)) {
+ size_t decodedSize;
+ size_t const frameSize = ZSTD_findFrameCompressedSizeLegacy(src, srcSize);
+ if (ZSTD_isError(frameSize)) return frameSize;
+ RETURN_ERROR_IF(dctx->staticSize, memory_allocation,
+ "legacy support is not compatible with static dctx");
+
+ decodedSize = ZSTD_decompressLegacy(dst, dstCapacity, src, frameSize, dict, dictSize);
+ if (ZSTD_isError(decodedSize)) return decodedSize;
+
+ {
+ unsigned long long const expectedSize = ZSTD_getFrameContentSize(src, srcSize);
+ RETURN_ERROR_IF(expectedSize == ZSTD_CONTENTSIZE_ERROR, corruption_detected, "Corrupted frame header!");
+ if (expectedSize != ZSTD_CONTENTSIZE_UNKNOWN) {
+ RETURN_ERROR_IF(expectedSize != decodedSize, corruption_detected,
+ "Frame header size does not match decoded size!");
+ }
+ }
+
+ assert(decodedSize <= dstCapacity);
+ dst = (BYTE*)dst + decodedSize;
+ dstCapacity -= decodedSize;
+
+ src = (const BYTE*)src + frameSize;
+ srcSize -= frameSize;
+
+ continue;
+ }
+#endif
+
+ if (dctx->format == ZSTD_f_zstd1 && srcSize >= 4) {
+ U32 const magicNumber = MEM_readLE32(src);
+ DEBUGLOG(5, "reading magic number %08X", (unsigned)magicNumber);
+ if ((magicNumber & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) {
+ /* skippable frame detected : skip it */
+ size_t const skippableSize = readSkippableFrameSize(src, srcSize);
+ FORWARD_IF_ERROR(skippableSize, "invalid skippable frame");
+ assert(skippableSize <= srcSize);
+
+ src = (const BYTE *)src + skippableSize;
+ srcSize -= skippableSize;
+ continue; /* check next frame */
+ } }
+
+ if (ddict) {
+ /* we were called from ZSTD_decompress_usingDDict */
+ FORWARD_IF_ERROR(ZSTD_decompressBegin_usingDDict(dctx, ddict), "");
+ } else {
+ /* this will initialize correctly with no dict if dict == NULL, so
+ * use this in all cases but ddict */
+ FORWARD_IF_ERROR(ZSTD_decompressBegin_usingDict(dctx, dict, dictSize), "");
+ }
+ ZSTD_checkContinuity(dctx, dst, dstCapacity);
+
+ { const size_t res = ZSTD_decompressFrame(dctx, dst, dstCapacity,
+ &src, &srcSize);
+ RETURN_ERROR_IF(
+ (ZSTD_getErrorCode(res) == ZSTD_error_prefix_unknown)
+ && (moreThan1Frame==1),
+ srcSize_wrong,
+ "At least one frame successfully completed, "
+ "but following bytes are garbage: "
+ "it's more likely to be a srcSize error, "
+ "specifying more input bytes than size of frame(s). "
+ "Note: one could be unlucky, it might be a corruption error instead, "
+ "happening right at the place where we expect zstd magic bytes. "
+ "But this is _much_ less likely than a srcSize field error.");
+ if (ZSTD_isError(res)) return res;
+ assert(res <= dstCapacity);
+ if (res != 0)
+ dst = (BYTE*)dst + res;
+ dstCapacity -= res;
+ }
+ moreThan1Frame = 1;
+ } /* while (srcSize >= ZSTD_frameHeaderSize_prefix) */
+
+ RETURN_ERROR_IF(srcSize, srcSize_wrong, "input not entirely consumed");
+
+ return (size_t)((BYTE*)dst - (BYTE*)dststart);
+}
+
+size_t ZSTD_decompress_usingDict(ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize,
+ const void* dict, size_t dictSize)
+{
+ return ZSTD_decompressMultiFrame(dctx, dst, dstCapacity, src, srcSize, dict, dictSize, NULL);
+}
+
+
+static ZSTD_DDict const* ZSTD_getDDict(ZSTD_DCtx* dctx)
+{
+ switch (dctx->dictUses) {
+ default:
+ assert(0 /* Impossible */);
+ ZSTD_FALLTHROUGH;
+ case ZSTD_dont_use:
+ ZSTD_clearDict(dctx);
+ return NULL;
+ case ZSTD_use_indefinitely:
+ return dctx->ddict;
+ case ZSTD_use_once:
+ dctx->dictUses = ZSTD_dont_use;
+ return dctx->ddict;
+ }
+}
+
+size_t ZSTD_decompressDCtx(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize)
+{
+ return ZSTD_decompress_usingDDict(dctx, dst, dstCapacity, src, srcSize, ZSTD_getDDict(dctx));
+}
+
+
+size_t ZSTD_decompress(void* dst, size_t dstCapacity, const void* src, size_t srcSize)
+{
+#if defined(ZSTD_HEAPMODE) && (ZSTD_HEAPMODE>=1)
+ size_t regenSize;
+ ZSTD_DCtx* const dctx = ZSTD_createDCtx_internal(ZSTD_defaultCMem);
+ RETURN_ERROR_IF(dctx==NULL, memory_allocation, "NULL pointer!");
+ regenSize = ZSTD_decompressDCtx(dctx, dst, dstCapacity, src, srcSize);
+ ZSTD_freeDCtx(dctx);
+ return regenSize;
+#else /* stack mode */
+ ZSTD_DCtx dctx;
+ ZSTD_initDCtx_internal(&dctx);
+ return ZSTD_decompressDCtx(&dctx, dst, dstCapacity, src, srcSize);
+#endif
+}
+
+
+/*-**************************************
+* Advanced Streaming Decompression API
+* Bufferless and synchronous
+****************************************/
+size_t ZSTD_nextSrcSizeToDecompress(ZSTD_DCtx* dctx) { return dctx->expected; }
+
+/**
+ * Similar to ZSTD_nextSrcSizeToDecompress(), but when a block input can be streamed, we
+ * allow taking a partial block as the input. Currently only raw uncompressed blocks can
+ * be streamed.
+ *
+ * For blocks that can be streamed, this allows us to reduce the latency until we produce
+ * output, and avoid copying the input.
+ *
+ * @param inputSize - The total amount of input that the caller currently has.
+ */
+static size_t ZSTD_nextSrcSizeToDecompressWithInputSize(ZSTD_DCtx* dctx, size_t inputSize) {
+ if (!(dctx->stage == ZSTDds_decompressBlock || dctx->stage == ZSTDds_decompressLastBlock))
+ return dctx->expected;
+ if (dctx->bType != bt_raw)
+ return dctx->expected;
+ return BOUNDED(1, inputSize, dctx->expected);
+}
+
+ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx) {
+ switch(dctx->stage)
+ {
+ default: /* should not happen */
+ assert(0);
+ ZSTD_FALLTHROUGH;
+ case ZSTDds_getFrameHeaderSize:
+ ZSTD_FALLTHROUGH;
+ case ZSTDds_decodeFrameHeader:
+ return ZSTDnit_frameHeader;
+ case ZSTDds_decodeBlockHeader:
+ return ZSTDnit_blockHeader;
+ case ZSTDds_decompressBlock:
+ return ZSTDnit_block;
+ case ZSTDds_decompressLastBlock:
+ return ZSTDnit_lastBlock;
+ case ZSTDds_checkChecksum:
+ return ZSTDnit_checksum;
+ case ZSTDds_decodeSkippableHeader:
+ ZSTD_FALLTHROUGH;
+ case ZSTDds_skipFrame:
+ return ZSTDnit_skippableFrame;
+ }
+}
+
+static int ZSTD_isSkipFrame(ZSTD_DCtx* dctx) { return dctx->stage == ZSTDds_skipFrame; }
+
+/** ZSTD_decompressContinue() :
+ * srcSize : must be the exact nb of bytes expected (see ZSTD_nextSrcSizeToDecompress())
+ * @return : nb of bytes generated into `dst` (necessarily <= `dstCapacity)
+ * or an error code, which can be tested using ZSTD_isError() */
+size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize)
+{
+ DEBUGLOG(5, "ZSTD_decompressContinue (srcSize:%u)", (unsigned)srcSize);
+ /* Sanity check */
+ RETURN_ERROR_IF(srcSize != ZSTD_nextSrcSizeToDecompressWithInputSize(dctx, srcSize), srcSize_wrong, "not allowed");
+ ZSTD_checkContinuity(dctx, dst, dstCapacity);
+
+ dctx->processedCSize += srcSize;
+
+ switch (dctx->stage)
+ {
+ case ZSTDds_getFrameHeaderSize :
+ assert(src != NULL);
+ if (dctx->format == ZSTD_f_zstd1) { /* allows header */
+ assert(srcSize >= ZSTD_FRAMEIDSIZE); /* to read skippable magic number */
+ if ((MEM_readLE32(src) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { /* skippable frame */
+ ZSTD_memcpy(dctx->headerBuffer, src, srcSize);
+ dctx->expected = ZSTD_SKIPPABLEHEADERSIZE - srcSize; /* remaining to load to get full skippable frame header */
+ dctx->stage = ZSTDds_decodeSkippableHeader;
+ return 0;
+ } }
+ dctx->headerSize = ZSTD_frameHeaderSize_internal(src, srcSize, dctx->format);
+ if (ZSTD_isError(dctx->headerSize)) return dctx->headerSize;
+ ZSTD_memcpy(dctx->headerBuffer, src, srcSize);
+ dctx->expected = dctx->headerSize - srcSize;
+ dctx->stage = ZSTDds_decodeFrameHeader;
+ return 0;
+
+ case ZSTDds_decodeFrameHeader:
+ assert(src != NULL);
+ ZSTD_memcpy(dctx->headerBuffer + (dctx->headerSize - srcSize), src, srcSize);
+ FORWARD_IF_ERROR(ZSTD_decodeFrameHeader(dctx, dctx->headerBuffer, dctx->headerSize), "");
+ dctx->expected = ZSTD_blockHeaderSize;
+ dctx->stage = ZSTDds_decodeBlockHeader;
+ return 0;
+
+ case ZSTDds_decodeBlockHeader:
+ { blockProperties_t bp;
+ size_t const cBlockSize = ZSTD_getcBlockSize(src, ZSTD_blockHeaderSize, &bp);
+ if (ZSTD_isError(cBlockSize)) return cBlockSize;
+ RETURN_ERROR_IF(cBlockSize > dctx->fParams.blockSizeMax, corruption_detected, "Block Size Exceeds Maximum");
+ dctx->expected = cBlockSize;
+ dctx->bType = bp.blockType;
+ dctx->rleSize = bp.origSize;
+ if (cBlockSize) {
+ dctx->stage = bp.lastBlock ? ZSTDds_decompressLastBlock : ZSTDds_decompressBlock;
+ return 0;
+ }
+ /* empty block */
+ if (bp.lastBlock) {
+ if (dctx->fParams.checksumFlag) {
+ dctx->expected = 4;
+ dctx->stage = ZSTDds_checkChecksum;
+ } else {
+ dctx->expected = 0; /* end of frame */
+ dctx->stage = ZSTDds_getFrameHeaderSize;
+ }
+ } else {
+ dctx->expected = ZSTD_blockHeaderSize; /* jump to next header */
+ dctx->stage = ZSTDds_decodeBlockHeader;
+ }
+ return 0;
+ }
+
+ case ZSTDds_decompressLastBlock:
+ case ZSTDds_decompressBlock:
+ DEBUGLOG(5, "ZSTD_decompressContinue: case ZSTDds_decompressBlock");
+ { size_t rSize;
+ switch(dctx->bType)
+ {
+ case bt_compressed:
+ DEBUGLOG(5, "ZSTD_decompressContinue: case bt_compressed");
+ assert(dctx->isFrameDecompression == 1);
+ rSize = ZSTD_decompressBlock_internal(dctx, dst, dstCapacity, src, srcSize, is_streaming);
+ dctx->expected = 0; /* Streaming not supported */
+ break;
+ case bt_raw :
+ assert(srcSize <= dctx->expected);
+ rSize = ZSTD_copyRawBlock(dst, dstCapacity, src, srcSize);
+ FORWARD_IF_ERROR(rSize, "ZSTD_copyRawBlock failed");
+ assert(rSize == srcSize);
+ dctx->expected -= rSize;
+ break;
+ case bt_rle :
+ rSize = ZSTD_setRleBlock(dst, dstCapacity, *(const BYTE*)src, dctx->rleSize);
+ dctx->expected = 0; /* Streaming not supported */
+ break;
+ case bt_reserved : /* should never happen */
+ default:
+ RETURN_ERROR(corruption_detected, "invalid block type");
+ }
+ FORWARD_IF_ERROR(rSize, "");
+ RETURN_ERROR_IF(rSize > dctx->fParams.blockSizeMax, corruption_detected, "Decompressed Block Size Exceeds Maximum");
+ DEBUGLOG(5, "ZSTD_decompressContinue: decoded size from block : %u", (unsigned)rSize);
+ dctx->decodedSize += rSize;
+ if (dctx->validateChecksum) XXH64_update(&dctx->xxhState, dst, rSize);
+ dctx->previousDstEnd = (char*)dst + rSize;
+
+ /* Stay on the same stage until we are finished streaming the block. */
+ if (dctx->expected > 0) {
+ return rSize;
+ }
+
+ if (dctx->stage == ZSTDds_decompressLastBlock) { /* end of frame */
+ DEBUGLOG(4, "ZSTD_decompressContinue: decoded size from frame : %u", (unsigned)dctx->decodedSize);
+ RETURN_ERROR_IF(
+ dctx->fParams.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN
+ && dctx->decodedSize != dctx->fParams.frameContentSize,
+ corruption_detected, "");
+ if (dctx->fParams.checksumFlag) { /* another round for frame checksum */
+ dctx->expected = 4;
+ dctx->stage = ZSTDds_checkChecksum;
+ } else {
+ ZSTD_DCtx_trace_end(dctx, dctx->decodedSize, dctx->processedCSize, /* streaming */ 1);
+ dctx->expected = 0; /* ends here */
+ dctx->stage = ZSTDds_getFrameHeaderSize;
+ }
+ } else {
+ dctx->stage = ZSTDds_decodeBlockHeader;
+ dctx->expected = ZSTD_blockHeaderSize;
+ }
+ return rSize;
+ }
+
+ case ZSTDds_checkChecksum:
+ assert(srcSize == 4); /* guaranteed by dctx->expected */
+ {
+ if (dctx->validateChecksum) {
+ U32 const h32 = (U32)XXH64_digest(&dctx->xxhState);
+ U32 const check32 = MEM_readLE32(src);
+ DEBUGLOG(4, "ZSTD_decompressContinue: checksum : calculated %08X :: %08X read", (unsigned)h32, (unsigned)check32);
+ RETURN_ERROR_IF(check32 != h32, checksum_wrong, "");
+ }
+ ZSTD_DCtx_trace_end(dctx, dctx->decodedSize, dctx->processedCSize, /* streaming */ 1);
+ dctx->expected = 0;
+ dctx->stage = ZSTDds_getFrameHeaderSize;
+ return 0;
+ }
+
+ case ZSTDds_decodeSkippableHeader:
+ assert(src != NULL);
+ assert(srcSize <= ZSTD_SKIPPABLEHEADERSIZE);
+ assert(dctx->format != ZSTD_f_zstd1_magicless);
+ ZSTD_memcpy(dctx->headerBuffer + (ZSTD_SKIPPABLEHEADERSIZE - srcSize), src, srcSize); /* complete skippable header */
+ dctx->expected = MEM_readLE32(dctx->headerBuffer + ZSTD_FRAMEIDSIZE); /* note : dctx->expected can grow seriously large, beyond local buffer size */
+ dctx->stage = ZSTDds_skipFrame;
+ return 0;
+
+ case ZSTDds_skipFrame:
+ dctx->expected = 0;
+ dctx->stage = ZSTDds_getFrameHeaderSize;
+ return 0;
+
+ default:
+ assert(0); /* impossible */
+ RETURN_ERROR(GENERIC, "impossible to reach"); /* some compilers require default to do something */
+ }
+}
+
+
+static size_t ZSTD_refDictContent(ZSTD_DCtx* dctx, const void* dict, size_t dictSize)
+{
+ dctx->dictEnd = dctx->previousDstEnd;
+ dctx->virtualStart = (const char*)dict - ((const char*)(dctx->previousDstEnd) - (const char*)(dctx->prefixStart));
+ dctx->prefixStart = dict;
+ dctx->previousDstEnd = (const char*)dict + dictSize;
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ dctx->dictContentBeginForFuzzing = dctx->prefixStart;
+ dctx->dictContentEndForFuzzing = dctx->previousDstEnd;
+#endif
+ return 0;
+}
+
+/*! ZSTD_loadDEntropy() :
+ * dict : must point at beginning of a valid zstd dictionary.
+ * @return : size of entropy tables read */
+size_t
+ZSTD_loadDEntropy(ZSTD_entropyDTables_t* entropy,
+ const void* const dict, size_t const dictSize)
+{
+ const BYTE* dictPtr = (const BYTE*)dict;
+ const BYTE* const dictEnd = dictPtr + dictSize;
+
+ RETURN_ERROR_IF(dictSize <= 8, dictionary_corrupted, "dict is too small");
+ assert(MEM_readLE32(dict) == ZSTD_MAGIC_DICTIONARY); /* dict must be valid */
+ dictPtr += 8; /* skip header = magic + dictID */
+
+ ZSTD_STATIC_ASSERT(offsetof(ZSTD_entropyDTables_t, OFTable) == offsetof(ZSTD_entropyDTables_t, LLTable) + sizeof(entropy->LLTable));
+ ZSTD_STATIC_ASSERT(offsetof(ZSTD_entropyDTables_t, MLTable) == offsetof(ZSTD_entropyDTables_t, OFTable) + sizeof(entropy->OFTable));
+ ZSTD_STATIC_ASSERT(sizeof(entropy->LLTable) + sizeof(entropy->OFTable) + sizeof(entropy->MLTable) >= HUF_DECOMPRESS_WORKSPACE_SIZE);
+ { void* const workspace = &entropy->LLTable; /* use fse tables as temporary workspace; implies fse tables are grouped together */
+ size_t const workspaceSize = sizeof(entropy->LLTable) + sizeof(entropy->OFTable) + sizeof(entropy->MLTable);
+#ifdef HUF_FORCE_DECOMPRESS_X1
+ /* in minimal huffman, we always use X1 variants */
+ size_t const hSize = HUF_readDTableX1_wksp(entropy->hufTable,
+ dictPtr, dictEnd - dictPtr,
+ workspace, workspaceSize, /* flags */ 0);
+#else
+ size_t const hSize = HUF_readDTableX2_wksp(entropy->hufTable,
+ dictPtr, (size_t)(dictEnd - dictPtr),
+ workspace, workspaceSize, /* flags */ 0);
+#endif
+ RETURN_ERROR_IF(HUF_isError(hSize), dictionary_corrupted, "");
+ dictPtr += hSize;
+ }
+
+ { short offcodeNCount[MaxOff+1];
+ unsigned offcodeMaxValue = MaxOff, offcodeLog;
+ size_t const offcodeHeaderSize = FSE_readNCount(offcodeNCount, &offcodeMaxValue, &offcodeLog, dictPtr, (size_t)(dictEnd-dictPtr));
+ RETURN_ERROR_IF(FSE_isError(offcodeHeaderSize), dictionary_corrupted, "");
+ RETURN_ERROR_IF(offcodeMaxValue > MaxOff, dictionary_corrupted, "");
+ RETURN_ERROR_IF(offcodeLog > OffFSELog, dictionary_corrupted, "");
+ ZSTD_buildFSETable( entropy->OFTable,
+ offcodeNCount, offcodeMaxValue,
+ OF_base, OF_bits,
+ offcodeLog,
+ entropy->workspace, sizeof(entropy->workspace),
+ /* bmi2 */0);
+ dictPtr += offcodeHeaderSize;
+ }
+
+ { short matchlengthNCount[MaxML+1];
+ unsigned matchlengthMaxValue = MaxML, matchlengthLog;
+ size_t const matchlengthHeaderSize = FSE_readNCount(matchlengthNCount, &matchlengthMaxValue, &matchlengthLog, dictPtr, (size_t)(dictEnd-dictPtr));
+ RETURN_ERROR_IF(FSE_isError(matchlengthHeaderSize), dictionary_corrupted, "");
+ RETURN_ERROR_IF(matchlengthMaxValue > MaxML, dictionary_corrupted, "");
+ RETURN_ERROR_IF(matchlengthLog > MLFSELog, dictionary_corrupted, "");
+ ZSTD_buildFSETable( entropy->MLTable,
+ matchlengthNCount, matchlengthMaxValue,
+ ML_base, ML_bits,
+ matchlengthLog,
+ entropy->workspace, sizeof(entropy->workspace),
+ /* bmi2 */ 0);
+ dictPtr += matchlengthHeaderSize;
+ }
+
+ { short litlengthNCount[MaxLL+1];
+ unsigned litlengthMaxValue = MaxLL, litlengthLog;
+ size_t const litlengthHeaderSize = FSE_readNCount(litlengthNCount, &litlengthMaxValue, &litlengthLog, dictPtr, (size_t)(dictEnd-dictPtr));
+ RETURN_ERROR_IF(FSE_isError(litlengthHeaderSize), dictionary_corrupted, "");
+ RETURN_ERROR_IF(litlengthMaxValue > MaxLL, dictionary_corrupted, "");
+ RETURN_ERROR_IF(litlengthLog > LLFSELog, dictionary_corrupted, "");
+ ZSTD_buildFSETable( entropy->LLTable,
+ litlengthNCount, litlengthMaxValue,
+ LL_base, LL_bits,
+ litlengthLog,
+ entropy->workspace, sizeof(entropy->workspace),
+ /* bmi2 */ 0);
+ dictPtr += litlengthHeaderSize;
+ }
+
+ RETURN_ERROR_IF(dictPtr+12 > dictEnd, dictionary_corrupted, "");
+ { int i;
+ size_t const dictContentSize = (size_t)(dictEnd - (dictPtr+12));
+ for (i=0; i<3; i++) {
+ U32 const rep = MEM_readLE32(dictPtr); dictPtr += 4;
+ RETURN_ERROR_IF(rep==0 || rep > dictContentSize,
+ dictionary_corrupted, "");
+ entropy->rep[i] = rep;
+ } }
+
+ return (size_t)(dictPtr - (const BYTE*)dict);
+}
+
+static size_t ZSTD_decompress_insertDictionary(ZSTD_DCtx* dctx, const void* dict, size_t dictSize)
+{
+ if (dictSize < 8) return ZSTD_refDictContent(dctx, dict, dictSize);
+ { U32 const magic = MEM_readLE32(dict);
+ if (magic != ZSTD_MAGIC_DICTIONARY) {
+ return ZSTD_refDictContent(dctx, dict, dictSize); /* pure content mode */
+ } }
+ dctx->dictID = MEM_readLE32((const char*)dict + ZSTD_FRAMEIDSIZE);
+
+ /* load entropy tables */
+ { size_t const eSize = ZSTD_loadDEntropy(&dctx->entropy, dict, dictSize);
+ RETURN_ERROR_IF(ZSTD_isError(eSize), dictionary_corrupted, "");
+ dict = (const char*)dict + eSize;
+ dictSize -= eSize;
+ }
+ dctx->litEntropy = dctx->fseEntropy = 1;
+
+ /* reference dictionary content */
+ return ZSTD_refDictContent(dctx, dict, dictSize);
+}
+
+size_t ZSTD_decompressBegin(ZSTD_DCtx* dctx)
+{
+ assert(dctx != NULL);
+#if ZSTD_TRACE
+ dctx->traceCtx = (ZSTD_trace_decompress_begin != NULL) ? ZSTD_trace_decompress_begin(dctx) : 0;
+#endif
+ dctx->expected = ZSTD_startingInputLength(dctx->format); /* dctx->format must be properly set */
+ dctx->stage = ZSTDds_getFrameHeaderSize;
+ dctx->processedCSize = 0;
+ dctx->decodedSize = 0;
+ dctx->previousDstEnd = NULL;
+ dctx->prefixStart = NULL;
+ dctx->virtualStart = NULL;
+ dctx->dictEnd = NULL;
+ dctx->entropy.hufTable[0] = (HUF_DTable)((ZSTD_HUFFDTABLE_CAPACITY_LOG)*0x1000001); /* cover both little and big endian */
+ dctx->litEntropy = dctx->fseEntropy = 0;
+ dctx->dictID = 0;
+ dctx->bType = bt_reserved;
+ dctx->isFrameDecompression = 1;
+ ZSTD_STATIC_ASSERT(sizeof(dctx->entropy.rep) == sizeof(repStartValue));
+ ZSTD_memcpy(dctx->entropy.rep, repStartValue, sizeof(repStartValue)); /* initial repcodes */
+ dctx->LLTptr = dctx->entropy.LLTable;
+ dctx->MLTptr = dctx->entropy.MLTable;
+ dctx->OFTptr = dctx->entropy.OFTable;
+ dctx->HUFptr = dctx->entropy.hufTable;
+ return 0;
+}
+
+size_t ZSTD_decompressBegin_usingDict(ZSTD_DCtx* dctx, const void* dict, size_t dictSize)
+{
+ FORWARD_IF_ERROR( ZSTD_decompressBegin(dctx) , "");
+ if (dict && dictSize)
+ RETURN_ERROR_IF(
+ ZSTD_isError(ZSTD_decompress_insertDictionary(dctx, dict, dictSize)),
+ dictionary_corrupted, "");
+ return 0;
+}
+
+
+/* ====== ZSTD_DDict ====== */
+
+size_t ZSTD_decompressBegin_usingDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict)
+{
+ DEBUGLOG(4, "ZSTD_decompressBegin_usingDDict");
+ assert(dctx != NULL);
+ if (ddict) {
+ const char* const dictStart = (const char*)ZSTD_DDict_dictContent(ddict);
+ size_t const dictSize = ZSTD_DDict_dictSize(ddict);
+ const void* const dictEnd = dictStart + dictSize;
+ dctx->ddictIsCold = (dctx->dictEnd != dictEnd);
+ DEBUGLOG(4, "DDict is %s",
+ dctx->ddictIsCold ? "~cold~" : "hot!");
+ }
+ FORWARD_IF_ERROR( ZSTD_decompressBegin(dctx) , "");
+ if (ddict) { /* NULL ddict is equivalent to no dictionary */
+ ZSTD_copyDDictParameters(dctx, ddict);
+ }
+ return 0;
+}
+
+/*! ZSTD_getDictID_fromDict() :
+ * Provides the dictID stored within dictionary.
+ * if @return == 0, the dictionary is not conformant with Zstandard specification.
+ * It can still be loaded, but as a content-only dictionary. */
+unsigned ZSTD_getDictID_fromDict(const void* dict, size_t dictSize)
+{
+ if (dictSize < 8) return 0;
+ if (MEM_readLE32(dict) != ZSTD_MAGIC_DICTIONARY) return 0;
+ return MEM_readLE32((const char*)dict + ZSTD_FRAMEIDSIZE);
+}
+
+/*! ZSTD_getDictID_fromFrame() :
+ * Provides the dictID required to decompress frame stored within `src`.
+ * If @return == 0, the dictID could not be decoded.
+ * This could for one of the following reasons :
+ * - The frame does not require a dictionary (most common case).
+ * - The frame was built with dictID intentionally removed.
+ * Needed dictionary is a hidden piece of information.
+ * Note : this use case also happens when using a non-conformant dictionary.
+ * - `srcSize` is too small, and as a result, frame header could not be decoded.
+ * Note : possible if `srcSize < ZSTD_FRAMEHEADERSIZE_MAX`.
+ * - This is not a Zstandard frame.
+ * When identifying the exact failure cause, it's possible to use
+ * ZSTD_getFrameHeader(), which will provide a more precise error code. */
+unsigned ZSTD_getDictID_fromFrame(const void* src, size_t srcSize)
+{
+ ZSTD_frameHeader zfp = { 0, 0, 0, ZSTD_frame, 0, 0, 0, 0, 0 };
+ size_t const hError = ZSTD_getFrameHeader(&zfp, src, srcSize);
+ if (ZSTD_isError(hError)) return 0;
+ return zfp.dictID;
+}
+
+
+/*! ZSTD_decompress_usingDDict() :
+* Decompression using a pre-digested Dictionary
+* Use dictionary without significant overhead. */
+size_t ZSTD_decompress_usingDDict(ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize,
+ const ZSTD_DDict* ddict)
+{
+ /* pass content and size in case legacy frames are encountered */
+ return ZSTD_decompressMultiFrame(dctx, dst, dstCapacity, src, srcSize,
+ NULL, 0,
+ ddict);
+}
+
+
+/*=====================================
+* Streaming decompression
+*====================================*/
+
+ZSTD_DStream* ZSTD_createDStream(void)
+{
+ DEBUGLOG(3, "ZSTD_createDStream");
+ return ZSTD_createDCtx_internal(ZSTD_defaultCMem);
+}
+
+ZSTD_DStream* ZSTD_initStaticDStream(void *workspace, size_t workspaceSize)
+{
+ return ZSTD_initStaticDCtx(workspace, workspaceSize);
+}
+
+ZSTD_DStream* ZSTD_createDStream_advanced(ZSTD_customMem customMem)
+{
+ return ZSTD_createDCtx_internal(customMem);
+}
+
+size_t ZSTD_freeDStream(ZSTD_DStream* zds)
+{
+ return ZSTD_freeDCtx(zds);
+}
+
+
+/* *** Initialization *** */
+
+size_t ZSTD_DStreamInSize(void) { return ZSTD_BLOCKSIZE_MAX + ZSTD_blockHeaderSize; }
+size_t ZSTD_DStreamOutSize(void) { return ZSTD_BLOCKSIZE_MAX; }
+
+size_t ZSTD_DCtx_loadDictionary_advanced(ZSTD_DCtx* dctx,
+ const void* dict, size_t dictSize,
+ ZSTD_dictLoadMethod_e dictLoadMethod,
+ ZSTD_dictContentType_e dictContentType)
+{
+ RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, "");
+ ZSTD_clearDict(dctx);
+ if (dict && dictSize != 0) {
+ dctx->ddictLocal = ZSTD_createDDict_advanced(dict, dictSize, dictLoadMethod, dictContentType, dctx->customMem);
+ RETURN_ERROR_IF(dctx->ddictLocal == NULL, memory_allocation, "NULL pointer!");
+ dctx->ddict = dctx->ddictLocal;
+ dctx->dictUses = ZSTD_use_indefinitely;
+ }
+ return 0;
+}
+
+size_t ZSTD_DCtx_loadDictionary_byReference(ZSTD_DCtx* dctx, const void* dict, size_t dictSize)
+{
+ return ZSTD_DCtx_loadDictionary_advanced(dctx, dict, dictSize, ZSTD_dlm_byRef, ZSTD_dct_auto);
+}
+
+size_t ZSTD_DCtx_loadDictionary(ZSTD_DCtx* dctx, const void* dict, size_t dictSize)
+{
+ return ZSTD_DCtx_loadDictionary_advanced(dctx, dict, dictSize, ZSTD_dlm_byCopy, ZSTD_dct_auto);
+}
+
+size_t ZSTD_DCtx_refPrefix_advanced(ZSTD_DCtx* dctx, const void* prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType)
+{
+ FORWARD_IF_ERROR(ZSTD_DCtx_loadDictionary_advanced(dctx, prefix, prefixSize, ZSTD_dlm_byRef, dictContentType), "");
+ dctx->dictUses = ZSTD_use_once;
+ return 0;
+}
+
+size_t ZSTD_DCtx_refPrefix(ZSTD_DCtx* dctx, const void* prefix, size_t prefixSize)
+{
+ return ZSTD_DCtx_refPrefix_advanced(dctx, prefix, prefixSize, ZSTD_dct_rawContent);
+}
+
+
+/* ZSTD_initDStream_usingDict() :
+ * return : expected size, aka ZSTD_startingInputLength().
+ * this function cannot fail */
+size_t ZSTD_initDStream_usingDict(ZSTD_DStream* zds, const void* dict, size_t dictSize)
+{
+ DEBUGLOG(4, "ZSTD_initDStream_usingDict");
+ FORWARD_IF_ERROR( ZSTD_DCtx_reset(zds, ZSTD_reset_session_only) , "");
+ FORWARD_IF_ERROR( ZSTD_DCtx_loadDictionary(zds, dict, dictSize) , "");
+ return ZSTD_startingInputLength(zds->format);
+}
+
+/* note : this variant can't fail */
+size_t ZSTD_initDStream(ZSTD_DStream* zds)
+{
+ DEBUGLOG(4, "ZSTD_initDStream");
+ FORWARD_IF_ERROR(ZSTD_DCtx_reset(zds, ZSTD_reset_session_only), "");
+ FORWARD_IF_ERROR(ZSTD_DCtx_refDDict(zds, NULL), "");
+ return ZSTD_startingInputLength(zds->format);
+}
+
+/* ZSTD_initDStream_usingDDict() :
+ * ddict will just be referenced, and must outlive decompression session
+ * this function cannot fail */
+size_t ZSTD_initDStream_usingDDict(ZSTD_DStream* dctx, const ZSTD_DDict* ddict)
+{
+ DEBUGLOG(4, "ZSTD_initDStream_usingDDict");
+ FORWARD_IF_ERROR( ZSTD_DCtx_reset(dctx, ZSTD_reset_session_only) , "");
+ FORWARD_IF_ERROR( ZSTD_DCtx_refDDict(dctx, ddict) , "");
+ return ZSTD_startingInputLength(dctx->format);
+}
+
+/* ZSTD_resetDStream() :
+ * return : expected size, aka ZSTD_startingInputLength().
+ * this function cannot fail */
+size_t ZSTD_resetDStream(ZSTD_DStream* dctx)
+{
+ DEBUGLOG(4, "ZSTD_resetDStream");
+ FORWARD_IF_ERROR(ZSTD_DCtx_reset(dctx, ZSTD_reset_session_only), "");
+ return ZSTD_startingInputLength(dctx->format);
+}
+
+
+size_t ZSTD_DCtx_refDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict)
+{
+ RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, "");
+ ZSTD_clearDict(dctx);
+ if (ddict) {
+ dctx->ddict = ddict;
+ dctx->dictUses = ZSTD_use_indefinitely;
+ if (dctx->refMultipleDDicts == ZSTD_rmd_refMultipleDDicts) {
+ if (dctx->ddictSet == NULL) {
+ dctx->ddictSet = ZSTD_createDDictHashSet(dctx->customMem);
+ if (!dctx->ddictSet) {
+ RETURN_ERROR(memory_allocation, "Failed to allocate memory for hash set!");
+ }
+ }
+ assert(!dctx->staticSize); /* Impossible: ddictSet cannot have been allocated if static dctx */
+ FORWARD_IF_ERROR(ZSTD_DDictHashSet_addDDict(dctx->ddictSet, ddict, dctx->customMem), "");
+ }
+ }
+ return 0;
+}
+
+/* ZSTD_DCtx_setMaxWindowSize() :
+ * note : no direct equivalence in ZSTD_DCtx_setParameter,
+ * since this version sets windowSize, and the other sets windowLog */
+size_t ZSTD_DCtx_setMaxWindowSize(ZSTD_DCtx* dctx, size_t maxWindowSize)
+{
+ ZSTD_bounds const bounds = ZSTD_dParam_getBounds(ZSTD_d_windowLogMax);
+ size_t const min = (size_t)1 << bounds.lowerBound;
+ size_t const max = (size_t)1 << bounds.upperBound;
+ RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, "");
+ RETURN_ERROR_IF(maxWindowSize < min, parameter_outOfBound, "");
+ RETURN_ERROR_IF(maxWindowSize > max, parameter_outOfBound, "");
+ dctx->maxWindowSize = maxWindowSize;
+ return 0;
+}
+
+size_t ZSTD_DCtx_setFormat(ZSTD_DCtx* dctx, ZSTD_format_e format)
+{
+ return ZSTD_DCtx_setParameter(dctx, ZSTD_d_format, (int)format);
+}
+
+ZSTD_bounds ZSTD_dParam_getBounds(ZSTD_dParameter dParam)
+{
+ ZSTD_bounds bounds = { 0, 0, 0 };
+ switch(dParam) {
+ case ZSTD_d_windowLogMax:
+ bounds.lowerBound = ZSTD_WINDOWLOG_ABSOLUTEMIN;
+ bounds.upperBound = ZSTD_WINDOWLOG_MAX;
+ return bounds;
+ case ZSTD_d_format:
+ bounds.lowerBound = (int)ZSTD_f_zstd1;
+ bounds.upperBound = (int)ZSTD_f_zstd1_magicless;
+ ZSTD_STATIC_ASSERT(ZSTD_f_zstd1 < ZSTD_f_zstd1_magicless);
+ return bounds;
+ case ZSTD_d_stableOutBuffer:
+ bounds.lowerBound = (int)ZSTD_bm_buffered;
+ bounds.upperBound = (int)ZSTD_bm_stable;
+ return bounds;
+ case ZSTD_d_forceIgnoreChecksum:
+ bounds.lowerBound = (int)ZSTD_d_validateChecksum;
+ bounds.upperBound = (int)ZSTD_d_ignoreChecksum;
+ return bounds;
+ case ZSTD_d_refMultipleDDicts:
+ bounds.lowerBound = (int)ZSTD_rmd_refSingleDDict;
+ bounds.upperBound = (int)ZSTD_rmd_refMultipleDDicts;
+ return bounds;
+ case ZSTD_d_disableHuffmanAssembly:
+ bounds.lowerBound = 0;
+ bounds.upperBound = 1;
+ return bounds;
+ case ZSTD_d_maxBlockSize:
+ bounds.lowerBound = ZSTD_BLOCKSIZE_MAX_MIN;
+ bounds.upperBound = ZSTD_BLOCKSIZE_MAX;
+ return bounds;
+
+ default:;
+ }
+ bounds.error = ERROR(parameter_unsupported);
+ return bounds;
+}
+
+/* ZSTD_dParam_withinBounds:
+ * @return 1 if value is within dParam bounds,
+ * 0 otherwise */
+static int ZSTD_dParam_withinBounds(ZSTD_dParameter dParam, int value)
+{
+ ZSTD_bounds const bounds = ZSTD_dParam_getBounds(dParam);
+ if (ZSTD_isError(bounds.error)) return 0;
+ if (value < bounds.lowerBound) return 0;
+ if (value > bounds.upperBound) return 0;
+ return 1;
+}
+
+#define CHECK_DBOUNDS(p,v) { \
+ RETURN_ERROR_IF(!ZSTD_dParam_withinBounds(p, v), parameter_outOfBound, ""); \
+}
+
+size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value)
+{
+ switch (param) {
+ case ZSTD_d_windowLogMax:
+ *value = (int)ZSTD_highbit32((U32)dctx->maxWindowSize);
+ return 0;
+ case ZSTD_d_format:
+ *value = (int)dctx->format;
+ return 0;
+ case ZSTD_d_stableOutBuffer:
+ *value = (int)dctx->outBufferMode;
+ return 0;
+ case ZSTD_d_forceIgnoreChecksum:
+ *value = (int)dctx->forceIgnoreChecksum;
+ return 0;
+ case ZSTD_d_refMultipleDDicts:
+ *value = (int)dctx->refMultipleDDicts;
+ return 0;
+ case ZSTD_d_disableHuffmanAssembly:
+ *value = (int)dctx->disableHufAsm;
+ return 0;
+ case ZSTD_d_maxBlockSize:
+ *value = dctx->maxBlockSizeParam;
+ return 0;
+ default:;
+ }
+ RETURN_ERROR(parameter_unsupported, "");
+}
+
+size_t ZSTD_DCtx_setParameter(ZSTD_DCtx* dctx, ZSTD_dParameter dParam, int value)
+{
+ RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, "");
+ switch(dParam) {
+ case ZSTD_d_windowLogMax:
+ if (value == 0) value = ZSTD_WINDOWLOG_LIMIT_DEFAULT;
+ CHECK_DBOUNDS(ZSTD_d_windowLogMax, value);
+ dctx->maxWindowSize = ((size_t)1) << value;
+ return 0;
+ case ZSTD_d_format:
+ CHECK_DBOUNDS(ZSTD_d_format, value);
+ dctx->format = (ZSTD_format_e)value;
+ return 0;
+ case ZSTD_d_stableOutBuffer:
+ CHECK_DBOUNDS(ZSTD_d_stableOutBuffer, value);
+ dctx->outBufferMode = (ZSTD_bufferMode_e)value;
+ return 0;
+ case ZSTD_d_forceIgnoreChecksum:
+ CHECK_DBOUNDS(ZSTD_d_forceIgnoreChecksum, value);
+ dctx->forceIgnoreChecksum = (ZSTD_forceIgnoreChecksum_e)value;
+ return 0;
+ case ZSTD_d_refMultipleDDicts:
+ CHECK_DBOUNDS(ZSTD_d_refMultipleDDicts, value);
+ if (dctx->staticSize != 0) {
+ RETURN_ERROR(parameter_unsupported, "Static dctx does not support multiple DDicts!");
+ }
+ dctx->refMultipleDDicts = (ZSTD_refMultipleDDicts_e)value;
+ return 0;
+ case ZSTD_d_disableHuffmanAssembly:
+ CHECK_DBOUNDS(ZSTD_d_disableHuffmanAssembly, value);
+ dctx->disableHufAsm = value != 0;
+ return 0;
+ case ZSTD_d_maxBlockSize:
+ if (value != 0) CHECK_DBOUNDS(ZSTD_d_maxBlockSize, value);
+ dctx->maxBlockSizeParam = value;
+ return 0;
+ default:;
+ }
+ RETURN_ERROR(parameter_unsupported, "");
+}
+
+size_t ZSTD_DCtx_reset(ZSTD_DCtx* dctx, ZSTD_ResetDirective reset)
+{
+ if ( (reset == ZSTD_reset_session_only)
+ || (reset == ZSTD_reset_session_and_parameters) ) {
+ dctx->streamStage = zdss_init;
+ dctx->noForwardProgress = 0;
+ dctx->isFrameDecompression = 1;
+ }
+ if ( (reset == ZSTD_reset_parameters)
+ || (reset == ZSTD_reset_session_and_parameters) ) {
+ RETURN_ERROR_IF(dctx->streamStage != zdss_init, stage_wrong, "");
+ ZSTD_clearDict(dctx);
+ ZSTD_DCtx_resetParameters(dctx);
+ }
+ return 0;
+}
+
+
+size_t ZSTD_sizeof_DStream(const ZSTD_DStream* dctx)
+{
+ return ZSTD_sizeof_DCtx(dctx);
+}
+
+static size_t ZSTD_decodingBufferSize_internal(unsigned long long windowSize, unsigned long long frameContentSize, size_t blockSizeMax)
+{
+ size_t const blockSize = MIN((size_t)MIN(windowSize, ZSTD_BLOCKSIZE_MAX), blockSizeMax);
+ /* We need blockSize + WILDCOPY_OVERLENGTH worth of buffer so that if a block
+ * ends at windowSize + WILDCOPY_OVERLENGTH + 1 bytes, we can start writing
+ * the block at the beginning of the output buffer, and maintain a full window.
+ *
+ * We need another blockSize worth of buffer so that we can store split
+ * literals at the end of the block without overwriting the extDict window.
+ */
+ unsigned long long const neededRBSize = windowSize + (blockSize * 2) + (WILDCOPY_OVERLENGTH * 2);
+ unsigned long long const neededSize = MIN(frameContentSize, neededRBSize);
+ size_t const minRBSize = (size_t) neededSize;
+ RETURN_ERROR_IF((unsigned long long)minRBSize != neededSize,
+ frameParameter_windowTooLarge, "");
+ return minRBSize;
+}
+
+size_t ZSTD_decodingBufferSize_min(unsigned long long windowSize, unsigned long long frameContentSize)
+{
+ return ZSTD_decodingBufferSize_internal(windowSize, frameContentSize, ZSTD_BLOCKSIZE_MAX);
+}
+
+size_t ZSTD_estimateDStreamSize(size_t windowSize)
+{
+ size_t const blockSize = MIN(windowSize, ZSTD_BLOCKSIZE_MAX);
+ size_t const inBuffSize = blockSize; /* no block can be larger */
+ size_t const outBuffSize = ZSTD_decodingBufferSize_min(windowSize, ZSTD_CONTENTSIZE_UNKNOWN);
+ return ZSTD_estimateDCtxSize() + inBuffSize + outBuffSize;
+}
+
+size_t ZSTD_estimateDStreamSize_fromFrame(const void* src, size_t srcSize)
+{
+ U32 const windowSizeMax = 1U << ZSTD_WINDOWLOG_MAX; /* note : should be user-selectable, but requires an additional parameter (or a dctx) */
+ ZSTD_frameHeader zfh;
+ size_t const err = ZSTD_getFrameHeader(&zfh, src, srcSize);
+ if (ZSTD_isError(err)) return err;
+ RETURN_ERROR_IF(err>0, srcSize_wrong, "");
+ RETURN_ERROR_IF(zfh.windowSize > windowSizeMax,
+ frameParameter_windowTooLarge, "");
+ return ZSTD_estimateDStreamSize((size_t)zfh.windowSize);
+}
+
+
+/* ***** Decompression ***** */
+
+static int ZSTD_DCtx_isOverflow(ZSTD_DStream* zds, size_t const neededInBuffSize, size_t const neededOutBuffSize)
+{
+ return (zds->inBuffSize + zds->outBuffSize) >= (neededInBuffSize + neededOutBuffSize) * ZSTD_WORKSPACETOOLARGE_FACTOR;
+}
+
+static void ZSTD_DCtx_updateOversizedDuration(ZSTD_DStream* zds, size_t const neededInBuffSize, size_t const neededOutBuffSize)
+{
+ if (ZSTD_DCtx_isOverflow(zds, neededInBuffSize, neededOutBuffSize))
+ zds->oversizedDuration++;
+ else
+ zds->oversizedDuration = 0;
+}
+
+static int ZSTD_DCtx_isOversizedTooLong(ZSTD_DStream* zds)
+{
+ return zds->oversizedDuration >= ZSTD_WORKSPACETOOLARGE_MAXDURATION;
+}
+
+/* Checks that the output buffer hasn't changed if ZSTD_obm_stable is used. */
+static size_t ZSTD_checkOutBuffer(ZSTD_DStream const* zds, ZSTD_outBuffer const* output)
+{
+ ZSTD_outBuffer const expect = zds->expectedOutBuffer;
+ /* No requirement when ZSTD_obm_stable is not enabled. */
+ if (zds->outBufferMode != ZSTD_bm_stable)
+ return 0;
+ /* Any buffer is allowed in zdss_init, this must be the same for every other call until
+ * the context is reset.
+ */
+ if (zds->streamStage == zdss_init)
+ return 0;
+ /* The buffer must match our expectation exactly. */
+ if (expect.dst == output->dst && expect.pos == output->pos && expect.size == output->size)
+ return 0;
+ RETURN_ERROR(dstBuffer_wrong, "ZSTD_d_stableOutBuffer enabled but output differs!");
+}
+
+/* Calls ZSTD_decompressContinue() with the right parameters for ZSTD_decompressStream()
+ * and updates the stage and the output buffer state. This call is extracted so it can be
+ * used both when reading directly from the ZSTD_inBuffer, and in buffered input mode.
+ * NOTE: You must break after calling this function since the streamStage is modified.
+ */
+static size_t ZSTD_decompressContinueStream(
+ ZSTD_DStream* zds, char** op, char* oend,
+ void const* src, size_t srcSize) {
+ int const isSkipFrame = ZSTD_isSkipFrame(zds);
+ if (zds->outBufferMode == ZSTD_bm_buffered) {
+ size_t const dstSize = isSkipFrame ? 0 : zds->outBuffSize - zds->outStart;
+ size_t const decodedSize = ZSTD_decompressContinue(zds,
+ zds->outBuff + zds->outStart, dstSize, src, srcSize);
+ FORWARD_IF_ERROR(decodedSize, "");
+ if (!decodedSize && !isSkipFrame) {
+ zds->streamStage = zdss_read;
+ } else {
+ zds->outEnd = zds->outStart + decodedSize;
+ zds->streamStage = zdss_flush;
+ }
+ } else {
+ /* Write directly into the output buffer */
+ size_t const dstSize = isSkipFrame ? 0 : (size_t)(oend - *op);
+ size_t const decodedSize = ZSTD_decompressContinue(zds, *op, dstSize, src, srcSize);
+ FORWARD_IF_ERROR(decodedSize, "");
+ *op += decodedSize;
+ /* Flushing is not needed. */
+ zds->streamStage = zdss_read;
+ assert(*op <= oend);
+ assert(zds->outBufferMode == ZSTD_bm_stable);
+ }
+ return 0;
+}
+
+size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inBuffer* input)
+{
+ const char* const src = (const char*)input->src;
+ const char* const istart = input->pos != 0 ? src + input->pos : src;
+ const char* const iend = input->size != 0 ? src + input->size : src;
+ const char* ip = istart;
+ char* const dst = (char*)output->dst;
+ char* const ostart = output->pos != 0 ? dst + output->pos : dst;
+ char* const oend = output->size != 0 ? dst + output->size : dst;
+ char* op = ostart;
+ U32 someMoreWork = 1;
+
+ DEBUGLOG(5, "ZSTD_decompressStream");
+ RETURN_ERROR_IF(
+ input->pos > input->size,
+ srcSize_wrong,
+ "forbidden. in: pos: %u vs size: %u",
+ (U32)input->pos, (U32)input->size);
+ RETURN_ERROR_IF(
+ output->pos > output->size,
+ dstSize_tooSmall,
+ "forbidden. out: pos: %u vs size: %u",
+ (U32)output->pos, (U32)output->size);
+ DEBUGLOG(5, "input size : %u", (U32)(input->size - input->pos));
+ FORWARD_IF_ERROR(ZSTD_checkOutBuffer(zds, output), "");
+
+ while (someMoreWork) {
+ switch(zds->streamStage)
+ {
+ case zdss_init :
+ DEBUGLOG(5, "stage zdss_init => transparent reset ");
+ zds->streamStage = zdss_loadHeader;
+ zds->lhSize = zds->inPos = zds->outStart = zds->outEnd = 0;
+#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1)
+ zds->legacyVersion = 0;
+#endif
+ zds->hostageByte = 0;
+ zds->expectedOutBuffer = *output;
+ ZSTD_FALLTHROUGH;
+
+ case zdss_loadHeader :
+ DEBUGLOG(5, "stage zdss_loadHeader (srcSize : %u)", (U32)(iend - ip));
+#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1)
+ if (zds->legacyVersion) {
+ RETURN_ERROR_IF(zds->staticSize, memory_allocation,
+ "legacy support is incompatible with static dctx");
+ { size_t const hint = ZSTD_decompressLegacyStream(zds->legacyContext, zds->legacyVersion, output, input);
+ if (hint==0) zds->streamStage = zdss_init;
+ return hint;
+ } }
+#endif
+ { size_t const hSize = ZSTD_getFrameHeader_advanced(&zds->fParams, zds->headerBuffer, zds->lhSize, zds->format);
+ if (zds->refMultipleDDicts && zds->ddictSet) {
+ ZSTD_DCtx_selectFrameDDict(zds);
+ }
+ if (ZSTD_isError(hSize)) {
+#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1)
+ U32 const legacyVersion = ZSTD_isLegacy(istart, iend-istart);
+ if (legacyVersion) {
+ ZSTD_DDict const* const ddict = ZSTD_getDDict(zds);
+ const void* const dict = ddict ? ZSTD_DDict_dictContent(ddict) : NULL;
+ size_t const dictSize = ddict ? ZSTD_DDict_dictSize(ddict) : 0;
+ DEBUGLOG(5, "ZSTD_decompressStream: detected legacy version v0.%u", legacyVersion);
+ RETURN_ERROR_IF(zds->staticSize, memory_allocation,
+ "legacy support is incompatible with static dctx");
+ FORWARD_IF_ERROR(ZSTD_initLegacyStream(&zds->legacyContext,
+ zds->previousLegacyVersion, legacyVersion,
+ dict, dictSize), "");
+ zds->legacyVersion = zds->previousLegacyVersion = legacyVersion;
+ { size_t const hint = ZSTD_decompressLegacyStream(zds->legacyContext, legacyVersion, output, input);
+ if (hint==0) zds->streamStage = zdss_init; /* or stay in stage zdss_loadHeader */
+ return hint;
+ } }
+#endif
+ return hSize; /* error */
+ }
+ if (hSize != 0) { /* need more input */
+ size_t const toLoad = hSize - zds->lhSize; /* if hSize!=0, hSize > zds->lhSize */
+ size_t const remainingInput = (size_t)(iend-ip);
+ assert(iend >= ip);
+ if (toLoad > remainingInput) { /* not enough input to load full header */
+ if (remainingInput > 0) {
+ ZSTD_memcpy(zds->headerBuffer + zds->lhSize, ip, remainingInput);
+ zds->lhSize += remainingInput;
+ }
+ input->pos = input->size;
+ /* check first few bytes */
+ FORWARD_IF_ERROR(
+ ZSTD_getFrameHeader_advanced(&zds->fParams, zds->headerBuffer, zds->lhSize, zds->format),
+ "First few bytes detected incorrect" );
+ /* return hint input size */
+ return (MAX((size_t)ZSTD_FRAMEHEADERSIZE_MIN(zds->format), hSize) - zds->lhSize) + ZSTD_blockHeaderSize; /* remaining header bytes + next block header */
+ }
+ assert(ip != NULL);
+ ZSTD_memcpy(zds->headerBuffer + zds->lhSize, ip, toLoad); zds->lhSize = hSize; ip += toLoad;
+ break;
+ } }
+
+ /* check for single-pass mode opportunity */
+ if (zds->fParams.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN
+ && zds->fParams.frameType != ZSTD_skippableFrame
+ && (U64)(size_t)(oend-op) >= zds->fParams.frameContentSize) {
+ size_t const cSize = ZSTD_findFrameCompressedSize_advanced(istart, (size_t)(iend-istart), zds->format);
+ if (cSize <= (size_t)(iend-istart)) {
+ /* shortcut : using single-pass mode */
+ size_t const decompressedSize = ZSTD_decompress_usingDDict(zds, op, (size_t)(oend-op), istart, cSize, ZSTD_getDDict(zds));
+ if (ZSTD_isError(decompressedSize)) return decompressedSize;
+ DEBUGLOG(4, "shortcut to single-pass ZSTD_decompress_usingDDict()");
+ assert(istart != NULL);
+ ip = istart + cSize;
+ op = op ? op + decompressedSize : op; /* can occur if frameContentSize = 0 (empty frame) */
+ zds->expected = 0;
+ zds->streamStage = zdss_init;
+ someMoreWork = 0;
+ break;
+ } }
+
+ /* Check output buffer is large enough for ZSTD_odm_stable. */
+ if (zds->outBufferMode == ZSTD_bm_stable
+ && zds->fParams.frameType != ZSTD_skippableFrame
+ && zds->fParams.frameContentSize != ZSTD_CONTENTSIZE_UNKNOWN
+ && (U64)(size_t)(oend-op) < zds->fParams.frameContentSize) {
+ RETURN_ERROR(dstSize_tooSmall, "ZSTD_obm_stable passed but ZSTD_outBuffer is too small");
+ }
+
+ /* Consume header (see ZSTDds_decodeFrameHeader) */
+ DEBUGLOG(4, "Consume header");
+ FORWARD_IF_ERROR(ZSTD_decompressBegin_usingDDict(zds, ZSTD_getDDict(zds)), "");
+
+ if (zds->format == ZSTD_f_zstd1
+ && (MEM_readLE32(zds->headerBuffer) & ZSTD_MAGIC_SKIPPABLE_MASK) == ZSTD_MAGIC_SKIPPABLE_START) { /* skippable frame */
+ zds->expected = MEM_readLE32(zds->headerBuffer + ZSTD_FRAMEIDSIZE);
+ zds->stage = ZSTDds_skipFrame;
+ } else {
+ FORWARD_IF_ERROR(ZSTD_decodeFrameHeader(zds, zds->headerBuffer, zds->lhSize), "");
+ zds->expected = ZSTD_blockHeaderSize;
+ zds->stage = ZSTDds_decodeBlockHeader;
+ }
+
+ /* control buffer memory usage */
+ DEBUGLOG(4, "Control max memory usage (%u KB <= max %u KB)",
+ (U32)(zds->fParams.windowSize >>10),
+ (U32)(zds->maxWindowSize >> 10) );
+ zds->fParams.windowSize = MAX(zds->fParams.windowSize, 1U << ZSTD_WINDOWLOG_ABSOLUTEMIN);
+ RETURN_ERROR_IF(zds->fParams.windowSize > zds->maxWindowSize,
+ frameParameter_windowTooLarge, "");
+ if (zds->maxBlockSizeParam != 0)
+ zds->fParams.blockSizeMax = MIN(zds->fParams.blockSizeMax, (unsigned)zds->maxBlockSizeParam);
+
+ /* Adapt buffer sizes to frame header instructions */
+ { size_t const neededInBuffSize = MAX(zds->fParams.blockSizeMax, 4 /* frame checksum */);
+ size_t const neededOutBuffSize = zds->outBufferMode == ZSTD_bm_buffered
+ ? ZSTD_decodingBufferSize_internal(zds->fParams.windowSize, zds->fParams.frameContentSize, zds->fParams.blockSizeMax)
+ : 0;
+
+ ZSTD_DCtx_updateOversizedDuration(zds, neededInBuffSize, neededOutBuffSize);
+
+ { int const tooSmall = (zds->inBuffSize < neededInBuffSize) || (zds->outBuffSize < neededOutBuffSize);
+ int const tooLarge = ZSTD_DCtx_isOversizedTooLong(zds);
+
+ if (tooSmall || tooLarge) {
+ size_t const bufferSize = neededInBuffSize + neededOutBuffSize;
+ DEBUGLOG(4, "inBuff : from %u to %u",
+ (U32)zds->inBuffSize, (U32)neededInBuffSize);
+ DEBUGLOG(4, "outBuff : from %u to %u",
+ (U32)zds->outBuffSize, (U32)neededOutBuffSize);
+ if (zds->staticSize) { /* static DCtx */
+ DEBUGLOG(4, "staticSize : %u", (U32)zds->staticSize);
+ assert(zds->staticSize >= sizeof(ZSTD_DCtx)); /* controlled at init */
+ RETURN_ERROR_IF(
+ bufferSize > zds->staticSize - sizeof(ZSTD_DCtx),
+ memory_allocation, "");
+ } else {
+ ZSTD_customFree(zds->inBuff, zds->customMem);
+ zds->inBuffSize = 0;
+ zds->outBuffSize = 0;
+ zds->inBuff = (char*)ZSTD_customMalloc(bufferSize, zds->customMem);
+ RETURN_ERROR_IF(zds->inBuff == NULL, memory_allocation, "");
+ }
+ zds->inBuffSize = neededInBuffSize;
+ zds->outBuff = zds->inBuff + zds->inBuffSize;
+ zds->outBuffSize = neededOutBuffSize;
+ } } }
+ zds->streamStage = zdss_read;
+ ZSTD_FALLTHROUGH;
+
+ case zdss_read:
+ DEBUGLOG(5, "stage zdss_read");
+ { size_t const neededInSize = ZSTD_nextSrcSizeToDecompressWithInputSize(zds, (size_t)(iend - ip));
+ DEBUGLOG(5, "neededInSize = %u", (U32)neededInSize);
+ if (neededInSize==0) { /* end of frame */
+ zds->streamStage = zdss_init;
+ someMoreWork = 0;
+ break;
+ }
+ if ((size_t)(iend-ip) >= neededInSize) { /* decode directly from src */
+ FORWARD_IF_ERROR(ZSTD_decompressContinueStream(zds, &op, oend, ip, neededInSize), "");
+ assert(ip != NULL);
+ ip += neededInSize;
+ /* Function modifies the stage so we must break */
+ break;
+ } }
+ if (ip==iend) { someMoreWork = 0; break; } /* no more input */
+ zds->streamStage = zdss_load;
+ ZSTD_FALLTHROUGH;
+
+ case zdss_load:
+ { size_t const neededInSize = ZSTD_nextSrcSizeToDecompress(zds);
+ size_t const toLoad = neededInSize - zds->inPos;
+ int const isSkipFrame = ZSTD_isSkipFrame(zds);
+ size_t loadedSize;
+ /* At this point we shouldn't be decompressing a block that we can stream. */
+ assert(neededInSize == ZSTD_nextSrcSizeToDecompressWithInputSize(zds, (size_t)(iend - ip)));
+ if (isSkipFrame) {
+ loadedSize = MIN(toLoad, (size_t)(iend-ip));
+ } else {
+ RETURN_ERROR_IF(toLoad > zds->inBuffSize - zds->inPos,
+ corruption_detected,
+ "should never happen");
+ loadedSize = ZSTD_limitCopy(zds->inBuff + zds->inPos, toLoad, ip, (size_t)(iend-ip));
+ }
+ if (loadedSize != 0) {
+ /* ip may be NULL */
+ ip += loadedSize;
+ zds->inPos += loadedSize;
+ }
+ if (loadedSize < toLoad) { someMoreWork = 0; break; } /* not enough input, wait for more */
+
+ /* decode loaded input */
+ zds->inPos = 0; /* input is consumed */
+ FORWARD_IF_ERROR(ZSTD_decompressContinueStream(zds, &op, oend, zds->inBuff, neededInSize), "");
+ /* Function modifies the stage so we must break */
+ break;
+ }
+ case zdss_flush:
+ {
+ size_t const toFlushSize = zds->outEnd - zds->outStart;
+ size_t const flushedSize = ZSTD_limitCopy(op, (size_t)(oend-op), zds->outBuff + zds->outStart, toFlushSize);
+
+ op = op ? op + flushedSize : op;
+
+ zds->outStart += flushedSize;
+ if (flushedSize == toFlushSize) { /* flush completed */
+ zds->streamStage = zdss_read;
+ if ( (zds->outBuffSize < zds->fParams.frameContentSize)
+ && (zds->outStart + zds->fParams.blockSizeMax > zds->outBuffSize) ) {
+ DEBUGLOG(5, "restart filling outBuff from beginning (left:%i, needed:%u)",
+ (int)(zds->outBuffSize - zds->outStart),
+ (U32)zds->fParams.blockSizeMax);
+ zds->outStart = zds->outEnd = 0;
+ }
+ break;
+ } }
+ /* cannot complete flush */
+ someMoreWork = 0;
+ break;
+
+ default:
+ assert(0); /* impossible */
+ RETURN_ERROR(GENERIC, "impossible to reach"); /* some compilers require default to do something */
+ } }
+
+ /* result */
+ input->pos = (size_t)(ip - (const char*)(input->src));
+ output->pos = (size_t)(op - (char*)(output->dst));
+
+ /* Update the expected output buffer for ZSTD_obm_stable. */
+ zds->expectedOutBuffer = *output;
+
+ if ((ip==istart) && (op==ostart)) { /* no forward progress */
+ zds->noForwardProgress ++;
+ if (zds->noForwardProgress >= ZSTD_NO_FORWARD_PROGRESS_MAX) {
+ RETURN_ERROR_IF(op==oend, noForwardProgress_destFull, "");
+ RETURN_ERROR_IF(ip==iend, noForwardProgress_inputEmpty, "");
+ assert(0);
+ }
+ } else {
+ zds->noForwardProgress = 0;
+ }
+ { size_t nextSrcSizeHint = ZSTD_nextSrcSizeToDecompress(zds);
+ if (!nextSrcSizeHint) { /* frame fully decoded */
+ if (zds->outEnd == zds->outStart) { /* output fully flushed */
+ if (zds->hostageByte) {
+ if (input->pos >= input->size) {
+ /* can't release hostage (not present) */
+ zds->streamStage = zdss_read;
+ return 1;
+ }
+ input->pos++; /* release hostage */
+ } /* zds->hostageByte */
+ return 0;
+ } /* zds->outEnd == zds->outStart */
+ if (!zds->hostageByte) { /* output not fully flushed; keep last byte as hostage; will be released when all output is flushed */
+ input->pos--; /* note : pos > 0, otherwise, impossible to finish reading last block */
+ zds->hostageByte=1;
+ }
+ return 1;
+ } /* nextSrcSizeHint==0 */
+ nextSrcSizeHint += ZSTD_blockHeaderSize * (ZSTD_nextInputType(zds) == ZSTDnit_block); /* preload header of next block */
+ assert(zds->inPos <= nextSrcSizeHint);
+ nextSrcSizeHint -= zds->inPos; /* part already loaded*/
+ return nextSrcSizeHint;
+ }
+}
+
+size_t ZSTD_decompressStream_simpleArgs (
+ ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity, size_t* dstPos,
+ const void* src, size_t srcSize, size_t* srcPos)
+{
+ ZSTD_outBuffer output;
+ ZSTD_inBuffer input;
+ output.dst = dst;
+ output.size = dstCapacity;
+ output.pos = *dstPos;
+ input.src = src;
+ input.size = srcSize;
+ input.pos = *srcPos;
+ { size_t const cErr = ZSTD_decompressStream(dctx, &output, &input);
+ *dstPos = output.pos;
+ *srcPos = input.pos;
+ return cErr;
+ }
+}
diff --git a/third_party/zstd/lib/decompress/zstd_decompress_block.c b/third_party/zstd/lib/decompress/zstd_decompress_block.c
new file mode 100644
index 0000000000..76d7332e88
--- /dev/null
+++ b/third_party/zstd/lib/decompress/zstd_decompress_block.c
@@ -0,0 +1,2215 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+/* zstd_decompress_block :
+ * this module takes care of decompressing _compressed_ block */
+
+/*-*******************************************************
+* Dependencies
+*********************************************************/
+#include "../common/zstd_deps.h" /* ZSTD_memcpy, ZSTD_memmove, ZSTD_memset */
+#include "../common/compiler.h" /* prefetch */
+#include "../common/cpu.h" /* bmi2 */
+#include "../common/mem.h" /* low level memory routines */
+#define FSE_STATIC_LINKING_ONLY
+#include "../common/fse.h"
+#include "../common/huf.h"
+#include "../common/zstd_internal.h"
+#include "zstd_decompress_internal.h" /* ZSTD_DCtx */
+#include "zstd_ddict.h" /* ZSTD_DDictDictContent */
+#include "zstd_decompress_block.h"
+#include "../common/bits.h" /* ZSTD_highbit32 */
+
+/*_*******************************************************
+* Macros
+**********************************************************/
+
+/* These two optional macros force the use one way or another of the two
+ * ZSTD_decompressSequences implementations. You can't force in both directions
+ * at the same time.
+ */
+#if defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) && \
+ defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG)
+#error "Cannot force the use of the short and the long ZSTD_decompressSequences variants!"
+#endif
+
+
+/*_*******************************************************
+* Memory operations
+**********************************************************/
+static void ZSTD_copy4(void* dst, const void* src) { ZSTD_memcpy(dst, src, 4); }
+
+
+/*-*************************************************************
+ * Block decoding
+ ***************************************************************/
+
+static size_t ZSTD_blockSizeMax(ZSTD_DCtx const* dctx)
+{
+ size_t const blockSizeMax = dctx->isFrameDecompression ? dctx->fParams.blockSizeMax : ZSTD_BLOCKSIZE_MAX;
+ assert(blockSizeMax <= ZSTD_BLOCKSIZE_MAX);
+ return blockSizeMax;
+}
+
+/*! ZSTD_getcBlockSize() :
+ * Provides the size of compressed block from block header `src` */
+size_t ZSTD_getcBlockSize(const void* src, size_t srcSize,
+ blockProperties_t* bpPtr)
+{
+ RETURN_ERROR_IF(srcSize < ZSTD_blockHeaderSize, srcSize_wrong, "");
+
+ { U32 const cBlockHeader = MEM_readLE24(src);
+ U32 const cSize = cBlockHeader >> 3;
+ bpPtr->lastBlock = cBlockHeader & 1;
+ bpPtr->blockType = (blockType_e)((cBlockHeader >> 1) & 3);
+ bpPtr->origSize = cSize; /* only useful for RLE */
+ if (bpPtr->blockType == bt_rle) return 1;
+ RETURN_ERROR_IF(bpPtr->blockType == bt_reserved, corruption_detected, "");
+ return cSize;
+ }
+}
+
+/* Allocate buffer for literals, either overlapping current dst, or split between dst and litExtraBuffer, or stored entirely within litExtraBuffer */
+static void ZSTD_allocateLiteralsBuffer(ZSTD_DCtx* dctx, void* const dst, const size_t dstCapacity, const size_t litSize,
+ const streaming_operation streaming, const size_t expectedWriteSize, const unsigned splitImmediately)
+{
+ size_t const blockSizeMax = ZSTD_blockSizeMax(dctx);
+ assert(litSize <= blockSizeMax);
+ assert(dctx->isFrameDecompression || streaming == not_streaming);
+ assert(expectedWriteSize <= blockSizeMax);
+ if (streaming == not_streaming && dstCapacity > blockSizeMax + WILDCOPY_OVERLENGTH + litSize + WILDCOPY_OVERLENGTH) {
+ /* If we aren't streaming, we can just put the literals after the output
+ * of the current block. We don't need to worry about overwriting the
+ * extDict of our window, because it doesn't exist.
+ * So if we have space after the end of the block, just put it there.
+ */
+ dctx->litBuffer = (BYTE*)dst + blockSizeMax + WILDCOPY_OVERLENGTH;
+ dctx->litBufferEnd = dctx->litBuffer + litSize;
+ dctx->litBufferLocation = ZSTD_in_dst;
+ } else if (litSize <= ZSTD_LITBUFFEREXTRASIZE) {
+ /* Literals fit entirely within the extra buffer, put them there to avoid
+ * having to split the literals.
+ */
+ dctx->litBuffer = dctx->litExtraBuffer;
+ dctx->litBufferEnd = dctx->litBuffer + litSize;
+ dctx->litBufferLocation = ZSTD_not_in_dst;
+ } else {
+ assert(blockSizeMax > ZSTD_LITBUFFEREXTRASIZE);
+ /* Literals must be split between the output block and the extra lit
+ * buffer. We fill the extra lit buffer with the tail of the literals,
+ * and put the rest of the literals at the end of the block, with
+ * WILDCOPY_OVERLENGTH of buffer room to allow for overreads.
+ * This MUST not write more than our maxBlockSize beyond dst, because in
+ * streaming mode, that could overwrite part of our extDict window.
+ */
+ if (splitImmediately) {
+ /* won't fit in litExtraBuffer, so it will be split between end of dst and extra buffer */
+ dctx->litBuffer = (BYTE*)dst + expectedWriteSize - litSize + ZSTD_LITBUFFEREXTRASIZE - WILDCOPY_OVERLENGTH;
+ dctx->litBufferEnd = dctx->litBuffer + litSize - ZSTD_LITBUFFEREXTRASIZE;
+ } else {
+ /* initially this will be stored entirely in dst during huffman decoding, it will partially be shifted to litExtraBuffer after */
+ dctx->litBuffer = (BYTE*)dst + expectedWriteSize - litSize;
+ dctx->litBufferEnd = (BYTE*)dst + expectedWriteSize;
+ }
+ dctx->litBufferLocation = ZSTD_split;
+ assert(dctx->litBufferEnd <= (BYTE*)dst + expectedWriteSize);
+ }
+}
+
+/*! ZSTD_decodeLiteralsBlock() :
+ * Where it is possible to do so without being stomped by the output during decompression, the literals block will be stored
+ * in the dstBuffer. If there is room to do so, it will be stored in full in the excess dst space after where the current
+ * block will be output. Otherwise it will be stored at the end of the current dst blockspace, with a small portion being
+ * stored in dctx->litExtraBuffer to help keep it "ahead" of the current output write.
+ *
+ * @return : nb of bytes read from src (< srcSize )
+ * note : symbol not declared but exposed for fullbench */
+static size_t ZSTD_decodeLiteralsBlock(ZSTD_DCtx* dctx,
+ const void* src, size_t srcSize, /* note : srcSize < BLOCKSIZE */
+ void* dst, size_t dstCapacity, const streaming_operation streaming)
+{
+ DEBUGLOG(5, "ZSTD_decodeLiteralsBlock");
+ RETURN_ERROR_IF(srcSize < MIN_CBLOCK_SIZE, corruption_detected, "");
+
+ { const BYTE* const istart = (const BYTE*) src;
+ symbolEncodingType_e const litEncType = (symbolEncodingType_e)(istart[0] & 3);
+ size_t const blockSizeMax = ZSTD_blockSizeMax(dctx);
+
+ switch(litEncType)
+ {
+ case set_repeat:
+ DEBUGLOG(5, "set_repeat flag : re-using stats from previous compressed literals block");
+ RETURN_ERROR_IF(dctx->litEntropy==0, dictionary_corrupted, "");
+ ZSTD_FALLTHROUGH;
+
+ case set_compressed:
+ RETURN_ERROR_IF(srcSize < 5, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 2; here we need up to 5 for case 3");
+ { size_t lhSize, litSize, litCSize;
+ U32 singleStream=0;
+ U32 const lhlCode = (istart[0] >> 2) & 3;
+ U32 const lhc = MEM_readLE32(istart);
+ size_t hufSuccess;
+ size_t expectedWriteSize = MIN(blockSizeMax, dstCapacity);
+ int const flags = 0
+ | (ZSTD_DCtx_get_bmi2(dctx) ? HUF_flags_bmi2 : 0)
+ | (dctx->disableHufAsm ? HUF_flags_disableAsm : 0);
+ switch(lhlCode)
+ {
+ case 0: case 1: default: /* note : default is impossible, since lhlCode into [0..3] */
+ /* 2 - 2 - 10 - 10 */
+ singleStream = !lhlCode;
+ lhSize = 3;
+ litSize = (lhc >> 4) & 0x3FF;
+ litCSize = (lhc >> 14) & 0x3FF;
+ break;
+ case 2:
+ /* 2 - 2 - 14 - 14 */
+ lhSize = 4;
+ litSize = (lhc >> 4) & 0x3FFF;
+ litCSize = lhc >> 18;
+ break;
+ case 3:
+ /* 2 - 2 - 18 - 18 */
+ lhSize = 5;
+ litSize = (lhc >> 4) & 0x3FFFF;
+ litCSize = (lhc >> 22) + ((size_t)istart[4] << 10);
+ break;
+ }
+ RETURN_ERROR_IF(litSize > 0 && dst == NULL, dstSize_tooSmall, "NULL not handled");
+ RETURN_ERROR_IF(litSize > blockSizeMax, corruption_detected, "");
+ if (!singleStream)
+ RETURN_ERROR_IF(litSize < MIN_LITERALS_FOR_4_STREAMS, literals_headerWrong,
+ "Not enough literals (%zu) for the 4-streams mode (min %u)",
+ litSize, MIN_LITERALS_FOR_4_STREAMS);
+ RETURN_ERROR_IF(litCSize + lhSize > srcSize, corruption_detected, "");
+ RETURN_ERROR_IF(expectedWriteSize < litSize , dstSize_tooSmall, "");
+ ZSTD_allocateLiteralsBuffer(dctx, dst, dstCapacity, litSize, streaming, expectedWriteSize, 0);
+
+ /* prefetch huffman table if cold */
+ if (dctx->ddictIsCold && (litSize > 768 /* heuristic */)) {
+ PREFETCH_AREA(dctx->HUFptr, sizeof(dctx->entropy.hufTable));
+ }
+
+ if (litEncType==set_repeat) {
+ if (singleStream) {
+ hufSuccess = HUF_decompress1X_usingDTable(
+ dctx->litBuffer, litSize, istart+lhSize, litCSize,
+ dctx->HUFptr, flags);
+ } else {
+ assert(litSize >= MIN_LITERALS_FOR_4_STREAMS);
+ hufSuccess = HUF_decompress4X_usingDTable(
+ dctx->litBuffer, litSize, istart+lhSize, litCSize,
+ dctx->HUFptr, flags);
+ }
+ } else {
+ if (singleStream) {
+#if defined(HUF_FORCE_DECOMPRESS_X2)
+ hufSuccess = HUF_decompress1X_DCtx_wksp(
+ dctx->entropy.hufTable, dctx->litBuffer, litSize,
+ istart+lhSize, litCSize, dctx->workspace,
+ sizeof(dctx->workspace), flags);
+#else
+ hufSuccess = HUF_decompress1X1_DCtx_wksp(
+ dctx->entropy.hufTable, dctx->litBuffer, litSize,
+ istart+lhSize, litCSize, dctx->workspace,
+ sizeof(dctx->workspace), flags);
+#endif
+ } else {
+ hufSuccess = HUF_decompress4X_hufOnly_wksp(
+ dctx->entropy.hufTable, dctx->litBuffer, litSize,
+ istart+lhSize, litCSize, dctx->workspace,
+ sizeof(dctx->workspace), flags);
+ }
+ }
+ if (dctx->litBufferLocation == ZSTD_split)
+ {
+ assert(litSize > ZSTD_LITBUFFEREXTRASIZE);
+ ZSTD_memcpy(dctx->litExtraBuffer, dctx->litBufferEnd - ZSTD_LITBUFFEREXTRASIZE, ZSTD_LITBUFFEREXTRASIZE);
+ ZSTD_memmove(dctx->litBuffer + ZSTD_LITBUFFEREXTRASIZE - WILDCOPY_OVERLENGTH, dctx->litBuffer, litSize - ZSTD_LITBUFFEREXTRASIZE);
+ dctx->litBuffer += ZSTD_LITBUFFEREXTRASIZE - WILDCOPY_OVERLENGTH;
+ dctx->litBufferEnd -= WILDCOPY_OVERLENGTH;
+ assert(dctx->litBufferEnd <= (BYTE*)dst + blockSizeMax);
+ }
+
+ RETURN_ERROR_IF(HUF_isError(hufSuccess), corruption_detected, "");
+
+ dctx->litPtr = dctx->litBuffer;
+ dctx->litSize = litSize;
+ dctx->litEntropy = 1;
+ if (litEncType==set_compressed) dctx->HUFptr = dctx->entropy.hufTable;
+ return litCSize + lhSize;
+ }
+
+ case set_basic:
+ { size_t litSize, lhSize;
+ U32 const lhlCode = ((istart[0]) >> 2) & 3;
+ size_t expectedWriteSize = MIN(blockSizeMax, dstCapacity);
+ switch(lhlCode)
+ {
+ case 0: case 2: default: /* note : default is impossible, since lhlCode into [0..3] */
+ lhSize = 1;
+ litSize = istart[0] >> 3;
+ break;
+ case 1:
+ lhSize = 2;
+ litSize = MEM_readLE16(istart) >> 4;
+ break;
+ case 3:
+ lhSize = 3;
+ RETURN_ERROR_IF(srcSize<3, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 2; here we need lhSize = 3");
+ litSize = MEM_readLE24(istart) >> 4;
+ break;
+ }
+
+ RETURN_ERROR_IF(litSize > 0 && dst == NULL, dstSize_tooSmall, "NULL not handled");
+ RETURN_ERROR_IF(litSize > blockSizeMax, corruption_detected, "");
+ RETURN_ERROR_IF(expectedWriteSize < litSize, dstSize_tooSmall, "");
+ ZSTD_allocateLiteralsBuffer(dctx, dst, dstCapacity, litSize, streaming, expectedWriteSize, 1);
+ if (lhSize+litSize+WILDCOPY_OVERLENGTH > srcSize) { /* risk reading beyond src buffer with wildcopy */
+ RETURN_ERROR_IF(litSize+lhSize > srcSize, corruption_detected, "");
+ if (dctx->litBufferLocation == ZSTD_split)
+ {
+ ZSTD_memcpy(dctx->litBuffer, istart + lhSize, litSize - ZSTD_LITBUFFEREXTRASIZE);
+ ZSTD_memcpy(dctx->litExtraBuffer, istart + lhSize + litSize - ZSTD_LITBUFFEREXTRASIZE, ZSTD_LITBUFFEREXTRASIZE);
+ }
+ else
+ {
+ ZSTD_memcpy(dctx->litBuffer, istart + lhSize, litSize);
+ }
+ dctx->litPtr = dctx->litBuffer;
+ dctx->litSize = litSize;
+ return lhSize+litSize;
+ }
+ /* direct reference into compressed stream */
+ dctx->litPtr = istart+lhSize;
+ dctx->litSize = litSize;
+ dctx->litBufferEnd = dctx->litPtr + litSize;
+ dctx->litBufferLocation = ZSTD_not_in_dst;
+ return lhSize+litSize;
+ }
+
+ case set_rle:
+ { U32 const lhlCode = ((istart[0]) >> 2) & 3;
+ size_t litSize, lhSize;
+ size_t expectedWriteSize = MIN(blockSizeMax, dstCapacity);
+ switch(lhlCode)
+ {
+ case 0: case 2: default: /* note : default is impossible, since lhlCode into [0..3] */
+ lhSize = 1;
+ litSize = istart[0] >> 3;
+ break;
+ case 1:
+ lhSize = 2;
+ RETURN_ERROR_IF(srcSize<3, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 2; here we need lhSize+1 = 3");
+ litSize = MEM_readLE16(istart) >> 4;
+ break;
+ case 3:
+ lhSize = 3;
+ RETURN_ERROR_IF(srcSize<4, corruption_detected, "srcSize >= MIN_CBLOCK_SIZE == 2; here we need lhSize+1 = 4");
+ litSize = MEM_readLE24(istart) >> 4;
+ break;
+ }
+ RETURN_ERROR_IF(litSize > 0 && dst == NULL, dstSize_tooSmall, "NULL not handled");
+ RETURN_ERROR_IF(litSize > blockSizeMax, corruption_detected, "");
+ RETURN_ERROR_IF(expectedWriteSize < litSize, dstSize_tooSmall, "");
+ ZSTD_allocateLiteralsBuffer(dctx, dst, dstCapacity, litSize, streaming, expectedWriteSize, 1);
+ if (dctx->litBufferLocation == ZSTD_split)
+ {
+ ZSTD_memset(dctx->litBuffer, istart[lhSize], litSize - ZSTD_LITBUFFEREXTRASIZE);
+ ZSTD_memset(dctx->litExtraBuffer, istart[lhSize], ZSTD_LITBUFFEREXTRASIZE);
+ }
+ else
+ {
+ ZSTD_memset(dctx->litBuffer, istart[lhSize], litSize);
+ }
+ dctx->litPtr = dctx->litBuffer;
+ dctx->litSize = litSize;
+ return lhSize+1;
+ }
+ default:
+ RETURN_ERROR(corruption_detected, "impossible");
+ }
+ }
+}
+
+/* Hidden declaration for fullbench */
+size_t ZSTD_decodeLiteralsBlock_wrapper(ZSTD_DCtx* dctx,
+ const void* src, size_t srcSize,
+ void* dst, size_t dstCapacity);
+size_t ZSTD_decodeLiteralsBlock_wrapper(ZSTD_DCtx* dctx,
+ const void* src, size_t srcSize,
+ void* dst, size_t dstCapacity)
+{
+ dctx->isFrameDecompression = 0;
+ return ZSTD_decodeLiteralsBlock(dctx, src, srcSize, dst, dstCapacity, not_streaming);
+}
+
+/* Default FSE distribution tables.
+ * These are pre-calculated FSE decoding tables using default distributions as defined in specification :
+ * https://github.com/facebook/zstd/blob/release/doc/zstd_compression_format.md#default-distributions
+ * They were generated programmatically with following method :
+ * - start from default distributions, present in /lib/common/zstd_internal.h
+ * - generate tables normally, using ZSTD_buildFSETable()
+ * - printout the content of tables
+ * - pretify output, report below, test with fuzzer to ensure it's correct */
+
+/* Default FSE distribution table for Literal Lengths */
+static const ZSTD_seqSymbol LL_defaultDTable[(1<<LL_DEFAULTNORMLOG)+1] = {
+ { 1, 1, 1, LL_DEFAULTNORMLOG}, /* header : fastMode, tableLog */
+ /* nextState, nbAddBits, nbBits, baseVal */
+ { 0, 0, 4, 0}, { 16, 0, 4, 0},
+ { 32, 0, 5, 1}, { 0, 0, 5, 3},
+ { 0, 0, 5, 4}, { 0, 0, 5, 6},
+ { 0, 0, 5, 7}, { 0, 0, 5, 9},
+ { 0, 0, 5, 10}, { 0, 0, 5, 12},
+ { 0, 0, 6, 14}, { 0, 1, 5, 16},
+ { 0, 1, 5, 20}, { 0, 1, 5, 22},
+ { 0, 2, 5, 28}, { 0, 3, 5, 32},
+ { 0, 4, 5, 48}, { 32, 6, 5, 64},
+ { 0, 7, 5, 128}, { 0, 8, 6, 256},
+ { 0, 10, 6, 1024}, { 0, 12, 6, 4096},
+ { 32, 0, 4, 0}, { 0, 0, 4, 1},
+ { 0, 0, 5, 2}, { 32, 0, 5, 4},
+ { 0, 0, 5, 5}, { 32, 0, 5, 7},
+ { 0, 0, 5, 8}, { 32, 0, 5, 10},
+ { 0, 0, 5, 11}, { 0, 0, 6, 13},
+ { 32, 1, 5, 16}, { 0, 1, 5, 18},
+ { 32, 1, 5, 22}, { 0, 2, 5, 24},
+ { 32, 3, 5, 32}, { 0, 3, 5, 40},
+ { 0, 6, 4, 64}, { 16, 6, 4, 64},
+ { 32, 7, 5, 128}, { 0, 9, 6, 512},
+ { 0, 11, 6, 2048}, { 48, 0, 4, 0},
+ { 16, 0, 4, 1}, { 32, 0, 5, 2},
+ { 32, 0, 5, 3}, { 32, 0, 5, 5},
+ { 32, 0, 5, 6}, { 32, 0, 5, 8},
+ { 32, 0, 5, 9}, { 32, 0, 5, 11},
+ { 32, 0, 5, 12}, { 0, 0, 6, 15},
+ { 32, 1, 5, 18}, { 32, 1, 5, 20},
+ { 32, 2, 5, 24}, { 32, 2, 5, 28},
+ { 32, 3, 5, 40}, { 32, 4, 5, 48},
+ { 0, 16, 6,65536}, { 0, 15, 6,32768},
+ { 0, 14, 6,16384}, { 0, 13, 6, 8192},
+}; /* LL_defaultDTable */
+
+/* Default FSE distribution table for Offset Codes */
+static const ZSTD_seqSymbol OF_defaultDTable[(1<<OF_DEFAULTNORMLOG)+1] = {
+ { 1, 1, 1, OF_DEFAULTNORMLOG}, /* header : fastMode, tableLog */
+ /* nextState, nbAddBits, nbBits, baseVal */
+ { 0, 0, 5, 0}, { 0, 6, 4, 61},
+ { 0, 9, 5, 509}, { 0, 15, 5,32765},
+ { 0, 21, 5,2097149}, { 0, 3, 5, 5},
+ { 0, 7, 4, 125}, { 0, 12, 5, 4093},
+ { 0, 18, 5,262141}, { 0, 23, 5,8388605},
+ { 0, 5, 5, 29}, { 0, 8, 4, 253},
+ { 0, 14, 5,16381}, { 0, 20, 5,1048573},
+ { 0, 2, 5, 1}, { 16, 7, 4, 125},
+ { 0, 11, 5, 2045}, { 0, 17, 5,131069},
+ { 0, 22, 5,4194301}, { 0, 4, 5, 13},
+ { 16, 8, 4, 253}, { 0, 13, 5, 8189},
+ { 0, 19, 5,524285}, { 0, 1, 5, 1},
+ { 16, 6, 4, 61}, { 0, 10, 5, 1021},
+ { 0, 16, 5,65533}, { 0, 28, 5,268435453},
+ { 0, 27, 5,134217725}, { 0, 26, 5,67108861},
+ { 0, 25, 5,33554429}, { 0, 24, 5,16777213},
+}; /* OF_defaultDTable */
+
+
+/* Default FSE distribution table for Match Lengths */
+static const ZSTD_seqSymbol ML_defaultDTable[(1<<ML_DEFAULTNORMLOG)+1] = {
+ { 1, 1, 1, ML_DEFAULTNORMLOG}, /* header : fastMode, tableLog */
+ /* nextState, nbAddBits, nbBits, baseVal */
+ { 0, 0, 6, 3}, { 0, 0, 4, 4},
+ { 32, 0, 5, 5}, { 0, 0, 5, 6},
+ { 0, 0, 5, 8}, { 0, 0, 5, 9},
+ { 0, 0, 5, 11}, { 0, 0, 6, 13},
+ { 0, 0, 6, 16}, { 0, 0, 6, 19},
+ { 0, 0, 6, 22}, { 0, 0, 6, 25},
+ { 0, 0, 6, 28}, { 0, 0, 6, 31},
+ { 0, 0, 6, 34}, { 0, 1, 6, 37},
+ { 0, 1, 6, 41}, { 0, 2, 6, 47},
+ { 0, 3, 6, 59}, { 0, 4, 6, 83},
+ { 0, 7, 6, 131}, { 0, 9, 6, 515},
+ { 16, 0, 4, 4}, { 0, 0, 4, 5},
+ { 32, 0, 5, 6}, { 0, 0, 5, 7},
+ { 32, 0, 5, 9}, { 0, 0, 5, 10},
+ { 0, 0, 6, 12}, { 0, 0, 6, 15},
+ { 0, 0, 6, 18}, { 0, 0, 6, 21},
+ { 0, 0, 6, 24}, { 0, 0, 6, 27},
+ { 0, 0, 6, 30}, { 0, 0, 6, 33},
+ { 0, 1, 6, 35}, { 0, 1, 6, 39},
+ { 0, 2, 6, 43}, { 0, 3, 6, 51},
+ { 0, 4, 6, 67}, { 0, 5, 6, 99},
+ { 0, 8, 6, 259}, { 32, 0, 4, 4},
+ { 48, 0, 4, 4}, { 16, 0, 4, 5},
+ { 32, 0, 5, 7}, { 32, 0, 5, 8},
+ { 32, 0, 5, 10}, { 32, 0, 5, 11},
+ { 0, 0, 6, 14}, { 0, 0, 6, 17},
+ { 0, 0, 6, 20}, { 0, 0, 6, 23},
+ { 0, 0, 6, 26}, { 0, 0, 6, 29},
+ { 0, 0, 6, 32}, { 0, 16, 6,65539},
+ { 0, 15, 6,32771}, { 0, 14, 6,16387},
+ { 0, 13, 6, 8195}, { 0, 12, 6, 4099},
+ { 0, 11, 6, 2051}, { 0, 10, 6, 1027},
+}; /* ML_defaultDTable */
+
+
+static void ZSTD_buildSeqTable_rle(ZSTD_seqSymbol* dt, U32 baseValue, U8 nbAddBits)
+{
+ void* ptr = dt;
+ ZSTD_seqSymbol_header* const DTableH = (ZSTD_seqSymbol_header*)ptr;
+ ZSTD_seqSymbol* const cell = dt + 1;
+
+ DTableH->tableLog = 0;
+ DTableH->fastMode = 0;
+
+ cell->nbBits = 0;
+ cell->nextState = 0;
+ assert(nbAddBits < 255);
+ cell->nbAdditionalBits = nbAddBits;
+ cell->baseValue = baseValue;
+}
+
+
+/* ZSTD_buildFSETable() :
+ * generate FSE decoding table for one symbol (ll, ml or off)
+ * cannot fail if input is valid =>
+ * all inputs are presumed validated at this stage */
+FORCE_INLINE_TEMPLATE
+void ZSTD_buildFSETable_body(ZSTD_seqSymbol* dt,
+ const short* normalizedCounter, unsigned maxSymbolValue,
+ const U32* baseValue, const U8* nbAdditionalBits,
+ unsigned tableLog, void* wksp, size_t wkspSize)
+{
+ ZSTD_seqSymbol* const tableDecode = dt+1;
+ U32 const maxSV1 = maxSymbolValue + 1;
+ U32 const tableSize = 1 << tableLog;
+
+ U16* symbolNext = (U16*)wksp;
+ BYTE* spread = (BYTE*)(symbolNext + MaxSeq + 1);
+ U32 highThreshold = tableSize - 1;
+
+
+ /* Sanity Checks */
+ assert(maxSymbolValue <= MaxSeq);
+ assert(tableLog <= MaxFSELog);
+ assert(wkspSize >= ZSTD_BUILD_FSE_TABLE_WKSP_SIZE);
+ (void)wkspSize;
+ /* Init, lay down lowprob symbols */
+ { ZSTD_seqSymbol_header DTableH;
+ DTableH.tableLog = tableLog;
+ DTableH.fastMode = 1;
+ { S16 const largeLimit= (S16)(1 << (tableLog-1));
+ U32 s;
+ for (s=0; s<maxSV1; s++) {
+ if (normalizedCounter[s]==-1) {
+ tableDecode[highThreshold--].baseValue = s;
+ symbolNext[s] = 1;
+ } else {
+ if (normalizedCounter[s] >= largeLimit) DTableH.fastMode=0;
+ assert(normalizedCounter[s]>=0);
+ symbolNext[s] = (U16)normalizedCounter[s];
+ } } }
+ ZSTD_memcpy(dt, &DTableH, sizeof(DTableH));
+ }
+
+ /* Spread symbols */
+ assert(tableSize <= 512);
+ /* Specialized symbol spreading for the case when there are
+ * no low probability (-1 count) symbols. When compressing
+ * small blocks we avoid low probability symbols to hit this
+ * case, since header decoding speed matters more.
+ */
+ if (highThreshold == tableSize - 1) {
+ size_t const tableMask = tableSize-1;
+ size_t const step = FSE_TABLESTEP(tableSize);
+ /* First lay down the symbols in order.
+ * We use a uint64_t to lay down 8 bytes at a time. This reduces branch
+ * misses since small blocks generally have small table logs, so nearly
+ * all symbols have counts <= 8. We ensure we have 8 bytes at the end of
+ * our buffer to handle the over-write.
+ */
+ {
+ U64 const add = 0x0101010101010101ull;
+ size_t pos = 0;
+ U64 sv = 0;
+ U32 s;
+ for (s=0; s<maxSV1; ++s, sv += add) {
+ int i;
+ int const n = normalizedCounter[s];
+ MEM_write64(spread + pos, sv);
+ for (i = 8; i < n; i += 8) {
+ MEM_write64(spread + pos + i, sv);
+ }
+ assert(n>=0);
+ pos += (size_t)n;
+ }
+ }
+ /* Now we spread those positions across the table.
+ * The benefit of doing it in two stages is that we avoid the
+ * variable size inner loop, which caused lots of branch misses.
+ * Now we can run through all the positions without any branch misses.
+ * We unroll the loop twice, since that is what empirically worked best.
+ */
+ {
+ size_t position = 0;
+ size_t s;
+ size_t const unroll = 2;
+ assert(tableSize % unroll == 0); /* FSE_MIN_TABLELOG is 5 */
+ for (s = 0; s < (size_t)tableSize; s += unroll) {
+ size_t u;
+ for (u = 0; u < unroll; ++u) {
+ size_t const uPosition = (position + (u * step)) & tableMask;
+ tableDecode[uPosition].baseValue = spread[s + u];
+ }
+ position = (position + (unroll * step)) & tableMask;
+ }
+ assert(position == 0);
+ }
+ } else {
+ U32 const tableMask = tableSize-1;
+ U32 const step = FSE_TABLESTEP(tableSize);
+ U32 s, position = 0;
+ for (s=0; s<maxSV1; s++) {
+ int i;
+ int const n = normalizedCounter[s];
+ for (i=0; i<n; i++) {
+ tableDecode[position].baseValue = s;
+ position = (position + step) & tableMask;
+ while (UNLIKELY(position > highThreshold)) position = (position + step) & tableMask; /* lowprob area */
+ } }
+ assert(position == 0); /* position must reach all cells once, otherwise normalizedCounter is incorrect */
+ }
+
+ /* Build Decoding table */
+ {
+ U32 u;
+ for (u=0; u<tableSize; u++) {
+ U32 const symbol = tableDecode[u].baseValue;
+ U32 const nextState = symbolNext[symbol]++;
+ tableDecode[u].nbBits = (BYTE) (tableLog - ZSTD_highbit32(nextState) );
+ tableDecode[u].nextState = (U16) ( (nextState << tableDecode[u].nbBits) - tableSize);
+ assert(nbAdditionalBits[symbol] < 255);
+ tableDecode[u].nbAdditionalBits = nbAdditionalBits[symbol];
+ tableDecode[u].baseValue = baseValue[symbol];
+ }
+ }
+}
+
+/* Avoids the FORCE_INLINE of the _body() function. */
+static void ZSTD_buildFSETable_body_default(ZSTD_seqSymbol* dt,
+ const short* normalizedCounter, unsigned maxSymbolValue,
+ const U32* baseValue, const U8* nbAdditionalBits,
+ unsigned tableLog, void* wksp, size_t wkspSize)
+{
+ ZSTD_buildFSETable_body(dt, normalizedCounter, maxSymbolValue,
+ baseValue, nbAdditionalBits, tableLog, wksp, wkspSize);
+}
+
+#if DYNAMIC_BMI2
+BMI2_TARGET_ATTRIBUTE static void ZSTD_buildFSETable_body_bmi2(ZSTD_seqSymbol* dt,
+ const short* normalizedCounter, unsigned maxSymbolValue,
+ const U32* baseValue, const U8* nbAdditionalBits,
+ unsigned tableLog, void* wksp, size_t wkspSize)
+{
+ ZSTD_buildFSETable_body(dt, normalizedCounter, maxSymbolValue,
+ baseValue, nbAdditionalBits, tableLog, wksp, wkspSize);
+}
+#endif
+
+void ZSTD_buildFSETable(ZSTD_seqSymbol* dt,
+ const short* normalizedCounter, unsigned maxSymbolValue,
+ const U32* baseValue, const U8* nbAdditionalBits,
+ unsigned tableLog, void* wksp, size_t wkspSize, int bmi2)
+{
+#if DYNAMIC_BMI2
+ if (bmi2) {
+ ZSTD_buildFSETable_body_bmi2(dt, normalizedCounter, maxSymbolValue,
+ baseValue, nbAdditionalBits, tableLog, wksp, wkspSize);
+ return;
+ }
+#endif
+ (void)bmi2;
+ ZSTD_buildFSETable_body_default(dt, normalizedCounter, maxSymbolValue,
+ baseValue, nbAdditionalBits, tableLog, wksp, wkspSize);
+}
+
+
+/*! ZSTD_buildSeqTable() :
+ * @return : nb bytes read from src,
+ * or an error code if it fails */
+static size_t ZSTD_buildSeqTable(ZSTD_seqSymbol* DTableSpace, const ZSTD_seqSymbol** DTablePtr,
+ symbolEncodingType_e type, unsigned max, U32 maxLog,
+ const void* src, size_t srcSize,
+ const U32* baseValue, const U8* nbAdditionalBits,
+ const ZSTD_seqSymbol* defaultTable, U32 flagRepeatTable,
+ int ddictIsCold, int nbSeq, U32* wksp, size_t wkspSize,
+ int bmi2)
+{
+ switch(type)
+ {
+ case set_rle :
+ RETURN_ERROR_IF(!srcSize, srcSize_wrong, "");
+ RETURN_ERROR_IF((*(const BYTE*)src) > max, corruption_detected, "");
+ { U32 const symbol = *(const BYTE*)src;
+ U32 const baseline = baseValue[symbol];
+ U8 const nbBits = nbAdditionalBits[symbol];
+ ZSTD_buildSeqTable_rle(DTableSpace, baseline, nbBits);
+ }
+ *DTablePtr = DTableSpace;
+ return 1;
+ case set_basic :
+ *DTablePtr = defaultTable;
+ return 0;
+ case set_repeat:
+ RETURN_ERROR_IF(!flagRepeatTable, corruption_detected, "");
+ /* prefetch FSE table if used */
+ if (ddictIsCold && (nbSeq > 24 /* heuristic */)) {
+ const void* const pStart = *DTablePtr;
+ size_t const pSize = sizeof(ZSTD_seqSymbol) * (SEQSYMBOL_TABLE_SIZE(maxLog));
+ PREFETCH_AREA(pStart, pSize);
+ }
+ return 0;
+ case set_compressed :
+ { unsigned tableLog;
+ S16 norm[MaxSeq+1];
+ size_t const headerSize = FSE_readNCount(norm, &max, &tableLog, src, srcSize);
+ RETURN_ERROR_IF(FSE_isError(headerSize), corruption_detected, "");
+ RETURN_ERROR_IF(tableLog > maxLog, corruption_detected, "");
+ ZSTD_buildFSETable(DTableSpace, norm, max, baseValue, nbAdditionalBits, tableLog, wksp, wkspSize, bmi2);
+ *DTablePtr = DTableSpace;
+ return headerSize;
+ }
+ default :
+ assert(0);
+ RETURN_ERROR(GENERIC, "impossible");
+ }
+}
+
+size_t ZSTD_decodeSeqHeaders(ZSTD_DCtx* dctx, int* nbSeqPtr,
+ const void* src, size_t srcSize)
+{
+ const BYTE* const istart = (const BYTE*)src;
+ const BYTE* const iend = istart + srcSize;
+ const BYTE* ip = istart;
+ int nbSeq;
+ DEBUGLOG(5, "ZSTD_decodeSeqHeaders");
+
+ /* check */
+ RETURN_ERROR_IF(srcSize < MIN_SEQUENCES_SIZE, srcSize_wrong, "");
+
+ /* SeqHead */
+ nbSeq = *ip++;
+ if (nbSeq > 0x7F) {
+ if (nbSeq == 0xFF) {
+ RETURN_ERROR_IF(ip+2 > iend, srcSize_wrong, "");
+ nbSeq = MEM_readLE16(ip) + LONGNBSEQ;
+ ip+=2;
+ } else {
+ RETURN_ERROR_IF(ip >= iend, srcSize_wrong, "");
+ nbSeq = ((nbSeq-0x80)<<8) + *ip++;
+ }
+ }
+ *nbSeqPtr = nbSeq;
+
+ if (nbSeq == 0) {
+ /* No sequence : section ends immediately */
+ RETURN_ERROR_IF(ip != iend, corruption_detected,
+ "extraneous data present in the Sequences section");
+ return (size_t)(ip - istart);
+ }
+
+ /* FSE table descriptors */
+ RETURN_ERROR_IF(ip+1 > iend, srcSize_wrong, ""); /* minimum possible size: 1 byte for symbol encoding types */
+ RETURN_ERROR_IF(*ip & 3, corruption_detected, ""); /* The last field, Reserved, must be all-zeroes. */
+ { symbolEncodingType_e const LLtype = (symbolEncodingType_e)(*ip >> 6);
+ symbolEncodingType_e const OFtype = (symbolEncodingType_e)((*ip >> 4) & 3);
+ symbolEncodingType_e const MLtype = (symbolEncodingType_e)((*ip >> 2) & 3);
+ ip++;
+
+ /* Build DTables */
+ { size_t const llhSize = ZSTD_buildSeqTable(dctx->entropy.LLTable, &dctx->LLTptr,
+ LLtype, MaxLL, LLFSELog,
+ ip, iend-ip,
+ LL_base, LL_bits,
+ LL_defaultDTable, dctx->fseEntropy,
+ dctx->ddictIsCold, nbSeq,
+ dctx->workspace, sizeof(dctx->workspace),
+ ZSTD_DCtx_get_bmi2(dctx));
+ RETURN_ERROR_IF(ZSTD_isError(llhSize), corruption_detected, "ZSTD_buildSeqTable failed");
+ ip += llhSize;
+ }
+
+ { size_t const ofhSize = ZSTD_buildSeqTable(dctx->entropy.OFTable, &dctx->OFTptr,
+ OFtype, MaxOff, OffFSELog,
+ ip, iend-ip,
+ OF_base, OF_bits,
+ OF_defaultDTable, dctx->fseEntropy,
+ dctx->ddictIsCold, nbSeq,
+ dctx->workspace, sizeof(dctx->workspace),
+ ZSTD_DCtx_get_bmi2(dctx));
+ RETURN_ERROR_IF(ZSTD_isError(ofhSize), corruption_detected, "ZSTD_buildSeqTable failed");
+ ip += ofhSize;
+ }
+
+ { size_t const mlhSize = ZSTD_buildSeqTable(dctx->entropy.MLTable, &dctx->MLTptr,
+ MLtype, MaxML, MLFSELog,
+ ip, iend-ip,
+ ML_base, ML_bits,
+ ML_defaultDTable, dctx->fseEntropy,
+ dctx->ddictIsCold, nbSeq,
+ dctx->workspace, sizeof(dctx->workspace),
+ ZSTD_DCtx_get_bmi2(dctx));
+ RETURN_ERROR_IF(ZSTD_isError(mlhSize), corruption_detected, "ZSTD_buildSeqTable failed");
+ ip += mlhSize;
+ }
+ }
+
+ return ip-istart;
+}
+
+
+typedef struct {
+ size_t litLength;
+ size_t matchLength;
+ size_t offset;
+} seq_t;
+
+typedef struct {
+ size_t state;
+ const ZSTD_seqSymbol* table;
+} ZSTD_fseState;
+
+typedef struct {
+ BIT_DStream_t DStream;
+ ZSTD_fseState stateLL;
+ ZSTD_fseState stateOffb;
+ ZSTD_fseState stateML;
+ size_t prevOffset[ZSTD_REP_NUM];
+} seqState_t;
+
+/*! ZSTD_overlapCopy8() :
+ * Copies 8 bytes from ip to op and updates op and ip where ip <= op.
+ * If the offset is < 8 then the offset is spread to at least 8 bytes.
+ *
+ * Precondition: *ip <= *op
+ * Postcondition: *op - *op >= 8
+ */
+HINT_INLINE void ZSTD_overlapCopy8(BYTE** op, BYTE const** ip, size_t offset) {
+ assert(*ip <= *op);
+ if (offset < 8) {
+ /* close range match, overlap */
+ static const U32 dec32table[] = { 0, 1, 2, 1, 4, 4, 4, 4 }; /* added */
+ static const int dec64table[] = { 8, 8, 8, 7, 8, 9,10,11 }; /* subtracted */
+ int const sub2 = dec64table[offset];
+ (*op)[0] = (*ip)[0];
+ (*op)[1] = (*ip)[1];
+ (*op)[2] = (*ip)[2];
+ (*op)[3] = (*ip)[3];
+ *ip += dec32table[offset];
+ ZSTD_copy4(*op+4, *ip);
+ *ip -= sub2;
+ } else {
+ ZSTD_copy8(*op, *ip);
+ }
+ *ip += 8;
+ *op += 8;
+ assert(*op - *ip >= 8);
+}
+
+/*! ZSTD_safecopy() :
+ * Specialized version of memcpy() that is allowed to READ up to WILDCOPY_OVERLENGTH past the input buffer
+ * and write up to 16 bytes past oend_w (op >= oend_w is allowed).
+ * This function is only called in the uncommon case where the sequence is near the end of the block. It
+ * should be fast for a single long sequence, but can be slow for several short sequences.
+ *
+ * @param ovtype controls the overlap detection
+ * - ZSTD_no_overlap: The source and destination are guaranteed to be at least WILDCOPY_VECLEN bytes apart.
+ * - ZSTD_overlap_src_before_dst: The src and dst may overlap and may be any distance apart.
+ * The src buffer must be before the dst buffer.
+ */
+static void ZSTD_safecopy(BYTE* op, const BYTE* const oend_w, BYTE const* ip, ptrdiff_t length, ZSTD_overlap_e ovtype) {
+ ptrdiff_t const diff = op - ip;
+ BYTE* const oend = op + length;
+
+ assert((ovtype == ZSTD_no_overlap && (diff <= -8 || diff >= 8 || op >= oend_w)) ||
+ (ovtype == ZSTD_overlap_src_before_dst && diff >= 0));
+
+ if (length < 8) {
+ /* Handle short lengths. */
+ while (op < oend) *op++ = *ip++;
+ return;
+ }
+ if (ovtype == ZSTD_overlap_src_before_dst) {
+ /* Copy 8 bytes and ensure the offset >= 8 when there can be overlap. */
+ assert(length >= 8);
+ ZSTD_overlapCopy8(&op, &ip, diff);
+ length -= 8;
+ assert(op - ip >= 8);
+ assert(op <= oend);
+ }
+
+ if (oend <= oend_w) {
+ /* No risk of overwrite. */
+ ZSTD_wildcopy(op, ip, length, ovtype);
+ return;
+ }
+ if (op <= oend_w) {
+ /* Wildcopy until we get close to the end. */
+ assert(oend > oend_w);
+ ZSTD_wildcopy(op, ip, oend_w - op, ovtype);
+ ip += oend_w - op;
+ op += oend_w - op;
+ }
+ /* Handle the leftovers. */
+ while (op < oend) *op++ = *ip++;
+}
+
+/* ZSTD_safecopyDstBeforeSrc():
+ * This version allows overlap with dst before src, or handles the non-overlap case with dst after src
+ * Kept separate from more common ZSTD_safecopy case to avoid performance impact to the safecopy common case */
+static void ZSTD_safecopyDstBeforeSrc(BYTE* op, const BYTE* ip, ptrdiff_t length) {
+ ptrdiff_t const diff = op - ip;
+ BYTE* const oend = op + length;
+
+ if (length < 8 || diff > -8) {
+ /* Handle short lengths, close overlaps, and dst not before src. */
+ while (op < oend) *op++ = *ip++;
+ return;
+ }
+
+ if (op <= oend - WILDCOPY_OVERLENGTH && diff < -WILDCOPY_VECLEN) {
+ ZSTD_wildcopy(op, ip, oend - WILDCOPY_OVERLENGTH - op, ZSTD_no_overlap);
+ ip += oend - WILDCOPY_OVERLENGTH - op;
+ op += oend - WILDCOPY_OVERLENGTH - op;
+ }
+
+ /* Handle the leftovers. */
+ while (op < oend) *op++ = *ip++;
+}
+
+/* ZSTD_execSequenceEnd():
+ * This version handles cases that are near the end of the output buffer. It requires
+ * more careful checks to make sure there is no overflow. By separating out these hard
+ * and unlikely cases, we can speed up the common cases.
+ *
+ * NOTE: This function needs to be fast for a single long sequence, but doesn't need
+ * to be optimized for many small sequences, since those fall into ZSTD_execSequence().
+ */
+FORCE_NOINLINE
+ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
+size_t ZSTD_execSequenceEnd(BYTE* op,
+ BYTE* const oend, seq_t sequence,
+ const BYTE** litPtr, const BYTE* const litLimit,
+ const BYTE* const prefixStart, const BYTE* const virtualStart, const BYTE* const dictEnd)
+{
+ BYTE* const oLitEnd = op + sequence.litLength;
+ size_t const sequenceLength = sequence.litLength + sequence.matchLength;
+ const BYTE* const iLitEnd = *litPtr + sequence.litLength;
+ const BYTE* match = oLitEnd - sequence.offset;
+ BYTE* const oend_w = oend - WILDCOPY_OVERLENGTH;
+
+ /* bounds checks : careful of address space overflow in 32-bit mode */
+ RETURN_ERROR_IF(sequenceLength > (size_t)(oend - op), dstSize_tooSmall, "last match must fit within dstBuffer");
+ RETURN_ERROR_IF(sequence.litLength > (size_t)(litLimit - *litPtr), corruption_detected, "try to read beyond literal buffer");
+ assert(op < op + sequenceLength);
+ assert(oLitEnd < op + sequenceLength);
+
+ /* copy literals */
+ ZSTD_safecopy(op, oend_w, *litPtr, sequence.litLength, ZSTD_no_overlap);
+ op = oLitEnd;
+ *litPtr = iLitEnd;
+
+ /* copy Match */
+ if (sequence.offset > (size_t)(oLitEnd - prefixStart)) {
+ /* offset beyond prefix */
+ RETURN_ERROR_IF(sequence.offset > (size_t)(oLitEnd - virtualStart), corruption_detected, "");
+ match = dictEnd - (prefixStart - match);
+ if (match + sequence.matchLength <= dictEnd) {
+ ZSTD_memmove(oLitEnd, match, sequence.matchLength);
+ return sequenceLength;
+ }
+ /* span extDict & currentPrefixSegment */
+ { size_t const length1 = dictEnd - match;
+ ZSTD_memmove(oLitEnd, match, length1);
+ op = oLitEnd + length1;
+ sequence.matchLength -= length1;
+ match = prefixStart;
+ }
+ }
+ ZSTD_safecopy(op, oend_w, match, sequence.matchLength, ZSTD_overlap_src_before_dst);
+ return sequenceLength;
+}
+
+/* ZSTD_execSequenceEndSplitLitBuffer():
+ * This version is intended to be used during instances where the litBuffer is still split. It is kept separate to avoid performance impact for the good case.
+ */
+FORCE_NOINLINE
+ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
+size_t ZSTD_execSequenceEndSplitLitBuffer(BYTE* op,
+ BYTE* const oend, const BYTE* const oend_w, seq_t sequence,
+ const BYTE** litPtr, const BYTE* const litLimit,
+ const BYTE* const prefixStart, const BYTE* const virtualStart, const BYTE* const dictEnd)
+{
+ BYTE* const oLitEnd = op + sequence.litLength;
+ size_t const sequenceLength = sequence.litLength + sequence.matchLength;
+ const BYTE* const iLitEnd = *litPtr + sequence.litLength;
+ const BYTE* match = oLitEnd - sequence.offset;
+
+
+ /* bounds checks : careful of address space overflow in 32-bit mode */
+ RETURN_ERROR_IF(sequenceLength > (size_t)(oend - op), dstSize_tooSmall, "last match must fit within dstBuffer");
+ RETURN_ERROR_IF(sequence.litLength > (size_t)(litLimit - *litPtr), corruption_detected, "try to read beyond literal buffer");
+ assert(op < op + sequenceLength);
+ assert(oLitEnd < op + sequenceLength);
+
+ /* copy literals */
+ RETURN_ERROR_IF(op > *litPtr && op < *litPtr + sequence.litLength, dstSize_tooSmall, "output should not catch up to and overwrite literal buffer");
+ ZSTD_safecopyDstBeforeSrc(op, *litPtr, sequence.litLength);
+ op = oLitEnd;
+ *litPtr = iLitEnd;
+
+ /* copy Match */
+ if (sequence.offset > (size_t)(oLitEnd - prefixStart)) {
+ /* offset beyond prefix */
+ RETURN_ERROR_IF(sequence.offset > (size_t)(oLitEnd - virtualStart), corruption_detected, "");
+ match = dictEnd - (prefixStart - match);
+ if (match + sequence.matchLength <= dictEnd) {
+ ZSTD_memmove(oLitEnd, match, sequence.matchLength);
+ return sequenceLength;
+ }
+ /* span extDict & currentPrefixSegment */
+ { size_t const length1 = dictEnd - match;
+ ZSTD_memmove(oLitEnd, match, length1);
+ op = oLitEnd + length1;
+ sequence.matchLength -= length1;
+ match = prefixStart;
+ }
+ }
+ ZSTD_safecopy(op, oend_w, match, sequence.matchLength, ZSTD_overlap_src_before_dst);
+ return sequenceLength;
+}
+
+HINT_INLINE
+ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
+size_t ZSTD_execSequence(BYTE* op,
+ BYTE* const oend, seq_t sequence,
+ const BYTE** litPtr, const BYTE* const litLimit,
+ const BYTE* const prefixStart, const BYTE* const virtualStart, const BYTE* const dictEnd)
+{
+ BYTE* const oLitEnd = op + sequence.litLength;
+ size_t const sequenceLength = sequence.litLength + sequence.matchLength;
+ BYTE* const oMatchEnd = op + sequenceLength; /* risk : address space overflow (32-bits) */
+ BYTE* const oend_w = oend - WILDCOPY_OVERLENGTH; /* risk : address space underflow on oend=NULL */
+ const BYTE* const iLitEnd = *litPtr + sequence.litLength;
+ const BYTE* match = oLitEnd - sequence.offset;
+
+ assert(op != NULL /* Precondition */);
+ assert(oend_w < oend /* No underflow */);
+
+#if defined(__aarch64__)
+ /* prefetch sequence starting from match that will be used for copy later */
+ PREFETCH_L1(match);
+#endif
+ /* Handle edge cases in a slow path:
+ * - Read beyond end of literals
+ * - Match end is within WILDCOPY_OVERLIMIT of oend
+ * - 32-bit mode and the match length overflows
+ */
+ if (UNLIKELY(
+ iLitEnd > litLimit ||
+ oMatchEnd > oend_w ||
+ (MEM_32bits() && (size_t)(oend - op) < sequenceLength + WILDCOPY_OVERLENGTH)))
+ return ZSTD_execSequenceEnd(op, oend, sequence, litPtr, litLimit, prefixStart, virtualStart, dictEnd);
+
+ /* Assumptions (everything else goes into ZSTD_execSequenceEnd()) */
+ assert(op <= oLitEnd /* No overflow */);
+ assert(oLitEnd < oMatchEnd /* Non-zero match & no overflow */);
+ assert(oMatchEnd <= oend /* No underflow */);
+ assert(iLitEnd <= litLimit /* Literal length is in bounds */);
+ assert(oLitEnd <= oend_w /* Can wildcopy literals */);
+ assert(oMatchEnd <= oend_w /* Can wildcopy matches */);
+
+ /* Copy Literals:
+ * Split out litLength <= 16 since it is nearly always true. +1.6% on gcc-9.
+ * We likely don't need the full 32-byte wildcopy.
+ */
+ assert(WILDCOPY_OVERLENGTH >= 16);
+ ZSTD_copy16(op, (*litPtr));
+ if (UNLIKELY(sequence.litLength > 16)) {
+ ZSTD_wildcopy(op + 16, (*litPtr) + 16, sequence.litLength - 16, ZSTD_no_overlap);
+ }
+ op = oLitEnd;
+ *litPtr = iLitEnd; /* update for next sequence */
+
+ /* Copy Match */
+ if (sequence.offset > (size_t)(oLitEnd - prefixStart)) {
+ /* offset beyond prefix -> go into extDict */
+ RETURN_ERROR_IF(UNLIKELY(sequence.offset > (size_t)(oLitEnd - virtualStart)), corruption_detected, "");
+ match = dictEnd + (match - prefixStart);
+ if (match + sequence.matchLength <= dictEnd) {
+ ZSTD_memmove(oLitEnd, match, sequence.matchLength);
+ return sequenceLength;
+ }
+ /* span extDict & currentPrefixSegment */
+ { size_t const length1 = dictEnd - match;
+ ZSTD_memmove(oLitEnd, match, length1);
+ op = oLitEnd + length1;
+ sequence.matchLength -= length1;
+ match = prefixStart;
+ }
+ }
+ /* Match within prefix of 1 or more bytes */
+ assert(op <= oMatchEnd);
+ assert(oMatchEnd <= oend_w);
+ assert(match >= prefixStart);
+ assert(sequence.matchLength >= 1);
+
+ /* Nearly all offsets are >= WILDCOPY_VECLEN bytes, which means we can use wildcopy
+ * without overlap checking.
+ */
+ if (LIKELY(sequence.offset >= WILDCOPY_VECLEN)) {
+ /* We bet on a full wildcopy for matches, since we expect matches to be
+ * longer than literals (in general). In silesia, ~10% of matches are longer
+ * than 16 bytes.
+ */
+ ZSTD_wildcopy(op, match, (ptrdiff_t)sequence.matchLength, ZSTD_no_overlap);
+ return sequenceLength;
+ }
+ assert(sequence.offset < WILDCOPY_VECLEN);
+
+ /* Copy 8 bytes and spread the offset to be >= 8. */
+ ZSTD_overlapCopy8(&op, &match, sequence.offset);
+
+ /* If the match length is > 8 bytes, then continue with the wildcopy. */
+ if (sequence.matchLength > 8) {
+ assert(op < oMatchEnd);
+ ZSTD_wildcopy(op, match, (ptrdiff_t)sequence.matchLength - 8, ZSTD_overlap_src_before_dst);
+ }
+ return sequenceLength;
+}
+
+HINT_INLINE
+ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
+size_t ZSTD_execSequenceSplitLitBuffer(BYTE* op,
+ BYTE* const oend, const BYTE* const oend_w, seq_t sequence,
+ const BYTE** litPtr, const BYTE* const litLimit,
+ const BYTE* const prefixStart, const BYTE* const virtualStart, const BYTE* const dictEnd)
+{
+ BYTE* const oLitEnd = op + sequence.litLength;
+ size_t const sequenceLength = sequence.litLength + sequence.matchLength;
+ BYTE* const oMatchEnd = op + sequenceLength; /* risk : address space overflow (32-bits) */
+ const BYTE* const iLitEnd = *litPtr + sequence.litLength;
+ const BYTE* match = oLitEnd - sequence.offset;
+
+ assert(op != NULL /* Precondition */);
+ assert(oend_w < oend /* No underflow */);
+ /* Handle edge cases in a slow path:
+ * - Read beyond end of literals
+ * - Match end is within WILDCOPY_OVERLIMIT of oend
+ * - 32-bit mode and the match length overflows
+ */
+ if (UNLIKELY(
+ iLitEnd > litLimit ||
+ oMatchEnd > oend_w ||
+ (MEM_32bits() && (size_t)(oend - op) < sequenceLength + WILDCOPY_OVERLENGTH)))
+ return ZSTD_execSequenceEndSplitLitBuffer(op, oend, oend_w, sequence, litPtr, litLimit, prefixStart, virtualStart, dictEnd);
+
+ /* Assumptions (everything else goes into ZSTD_execSequenceEnd()) */
+ assert(op <= oLitEnd /* No overflow */);
+ assert(oLitEnd < oMatchEnd /* Non-zero match & no overflow */);
+ assert(oMatchEnd <= oend /* No underflow */);
+ assert(iLitEnd <= litLimit /* Literal length is in bounds */);
+ assert(oLitEnd <= oend_w /* Can wildcopy literals */);
+ assert(oMatchEnd <= oend_w /* Can wildcopy matches */);
+
+ /* Copy Literals:
+ * Split out litLength <= 16 since it is nearly always true. +1.6% on gcc-9.
+ * We likely don't need the full 32-byte wildcopy.
+ */
+ assert(WILDCOPY_OVERLENGTH >= 16);
+ ZSTD_copy16(op, (*litPtr));
+ if (UNLIKELY(sequence.litLength > 16)) {
+ ZSTD_wildcopy(op+16, (*litPtr)+16, sequence.litLength-16, ZSTD_no_overlap);
+ }
+ op = oLitEnd;
+ *litPtr = iLitEnd; /* update for next sequence */
+
+ /* Copy Match */
+ if (sequence.offset > (size_t)(oLitEnd - prefixStart)) {
+ /* offset beyond prefix -> go into extDict */
+ RETURN_ERROR_IF(UNLIKELY(sequence.offset > (size_t)(oLitEnd - virtualStart)), corruption_detected, "");
+ match = dictEnd + (match - prefixStart);
+ if (match + sequence.matchLength <= dictEnd) {
+ ZSTD_memmove(oLitEnd, match, sequence.matchLength);
+ return sequenceLength;
+ }
+ /* span extDict & currentPrefixSegment */
+ { size_t const length1 = dictEnd - match;
+ ZSTD_memmove(oLitEnd, match, length1);
+ op = oLitEnd + length1;
+ sequence.matchLength -= length1;
+ match = prefixStart;
+ } }
+ /* Match within prefix of 1 or more bytes */
+ assert(op <= oMatchEnd);
+ assert(oMatchEnd <= oend_w);
+ assert(match >= prefixStart);
+ assert(sequence.matchLength >= 1);
+
+ /* Nearly all offsets are >= WILDCOPY_VECLEN bytes, which means we can use wildcopy
+ * without overlap checking.
+ */
+ if (LIKELY(sequence.offset >= WILDCOPY_VECLEN)) {
+ /* We bet on a full wildcopy for matches, since we expect matches to be
+ * longer than literals (in general). In silesia, ~10% of matches are longer
+ * than 16 bytes.
+ */
+ ZSTD_wildcopy(op, match, (ptrdiff_t)sequence.matchLength, ZSTD_no_overlap);
+ return sequenceLength;
+ }
+ assert(sequence.offset < WILDCOPY_VECLEN);
+
+ /* Copy 8 bytes and spread the offset to be >= 8. */
+ ZSTD_overlapCopy8(&op, &match, sequence.offset);
+
+ /* If the match length is > 8 bytes, then continue with the wildcopy. */
+ if (sequence.matchLength > 8) {
+ assert(op < oMatchEnd);
+ ZSTD_wildcopy(op, match, (ptrdiff_t)sequence.matchLength-8, ZSTD_overlap_src_before_dst);
+ }
+ return sequenceLength;
+}
+
+
+static void
+ZSTD_initFseState(ZSTD_fseState* DStatePtr, BIT_DStream_t* bitD, const ZSTD_seqSymbol* dt)
+{
+ const void* ptr = dt;
+ const ZSTD_seqSymbol_header* const DTableH = (const ZSTD_seqSymbol_header*)ptr;
+ DStatePtr->state = BIT_readBits(bitD, DTableH->tableLog);
+ DEBUGLOG(6, "ZSTD_initFseState : val=%u using %u bits",
+ (U32)DStatePtr->state, DTableH->tableLog);
+ BIT_reloadDStream(bitD);
+ DStatePtr->table = dt + 1;
+}
+
+FORCE_INLINE_TEMPLATE void
+ZSTD_updateFseStateWithDInfo(ZSTD_fseState* DStatePtr, BIT_DStream_t* bitD, U16 nextState, U32 nbBits)
+{
+ size_t const lowBits = BIT_readBits(bitD, nbBits);
+ DStatePtr->state = nextState + lowBits;
+}
+
+/* We need to add at most (ZSTD_WINDOWLOG_MAX_32 - 1) bits to read the maximum
+ * offset bits. But we can only read at most STREAM_ACCUMULATOR_MIN_32
+ * bits before reloading. This value is the maximum number of bytes we read
+ * after reloading when we are decoding long offsets.
+ */
+#define LONG_OFFSETS_MAX_EXTRA_BITS_32 \
+ (ZSTD_WINDOWLOG_MAX_32 > STREAM_ACCUMULATOR_MIN_32 \
+ ? ZSTD_WINDOWLOG_MAX_32 - STREAM_ACCUMULATOR_MIN_32 \
+ : 0)
+
+typedef enum { ZSTD_lo_isRegularOffset, ZSTD_lo_isLongOffset=1 } ZSTD_longOffset_e;
+
+/**
+ * ZSTD_decodeSequence():
+ * @p longOffsets : tells the decoder to reload more bit while decoding large offsets
+ * only used in 32-bit mode
+ * @return : Sequence (litL + matchL + offset)
+ */
+FORCE_INLINE_TEMPLATE seq_t
+ZSTD_decodeSequence(seqState_t* seqState, const ZSTD_longOffset_e longOffsets, const int isLastSeq)
+{
+ seq_t seq;
+ /*
+ * ZSTD_seqSymbol is a 64 bits wide structure.
+ * It can be loaded in one operation
+ * and its fields extracted by simply shifting or bit-extracting on aarch64.
+ * GCC doesn't recognize this and generates more unnecessary ldr/ldrb/ldrh
+ * operations that cause performance drop. This can be avoided by using this
+ * ZSTD_memcpy hack.
+ */
+#if defined(__aarch64__) && (defined(__GNUC__) && !defined(__clang__))
+ ZSTD_seqSymbol llDInfoS, mlDInfoS, ofDInfoS;
+ ZSTD_seqSymbol* const llDInfo = &llDInfoS;
+ ZSTD_seqSymbol* const mlDInfo = &mlDInfoS;
+ ZSTD_seqSymbol* const ofDInfo = &ofDInfoS;
+ ZSTD_memcpy(llDInfo, seqState->stateLL.table + seqState->stateLL.state, sizeof(ZSTD_seqSymbol));
+ ZSTD_memcpy(mlDInfo, seqState->stateML.table + seqState->stateML.state, sizeof(ZSTD_seqSymbol));
+ ZSTD_memcpy(ofDInfo, seqState->stateOffb.table + seqState->stateOffb.state, sizeof(ZSTD_seqSymbol));
+#else
+ const ZSTD_seqSymbol* const llDInfo = seqState->stateLL.table + seqState->stateLL.state;
+ const ZSTD_seqSymbol* const mlDInfo = seqState->stateML.table + seqState->stateML.state;
+ const ZSTD_seqSymbol* const ofDInfo = seqState->stateOffb.table + seqState->stateOffb.state;
+#endif
+ seq.matchLength = mlDInfo->baseValue;
+ seq.litLength = llDInfo->baseValue;
+ { U32 const ofBase = ofDInfo->baseValue;
+ BYTE const llBits = llDInfo->nbAdditionalBits;
+ BYTE const mlBits = mlDInfo->nbAdditionalBits;
+ BYTE const ofBits = ofDInfo->nbAdditionalBits;
+ BYTE const totalBits = llBits+mlBits+ofBits;
+
+ U16 const llNext = llDInfo->nextState;
+ U16 const mlNext = mlDInfo->nextState;
+ U16 const ofNext = ofDInfo->nextState;
+ U32 const llnbBits = llDInfo->nbBits;
+ U32 const mlnbBits = mlDInfo->nbBits;
+ U32 const ofnbBits = ofDInfo->nbBits;
+
+ assert(llBits <= MaxLLBits);
+ assert(mlBits <= MaxMLBits);
+ assert(ofBits <= MaxOff);
+ /*
+ * As gcc has better branch and block analyzers, sometimes it is only
+ * valuable to mark likeliness for clang, it gives around 3-4% of
+ * performance.
+ */
+
+ /* sequence */
+ { size_t offset;
+ if (ofBits > 1) {
+ ZSTD_STATIC_ASSERT(ZSTD_lo_isLongOffset == 1);
+ ZSTD_STATIC_ASSERT(LONG_OFFSETS_MAX_EXTRA_BITS_32 == 5);
+ ZSTD_STATIC_ASSERT(STREAM_ACCUMULATOR_MIN_32 > LONG_OFFSETS_MAX_EXTRA_BITS_32);
+ ZSTD_STATIC_ASSERT(STREAM_ACCUMULATOR_MIN_32 - LONG_OFFSETS_MAX_EXTRA_BITS_32 >= MaxMLBits);
+ if (MEM_32bits() && longOffsets && (ofBits >= STREAM_ACCUMULATOR_MIN_32)) {
+ /* Always read extra bits, this keeps the logic simple,
+ * avoids branches, and avoids accidentally reading 0 bits.
+ */
+ U32 const extraBits = LONG_OFFSETS_MAX_EXTRA_BITS_32;
+ offset = ofBase + (BIT_readBitsFast(&seqState->DStream, ofBits - extraBits) << extraBits);
+ BIT_reloadDStream(&seqState->DStream);
+ offset += BIT_readBitsFast(&seqState->DStream, extraBits);
+ } else {
+ offset = ofBase + BIT_readBitsFast(&seqState->DStream, ofBits/*>0*/); /* <= (ZSTD_WINDOWLOG_MAX-1) bits */
+ if (MEM_32bits()) BIT_reloadDStream(&seqState->DStream);
+ }
+ seqState->prevOffset[2] = seqState->prevOffset[1];
+ seqState->prevOffset[1] = seqState->prevOffset[0];
+ seqState->prevOffset[0] = offset;
+ } else {
+ U32 const ll0 = (llDInfo->baseValue == 0);
+ if (LIKELY((ofBits == 0))) {
+ offset = seqState->prevOffset[ll0];
+ seqState->prevOffset[1] = seqState->prevOffset[!ll0];
+ seqState->prevOffset[0] = offset;
+ } else {
+ offset = ofBase + ll0 + BIT_readBitsFast(&seqState->DStream, 1);
+ { size_t temp = (offset==3) ? seqState->prevOffset[0] - 1 : seqState->prevOffset[offset];
+ temp -= !temp; /* 0 is not valid: input corrupted => force offset to -1 => corruption detected at execSequence */
+ if (offset != 1) seqState->prevOffset[2] = seqState->prevOffset[1];
+ seqState->prevOffset[1] = seqState->prevOffset[0];
+ seqState->prevOffset[0] = offset = temp;
+ } } }
+ seq.offset = offset;
+ }
+
+ if (mlBits > 0)
+ seq.matchLength += BIT_readBitsFast(&seqState->DStream, mlBits/*>0*/);
+
+ if (MEM_32bits() && (mlBits+llBits >= STREAM_ACCUMULATOR_MIN_32-LONG_OFFSETS_MAX_EXTRA_BITS_32))
+ BIT_reloadDStream(&seqState->DStream);
+ if (MEM_64bits() && UNLIKELY(totalBits >= STREAM_ACCUMULATOR_MIN_64-(LLFSELog+MLFSELog+OffFSELog)))
+ BIT_reloadDStream(&seqState->DStream);
+ /* Ensure there are enough bits to read the rest of data in 64-bit mode. */
+ ZSTD_STATIC_ASSERT(16+LLFSELog+MLFSELog+OffFSELog < STREAM_ACCUMULATOR_MIN_64);
+
+ if (llBits > 0)
+ seq.litLength += BIT_readBitsFast(&seqState->DStream, llBits/*>0*/);
+
+ if (MEM_32bits())
+ BIT_reloadDStream(&seqState->DStream);
+
+ DEBUGLOG(6, "seq: litL=%u, matchL=%u, offset=%u",
+ (U32)seq.litLength, (U32)seq.matchLength, (U32)seq.offset);
+
+ if (!isLastSeq) {
+ /* don't update FSE state for last Sequence */
+ ZSTD_updateFseStateWithDInfo(&seqState->stateLL, &seqState->DStream, llNext, llnbBits); /* <= 9 bits */
+ ZSTD_updateFseStateWithDInfo(&seqState->stateML, &seqState->DStream, mlNext, mlnbBits); /* <= 9 bits */
+ if (MEM_32bits()) BIT_reloadDStream(&seqState->DStream); /* <= 18 bits */
+ ZSTD_updateFseStateWithDInfo(&seqState->stateOffb, &seqState->DStream, ofNext, ofnbBits); /* <= 8 bits */
+ BIT_reloadDStream(&seqState->DStream);
+ }
+ }
+
+ return seq;
+}
+
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE)
+#if DEBUGLEVEL >= 1
+static int ZSTD_dictionaryIsActive(ZSTD_DCtx const* dctx, BYTE const* prefixStart, BYTE const* oLitEnd)
+{
+ size_t const windowSize = dctx->fParams.windowSize;
+ /* No dictionary used. */
+ if (dctx->dictContentEndForFuzzing == NULL) return 0;
+ /* Dictionary is our prefix. */
+ if (prefixStart == dctx->dictContentBeginForFuzzing) return 1;
+ /* Dictionary is not our ext-dict. */
+ if (dctx->dictEnd != dctx->dictContentEndForFuzzing) return 0;
+ /* Dictionary is not within our window size. */
+ if ((size_t)(oLitEnd - prefixStart) >= windowSize) return 0;
+ /* Dictionary is active. */
+ return 1;
+}
+#endif
+
+static void ZSTD_assertValidSequence(
+ ZSTD_DCtx const* dctx,
+ BYTE const* op, BYTE const* oend,
+ seq_t const seq,
+ BYTE const* prefixStart, BYTE const* virtualStart)
+{
+#if DEBUGLEVEL >= 1
+ if (dctx->isFrameDecompression) {
+ size_t const windowSize = dctx->fParams.windowSize;
+ size_t const sequenceSize = seq.litLength + seq.matchLength;
+ BYTE const* const oLitEnd = op + seq.litLength;
+ DEBUGLOG(6, "Checking sequence: litL=%u matchL=%u offset=%u",
+ (U32)seq.litLength, (U32)seq.matchLength, (U32)seq.offset);
+ assert(op <= oend);
+ assert((size_t)(oend - op) >= sequenceSize);
+ assert(sequenceSize <= ZSTD_blockSizeMax(dctx));
+ if (ZSTD_dictionaryIsActive(dctx, prefixStart, oLitEnd)) {
+ size_t const dictSize = (size_t)((char const*)dctx->dictContentEndForFuzzing - (char const*)dctx->dictContentBeginForFuzzing);
+ /* Offset must be within the dictionary. */
+ assert(seq.offset <= (size_t)(oLitEnd - virtualStart));
+ assert(seq.offset <= windowSize + dictSize);
+ } else {
+ /* Offset must be within our window. */
+ assert(seq.offset <= windowSize);
+ }
+ }
+#else
+ (void)dctx, (void)op, (void)oend, (void)seq, (void)prefixStart, (void)virtualStart;
+#endif
+}
+#endif
+
+#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG
+
+
+FORCE_INLINE_TEMPLATE size_t
+DONT_VECTORIZE
+ZSTD_decompressSequences_bodySplitLitBuffer( ZSTD_DCtx* dctx,
+ void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset)
+{
+ const BYTE* ip = (const BYTE*)seqStart;
+ const BYTE* const iend = ip + seqSize;
+ BYTE* const ostart = (BYTE*)dst;
+ BYTE* const oend = ZSTD_maybeNullPtrAdd(ostart, maxDstSize);
+ BYTE* op = ostart;
+ const BYTE* litPtr = dctx->litPtr;
+ const BYTE* litBufferEnd = dctx->litBufferEnd;
+ const BYTE* const prefixStart = (const BYTE*) (dctx->prefixStart);
+ const BYTE* const vBase = (const BYTE*) (dctx->virtualStart);
+ const BYTE* const dictEnd = (const BYTE*) (dctx->dictEnd);
+ DEBUGLOG(5, "ZSTD_decompressSequences_bodySplitLitBuffer (%i seqs)", nbSeq);
+
+ /* Literals are split between internal buffer & output buffer */
+ if (nbSeq) {
+ seqState_t seqState;
+ dctx->fseEntropy = 1;
+ { U32 i; for (i=0; i<ZSTD_REP_NUM; i++) seqState.prevOffset[i] = dctx->entropy.rep[i]; }
+ RETURN_ERROR_IF(
+ ERR_isError(BIT_initDStream(&seqState.DStream, ip, iend-ip)),
+ corruption_detected, "");
+ ZSTD_initFseState(&seqState.stateLL, &seqState.DStream, dctx->LLTptr);
+ ZSTD_initFseState(&seqState.stateOffb, &seqState.DStream, dctx->OFTptr);
+ ZSTD_initFseState(&seqState.stateML, &seqState.DStream, dctx->MLTptr);
+ assert(dst != NULL);
+
+ ZSTD_STATIC_ASSERT(
+ BIT_DStream_unfinished < BIT_DStream_completed &&
+ BIT_DStream_endOfBuffer < BIT_DStream_completed &&
+ BIT_DStream_completed < BIT_DStream_overflow);
+
+ /* decompress without overrunning litPtr begins */
+ { seq_t sequence = {0,0,0}; /* some static analyzer believe that @sequence is not initialized (it necessarily is, since for(;;) loop as at least one iteration) */
+ /* Align the decompression loop to 32 + 16 bytes.
+ *
+ * zstd compiled with gcc-9 on an Intel i9-9900k shows 10% decompression
+ * speed swings based on the alignment of the decompression loop. This
+ * performance swing is caused by parts of the decompression loop falling
+ * out of the DSB. The entire decompression loop should fit in the DSB,
+ * when it can't we get much worse performance. You can measure if you've
+ * hit the good case or the bad case with this perf command for some
+ * compressed file test.zst:
+ *
+ * perf stat -e cycles -e instructions -e idq.all_dsb_cycles_any_uops \
+ * -e idq.all_mite_cycles_any_uops -- ./zstd -tq test.zst
+ *
+ * If you see most cycles served out of the MITE you've hit the bad case.
+ * If you see most cycles served out of the DSB you've hit the good case.
+ * If it is pretty even then you may be in an okay case.
+ *
+ * This issue has been reproduced on the following CPUs:
+ * - Kabylake: Macbook Pro (15-inch, 2019) 2.4 GHz Intel Core i9
+ * Use Instruments->Counters to get DSB/MITE cycles.
+ * I never got performance swings, but I was able to
+ * go from the good case of mostly DSB to half of the
+ * cycles served from MITE.
+ * - Coffeelake: Intel i9-9900k
+ * - Coffeelake: Intel i7-9700k
+ *
+ * I haven't been able to reproduce the instability or DSB misses on any
+ * of the following CPUS:
+ * - Haswell
+ * - Broadwell: Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GH
+ * - Skylake
+ *
+ * Alignment is done for each of the three major decompression loops:
+ * - ZSTD_decompressSequences_bodySplitLitBuffer - presplit section of the literal buffer
+ * - ZSTD_decompressSequences_bodySplitLitBuffer - postsplit section of the literal buffer
+ * - ZSTD_decompressSequences_body
+ * Alignment choices are made to minimize large swings on bad cases and influence on performance
+ * from changes external to this code, rather than to overoptimize on the current commit.
+ *
+ * If you are seeing performance stability this script can help test.
+ * It tests on 4 commits in zstd where I saw performance change.
+ *
+ * https://gist.github.com/terrelln/9889fc06a423fd5ca6e99351564473f4
+ */
+#if defined(__GNUC__) && defined(__x86_64__)
+ __asm__(".p2align 6");
+# if __GNUC__ >= 7
+ /* good for gcc-7, gcc-9, and gcc-11 */
+ __asm__("nop");
+ __asm__(".p2align 5");
+ __asm__("nop");
+ __asm__(".p2align 4");
+# if __GNUC__ == 8 || __GNUC__ == 10
+ /* good for gcc-8 and gcc-10 */
+ __asm__("nop");
+ __asm__(".p2align 3");
+# endif
+# endif
+#endif
+
+ /* Handle the initial state where litBuffer is currently split between dst and litExtraBuffer */
+ for ( ; nbSeq; nbSeq--) {
+ sequence = ZSTD_decodeSequence(&seqState, isLongOffset, nbSeq==1);
+ if (litPtr + sequence.litLength > dctx->litBufferEnd) break;
+ { size_t const oneSeqSize = ZSTD_execSequenceSplitLitBuffer(op, oend, litPtr + sequence.litLength - WILDCOPY_OVERLENGTH, sequence, &litPtr, litBufferEnd, prefixStart, vBase, dictEnd);
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE)
+ assert(!ZSTD_isError(oneSeqSize));
+ ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase);
+#endif
+ if (UNLIKELY(ZSTD_isError(oneSeqSize)))
+ return oneSeqSize;
+ DEBUGLOG(6, "regenerated sequence size : %u", (U32)oneSeqSize);
+ op += oneSeqSize;
+ } }
+ DEBUGLOG(6, "reached: (litPtr + sequence.litLength > dctx->litBufferEnd)");
+
+ /* If there are more sequences, they will need to read literals from litExtraBuffer; copy over the remainder from dst and update litPtr and litEnd */
+ if (nbSeq > 0) {
+ const size_t leftoverLit = dctx->litBufferEnd - litPtr;
+ DEBUGLOG(6, "There are %i sequences left, and %zu/%zu literals left in buffer", nbSeq, leftoverLit, sequence.litLength);
+ if (leftoverLit) {
+ RETURN_ERROR_IF(leftoverLit > (size_t)(oend - op), dstSize_tooSmall, "remaining lit must fit within dstBuffer");
+ ZSTD_safecopyDstBeforeSrc(op, litPtr, leftoverLit);
+ sequence.litLength -= leftoverLit;
+ op += leftoverLit;
+ }
+ litPtr = dctx->litExtraBuffer;
+ litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE;
+ dctx->litBufferLocation = ZSTD_not_in_dst;
+ { size_t const oneSeqSize = ZSTD_execSequence(op, oend, sequence, &litPtr, litBufferEnd, prefixStart, vBase, dictEnd);
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE)
+ assert(!ZSTD_isError(oneSeqSize));
+ ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase);
+#endif
+ if (UNLIKELY(ZSTD_isError(oneSeqSize)))
+ return oneSeqSize;
+ DEBUGLOG(6, "regenerated sequence size : %u", (U32)oneSeqSize);
+ op += oneSeqSize;
+ }
+ nbSeq--;
+ }
+ }
+
+ if (nbSeq > 0) {
+ /* there is remaining lit from extra buffer */
+
+#if defined(__GNUC__) && defined(__x86_64__)
+ __asm__(".p2align 6");
+ __asm__("nop");
+# if __GNUC__ != 7
+ /* worse for gcc-7 better for gcc-8, gcc-9, and gcc-10 and clang */
+ __asm__(".p2align 4");
+ __asm__("nop");
+ __asm__(".p2align 3");
+# elif __GNUC__ >= 11
+ __asm__(".p2align 3");
+# else
+ __asm__(".p2align 5");
+ __asm__("nop");
+ __asm__(".p2align 3");
+# endif
+#endif
+
+ for ( ; nbSeq ; nbSeq--) {
+ seq_t const sequence = ZSTD_decodeSequence(&seqState, isLongOffset, nbSeq==1);
+ size_t const oneSeqSize = ZSTD_execSequence(op, oend, sequence, &litPtr, litBufferEnd, prefixStart, vBase, dictEnd);
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE)
+ assert(!ZSTD_isError(oneSeqSize));
+ ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase);
+#endif
+ if (UNLIKELY(ZSTD_isError(oneSeqSize)))
+ return oneSeqSize;
+ DEBUGLOG(6, "regenerated sequence size : %u", (U32)oneSeqSize);
+ op += oneSeqSize;
+ }
+ }
+
+ /* check if reached exact end */
+ DEBUGLOG(5, "ZSTD_decompressSequences_bodySplitLitBuffer: after decode loop, remaining nbSeq : %i", nbSeq);
+ RETURN_ERROR_IF(nbSeq, corruption_detected, "");
+ DEBUGLOG(5, "bitStream : start=%p, ptr=%p, bitsConsumed=%u", seqState.DStream.start, seqState.DStream.ptr, seqState.DStream.bitsConsumed);
+ RETURN_ERROR_IF(!BIT_endOfDStream(&seqState.DStream), corruption_detected, "");
+ /* save reps for next block */
+ { U32 i; for (i=0; i<ZSTD_REP_NUM; i++) dctx->entropy.rep[i] = (U32)(seqState.prevOffset[i]); }
+ }
+
+ /* last literal segment */
+ if (dctx->litBufferLocation == ZSTD_split) {
+ /* split hasn't been reached yet, first get dst then copy litExtraBuffer */
+ size_t const lastLLSize = (size_t)(litBufferEnd - litPtr);
+ DEBUGLOG(6, "copy last literals from segment : %u", (U32)lastLLSize);
+ RETURN_ERROR_IF(lastLLSize > (size_t)(oend - op), dstSize_tooSmall, "");
+ if (op != NULL) {
+ ZSTD_memmove(op, litPtr, lastLLSize);
+ op += lastLLSize;
+ }
+ litPtr = dctx->litExtraBuffer;
+ litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE;
+ dctx->litBufferLocation = ZSTD_not_in_dst;
+ }
+ /* copy last literals from internal buffer */
+ { size_t const lastLLSize = (size_t)(litBufferEnd - litPtr);
+ DEBUGLOG(6, "copy last literals from internal buffer : %u", (U32)lastLLSize);
+ RETURN_ERROR_IF(lastLLSize > (size_t)(oend-op), dstSize_tooSmall, "");
+ if (op != NULL) {
+ ZSTD_memcpy(op, litPtr, lastLLSize);
+ op += lastLLSize;
+ } }
+
+ DEBUGLOG(6, "decoded block of size %u bytes", (U32)(op - ostart));
+ return (size_t)(op - ostart);
+}
+
+FORCE_INLINE_TEMPLATE size_t
+DONT_VECTORIZE
+ZSTD_decompressSequences_body(ZSTD_DCtx* dctx,
+ void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset)
+{
+ const BYTE* ip = (const BYTE*)seqStart;
+ const BYTE* const iend = ip + seqSize;
+ BYTE* const ostart = (BYTE*)dst;
+ BYTE* const oend = dctx->litBufferLocation == ZSTD_not_in_dst ? ZSTD_maybeNullPtrAdd(ostart, maxDstSize) : dctx->litBuffer;
+ BYTE* op = ostart;
+ const BYTE* litPtr = dctx->litPtr;
+ const BYTE* const litEnd = litPtr + dctx->litSize;
+ const BYTE* const prefixStart = (const BYTE*)(dctx->prefixStart);
+ const BYTE* const vBase = (const BYTE*)(dctx->virtualStart);
+ const BYTE* const dictEnd = (const BYTE*)(dctx->dictEnd);
+ DEBUGLOG(5, "ZSTD_decompressSequences_body: nbSeq = %d", nbSeq);
+
+ /* Regen sequences */
+ if (nbSeq) {
+ seqState_t seqState;
+ dctx->fseEntropy = 1;
+ { U32 i; for (i = 0; i < ZSTD_REP_NUM; i++) seqState.prevOffset[i] = dctx->entropy.rep[i]; }
+ RETURN_ERROR_IF(
+ ERR_isError(BIT_initDStream(&seqState.DStream, ip, iend - ip)),
+ corruption_detected, "");
+ ZSTD_initFseState(&seqState.stateLL, &seqState.DStream, dctx->LLTptr);
+ ZSTD_initFseState(&seqState.stateOffb, &seqState.DStream, dctx->OFTptr);
+ ZSTD_initFseState(&seqState.stateML, &seqState.DStream, dctx->MLTptr);
+ assert(dst != NULL);
+
+#if defined(__GNUC__) && defined(__x86_64__)
+ __asm__(".p2align 6");
+ __asm__("nop");
+# if __GNUC__ >= 7
+ __asm__(".p2align 5");
+ __asm__("nop");
+ __asm__(".p2align 3");
+# else
+ __asm__(".p2align 4");
+ __asm__("nop");
+ __asm__(".p2align 3");
+# endif
+#endif
+
+ for ( ; nbSeq ; nbSeq--) {
+ seq_t const sequence = ZSTD_decodeSequence(&seqState, isLongOffset, nbSeq==1);
+ size_t const oneSeqSize = ZSTD_execSequence(op, oend, sequence, &litPtr, litEnd, prefixStart, vBase, dictEnd);
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE)
+ assert(!ZSTD_isError(oneSeqSize));
+ ZSTD_assertValidSequence(dctx, op, oend, sequence, prefixStart, vBase);
+#endif
+ if (UNLIKELY(ZSTD_isError(oneSeqSize)))
+ return oneSeqSize;
+ DEBUGLOG(6, "regenerated sequence size : %u", (U32)oneSeqSize);
+ op += oneSeqSize;
+ }
+
+ /* check if reached exact end */
+ assert(nbSeq == 0);
+ RETURN_ERROR_IF(!BIT_endOfDStream(&seqState.DStream), corruption_detected, "");
+ /* save reps for next block */
+ { U32 i; for (i=0; i<ZSTD_REP_NUM; i++) dctx->entropy.rep[i] = (U32)(seqState.prevOffset[i]); }
+ }
+
+ /* last literal segment */
+ { size_t const lastLLSize = (size_t)(litEnd - litPtr);
+ DEBUGLOG(6, "copy last literals : %u", (U32)lastLLSize);
+ RETURN_ERROR_IF(lastLLSize > (size_t)(oend-op), dstSize_tooSmall, "");
+ if (op != NULL) {
+ ZSTD_memcpy(op, litPtr, lastLLSize);
+ op += lastLLSize;
+ } }
+
+ DEBUGLOG(6, "decoded block of size %u bytes", (U32)(op - ostart));
+ return (size_t)(op - ostart);
+}
+
+static size_t
+ZSTD_decompressSequences_default(ZSTD_DCtx* dctx,
+ void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset)
+{
+ return ZSTD_decompressSequences_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset);
+}
+
+static size_t
+ZSTD_decompressSequencesSplitLitBuffer_default(ZSTD_DCtx* dctx,
+ void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset)
+{
+ return ZSTD_decompressSequences_bodySplitLitBuffer(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset);
+}
+#endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */
+
+#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT
+
+FORCE_INLINE_TEMPLATE
+
+size_t ZSTD_prefetchMatch(size_t prefetchPos, seq_t const sequence,
+ const BYTE* const prefixStart, const BYTE* const dictEnd)
+{
+ prefetchPos += sequence.litLength;
+ { const BYTE* const matchBase = (sequence.offset > prefetchPos) ? dictEnd : prefixStart;
+ /* note : this operation can overflow when seq.offset is really too large, which can only happen when input is corrupted.
+ * No consequence though : memory address is only used for prefetching, not for dereferencing */
+ const BYTE* const match = ZSTD_wrappedPtrSub(ZSTD_wrappedPtrAdd(matchBase, prefetchPos), sequence.offset);
+ PREFETCH_L1(match); PREFETCH_L1(match+CACHELINE_SIZE); /* note : it's safe to invoke PREFETCH() on any memory address, including invalid ones */
+ }
+ return prefetchPos + sequence.matchLength;
+}
+
+/* This decoding function employs prefetching
+ * to reduce latency impact of cache misses.
+ * It's generally employed when block contains a significant portion of long-distance matches
+ * or when coupled with a "cold" dictionary */
+FORCE_INLINE_TEMPLATE size_t
+ZSTD_decompressSequencesLong_body(
+ ZSTD_DCtx* dctx,
+ void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset)
+{
+ const BYTE* ip = (const BYTE*)seqStart;
+ const BYTE* const iend = ip + seqSize;
+ BYTE* const ostart = (BYTE*)dst;
+ BYTE* const oend = dctx->litBufferLocation == ZSTD_in_dst ? dctx->litBuffer : ZSTD_maybeNullPtrAdd(ostart, maxDstSize);
+ BYTE* op = ostart;
+ const BYTE* litPtr = dctx->litPtr;
+ const BYTE* litBufferEnd = dctx->litBufferEnd;
+ const BYTE* const prefixStart = (const BYTE*) (dctx->prefixStart);
+ const BYTE* const dictStart = (const BYTE*) (dctx->virtualStart);
+ const BYTE* const dictEnd = (const BYTE*) (dctx->dictEnd);
+
+ /* Regen sequences */
+ if (nbSeq) {
+#define STORED_SEQS 8
+#define STORED_SEQS_MASK (STORED_SEQS-1)
+#define ADVANCED_SEQS STORED_SEQS
+ seq_t sequences[STORED_SEQS];
+ int const seqAdvance = MIN(nbSeq, ADVANCED_SEQS);
+ seqState_t seqState;
+ int seqNb;
+ size_t prefetchPos = (size_t)(op-prefixStart); /* track position relative to prefixStart */
+
+ dctx->fseEntropy = 1;
+ { int i; for (i=0; i<ZSTD_REP_NUM; i++) seqState.prevOffset[i] = dctx->entropy.rep[i]; }
+ assert(dst != NULL);
+ assert(iend >= ip);
+ RETURN_ERROR_IF(
+ ERR_isError(BIT_initDStream(&seqState.DStream, ip, iend-ip)),
+ corruption_detected, "");
+ ZSTD_initFseState(&seqState.stateLL, &seqState.DStream, dctx->LLTptr);
+ ZSTD_initFseState(&seqState.stateOffb, &seqState.DStream, dctx->OFTptr);
+ ZSTD_initFseState(&seqState.stateML, &seqState.DStream, dctx->MLTptr);
+
+ /* prepare in advance */
+ for (seqNb=0; seqNb<seqAdvance; seqNb++) {
+ seq_t const sequence = ZSTD_decodeSequence(&seqState, isLongOffset, seqNb == nbSeq-1);
+ prefetchPos = ZSTD_prefetchMatch(prefetchPos, sequence, prefixStart, dictEnd);
+ sequences[seqNb] = sequence;
+ }
+
+ /* decompress without stomping litBuffer */
+ for (; seqNb < nbSeq; seqNb++) {
+ seq_t sequence = ZSTD_decodeSequence(&seqState, isLongOffset, seqNb == nbSeq-1);
+
+ if (dctx->litBufferLocation == ZSTD_split && litPtr + sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK].litLength > dctx->litBufferEnd) {
+ /* lit buffer is reaching split point, empty out the first buffer and transition to litExtraBuffer */
+ const size_t leftoverLit = dctx->litBufferEnd - litPtr;
+ if (leftoverLit)
+ {
+ RETURN_ERROR_IF(leftoverLit > (size_t)(oend - op), dstSize_tooSmall, "remaining lit must fit within dstBuffer");
+ ZSTD_safecopyDstBeforeSrc(op, litPtr, leftoverLit);
+ sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK].litLength -= leftoverLit;
+ op += leftoverLit;
+ }
+ litPtr = dctx->litExtraBuffer;
+ litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE;
+ dctx->litBufferLocation = ZSTD_not_in_dst;
+ { size_t const oneSeqSize = ZSTD_execSequence(op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd);
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE)
+ assert(!ZSTD_isError(oneSeqSize));
+ ZSTD_assertValidSequence(dctx, op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], prefixStart, dictStart);
+#endif
+ if (ZSTD_isError(oneSeqSize)) return oneSeqSize;
+
+ prefetchPos = ZSTD_prefetchMatch(prefetchPos, sequence, prefixStart, dictEnd);
+ sequences[seqNb & STORED_SEQS_MASK] = sequence;
+ op += oneSeqSize;
+ } }
+ else
+ {
+ /* lit buffer is either wholly contained in first or second split, or not split at all*/
+ size_t const oneSeqSize = dctx->litBufferLocation == ZSTD_split ?
+ ZSTD_execSequenceSplitLitBuffer(op, oend, litPtr + sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK].litLength - WILDCOPY_OVERLENGTH, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd) :
+ ZSTD_execSequence(op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd);
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE)
+ assert(!ZSTD_isError(oneSeqSize));
+ ZSTD_assertValidSequence(dctx, op, oend, sequences[(seqNb - ADVANCED_SEQS) & STORED_SEQS_MASK], prefixStart, dictStart);
+#endif
+ if (ZSTD_isError(oneSeqSize)) return oneSeqSize;
+
+ prefetchPos = ZSTD_prefetchMatch(prefetchPos, sequence, prefixStart, dictEnd);
+ sequences[seqNb & STORED_SEQS_MASK] = sequence;
+ op += oneSeqSize;
+ }
+ }
+ RETURN_ERROR_IF(!BIT_endOfDStream(&seqState.DStream), corruption_detected, "");
+
+ /* finish queue */
+ seqNb -= seqAdvance;
+ for ( ; seqNb<nbSeq ; seqNb++) {
+ seq_t *sequence = &(sequences[seqNb&STORED_SEQS_MASK]);
+ if (dctx->litBufferLocation == ZSTD_split && litPtr + sequence->litLength > dctx->litBufferEnd) {
+ const size_t leftoverLit = dctx->litBufferEnd - litPtr;
+ if (leftoverLit) {
+ RETURN_ERROR_IF(leftoverLit > (size_t)(oend - op), dstSize_tooSmall, "remaining lit must fit within dstBuffer");
+ ZSTD_safecopyDstBeforeSrc(op, litPtr, leftoverLit);
+ sequence->litLength -= leftoverLit;
+ op += leftoverLit;
+ }
+ litPtr = dctx->litExtraBuffer;
+ litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE;
+ dctx->litBufferLocation = ZSTD_not_in_dst;
+ { size_t const oneSeqSize = ZSTD_execSequence(op, oend, *sequence, &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd);
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE)
+ assert(!ZSTD_isError(oneSeqSize));
+ ZSTD_assertValidSequence(dctx, op, oend, sequences[seqNb&STORED_SEQS_MASK], prefixStart, dictStart);
+#endif
+ if (ZSTD_isError(oneSeqSize)) return oneSeqSize;
+ op += oneSeqSize;
+ }
+ }
+ else
+ {
+ size_t const oneSeqSize = dctx->litBufferLocation == ZSTD_split ?
+ ZSTD_execSequenceSplitLitBuffer(op, oend, litPtr + sequence->litLength - WILDCOPY_OVERLENGTH, *sequence, &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd) :
+ ZSTD_execSequence(op, oend, *sequence, &litPtr, litBufferEnd, prefixStart, dictStart, dictEnd);
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) && defined(FUZZING_ASSERT_VALID_SEQUENCE)
+ assert(!ZSTD_isError(oneSeqSize));
+ ZSTD_assertValidSequence(dctx, op, oend, sequences[seqNb&STORED_SEQS_MASK], prefixStart, dictStart);
+#endif
+ if (ZSTD_isError(oneSeqSize)) return oneSeqSize;
+ op += oneSeqSize;
+ }
+ }
+
+ /* save reps for next block */
+ { U32 i; for (i=0; i<ZSTD_REP_NUM; i++) dctx->entropy.rep[i] = (U32)(seqState.prevOffset[i]); }
+ }
+
+ /* last literal segment */
+ if (dctx->litBufferLocation == ZSTD_split) { /* first deplete literal buffer in dst, then copy litExtraBuffer */
+ size_t const lastLLSize = litBufferEnd - litPtr;
+ RETURN_ERROR_IF(lastLLSize > (size_t)(oend - op), dstSize_tooSmall, "");
+ if (op != NULL) {
+ ZSTD_memmove(op, litPtr, lastLLSize);
+ op += lastLLSize;
+ }
+ litPtr = dctx->litExtraBuffer;
+ litBufferEnd = dctx->litExtraBuffer + ZSTD_LITBUFFEREXTRASIZE;
+ }
+ { size_t const lastLLSize = litBufferEnd - litPtr;
+ RETURN_ERROR_IF(lastLLSize > (size_t)(oend-op), dstSize_tooSmall, "");
+ if (op != NULL) {
+ ZSTD_memmove(op, litPtr, lastLLSize);
+ op += lastLLSize;
+ }
+ }
+
+ return (size_t)(op - ostart);
+}
+
+static size_t
+ZSTD_decompressSequencesLong_default(ZSTD_DCtx* dctx,
+ void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset)
+{
+ return ZSTD_decompressSequencesLong_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset);
+}
+#endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */
+
+
+
+#if DYNAMIC_BMI2
+
+#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG
+static BMI2_TARGET_ATTRIBUTE size_t
+DONT_VECTORIZE
+ZSTD_decompressSequences_bmi2(ZSTD_DCtx* dctx,
+ void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset)
+{
+ return ZSTD_decompressSequences_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset);
+}
+static BMI2_TARGET_ATTRIBUTE size_t
+DONT_VECTORIZE
+ZSTD_decompressSequencesSplitLitBuffer_bmi2(ZSTD_DCtx* dctx,
+ void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset)
+{
+ return ZSTD_decompressSequences_bodySplitLitBuffer(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset);
+}
+#endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */
+
+#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT
+static BMI2_TARGET_ATTRIBUTE size_t
+ZSTD_decompressSequencesLong_bmi2(ZSTD_DCtx* dctx,
+ void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset)
+{
+ return ZSTD_decompressSequencesLong_body(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset);
+}
+#endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */
+
+#endif /* DYNAMIC_BMI2 */
+
+typedef size_t (*ZSTD_decompressSequences_t)(
+ ZSTD_DCtx* dctx,
+ void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset);
+
+#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG
+static size_t
+ZSTD_decompressSequences(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset)
+{
+ DEBUGLOG(5, "ZSTD_decompressSequences");
+#if DYNAMIC_BMI2
+ if (ZSTD_DCtx_get_bmi2(dctx)) {
+ return ZSTD_decompressSequences_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset);
+ }
+#endif
+ return ZSTD_decompressSequences_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset);
+}
+static size_t
+ZSTD_decompressSequencesSplitLitBuffer(ZSTD_DCtx* dctx, void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset)
+{
+ DEBUGLOG(5, "ZSTD_decompressSequencesSplitLitBuffer");
+#if DYNAMIC_BMI2
+ if (ZSTD_DCtx_get_bmi2(dctx)) {
+ return ZSTD_decompressSequencesSplitLitBuffer_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset);
+ }
+#endif
+ return ZSTD_decompressSequencesSplitLitBuffer_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset);
+}
+#endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG */
+
+
+#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT
+/* ZSTD_decompressSequencesLong() :
+ * decompression function triggered when a minimum share of offsets is considered "long",
+ * aka out of cache.
+ * note : "long" definition seems overloaded here, sometimes meaning "wider than bitstream register", and sometimes meaning "farther than memory cache distance".
+ * This function will try to mitigate main memory latency through the use of prefetching */
+static size_t
+ZSTD_decompressSequencesLong(ZSTD_DCtx* dctx,
+ void* dst, size_t maxDstSize,
+ const void* seqStart, size_t seqSize, int nbSeq,
+ const ZSTD_longOffset_e isLongOffset)
+{
+ DEBUGLOG(5, "ZSTD_decompressSequencesLong");
+#if DYNAMIC_BMI2
+ if (ZSTD_DCtx_get_bmi2(dctx)) {
+ return ZSTD_decompressSequencesLong_bmi2(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset);
+ }
+#endif
+ return ZSTD_decompressSequencesLong_default(dctx, dst, maxDstSize, seqStart, seqSize, nbSeq, isLongOffset);
+}
+#endif /* ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT */
+
+
+/**
+ * @returns The total size of the history referenceable by zstd, including
+ * both the prefix and the extDict. At @p op any offset larger than this
+ * is invalid.
+ */
+static size_t ZSTD_totalHistorySize(BYTE* op, BYTE const* virtualStart)
+{
+ return (size_t)(op - virtualStart);
+}
+
+typedef struct {
+ unsigned longOffsetShare;
+ unsigned maxNbAdditionalBits;
+} ZSTD_OffsetInfo;
+
+/* ZSTD_getOffsetInfo() :
+ * condition : offTable must be valid
+ * @return : "share" of long offsets (arbitrarily defined as > (1<<23))
+ * compared to maximum possible of (1<<OffFSELog),
+ * as well as the maximum number additional bits required.
+ */
+static ZSTD_OffsetInfo
+ZSTD_getOffsetInfo(const ZSTD_seqSymbol* offTable, int nbSeq)
+{
+ ZSTD_OffsetInfo info = {0, 0};
+ /* If nbSeq == 0, then the offTable is uninitialized, but we have
+ * no sequences, so both values should be 0.
+ */
+ if (nbSeq != 0) {
+ const void* ptr = offTable;
+ U32 const tableLog = ((const ZSTD_seqSymbol_header*)ptr)[0].tableLog;
+ const ZSTD_seqSymbol* table = offTable + 1;
+ U32 const max = 1 << tableLog;
+ U32 u;
+ DEBUGLOG(5, "ZSTD_getLongOffsetsShare: (tableLog=%u)", tableLog);
+
+ assert(max <= (1 << OffFSELog)); /* max not too large */
+ for (u=0; u<max; u++) {
+ info.maxNbAdditionalBits = MAX(info.maxNbAdditionalBits, table[u].nbAdditionalBits);
+ if (table[u].nbAdditionalBits > 22) info.longOffsetShare += 1;
+ }
+
+ assert(tableLog <= OffFSELog);
+ info.longOffsetShare <<= (OffFSELog - tableLog); /* scale to OffFSELog */
+ }
+
+ return info;
+}
+
+/**
+ * @returns The maximum offset we can decode in one read of our bitstream, without
+ * reloading more bits in the middle of the offset bits read. Any offsets larger
+ * than this must use the long offset decoder.
+ */
+static size_t ZSTD_maxShortOffset(void)
+{
+ if (MEM_64bits()) {
+ /* We can decode any offset without reloading bits.
+ * This might change if the max window size grows.
+ */
+ ZSTD_STATIC_ASSERT(ZSTD_WINDOWLOG_MAX <= 31);
+ return (size_t)-1;
+ } else {
+ /* The maximum offBase is (1 << (STREAM_ACCUMULATOR_MIN + 1)) - 1.
+ * This offBase would require STREAM_ACCUMULATOR_MIN extra bits.
+ * Then we have to subtract ZSTD_REP_NUM to get the maximum possible offset.
+ */
+ size_t const maxOffbase = ((size_t)1 << (STREAM_ACCUMULATOR_MIN + 1)) - 1;
+ size_t const maxOffset = maxOffbase - ZSTD_REP_NUM;
+ assert(ZSTD_highbit32((U32)maxOffbase) == STREAM_ACCUMULATOR_MIN);
+ return maxOffset;
+ }
+}
+
+size_t
+ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize, const streaming_operation streaming)
+{ /* blockType == blockCompressed */
+ const BYTE* ip = (const BYTE*)src;
+ DEBUGLOG(5, "ZSTD_decompressBlock_internal (cSize : %u)", (unsigned)srcSize);
+
+ /* Note : the wording of the specification
+ * allows compressed block to be sized exactly ZSTD_blockSizeMax(dctx).
+ * This generally does not happen, as it makes little sense,
+ * since an uncompressed block would feature same size and have no decompression cost.
+ * Also, note that decoder from reference libzstd before < v1.5.4
+ * would consider this edge case as an error.
+ * As a consequence, avoid generating compressed blocks of size ZSTD_blockSizeMax(dctx)
+ * for broader compatibility with the deployed ecosystem of zstd decoders */
+ RETURN_ERROR_IF(srcSize > ZSTD_blockSizeMax(dctx), srcSize_wrong, "");
+
+ /* Decode literals section */
+ { size_t const litCSize = ZSTD_decodeLiteralsBlock(dctx, src, srcSize, dst, dstCapacity, streaming);
+ DEBUGLOG(5, "ZSTD_decodeLiteralsBlock : cSize=%u, nbLiterals=%zu", (U32)litCSize, dctx->litSize);
+ if (ZSTD_isError(litCSize)) return litCSize;
+ ip += litCSize;
+ srcSize -= litCSize;
+ }
+
+ /* Build Decoding Tables */
+ {
+ /* Compute the maximum block size, which must also work when !frame and fParams are unset.
+ * Additionally, take the min with dstCapacity to ensure that the totalHistorySize fits in a size_t.
+ */
+ size_t const blockSizeMax = MIN(dstCapacity, ZSTD_blockSizeMax(dctx));
+ size_t const totalHistorySize = ZSTD_totalHistorySize(ZSTD_maybeNullPtrAdd((BYTE*)dst, blockSizeMax), (BYTE const*)dctx->virtualStart);
+ /* isLongOffset must be true if there are long offsets.
+ * Offsets are long if they are larger than ZSTD_maxShortOffset().
+ * We don't expect that to be the case in 64-bit mode.
+ *
+ * We check here to see if our history is large enough to allow long offsets.
+ * If it isn't, then we can't possible have (valid) long offsets. If the offset
+ * is invalid, then it is okay to read it incorrectly.
+ *
+ * If isLongOffsets is true, then we will later check our decoding table to see
+ * if it is even possible to generate long offsets.
+ */
+ ZSTD_longOffset_e isLongOffset = (ZSTD_longOffset_e)(MEM_32bits() && (totalHistorySize > ZSTD_maxShortOffset()));
+ /* These macros control at build-time which decompressor implementation
+ * we use. If neither is defined, we do some inspection and dispatch at
+ * runtime.
+ */
+#if !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) && \
+ !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG)
+ int usePrefetchDecoder = dctx->ddictIsCold;
+#else
+ /* Set to 1 to avoid computing offset info if we don't need to.
+ * Otherwise this value is ignored.
+ */
+ int usePrefetchDecoder = 1;
+#endif
+ int nbSeq;
+ size_t const seqHSize = ZSTD_decodeSeqHeaders(dctx, &nbSeq, ip, srcSize);
+ if (ZSTD_isError(seqHSize)) return seqHSize;
+ ip += seqHSize;
+ srcSize -= seqHSize;
+
+ RETURN_ERROR_IF((dst == NULL || dstCapacity == 0) && nbSeq > 0, dstSize_tooSmall, "NULL not handled");
+ RETURN_ERROR_IF(MEM_64bits() && sizeof(size_t) == sizeof(void*) && (size_t)(-1) - (size_t)dst < (size_t)(1 << 20), dstSize_tooSmall,
+ "invalid dst");
+
+ /* If we could potentially have long offsets, or we might want to use the prefetch decoder,
+ * compute information about the share of long offsets, and the maximum nbAdditionalBits.
+ * NOTE: could probably use a larger nbSeq limit
+ */
+ if (isLongOffset || (!usePrefetchDecoder && (totalHistorySize > (1u << 24)) && (nbSeq > 8))) {
+ ZSTD_OffsetInfo const info = ZSTD_getOffsetInfo(dctx->OFTptr, nbSeq);
+ if (isLongOffset && info.maxNbAdditionalBits <= STREAM_ACCUMULATOR_MIN) {
+ /* If isLongOffset, but the maximum number of additional bits that we see in our table is small
+ * enough, then we know it is impossible to have too long an offset in this block, so we can
+ * use the regular offset decoder.
+ */
+ isLongOffset = ZSTD_lo_isRegularOffset;
+ }
+ if (!usePrefetchDecoder) {
+ U32 const minShare = MEM_64bits() ? 7 : 20; /* heuristic values, correspond to 2.73% and 7.81% */
+ usePrefetchDecoder = (info.longOffsetShare >= minShare);
+ }
+ }
+
+ dctx->ddictIsCold = 0;
+
+#if !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT) && \
+ !defined(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG)
+ if (usePrefetchDecoder) {
+#else
+ (void)usePrefetchDecoder;
+ {
+#endif
+#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT
+ return ZSTD_decompressSequencesLong(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset);
+#endif
+ }
+
+#ifndef ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG
+ /* else */
+ if (dctx->litBufferLocation == ZSTD_split)
+ return ZSTD_decompressSequencesSplitLitBuffer(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset);
+ else
+ return ZSTD_decompressSequences(dctx, dst, dstCapacity, ip, srcSize, nbSeq, isLongOffset);
+#endif
+ }
+}
+
+
+ZSTD_ALLOW_POINTER_OVERFLOW_ATTR
+void ZSTD_checkContinuity(ZSTD_DCtx* dctx, const void* dst, size_t dstSize)
+{
+ if (dst != dctx->previousDstEnd && dstSize > 0) { /* not contiguous */
+ dctx->dictEnd = dctx->previousDstEnd;
+ dctx->virtualStart = (const char*)dst - ((const char*)(dctx->previousDstEnd) - (const char*)(dctx->prefixStart));
+ dctx->prefixStart = dst;
+ dctx->previousDstEnd = dst;
+ }
+}
+
+
+size_t ZSTD_decompressBlock_deprecated(ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize)
+{
+ size_t dSize;
+ dctx->isFrameDecompression = 0;
+ ZSTD_checkContinuity(dctx, dst, dstCapacity);
+ dSize = ZSTD_decompressBlock_internal(dctx, dst, dstCapacity, src, srcSize, not_streaming);
+ FORWARD_IF_ERROR(dSize, "");
+ dctx->previousDstEnd = (char*)dst + dSize;
+ return dSize;
+}
+
+
+/* NOTE: Must just wrap ZSTD_decompressBlock_deprecated() */
+size_t ZSTD_decompressBlock(ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize)
+{
+ return ZSTD_decompressBlock_deprecated(dctx, dst, dstCapacity, src, srcSize);
+}
diff --git a/third_party/zstd/lib/decompress/zstd_decompress_block.h b/third_party/zstd/lib/decompress/zstd_decompress_block.h
new file mode 100644
index 0000000000..ab152404ba
--- /dev/null
+++ b/third_party/zstd/lib/decompress/zstd_decompress_block.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+
+#ifndef ZSTD_DEC_BLOCK_H
+#define ZSTD_DEC_BLOCK_H
+
+/*-*******************************************************
+ * Dependencies
+ *********************************************************/
+#include "../common/zstd_deps.h" /* size_t */
+#include "../zstd.h" /* DCtx, and some public functions */
+#include "../common/zstd_internal.h" /* blockProperties_t, and some public functions */
+#include "zstd_decompress_internal.h" /* ZSTD_seqSymbol */
+
+
+/* === Prototypes === */
+
+/* note: prototypes already published within `zstd.h` :
+ * ZSTD_decompressBlock()
+ */
+
+/* note: prototypes already published within `zstd_internal.h` :
+ * ZSTD_getcBlockSize()
+ * ZSTD_decodeSeqHeaders()
+ */
+
+
+ /* Streaming state is used to inform allocation of the literal buffer */
+typedef enum {
+ not_streaming = 0,
+ is_streaming = 1
+} streaming_operation;
+
+/* ZSTD_decompressBlock_internal() :
+ * decompress block, starting at `src`,
+ * into destination buffer `dst`.
+ * @return : decompressed block size,
+ * or an error code (which can be tested using ZSTD_isError())
+ */
+size_t ZSTD_decompressBlock_internal(ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize, const streaming_operation streaming);
+
+/* ZSTD_buildFSETable() :
+ * generate FSE decoding table for one symbol (ll, ml or off)
+ * this function must be called with valid parameters only
+ * (dt is large enough, normalizedCounter distribution total is a power of 2, max is within range, etc.)
+ * in which case it cannot fail.
+ * The workspace must be 4-byte aligned and at least ZSTD_BUILD_FSE_TABLE_WKSP_SIZE bytes, which is
+ * defined in zstd_decompress_internal.h.
+ * Internal use only.
+ */
+void ZSTD_buildFSETable(ZSTD_seqSymbol* dt,
+ const short* normalizedCounter, unsigned maxSymbolValue,
+ const U32* baseValue, const U8* nbAdditionalBits,
+ unsigned tableLog, void* wksp, size_t wkspSize,
+ int bmi2);
+
+/* Internal definition of ZSTD_decompressBlock() to avoid deprecation warnings. */
+size_t ZSTD_decompressBlock_deprecated(ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize);
+
+
+#endif /* ZSTD_DEC_BLOCK_H */
diff --git a/third_party/zstd/lib/decompress/zstd_decompress_internal.h b/third_party/zstd/lib/decompress/zstd_decompress_internal.h
new file mode 100644
index 0000000000..83a7a0115f
--- /dev/null
+++ b/third_party/zstd/lib/decompress/zstd_decompress_internal.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+
+/* zstd_decompress_internal:
+ * objects and definitions shared within lib/decompress modules */
+
+ #ifndef ZSTD_DECOMPRESS_INTERNAL_H
+ #define ZSTD_DECOMPRESS_INTERNAL_H
+
+
+/*-*******************************************************
+ * Dependencies
+ *********************************************************/
+#include "../common/mem.h" /* BYTE, U16, U32 */
+#include "../common/zstd_internal.h" /* constants : MaxLL, MaxML, MaxOff, LLFSELog, etc. */
+
+
+
+/*-*******************************************************
+ * Constants
+ *********************************************************/
+static UNUSED_ATTR const U32 LL_base[MaxLL+1] = {
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 18, 20, 22, 24, 28, 32, 40,
+ 48, 64, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000,
+ 0x2000, 0x4000, 0x8000, 0x10000 };
+
+static UNUSED_ATTR const U32 OF_base[MaxOff+1] = {
+ 0, 1, 1, 5, 0xD, 0x1D, 0x3D, 0x7D,
+ 0xFD, 0x1FD, 0x3FD, 0x7FD, 0xFFD, 0x1FFD, 0x3FFD, 0x7FFD,
+ 0xFFFD, 0x1FFFD, 0x3FFFD, 0x7FFFD, 0xFFFFD, 0x1FFFFD, 0x3FFFFD, 0x7FFFFD,
+ 0xFFFFFD, 0x1FFFFFD, 0x3FFFFFD, 0x7FFFFFD, 0xFFFFFFD, 0x1FFFFFFD, 0x3FFFFFFD, 0x7FFFFFFD };
+
+static UNUSED_ATTR const U8 OF_bits[MaxOff+1] = {
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31 };
+
+static UNUSED_ATTR const U32 ML_base[MaxML+1] = {
+ 3, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, 26,
+ 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 37, 39, 41, 43, 47, 51, 59,
+ 67, 83, 99, 0x83, 0x103, 0x203, 0x403, 0x803,
+ 0x1003, 0x2003, 0x4003, 0x8003, 0x10003 };
+
+
+/*-*******************************************************
+ * Decompression types
+ *********************************************************/
+ typedef struct {
+ U32 fastMode;
+ U32 tableLog;
+ } ZSTD_seqSymbol_header;
+
+ typedef struct {
+ U16 nextState;
+ BYTE nbAdditionalBits;
+ BYTE nbBits;
+ U32 baseValue;
+ } ZSTD_seqSymbol;
+
+ #define SEQSYMBOL_TABLE_SIZE(log) (1 + (1 << (log)))
+
+#define ZSTD_BUILD_FSE_TABLE_WKSP_SIZE (sizeof(S16) * (MaxSeq + 1) + (1u << MaxFSELog) + sizeof(U64))
+#define ZSTD_BUILD_FSE_TABLE_WKSP_SIZE_U32 ((ZSTD_BUILD_FSE_TABLE_WKSP_SIZE + sizeof(U32) - 1) / sizeof(U32))
+#define ZSTD_HUFFDTABLE_CAPACITY_LOG 12
+
+typedef struct {
+ ZSTD_seqSymbol LLTable[SEQSYMBOL_TABLE_SIZE(LLFSELog)]; /* Note : Space reserved for FSE Tables */
+ ZSTD_seqSymbol OFTable[SEQSYMBOL_TABLE_SIZE(OffFSELog)]; /* is also used as temporary workspace while building hufTable during DDict creation */
+ ZSTD_seqSymbol MLTable[SEQSYMBOL_TABLE_SIZE(MLFSELog)]; /* and therefore must be at least HUF_DECOMPRESS_WORKSPACE_SIZE large */
+ HUF_DTable hufTable[HUF_DTABLE_SIZE(ZSTD_HUFFDTABLE_CAPACITY_LOG)]; /* can accommodate HUF_decompress4X */
+ U32 rep[ZSTD_REP_NUM];
+ U32 workspace[ZSTD_BUILD_FSE_TABLE_WKSP_SIZE_U32];
+} ZSTD_entropyDTables_t;
+
+typedef enum { ZSTDds_getFrameHeaderSize, ZSTDds_decodeFrameHeader,
+ ZSTDds_decodeBlockHeader, ZSTDds_decompressBlock,
+ ZSTDds_decompressLastBlock, ZSTDds_checkChecksum,
+ ZSTDds_decodeSkippableHeader, ZSTDds_skipFrame } ZSTD_dStage;
+
+typedef enum { zdss_init=0, zdss_loadHeader,
+ zdss_read, zdss_load, zdss_flush } ZSTD_dStreamStage;
+
+typedef enum {
+ ZSTD_use_indefinitely = -1, /* Use the dictionary indefinitely */
+ ZSTD_dont_use = 0, /* Do not use the dictionary (if one exists free it) */
+ ZSTD_use_once = 1 /* Use the dictionary once and set to ZSTD_dont_use */
+} ZSTD_dictUses_e;
+
+/* Hashset for storing references to multiple ZSTD_DDict within ZSTD_DCtx */
+typedef struct {
+ const ZSTD_DDict** ddictPtrTable;
+ size_t ddictPtrTableSize;
+ size_t ddictPtrCount;
+} ZSTD_DDictHashSet;
+
+#ifndef ZSTD_DECODER_INTERNAL_BUFFER
+# define ZSTD_DECODER_INTERNAL_BUFFER (1 << 16)
+#endif
+
+#define ZSTD_LBMIN 64
+#define ZSTD_LBMAX (128 << 10)
+
+/* extra buffer, compensates when dst is not large enough to store litBuffer */
+#define ZSTD_LITBUFFEREXTRASIZE BOUNDED(ZSTD_LBMIN, ZSTD_DECODER_INTERNAL_BUFFER, ZSTD_LBMAX)
+
+typedef enum {
+ ZSTD_not_in_dst = 0, /* Stored entirely within litExtraBuffer */
+ ZSTD_in_dst = 1, /* Stored entirely within dst (in memory after current output write) */
+ ZSTD_split = 2 /* Split between litExtraBuffer and dst */
+} ZSTD_litLocation_e;
+
+struct ZSTD_DCtx_s
+{
+ const ZSTD_seqSymbol* LLTptr;
+ const ZSTD_seqSymbol* MLTptr;
+ const ZSTD_seqSymbol* OFTptr;
+ const HUF_DTable* HUFptr;
+ ZSTD_entropyDTables_t entropy;
+ U32 workspace[HUF_DECOMPRESS_WORKSPACE_SIZE_U32]; /* space needed when building huffman tables */
+ const void* previousDstEnd; /* detect continuity */
+ const void* prefixStart; /* start of current segment */
+ const void* virtualStart; /* virtual start of previous segment if it was just before current one */
+ const void* dictEnd; /* end of previous segment */
+ size_t expected;
+ ZSTD_frameHeader fParams;
+ U64 processedCSize;
+ U64 decodedSize;
+ blockType_e bType; /* used in ZSTD_decompressContinue(), store blockType between block header decoding and block decompression stages */
+ ZSTD_dStage stage;
+ U32 litEntropy;
+ U32 fseEntropy;
+ XXH64_state_t xxhState;
+ size_t headerSize;
+ ZSTD_format_e format;
+ ZSTD_forceIgnoreChecksum_e forceIgnoreChecksum; /* User specified: if == 1, will ignore checksums in compressed frame. Default == 0 */
+ U32 validateChecksum; /* if == 1, will validate checksum. Is == 1 if (fParams.checksumFlag == 1) and (forceIgnoreChecksum == 0). */
+ const BYTE* litPtr;
+ ZSTD_customMem customMem;
+ size_t litSize;
+ size_t rleSize;
+ size_t staticSize;
+ int isFrameDecompression;
+#if DYNAMIC_BMI2 != 0
+ int bmi2; /* == 1 if the CPU supports BMI2 and 0 otherwise. CPU support is determined dynamically once per context lifetime. */
+#endif
+
+ /* dictionary */
+ ZSTD_DDict* ddictLocal;
+ const ZSTD_DDict* ddict; /* set by ZSTD_initDStream_usingDDict(), or ZSTD_DCtx_refDDict() */
+ U32 dictID;
+ int ddictIsCold; /* if == 1 : dictionary is "new" for working context, and presumed "cold" (not in cpu cache) */
+ ZSTD_dictUses_e dictUses;
+ ZSTD_DDictHashSet* ddictSet; /* Hash set for multiple ddicts */
+ ZSTD_refMultipleDDicts_e refMultipleDDicts; /* User specified: if == 1, will allow references to multiple DDicts. Default == 0 (disabled) */
+ int disableHufAsm;
+ int maxBlockSizeParam;
+
+ /* streaming */
+ ZSTD_dStreamStage streamStage;
+ char* inBuff;
+ size_t inBuffSize;
+ size_t inPos;
+ size_t maxWindowSize;
+ char* outBuff;
+ size_t outBuffSize;
+ size_t outStart;
+ size_t outEnd;
+ size_t lhSize;
+#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT>=1)
+ void* legacyContext;
+ U32 previousLegacyVersion;
+ U32 legacyVersion;
+#endif
+ U32 hostageByte;
+ int noForwardProgress;
+ ZSTD_bufferMode_e outBufferMode;
+ ZSTD_outBuffer expectedOutBuffer;
+
+ /* workspace */
+ BYTE* litBuffer;
+ const BYTE* litBufferEnd;
+ ZSTD_litLocation_e litBufferLocation;
+ BYTE litExtraBuffer[ZSTD_LITBUFFEREXTRASIZE + WILDCOPY_OVERLENGTH]; /* literal buffer can be split between storage within dst and within this scratch buffer */
+ BYTE headerBuffer[ZSTD_FRAMEHEADERSIZE_MAX];
+
+ size_t oversizedDuration;
+
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ void const* dictContentBeginForFuzzing;
+ void const* dictContentEndForFuzzing;
+#endif
+
+ /* Tracing */
+#if ZSTD_TRACE
+ ZSTD_TraceCtx traceCtx;
+#endif
+}; /* typedef'd to ZSTD_DCtx within "zstd.h" */
+
+MEM_STATIC int ZSTD_DCtx_get_bmi2(const struct ZSTD_DCtx_s *dctx) {
+#if DYNAMIC_BMI2 != 0
+ return dctx->bmi2;
+#else
+ (void)dctx;
+ return 0;
+#endif
+}
+
+/*-*******************************************************
+ * Shared internal functions
+ *********************************************************/
+
+/*! ZSTD_loadDEntropy() :
+ * dict : must point at beginning of a valid zstd dictionary.
+ * @return : size of dictionary header (size of magic number + dict ID + entropy tables) */
+size_t ZSTD_loadDEntropy(ZSTD_entropyDTables_t* entropy,
+ const void* const dict, size_t const dictSize);
+
+/*! ZSTD_checkContinuity() :
+ * check if next `dst` follows previous position, where decompression ended.
+ * If yes, do nothing (continue on current segment).
+ * If not, classify previous segment as "external dictionary", and start a new segment.
+ * This function cannot fail. */
+void ZSTD_checkContinuity(ZSTD_DCtx* dctx, const void* dst, size_t dstSize);
+
+
+#endif /* ZSTD_DECOMPRESS_INTERNAL_H */
diff --git a/third_party/zstd/lib/libzstd.mk b/third_party/zstd/lib/libzstd.mk
new file mode 100644
index 0000000000..14b3b11d68
--- /dev/null
+++ b/third_party/zstd/lib/libzstd.mk
@@ -0,0 +1,235 @@
+# ################################################################
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+#
+# This source code is licensed under both the BSD-style license (found in the
+# LICENSE file in the root directory of this source tree) and the GPLv2 (found
+# in the COPYING file in the root directory of this source tree).
+# You may select, at your option, one of the above-listed licenses.
+# ################################################################
+
+# This included Makefile provides the following variables :
+# LIB_SRCDIR, LIB_BINDIR
+
+# Ensure the file is not included twice
+# Note : must be included after setting the default target
+ifndef LIBZSTD_MK_INCLUDED
+LIBZSTD_MK_INCLUDED := 1
+
+##################################################################
+# Input Variables
+##################################################################
+
+# By default, library's directory is same as this included makefile
+LIB_SRCDIR ?= $(dir $(realpath $(lastword $(MAKEFILE_LIST))))
+LIB_BINDIR ?= $(LIBSRC_DIR)
+
+# ZSTD_LIB_MINIFY is a helper variable that
+# configures a bunch of other variables to space-optimized defaults.
+ZSTD_LIB_MINIFY ?= 0
+
+# Legacy support
+ifneq ($(ZSTD_LIB_MINIFY), 0)
+ ZSTD_LEGACY_SUPPORT ?= 0
+else
+ ZSTD_LEGACY_SUPPORT ?= 5
+endif
+ZSTD_LEGACY_MULTITHREADED_API ?= 0
+
+# Build size optimizations
+ifneq ($(ZSTD_LIB_MINIFY), 0)
+ HUF_FORCE_DECOMPRESS_X1 ?= 1
+ HUF_FORCE_DECOMPRESS_X2 ?= 0
+ ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT ?= 1
+ ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG ?= 0
+ ZSTD_NO_INLINE ?= 1
+ ZSTD_STRIP_ERROR_STRINGS ?= 1
+else
+ HUF_FORCE_DECOMPRESS_X1 ?= 0
+ HUF_FORCE_DECOMPRESS_X2 ?= 0
+ ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT ?= 0
+ ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG ?= 0
+ ZSTD_NO_INLINE ?= 0
+ ZSTD_STRIP_ERROR_STRINGS ?= 0
+endif
+
+# Assembly support
+ZSTD_NO_ASM ?= 0
+
+ZSTD_LIB_EXCLUDE_COMPRESSORS_DFAST_AND_UP ?= 0
+ZSTD_LIB_EXCLUDE_COMPRESSORS_GREEDY_AND_UP ?= 0
+
+##################################################################
+# libzstd helpers
+##################################################################
+
+VOID ?= /dev/null
+
+# Make 4.3 doesn't support '\#' anymore (https://lwn.net/Articles/810071/)
+NUM_SYMBOL := \#
+
+# define silent mode as default (verbose mode with V=1 or VERBOSE=1)
+# Note : must be defined _after_ the default target
+$(V)$(VERBOSE).SILENT:
+
+# When cross-compiling from linux to windows,
+# one might need to specify TARGET_SYSTEM as "Windows."
+# Building from Fedora fails without it.
+# (but Ubuntu and Debian don't need to set anything)
+TARGET_SYSTEM ?= $(OS)
+
+# Version numbers
+LIBVER_SRC := $(LIB_SRCDIR)/zstd.h
+LIBVER_MAJOR_SCRIPT:=`sed -n '/define ZSTD_VERSION_MAJOR/s/.*[[:blank:]]\([0-9][0-9]*\).*/\1/p' < $(LIBVER_SRC)`
+LIBVER_MINOR_SCRIPT:=`sed -n '/define ZSTD_VERSION_MINOR/s/.*[[:blank:]]\([0-9][0-9]*\).*/\1/p' < $(LIBVER_SRC)`
+LIBVER_PATCH_SCRIPT:=`sed -n '/define ZSTD_VERSION_RELEASE/s/.*[[:blank:]]\([0-9][0-9]*\).*/\1/p' < $(LIBVER_SRC)`
+LIBVER_SCRIPT:= $(LIBVER_MAJOR_SCRIPT).$(LIBVER_MINOR_SCRIPT).$(LIBVER_PATCH_SCRIPT)
+LIBVER_MAJOR := $(shell echo $(LIBVER_MAJOR_SCRIPT))
+LIBVER_MINOR := $(shell echo $(LIBVER_MINOR_SCRIPT))
+LIBVER_PATCH := $(shell echo $(LIBVER_PATCH_SCRIPT))
+LIBVER := $(shell echo $(LIBVER_SCRIPT))
+CCVER := $(shell $(CC) --version)
+ZSTD_VERSION?= $(LIBVER)
+
+ifneq ($(ZSTD_LIB_MINIFY), 0)
+ HAVE_CC_OZ ?= $(shell echo "" | $(CC) -Oz -x c -c - -o /dev/null 2> /dev/null && echo 1 || echo 0)
+ifneq ($(HAVE_CC_OZ), 0)
+ # Some compilers (clang) support an even more space-optimized setting.
+ CFLAGS += -Oz
+else
+ CFLAGS += -Os
+endif
+ CFLAGS += -fno-stack-protector -fomit-frame-pointer -fno-ident \
+ -DDYNAMIC_BMI2=0 -DNDEBUG
+else
+ CFLAGS ?= -O3
+endif
+
+DEBUGLEVEL ?= 0
+CPPFLAGS += -DXXH_NAMESPACE=ZSTD_ -DDEBUGLEVEL=$(DEBUGLEVEL)
+ifeq ($(TARGET_SYSTEM),Windows_NT) # MinGW assumed
+ CPPFLAGS += -D__USE_MINGW_ANSI_STDIO # compatibility with %zu formatting
+endif
+DEBUGFLAGS= -Wall -Wextra -Wcast-qual -Wcast-align -Wshadow \
+ -Wstrict-aliasing=1 -Wswitch-enum -Wdeclaration-after-statement \
+ -Wstrict-prototypes -Wundef -Wpointer-arith \
+ -Wvla -Wformat=2 -Winit-self -Wfloat-equal -Wwrite-strings \
+ -Wredundant-decls -Wmissing-prototypes -Wc++-compat
+CFLAGS += $(DEBUGFLAGS) $(MOREFLAGS)
+ASFLAGS += $(DEBUGFLAGS) $(MOREFLAGS) $(CFLAGS)
+LDFLAGS += $(MOREFLAGS)
+FLAGS = $(CPPFLAGS) $(CFLAGS) $(ASFLAGS) $(LDFLAGS)
+
+ifndef ALREADY_APPENDED_NOEXECSTACK
+export ALREADY_APPENDED_NOEXECSTACK := 1
+ifeq ($(shell echo "int main(int argc, char* argv[]) { (void)argc; (void)argv; return 0; }" | $(CC) $(FLAGS) -z noexecstack -x c -Werror - -o $(VOID) 2>$(VOID) && echo 1 || echo 0),1)
+LDFLAGS += -z noexecstack
+endif
+ifeq ($(shell echo | $(CC) $(FLAGS) -Wa,--noexecstack -x assembler -Werror -c - -o $(VOID) 2>$(VOID) && echo 1 || echo 0),1)
+CFLAGS += -Wa,--noexecstack
+# CFLAGS are also added to ASFLAGS
+else ifeq ($(shell echo | $(CC) $(FLAGS) -Qunused-arguments -Wa,--noexecstack -x assembler -Werror -c - -o $(VOID) 2>$(VOID) && echo 1 || echo 0),1)
+# See e.g.: https://github.com/android/ndk/issues/171
+CFLAGS += -Qunused-arguments -Wa,--noexecstack
+# CFLAGS are also added to ASFLAGS
+endif
+endif
+
+ifeq ($(shell echo "int main(int argc, char* argv[]) { (void)argc; (void)argv; return 0; }" | $(CC) $(FLAGS) -z cet-report=error -x c -Werror - -o $(VOID) 2>$(VOID) && echo 1 || echo 0),1)
+LDFLAGS += -z cet-report=error
+endif
+
+HAVE_COLORNEVER = $(shell echo a | grep --color=never a > /dev/null 2> /dev/null && echo 1 || echo 0)
+GREP_OPTIONS ?=
+ifeq ($(HAVE_COLORNEVER), 1)
+ GREP_OPTIONS += --color=never
+endif
+GREP = grep $(GREP_OPTIONS)
+
+ZSTD_COMMON_FILES := $(sort $(wildcard $(LIB_SRCDIR)/common/*.c))
+ZSTD_COMPRESS_FILES := $(sort $(wildcard $(LIB_SRCDIR)/compress/*.c))
+ZSTD_DECOMPRESS_FILES := $(sort $(wildcard $(LIB_SRCDIR)/decompress/*.c))
+ZSTD_DICTBUILDER_FILES := $(sort $(wildcard $(LIB_SRCDIR)/dictBuilder/*.c))
+ZSTD_DEPRECATED_FILES := $(sort $(wildcard $(LIB_SRCDIR)/deprecated/*.c))
+ZSTD_LEGACY_FILES :=
+
+ZSTD_DECOMPRESS_AMD64_ASM_FILES := $(sort $(wildcard $(LIB_SRCDIR)/decompress/*_amd64.S))
+
+ifneq ($(ZSTD_NO_ASM), 0)
+ CPPFLAGS += -DZSTD_DISABLE_ASM
+else
+ # Unconditionally add the ASM files they are disabled by
+ # macros in the .S file.
+ ZSTD_DECOMPRESS_FILES += $(ZSTD_DECOMPRESS_AMD64_ASM_FILES)
+endif
+
+ifneq ($(HUF_FORCE_DECOMPRESS_X1), 0)
+ CFLAGS += -DHUF_FORCE_DECOMPRESS_X1
+endif
+
+ifneq ($(HUF_FORCE_DECOMPRESS_X2), 0)
+ CFLAGS += -DHUF_FORCE_DECOMPRESS_X2
+endif
+
+ifneq ($(ZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT), 0)
+ CFLAGS += -DZSTD_FORCE_DECOMPRESS_SEQUENCES_SHORT
+endif
+
+ifneq ($(ZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG), 0)
+ CFLAGS += -DZSTD_FORCE_DECOMPRESS_SEQUENCES_LONG
+endif
+
+ifneq ($(ZSTD_NO_INLINE), 0)
+ CFLAGS += -DZSTD_NO_INLINE
+endif
+
+ifneq ($(ZSTD_STRIP_ERROR_STRINGS), 0)
+ CFLAGS += -DZSTD_STRIP_ERROR_STRINGS
+endif
+
+ifneq ($(ZSTD_LEGACY_MULTITHREADED_API), 0)
+ CFLAGS += -DZSTD_LEGACY_MULTITHREADED_API
+endif
+
+ifneq ($(ZSTD_LIB_EXCLUDE_COMPRESSORS_DFAST_AND_UP), 0)
+ CFLAGS += -DZSTD_EXCLUDE_DFAST_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_GREEDY_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_LAZY2_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_BTOPT_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR
+else
+ifneq ($(ZSTD_LIB_EXCLUDE_COMPRESSORS_GREEDY_AND_UP), 0)
+ CFLAGS += -DZSTD_EXCLUDE_GREEDY_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_LAZY2_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_BTLAZY2_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_BTOPT_BLOCK_COMPRESSOR -DZSTD_EXCLUDE_BTULTRA_BLOCK_COMPRESSOR
+endif
+endif
+
+ifneq ($(ZSTD_LEGACY_SUPPORT), 0)
+ifeq ($(shell test $(ZSTD_LEGACY_SUPPORT) -lt 8; echo $$?), 0)
+ ZSTD_LEGACY_FILES += $(shell ls $(LIB_SRCDIR)/legacy/*.c | $(GREP) 'v0[$(ZSTD_LEGACY_SUPPORT)-7]')
+endif
+endif
+CPPFLAGS += -DZSTD_LEGACY_SUPPORT=$(ZSTD_LEGACY_SUPPORT)
+
+UNAME := $(shell uname)
+
+ifndef BUILD_DIR
+ifeq ($(UNAME), Darwin)
+ ifeq ($(shell md5 < /dev/null > /dev/null; echo $$?), 0)
+ HASH ?= md5
+ endif
+else ifeq ($(UNAME), NetBSD)
+ HASH ?= md5 -n
+else ifeq ($(UNAME), OpenBSD)
+ HASH ?= md5
+endif
+HASH ?= md5sum
+
+HASH_DIR = conf_$(shell echo $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(ZSTD_FILES) | $(HASH) | cut -f 1 -d " " )
+HAVE_HASH :=$(shell echo 1 | $(HASH) > /dev/null && echo 1 || echo 0)
+ifeq ($(HAVE_HASH),0)
+ $(info warning : could not find HASH ($(HASH)), needed to differentiate builds using different flags)
+ BUILD_DIR := obj/generic_noconf
+endif
+endif # BUILD_DIR
+
+ZSTD_SUBDIR := $(LIB_SRCDIR)/common $(LIB_SRCDIR)/compress $(LIB_SRCDIR)/decompress $(LIB_SRCDIR)/dictBuilder $(LIB_SRCDIR)/legacy $(LIB_SRCDIR)/deprecated
+vpath %.c $(ZSTD_SUBDIR)
+vpath %.S $(ZSTD_SUBDIR)
+
+endif # LIBZSTD_MK_INCLUDED
diff --git a/third_party/zstd/lib/libzstd.pc.in b/third_party/zstd/lib/libzstd.pc.in
new file mode 100644
index 0000000000..d5cc0270ce
--- /dev/null
+++ b/third_party/zstd/lib/libzstd.pc.in
@@ -0,0 +1,16 @@
+# ZSTD - standard compression algorithm
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# BSD 2-Clause License (https://opensource.org/licenses/bsd-license.php)
+
+prefix=@PREFIX@
+exec_prefix=@EXEC_PREFIX@
+includedir=@INCLUDEDIR@
+libdir=@LIBDIR@
+
+Name: zstd
+Description: fast lossless compression algorithm library
+URL: https://facebook.github.io/zstd/
+Version: @VERSION@
+Libs: -L${libdir} -lzstd
+Libs.private: @LIBS_PRIVATE@
+Cflags: -I${includedir}
diff --git a/third_party/zstd/lib/module.modulemap b/third_party/zstd/lib/module.modulemap
new file mode 100644
index 0000000000..eff98dface
--- /dev/null
+++ b/third_party/zstd/lib/module.modulemap
@@ -0,0 +1,35 @@
+module libzstd [extern_c] {
+ header "zstd.h"
+ export *
+ config_macros [exhaustive] \
+ /* zstd.h */ \
+ ZSTD_STATIC_LINKING_ONLY, \
+ ZSTDLIB_VISIBILITY, \
+ ZSTDLIB_VISIBLE, \
+ ZSTDLIB_HIDDEN, \
+ ZSTD_DLL_EXPORT, \
+ ZSTDLIB_STATIC_API, \
+ ZSTD_DISABLE_DEPRECATE_WARNINGS, \
+ ZSTD_CLEVEL_DEFAULT, \
+ /* zdict.h */ \
+ ZDICT_STATIC_LINKING_ONLY, \
+ ZDICTLIB_VISIBLE, \
+ ZDICTLIB_HIDDEN, \
+ ZDICTLIB_VISIBILITY, \
+ ZDICTLIB_STATIC_API, \
+ ZDICT_DISABLE_DEPRECATE_WARNINGS, \
+ /* zstd_errors.h */ \
+ ZSTDERRORLIB_VISIBLE, \
+ ZSTDERRORLIB_HIDDEN, \
+ ZSTDERRORLIB_VISIBILITY
+
+ module dictbuilder [extern_c] {
+ header "zdict.h"
+ export *
+ }
+
+ module errors [extern_c] {
+ header "zstd_errors.h"
+ export *
+ }
+}
diff --git a/third_party/zstd/lib/zdict.h b/third_party/zstd/lib/zdict.h
new file mode 100644
index 0000000000..2268f948a5
--- /dev/null
+++ b/third_party/zstd/lib/zdict.h
@@ -0,0 +1,474 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+#ifndef ZSTD_ZDICT_H
+#define ZSTD_ZDICT_H
+
+/*====== Dependencies ======*/
+#include <stddef.h> /* size_t */
+
+
+/* ===== ZDICTLIB_API : control library symbols visibility ===== */
+#ifndef ZDICTLIB_VISIBLE
+ /* Backwards compatibility with old macro name */
+# ifdef ZDICTLIB_VISIBILITY
+# define ZDICTLIB_VISIBLE ZDICTLIB_VISIBILITY
+# elif defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__)
+# define ZDICTLIB_VISIBLE __attribute__ ((visibility ("default")))
+# else
+# define ZDICTLIB_VISIBLE
+# endif
+#endif
+
+#ifndef ZDICTLIB_HIDDEN
+# if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__)
+# define ZDICTLIB_HIDDEN __attribute__ ((visibility ("hidden")))
+# else
+# define ZDICTLIB_HIDDEN
+# endif
+#endif
+
+#if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1)
+# define ZDICTLIB_API __declspec(dllexport) ZDICTLIB_VISIBLE
+#elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1)
+# define ZDICTLIB_API __declspec(dllimport) ZDICTLIB_VISIBLE /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
+#else
+# define ZDICTLIB_API ZDICTLIB_VISIBLE
+#endif
+
+/*******************************************************************************
+ * Zstd dictionary builder
+ *
+ * FAQ
+ * ===
+ * Why should I use a dictionary?
+ * ------------------------------
+ *
+ * Zstd can use dictionaries to improve compression ratio of small data.
+ * Traditionally small files don't compress well because there is very little
+ * repetition in a single sample, since it is small. But, if you are compressing
+ * many similar files, like a bunch of JSON records that share the same
+ * structure, you can train a dictionary on ahead of time on some samples of
+ * these files. Then, zstd can use the dictionary to find repetitions that are
+ * present across samples. This can vastly improve compression ratio.
+ *
+ * When is a dictionary useful?
+ * ----------------------------
+ *
+ * Dictionaries are useful when compressing many small files that are similar.
+ * The larger a file is, the less benefit a dictionary will have. Generally,
+ * we don't expect dictionary compression to be effective past 100KB. And the
+ * smaller a file is, the more we would expect the dictionary to help.
+ *
+ * How do I use a dictionary?
+ * --------------------------
+ *
+ * Simply pass the dictionary to the zstd compressor with
+ * `ZSTD_CCtx_loadDictionary()`. The same dictionary must then be passed to
+ * the decompressor, using `ZSTD_DCtx_loadDictionary()`. There are other
+ * more advanced functions that allow selecting some options, see zstd.h for
+ * complete documentation.
+ *
+ * What is a zstd dictionary?
+ * --------------------------
+ *
+ * A zstd dictionary has two pieces: Its header, and its content. The header
+ * contains a magic number, the dictionary ID, and entropy tables. These
+ * entropy tables allow zstd to save on header costs in the compressed file,
+ * which really matters for small data. The content is just bytes, which are
+ * repeated content that is common across many samples.
+ *
+ * What is a raw content dictionary?
+ * ---------------------------------
+ *
+ * A raw content dictionary is just bytes. It doesn't have a zstd dictionary
+ * header, a dictionary ID, or entropy tables. Any buffer is a valid raw
+ * content dictionary.
+ *
+ * How do I train a dictionary?
+ * ----------------------------
+ *
+ * Gather samples from your use case. These samples should be similar to each
+ * other. If you have several use cases, you could try to train one dictionary
+ * per use case.
+ *
+ * Pass those samples to `ZDICT_trainFromBuffer()` and that will train your
+ * dictionary. There are a few advanced versions of this function, but this
+ * is a great starting point. If you want to further tune your dictionary
+ * you could try `ZDICT_optimizeTrainFromBuffer_cover()`. If that is too slow
+ * you can try `ZDICT_optimizeTrainFromBuffer_fastCover()`.
+ *
+ * If the dictionary training function fails, that is likely because you
+ * either passed too few samples, or a dictionary would not be effective
+ * for your data. Look at the messages that the dictionary trainer printed,
+ * if it doesn't say too few samples, then a dictionary would not be effective.
+ *
+ * How large should my dictionary be?
+ * ----------------------------------
+ *
+ * A reasonable dictionary size, the `dictBufferCapacity`, is about 100KB.
+ * The zstd CLI defaults to a 110KB dictionary. You likely don't need a
+ * dictionary larger than that. But, most use cases can get away with a
+ * smaller dictionary. The advanced dictionary builders can automatically
+ * shrink the dictionary for you, and select the smallest size that doesn't
+ * hurt compression ratio too much. See the `shrinkDict` parameter.
+ * A smaller dictionary can save memory, and potentially speed up
+ * compression.
+ *
+ * How many samples should I provide to the dictionary builder?
+ * ------------------------------------------------------------
+ *
+ * We generally recommend passing ~100x the size of the dictionary
+ * in samples. A few thousand should suffice. Having too few samples
+ * can hurt the dictionaries effectiveness. Having more samples will
+ * only improve the dictionaries effectiveness. But having too many
+ * samples can slow down the dictionary builder.
+ *
+ * How do I determine if a dictionary will be effective?
+ * -----------------------------------------------------
+ *
+ * Simply train a dictionary and try it out. You can use zstd's built in
+ * benchmarking tool to test the dictionary effectiveness.
+ *
+ * # Benchmark levels 1-3 without a dictionary
+ * zstd -b1e3 -r /path/to/my/files
+ * # Benchmark levels 1-3 with a dictionary
+ * zstd -b1e3 -r /path/to/my/files -D /path/to/my/dictionary
+ *
+ * When should I retrain a dictionary?
+ * -----------------------------------
+ *
+ * You should retrain a dictionary when its effectiveness drops. Dictionary
+ * effectiveness drops as the data you are compressing changes. Generally, we do
+ * expect dictionaries to "decay" over time, as your data changes, but the rate
+ * at which they decay depends on your use case. Internally, we regularly
+ * retrain dictionaries, and if the new dictionary performs significantly
+ * better than the old dictionary, we will ship the new dictionary.
+ *
+ * I have a raw content dictionary, how do I turn it into a zstd dictionary?
+ * -------------------------------------------------------------------------
+ *
+ * If you have a raw content dictionary, e.g. by manually constructing it, or
+ * using a third-party dictionary builder, you can turn it into a zstd
+ * dictionary by using `ZDICT_finalizeDictionary()`. You'll also have to
+ * provide some samples of the data. It will add the zstd header to the
+ * raw content, which contains a dictionary ID and entropy tables, which
+ * will improve compression ratio, and allow zstd to write the dictionary ID
+ * into the frame, if you so choose.
+ *
+ * Do I have to use zstd's dictionary builder?
+ * -------------------------------------------
+ *
+ * No! You can construct dictionary content however you please, it is just
+ * bytes. It will always be valid as a raw content dictionary. If you want
+ * a zstd dictionary, which can improve compression ratio, use
+ * `ZDICT_finalizeDictionary()`.
+ *
+ * What is the attack surface of a zstd dictionary?
+ * ------------------------------------------------
+ *
+ * Zstd is heavily fuzz tested, including loading fuzzed dictionaries, so
+ * zstd should never crash, or access out-of-bounds memory no matter what
+ * the dictionary is. However, if an attacker can control the dictionary
+ * during decompression, they can cause zstd to generate arbitrary bytes,
+ * just like if they controlled the compressed data.
+ *
+ ******************************************************************************/
+
+
+/*! ZDICT_trainFromBuffer():
+ * Train a dictionary from an array of samples.
+ * Redirect towards ZDICT_optimizeTrainFromBuffer_fastCover() single-threaded, with d=8, steps=4,
+ * f=20, and accel=1.
+ * Samples must be stored concatenated in a single flat buffer `samplesBuffer`,
+ * supplied with an array of sizes `samplesSizes`, providing the size of each sample, in order.
+ * The resulting dictionary will be saved into `dictBuffer`.
+ * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`)
+ * or an error code, which can be tested with ZDICT_isError().
+ * Note: Dictionary training will fail if there are not enough samples to construct a
+ * dictionary, or if most of the samples are too small (< 8 bytes being the lower limit).
+ * If dictionary training fails, you should use zstd without a dictionary, as the dictionary
+ * would've been ineffective anyways. If you believe your samples would benefit from a dictionary
+ * please open an issue with details, and we can look into it.
+ * Note: ZDICT_trainFromBuffer()'s memory usage is about 6 MB.
+ * Tips: In general, a reasonable dictionary has a size of ~ 100 KB.
+ * It's possible to select smaller or larger size, just by specifying `dictBufferCapacity`.
+ * In general, it's recommended to provide a few thousands samples, though this can vary a lot.
+ * It's recommended that total size of all samples be about ~x100 times the target size of dictionary.
+ */
+ZDICTLIB_API size_t ZDICT_trainFromBuffer(void* dictBuffer, size_t dictBufferCapacity,
+ const void* samplesBuffer,
+ const size_t* samplesSizes, unsigned nbSamples);
+
+typedef struct {
+ int compressionLevel; /**< optimize for a specific zstd compression level; 0 means default */
+ unsigned notificationLevel; /**< Write log to stderr; 0 = none (default); 1 = errors; 2 = progression; 3 = details; 4 = debug; */
+ unsigned dictID; /**< force dictID value; 0 means auto mode (32-bits random value)
+ * NOTE: The zstd format reserves some dictionary IDs for future use.
+ * You may use them in private settings, but be warned that they
+ * may be used by zstd in a public dictionary registry in the future.
+ * These dictionary IDs are:
+ * - low range : <= 32767
+ * - high range : >= (2^31)
+ */
+} ZDICT_params_t;
+
+/*! ZDICT_finalizeDictionary():
+ * Given a custom content as a basis for dictionary, and a set of samples,
+ * finalize dictionary by adding headers and statistics according to the zstd
+ * dictionary format.
+ *
+ * Samples must be stored concatenated in a flat buffer `samplesBuffer`,
+ * supplied with an array of sizes `samplesSizes`, providing the size of each
+ * sample in order. The samples are used to construct the statistics, so they
+ * should be representative of what you will compress with this dictionary.
+ *
+ * The compression level can be set in `parameters`. You should pass the
+ * compression level you expect to use in production. The statistics for each
+ * compression level differ, so tuning the dictionary for the compression level
+ * can help quite a bit.
+ *
+ * You can set an explicit dictionary ID in `parameters`, or allow us to pick
+ * a random dictionary ID for you, but we can't guarantee no collisions.
+ *
+ * The dstDictBuffer and the dictContent may overlap, and the content will be
+ * appended to the end of the header. If the header + the content doesn't fit in
+ * maxDictSize the beginning of the content is truncated to make room, since it
+ * is presumed that the most profitable content is at the end of the dictionary,
+ * since that is the cheapest to reference.
+ *
+ * `maxDictSize` must be >= max(dictContentSize, ZSTD_DICTSIZE_MIN).
+ *
+ * @return: size of dictionary stored into `dstDictBuffer` (<= `maxDictSize`),
+ * or an error code, which can be tested by ZDICT_isError().
+ * Note: ZDICT_finalizeDictionary() will push notifications into stderr if
+ * instructed to, using notificationLevel>0.
+ * NOTE: This function currently may fail in several edge cases including:
+ * * Not enough samples
+ * * Samples are uncompressible
+ * * Samples are all exactly the same
+ */
+ZDICTLIB_API size_t ZDICT_finalizeDictionary(void* dstDictBuffer, size_t maxDictSize,
+ const void* dictContent, size_t dictContentSize,
+ const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples,
+ ZDICT_params_t parameters);
+
+
+/*====== Helper functions ======*/
+ZDICTLIB_API unsigned ZDICT_getDictID(const void* dictBuffer, size_t dictSize); /**< extracts dictID; @return zero if error (not a valid dictionary) */
+ZDICTLIB_API size_t ZDICT_getDictHeaderSize(const void* dictBuffer, size_t dictSize); /* returns dict header size; returns a ZSTD error code on failure */
+ZDICTLIB_API unsigned ZDICT_isError(size_t errorCode);
+ZDICTLIB_API const char* ZDICT_getErrorName(size_t errorCode);
+
+#endif /* ZSTD_ZDICT_H */
+
+#if defined(ZDICT_STATIC_LINKING_ONLY) && !defined(ZSTD_ZDICT_H_STATIC)
+#define ZSTD_ZDICT_H_STATIC
+
+/* This can be overridden externally to hide static symbols. */
+#ifndef ZDICTLIB_STATIC_API
+# if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1)
+# define ZDICTLIB_STATIC_API __declspec(dllexport) ZDICTLIB_VISIBLE
+# elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1)
+# define ZDICTLIB_STATIC_API __declspec(dllimport) ZDICTLIB_VISIBLE
+# else
+# define ZDICTLIB_STATIC_API ZDICTLIB_VISIBLE
+# endif
+#endif
+
+/* ====================================================================================
+ * The definitions in this section are considered experimental.
+ * They should never be used with a dynamic library, as they may change in the future.
+ * They are provided for advanced usages.
+ * Use them only in association with static linking.
+ * ==================================================================================== */
+
+#define ZDICT_DICTSIZE_MIN 256
+/* Deprecated: Remove in v1.6.0 */
+#define ZDICT_CONTENTSIZE_MIN 128
+
+/*! ZDICT_cover_params_t:
+ * k and d are the only required parameters.
+ * For others, value 0 means default.
+ */
+typedef struct {
+ unsigned k; /* Segment size : constraint: 0 < k : Reasonable range [16, 2048+] */
+ unsigned d; /* dmer size : constraint: 0 < d <= k : Reasonable range [6, 16] */
+ unsigned steps; /* Number of steps : Only used for optimization : 0 means default (40) : Higher means more parameters checked */
+ unsigned nbThreads; /* Number of threads : constraint: 0 < nbThreads : 1 means single-threaded : Only used for optimization : Ignored if ZSTD_MULTITHREAD is not defined */
+ double splitPoint; /* Percentage of samples used for training: Only used for optimization : the first nbSamples * splitPoint samples will be used to training, the last nbSamples * (1 - splitPoint) samples will be used for testing, 0 means default (1.0), 1.0 when all samples are used for both training and testing */
+ unsigned shrinkDict; /* Train dictionaries to shrink in size starting from the minimum size and selects the smallest dictionary that is shrinkDictMaxRegression% worse than the largest dictionary. 0 means no shrinking and 1 means shrinking */
+ unsigned shrinkDictMaxRegression; /* Sets shrinkDictMaxRegression so that a smaller dictionary can be at worse shrinkDictMaxRegression% worse than the max dict size dictionary. */
+ ZDICT_params_t zParams;
+} ZDICT_cover_params_t;
+
+typedef struct {
+ unsigned k; /* Segment size : constraint: 0 < k : Reasonable range [16, 2048+] */
+ unsigned d; /* dmer size : constraint: 0 < d <= k : Reasonable range [6, 16] */
+ unsigned f; /* log of size of frequency array : constraint: 0 < f <= 31 : 1 means default(20)*/
+ unsigned steps; /* Number of steps : Only used for optimization : 0 means default (40) : Higher means more parameters checked */
+ unsigned nbThreads; /* Number of threads : constraint: 0 < nbThreads : 1 means single-threaded : Only used for optimization : Ignored if ZSTD_MULTITHREAD is not defined */
+ double splitPoint; /* Percentage of samples used for training: Only used for optimization : the first nbSamples * splitPoint samples will be used to training, the last nbSamples * (1 - splitPoint) samples will be used for testing, 0 means default (0.75), 1.0 when all samples are used for both training and testing */
+ unsigned accel; /* Acceleration level: constraint: 0 < accel <= 10, higher means faster and less accurate, 0 means default(1) */
+ unsigned shrinkDict; /* Train dictionaries to shrink in size starting from the minimum size and selects the smallest dictionary that is shrinkDictMaxRegression% worse than the largest dictionary. 0 means no shrinking and 1 means shrinking */
+ unsigned shrinkDictMaxRegression; /* Sets shrinkDictMaxRegression so that a smaller dictionary can be at worse shrinkDictMaxRegression% worse than the max dict size dictionary. */
+
+ ZDICT_params_t zParams;
+} ZDICT_fastCover_params_t;
+
+/*! ZDICT_trainFromBuffer_cover():
+ * Train a dictionary from an array of samples using the COVER algorithm.
+ * Samples must be stored concatenated in a single flat buffer `samplesBuffer`,
+ * supplied with an array of sizes `samplesSizes`, providing the size of each sample, in order.
+ * The resulting dictionary will be saved into `dictBuffer`.
+ * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`)
+ * or an error code, which can be tested with ZDICT_isError().
+ * See ZDICT_trainFromBuffer() for details on failure modes.
+ * Note: ZDICT_trainFromBuffer_cover() requires about 9 bytes of memory for each input byte.
+ * Tips: In general, a reasonable dictionary has a size of ~ 100 KB.
+ * It's possible to select smaller or larger size, just by specifying `dictBufferCapacity`.
+ * In general, it's recommended to provide a few thousands samples, though this can vary a lot.
+ * It's recommended that total size of all samples be about ~x100 times the target size of dictionary.
+ */
+ZDICTLIB_STATIC_API size_t ZDICT_trainFromBuffer_cover(
+ void *dictBuffer, size_t dictBufferCapacity,
+ const void *samplesBuffer, const size_t *samplesSizes, unsigned nbSamples,
+ ZDICT_cover_params_t parameters);
+
+/*! ZDICT_optimizeTrainFromBuffer_cover():
+ * The same requirements as above hold for all the parameters except `parameters`.
+ * This function tries many parameter combinations and picks the best parameters.
+ * `*parameters` is filled with the best parameters found,
+ * dictionary constructed with those parameters is stored in `dictBuffer`.
+ *
+ * All of the parameters d, k, steps are optional.
+ * If d is non-zero then we don't check multiple values of d, otherwise we check d = {6, 8}.
+ * if steps is zero it defaults to its default value.
+ * If k is non-zero then we don't check multiple values of k, otherwise we check steps values in [50, 2000].
+ *
+ * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`)
+ * or an error code, which can be tested with ZDICT_isError().
+ * On success `*parameters` contains the parameters selected.
+ * See ZDICT_trainFromBuffer() for details on failure modes.
+ * Note: ZDICT_optimizeTrainFromBuffer_cover() requires about 8 bytes of memory for each input byte and additionally another 5 bytes of memory for each byte of memory for each thread.
+ */
+ZDICTLIB_STATIC_API size_t ZDICT_optimizeTrainFromBuffer_cover(
+ void* dictBuffer, size_t dictBufferCapacity,
+ const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples,
+ ZDICT_cover_params_t* parameters);
+
+/*! ZDICT_trainFromBuffer_fastCover():
+ * Train a dictionary from an array of samples using a modified version of COVER algorithm.
+ * Samples must be stored concatenated in a single flat buffer `samplesBuffer`,
+ * supplied with an array of sizes `samplesSizes`, providing the size of each sample, in order.
+ * d and k are required.
+ * All other parameters are optional, will use default values if not provided
+ * The resulting dictionary will be saved into `dictBuffer`.
+ * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`)
+ * or an error code, which can be tested with ZDICT_isError().
+ * See ZDICT_trainFromBuffer() for details on failure modes.
+ * Note: ZDICT_trainFromBuffer_fastCover() requires 6 * 2^f bytes of memory.
+ * Tips: In general, a reasonable dictionary has a size of ~ 100 KB.
+ * It's possible to select smaller or larger size, just by specifying `dictBufferCapacity`.
+ * In general, it's recommended to provide a few thousands samples, though this can vary a lot.
+ * It's recommended that total size of all samples be about ~x100 times the target size of dictionary.
+ */
+ZDICTLIB_STATIC_API size_t ZDICT_trainFromBuffer_fastCover(void *dictBuffer,
+ size_t dictBufferCapacity, const void *samplesBuffer,
+ const size_t *samplesSizes, unsigned nbSamples,
+ ZDICT_fastCover_params_t parameters);
+
+/*! ZDICT_optimizeTrainFromBuffer_fastCover():
+ * The same requirements as above hold for all the parameters except `parameters`.
+ * This function tries many parameter combinations (specifically, k and d combinations)
+ * and picks the best parameters. `*parameters` is filled with the best parameters found,
+ * dictionary constructed with those parameters is stored in `dictBuffer`.
+ * All of the parameters d, k, steps, f, and accel are optional.
+ * If d is non-zero then we don't check multiple values of d, otherwise we check d = {6, 8}.
+ * if steps is zero it defaults to its default value.
+ * If k is non-zero then we don't check multiple values of k, otherwise we check steps values in [50, 2000].
+ * If f is zero, default value of 20 is used.
+ * If accel is zero, default value of 1 is used.
+ *
+ * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`)
+ * or an error code, which can be tested with ZDICT_isError().
+ * On success `*parameters` contains the parameters selected.
+ * See ZDICT_trainFromBuffer() for details on failure modes.
+ * Note: ZDICT_optimizeTrainFromBuffer_fastCover() requires about 6 * 2^f bytes of memory for each thread.
+ */
+ZDICTLIB_STATIC_API size_t ZDICT_optimizeTrainFromBuffer_fastCover(void* dictBuffer,
+ size_t dictBufferCapacity, const void* samplesBuffer,
+ const size_t* samplesSizes, unsigned nbSamples,
+ ZDICT_fastCover_params_t* parameters);
+
+typedef struct {
+ unsigned selectivityLevel; /* 0 means default; larger => select more => larger dictionary */
+ ZDICT_params_t zParams;
+} ZDICT_legacy_params_t;
+
+/*! ZDICT_trainFromBuffer_legacy():
+ * Train a dictionary from an array of samples.
+ * Samples must be stored concatenated in a single flat buffer `samplesBuffer`,
+ * supplied with an array of sizes `samplesSizes`, providing the size of each sample, in order.
+ * The resulting dictionary will be saved into `dictBuffer`.
+ * `parameters` is optional and can be provided with values set to 0 to mean "default".
+ * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`)
+ * or an error code, which can be tested with ZDICT_isError().
+ * See ZDICT_trainFromBuffer() for details on failure modes.
+ * Tips: In general, a reasonable dictionary has a size of ~ 100 KB.
+ * It's possible to select smaller or larger size, just by specifying `dictBufferCapacity`.
+ * In general, it's recommended to provide a few thousands samples, though this can vary a lot.
+ * It's recommended that total size of all samples be about ~x100 times the target size of dictionary.
+ * Note: ZDICT_trainFromBuffer_legacy() will send notifications into stderr if instructed to, using notificationLevel>0.
+ */
+ZDICTLIB_STATIC_API size_t ZDICT_trainFromBuffer_legacy(
+ void* dictBuffer, size_t dictBufferCapacity,
+ const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples,
+ ZDICT_legacy_params_t parameters);
+
+
+/* Deprecation warnings */
+/* It is generally possible to disable deprecation warnings from compiler,
+ for example with -Wno-deprecated-declarations for gcc
+ or _CRT_SECURE_NO_WARNINGS in Visual.
+ Otherwise, it's also possible to manually define ZDICT_DISABLE_DEPRECATE_WARNINGS */
+#ifdef ZDICT_DISABLE_DEPRECATE_WARNINGS
+# define ZDICT_DEPRECATED(message) /* disable deprecation warnings */
+#else
+# define ZDICT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
+# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */
+# define ZDICT_DEPRECATED(message) [[deprecated(message)]]
+# elif defined(__clang__) || (ZDICT_GCC_VERSION >= 405)
+# define ZDICT_DEPRECATED(message) __attribute__((deprecated(message)))
+# elif (ZDICT_GCC_VERSION >= 301)
+# define ZDICT_DEPRECATED(message) __attribute__((deprecated))
+# elif defined(_MSC_VER)
+# define ZDICT_DEPRECATED(message) __declspec(deprecated(message))
+# else
+# pragma message("WARNING: You need to implement ZDICT_DEPRECATED for this compiler")
+# define ZDICT_DEPRECATED(message)
+# endif
+#endif /* ZDICT_DISABLE_DEPRECATE_WARNINGS */
+
+ZDICT_DEPRECATED("use ZDICT_finalizeDictionary() instead")
+ZDICTLIB_STATIC_API
+size_t ZDICT_addEntropyTablesFromBuffer(void* dictBuffer, size_t dictContentSize, size_t dictBufferCapacity,
+ const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples);
+
+
+#endif /* ZSTD_ZDICT_H_STATIC */
+
+#if defined (__cplusplus)
+}
+#endif
diff --git a/third_party/zstd/lib/zstd.h b/third_party/zstd/lib/zstd.h
new file mode 100644
index 0000000000..5d1fef8a6b
--- /dev/null
+++ b/third_party/zstd/lib/zstd.h
@@ -0,0 +1,3089 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+#ifndef ZSTD_H_235446
+#define ZSTD_H_235446
+
+/* ====== Dependencies ======*/
+#include <limits.h> /* INT_MAX */
+#include <stddef.h> /* size_t */
+
+
+/* ===== ZSTDLIB_API : control library symbols visibility ===== */
+#ifndef ZSTDLIB_VISIBLE
+ /* Backwards compatibility with old macro name */
+# ifdef ZSTDLIB_VISIBILITY
+# define ZSTDLIB_VISIBLE ZSTDLIB_VISIBILITY
+# elif defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__)
+# define ZSTDLIB_VISIBLE __attribute__ ((visibility ("default")))
+# else
+# define ZSTDLIB_VISIBLE
+# endif
+#endif
+
+#ifndef ZSTDLIB_HIDDEN
+# if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__)
+# define ZSTDLIB_HIDDEN __attribute__ ((visibility ("hidden")))
+# else
+# define ZSTDLIB_HIDDEN
+# endif
+#endif
+
+#if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1)
+# define ZSTDLIB_API __declspec(dllexport) ZSTDLIB_VISIBLE
+#elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1)
+# define ZSTDLIB_API __declspec(dllimport) ZSTDLIB_VISIBLE /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
+#else
+# define ZSTDLIB_API ZSTDLIB_VISIBLE
+#endif
+
+/* Deprecation warnings :
+ * Should these warnings be a problem, it is generally possible to disable them,
+ * typically with -Wno-deprecated-declarations for gcc or _CRT_SECURE_NO_WARNINGS in Visual.
+ * Otherwise, it's also possible to define ZSTD_DISABLE_DEPRECATE_WARNINGS.
+ */
+#ifdef ZSTD_DISABLE_DEPRECATE_WARNINGS
+# define ZSTD_DEPRECATED(message) /* disable deprecation warnings */
+#else
+# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */
+# define ZSTD_DEPRECATED(message) [[deprecated(message)]]
+# elif (defined(GNUC) && (GNUC > 4 || (GNUC == 4 && GNUC_MINOR >= 5))) || defined(__clang__)
+# define ZSTD_DEPRECATED(message) __attribute__((deprecated(message)))
+# elif defined(__GNUC__) && (__GNUC__ >= 3)
+# define ZSTD_DEPRECATED(message) __attribute__((deprecated))
+# elif defined(_MSC_VER)
+# define ZSTD_DEPRECATED(message) __declspec(deprecated(message))
+# else
+# pragma message("WARNING: You need to implement ZSTD_DEPRECATED for this compiler")
+# define ZSTD_DEPRECATED(message)
+# endif
+#endif /* ZSTD_DISABLE_DEPRECATE_WARNINGS */
+
+
+/*******************************************************************************
+ Introduction
+
+ zstd, short for Zstandard, is a fast lossless compression algorithm, targeting
+ real-time compression scenarios at zlib-level and better compression ratios.
+ The zstd compression library provides in-memory compression and decompression
+ functions.
+
+ The library supports regular compression levels from 1 up to ZSTD_maxCLevel(),
+ which is currently 22. Levels >= 20, labeled `--ultra`, should be used with
+ caution, as they require more memory. The library also offers negative
+ compression levels, which extend the range of speed vs. ratio preferences.
+ The lower the level, the faster the speed (at the cost of compression).
+
+ Compression can be done in:
+ - a single step (described as Simple API)
+ - a single step, reusing a context (described as Explicit context)
+ - unbounded multiple steps (described as Streaming compression)
+
+ The compression ratio achievable on small data can be highly improved using
+ a dictionary. Dictionary compression can be performed in:
+ - a single step (described as Simple dictionary API)
+ - a single step, reusing a dictionary (described as Bulk-processing
+ dictionary API)
+
+ Advanced experimental functions can be accessed using
+ `#define ZSTD_STATIC_LINKING_ONLY` before including zstd.h.
+
+ Advanced experimental APIs should never be used with a dynamically-linked
+ library. They are not "stable"; their definitions or signatures may change in
+ the future. Only static linking is allowed.
+*******************************************************************************/
+
+/*------ Version ------*/
+#define ZSTD_VERSION_MAJOR 1
+#define ZSTD_VERSION_MINOR 5
+#define ZSTD_VERSION_RELEASE 6
+#define ZSTD_VERSION_NUMBER (ZSTD_VERSION_MAJOR *100*100 + ZSTD_VERSION_MINOR *100 + ZSTD_VERSION_RELEASE)
+
+/*! ZSTD_versionNumber() :
+ * Return runtime library version, the value is (MAJOR*100*100 + MINOR*100 + RELEASE). */
+ZSTDLIB_API unsigned ZSTD_versionNumber(void);
+
+#define ZSTD_LIB_VERSION ZSTD_VERSION_MAJOR.ZSTD_VERSION_MINOR.ZSTD_VERSION_RELEASE
+#define ZSTD_QUOTE(str) #str
+#define ZSTD_EXPAND_AND_QUOTE(str) ZSTD_QUOTE(str)
+#define ZSTD_VERSION_STRING ZSTD_EXPAND_AND_QUOTE(ZSTD_LIB_VERSION)
+
+/*! ZSTD_versionString() :
+ * Return runtime library version, like "1.4.5". Requires v1.3.0+. */
+ZSTDLIB_API const char* ZSTD_versionString(void);
+
+/* *************************************
+ * Default constant
+ ***************************************/
+#ifndef ZSTD_CLEVEL_DEFAULT
+# define ZSTD_CLEVEL_DEFAULT 3
+#endif
+
+/* *************************************
+ * Constants
+ ***************************************/
+
+/* All magic numbers are supposed read/written to/from files/memory using little-endian convention */
+#define ZSTD_MAGICNUMBER 0xFD2FB528 /* valid since v0.8.0 */
+#define ZSTD_MAGIC_DICTIONARY 0xEC30A437 /* valid since v0.7.0 */
+#define ZSTD_MAGIC_SKIPPABLE_START 0x184D2A50 /* all 16 values, from 0x184D2A50 to 0x184D2A5F, signal the beginning of a skippable frame */
+#define ZSTD_MAGIC_SKIPPABLE_MASK 0xFFFFFFF0
+
+#define ZSTD_BLOCKSIZELOG_MAX 17
+#define ZSTD_BLOCKSIZE_MAX (1<<ZSTD_BLOCKSIZELOG_MAX)
+
+
+/***************************************
+* Simple API
+***************************************/
+/*! ZSTD_compress() :
+ * Compresses `src` content as a single zstd compressed frame into already allocated `dst`.
+ * NOTE: Providing `dstCapacity >= ZSTD_compressBound(srcSize)` guarantees that zstd will have
+ * enough space to successfully compress the data.
+ * @return : compressed size written into `dst` (<= `dstCapacity),
+ * or an error code if it fails (which can be tested using ZSTD_isError()). */
+ZSTDLIB_API size_t ZSTD_compress( void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize,
+ int compressionLevel);
+
+/*! ZSTD_decompress() :
+ * `compressedSize` : must be the _exact_ size of some number of compressed and/or skippable frames.
+ * `dstCapacity` is an upper bound of originalSize to regenerate.
+ * If user cannot imply a maximum upper bound, it's better to use streaming mode to decompress data.
+ * @return : the number of bytes decompressed into `dst` (<= `dstCapacity`),
+ * or an errorCode if it fails (which can be tested using ZSTD_isError()). */
+ZSTDLIB_API size_t ZSTD_decompress( void* dst, size_t dstCapacity,
+ const void* src, size_t compressedSize);
+
+/*! ZSTD_getFrameContentSize() : requires v1.3.0+
+ * `src` should point to the start of a ZSTD encoded frame.
+ * `srcSize` must be at least as large as the frame header.
+ * hint : any size >= `ZSTD_frameHeaderSize_max` is large enough.
+ * @return : - decompressed size of `src` frame content, if known
+ * - ZSTD_CONTENTSIZE_UNKNOWN if the size cannot be determined
+ * - ZSTD_CONTENTSIZE_ERROR if an error occurred (e.g. invalid magic number, srcSize too small)
+ * note 1 : a 0 return value means the frame is valid but "empty".
+ * note 2 : decompressed size is an optional field, it may not be present, typically in streaming mode.
+ * When `return==ZSTD_CONTENTSIZE_UNKNOWN`, data to decompress could be any size.
+ * In which case, it's necessary to use streaming mode to decompress data.
+ * Optionally, application can rely on some implicit limit,
+ * as ZSTD_decompress() only needs an upper bound of decompressed size.
+ * (For example, data could be necessarily cut into blocks <= 16 KB).
+ * note 3 : decompressed size is always present when compression is completed using single-pass functions,
+ * such as ZSTD_compress(), ZSTD_compressCCtx() ZSTD_compress_usingDict() or ZSTD_compress_usingCDict().
+ * note 4 : decompressed size can be very large (64-bits value),
+ * potentially larger than what local system can handle as a single memory segment.
+ * In which case, it's necessary to use streaming mode to decompress data.
+ * note 5 : If source is untrusted, decompressed size could be wrong or intentionally modified.
+ * Always ensure return value fits within application's authorized limits.
+ * Each application can set its own limits.
+ * note 6 : This function replaces ZSTD_getDecompressedSize() */
+#define ZSTD_CONTENTSIZE_UNKNOWN (0ULL - 1)
+#define ZSTD_CONTENTSIZE_ERROR (0ULL - 2)
+ZSTDLIB_API unsigned long long ZSTD_getFrameContentSize(const void *src, size_t srcSize);
+
+/*! ZSTD_getDecompressedSize() :
+ * NOTE: This function is now obsolete, in favor of ZSTD_getFrameContentSize().
+ * Both functions work the same way, but ZSTD_getDecompressedSize() blends
+ * "empty", "unknown" and "error" results to the same return value (0),
+ * while ZSTD_getFrameContentSize() gives them separate return values.
+ * @return : decompressed size of `src` frame content _if known and not empty_, 0 otherwise. */
+ZSTD_DEPRECATED("Replaced by ZSTD_getFrameContentSize")
+ZSTDLIB_API
+unsigned long long ZSTD_getDecompressedSize(const void* src, size_t srcSize);
+
+/*! ZSTD_findFrameCompressedSize() : Requires v1.4.0+
+ * `src` should point to the start of a ZSTD frame or skippable frame.
+ * `srcSize` must be >= first frame size
+ * @return : the compressed size of the first frame starting at `src`,
+ * suitable to pass as `srcSize` to `ZSTD_decompress` or similar,
+ * or an error code if input is invalid */
+ZSTDLIB_API size_t ZSTD_findFrameCompressedSize(const void* src, size_t srcSize);
+
+
+/*====== Helper functions ======*/
+/* ZSTD_compressBound() :
+ * maximum compressed size in worst case single-pass scenario.
+ * When invoking `ZSTD_compress()` or any other one-pass compression function,
+ * it's recommended to provide @dstCapacity >= ZSTD_compressBound(srcSize)
+ * as it eliminates one potential failure scenario,
+ * aka not enough room in dst buffer to write the compressed frame.
+ * Note : ZSTD_compressBound() itself can fail, if @srcSize > ZSTD_MAX_INPUT_SIZE .
+ * In which case, ZSTD_compressBound() will return an error code
+ * which can be tested using ZSTD_isError().
+ *
+ * ZSTD_COMPRESSBOUND() :
+ * same as ZSTD_compressBound(), but as a macro.
+ * It can be used to produce constants, which can be useful for static allocation,
+ * for example to size a static array on stack.
+ * Will produce constant value 0 if srcSize too large.
+ */
+#define ZSTD_MAX_INPUT_SIZE ((sizeof(size_t)==8) ? 0xFF00FF00FF00FF00ULL : 0xFF00FF00U)
+#define ZSTD_COMPRESSBOUND(srcSize) (((size_t)(srcSize) >= ZSTD_MAX_INPUT_SIZE) ? 0 : (srcSize) + ((srcSize)>>8) + (((srcSize) < (128<<10)) ? (((128<<10) - (srcSize)) >> 11) /* margin, from 64 to 0 */ : 0)) /* this formula ensures that bound(A) + bound(B) <= bound(A+B) as long as A and B >= 128 KB */
+ZSTDLIB_API size_t ZSTD_compressBound(size_t srcSize); /*!< maximum compressed size in worst case single-pass scenario */
+/* ZSTD_isError() :
+ * Most ZSTD_* functions returning a size_t value can be tested for error,
+ * using ZSTD_isError().
+ * @return 1 if error, 0 otherwise
+ */
+ZSTDLIB_API unsigned ZSTD_isError(size_t code); /*!< tells if a `size_t` function result is an error code */
+ZSTDLIB_API const char* ZSTD_getErrorName(size_t code); /*!< provides readable string from an error code */
+ZSTDLIB_API int ZSTD_minCLevel(void); /*!< minimum negative compression level allowed, requires v1.4.0+ */
+ZSTDLIB_API int ZSTD_maxCLevel(void); /*!< maximum compression level available */
+ZSTDLIB_API int ZSTD_defaultCLevel(void); /*!< default compression level, specified by ZSTD_CLEVEL_DEFAULT, requires v1.5.0+ */
+
+
+/***************************************
+* Explicit context
+***************************************/
+/*= Compression context
+ * When compressing many times,
+ * it is recommended to allocate a context just once,
+ * and reuse it for each successive compression operation.
+ * This will make workload friendlier for system's memory.
+ * Note : re-using context is just a speed / resource optimization.
+ * It doesn't change the compression ratio, which remains identical.
+ * Note 2 : In multi-threaded environments,
+ * use one different context per thread for parallel execution.
+ */
+typedef struct ZSTD_CCtx_s ZSTD_CCtx;
+ZSTDLIB_API ZSTD_CCtx* ZSTD_createCCtx(void);
+ZSTDLIB_API size_t ZSTD_freeCCtx(ZSTD_CCtx* cctx); /* accept NULL pointer */
+
+/*! ZSTD_compressCCtx() :
+ * Same as ZSTD_compress(), using an explicit ZSTD_CCtx.
+ * Important : in order to mirror `ZSTD_compress()` behavior,
+ * this function compresses at the requested compression level,
+ * __ignoring any other advanced parameter__ .
+ * If any advanced parameter was set using the advanced API,
+ * they will all be reset. Only `compressionLevel` remains.
+ */
+ZSTDLIB_API size_t ZSTD_compressCCtx(ZSTD_CCtx* cctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize,
+ int compressionLevel);
+
+/*= Decompression context
+ * When decompressing many times,
+ * it is recommended to allocate a context only once,
+ * and reuse it for each successive compression operation.
+ * This will make workload friendlier for system's memory.
+ * Use one context per thread for parallel execution. */
+typedef struct ZSTD_DCtx_s ZSTD_DCtx;
+ZSTDLIB_API ZSTD_DCtx* ZSTD_createDCtx(void);
+ZSTDLIB_API size_t ZSTD_freeDCtx(ZSTD_DCtx* dctx); /* accept NULL pointer */
+
+/*! ZSTD_decompressDCtx() :
+ * Same as ZSTD_decompress(),
+ * requires an allocated ZSTD_DCtx.
+ * Compatible with sticky parameters (see below).
+ */
+ZSTDLIB_API size_t ZSTD_decompressDCtx(ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize);
+
+
+/*********************************************
+* Advanced compression API (Requires v1.4.0+)
+**********************************************/
+
+/* API design :
+ * Parameters are pushed one by one into an existing context,
+ * using ZSTD_CCtx_set*() functions.
+ * Pushed parameters are sticky : they are valid for next compressed frame, and any subsequent frame.
+ * "sticky" parameters are applicable to `ZSTD_compress2()` and `ZSTD_compressStream*()` !
+ * __They do not apply to one-shot variants such as ZSTD_compressCCtx()__ .
+ *
+ * It's possible to reset all parameters to "default" using ZSTD_CCtx_reset().
+ *
+ * This API supersedes all other "advanced" API entry points in the experimental section.
+ * In the future, we expect to remove API entry points from experimental which are redundant with this API.
+ */
+
+
+/* Compression strategies, listed from fastest to strongest */
+typedef enum { ZSTD_fast=1,
+ ZSTD_dfast=2,
+ ZSTD_greedy=3,
+ ZSTD_lazy=4,
+ ZSTD_lazy2=5,
+ ZSTD_btlazy2=6,
+ ZSTD_btopt=7,
+ ZSTD_btultra=8,
+ ZSTD_btultra2=9
+ /* note : new strategies _might_ be added in the future.
+ Only the order (from fast to strong) is guaranteed */
+} ZSTD_strategy;
+
+typedef enum {
+
+ /* compression parameters
+ * Note: When compressing with a ZSTD_CDict these parameters are superseded
+ * by the parameters used to construct the ZSTD_CDict.
+ * See ZSTD_CCtx_refCDict() for more info (superseded-by-cdict). */
+ ZSTD_c_compressionLevel=100, /* Set compression parameters according to pre-defined cLevel table.
+ * Note that exact compression parameters are dynamically determined,
+ * depending on both compression level and srcSize (when known).
+ * Default level is ZSTD_CLEVEL_DEFAULT==3.
+ * Special: value 0 means default, which is controlled by ZSTD_CLEVEL_DEFAULT.
+ * Note 1 : it's possible to pass a negative compression level.
+ * Note 2 : setting a level does not automatically set all other compression parameters
+ * to default. Setting this will however eventually dynamically impact the compression
+ * parameters which have not been manually set. The manually set
+ * ones will 'stick'. */
+ /* Advanced compression parameters :
+ * It's possible to pin down compression parameters to some specific values.
+ * In which case, these values are no longer dynamically selected by the compressor */
+ ZSTD_c_windowLog=101, /* Maximum allowed back-reference distance, expressed as power of 2.
+ * This will set a memory budget for streaming decompression,
+ * with larger values requiring more memory
+ * and typically compressing more.
+ * Must be clamped between ZSTD_WINDOWLOG_MIN and ZSTD_WINDOWLOG_MAX.
+ * Special: value 0 means "use default windowLog".
+ * Note: Using a windowLog greater than ZSTD_WINDOWLOG_LIMIT_DEFAULT
+ * requires explicitly allowing such size at streaming decompression stage. */
+ ZSTD_c_hashLog=102, /* Size of the initial probe table, as a power of 2.
+ * Resulting memory usage is (1 << (hashLog+2)).
+ * Must be clamped between ZSTD_HASHLOG_MIN and ZSTD_HASHLOG_MAX.
+ * Larger tables improve compression ratio of strategies <= dFast,
+ * and improve speed of strategies > dFast.
+ * Special: value 0 means "use default hashLog". */
+ ZSTD_c_chainLog=103, /* Size of the multi-probe search table, as a power of 2.
+ * Resulting memory usage is (1 << (chainLog+2)).
+ * Must be clamped between ZSTD_CHAINLOG_MIN and ZSTD_CHAINLOG_MAX.
+ * Larger tables result in better and slower compression.
+ * This parameter is useless for "fast" strategy.
+ * It's still useful when using "dfast" strategy,
+ * in which case it defines a secondary probe table.
+ * Special: value 0 means "use default chainLog". */
+ ZSTD_c_searchLog=104, /* Number of search attempts, as a power of 2.
+ * More attempts result in better and slower compression.
+ * This parameter is useless for "fast" and "dFast" strategies.
+ * Special: value 0 means "use default searchLog". */
+ ZSTD_c_minMatch=105, /* Minimum size of searched matches.
+ * Note that Zstandard can still find matches of smaller size,
+ * it just tweaks its search algorithm to look for this size and larger.
+ * Larger values increase compression and decompression speed, but decrease ratio.
+ * Must be clamped between ZSTD_MINMATCH_MIN and ZSTD_MINMATCH_MAX.
+ * Note that currently, for all strategies < btopt, effective minimum is 4.
+ * , for all strategies > fast, effective maximum is 6.
+ * Special: value 0 means "use default minMatchLength". */
+ ZSTD_c_targetLength=106, /* Impact of this field depends on strategy.
+ * For strategies btopt, btultra & btultra2:
+ * Length of Match considered "good enough" to stop search.
+ * Larger values make compression stronger, and slower.
+ * For strategy fast:
+ * Distance between match sampling.
+ * Larger values make compression faster, and weaker.
+ * Special: value 0 means "use default targetLength". */
+ ZSTD_c_strategy=107, /* See ZSTD_strategy enum definition.
+ * The higher the value of selected strategy, the more complex it is,
+ * resulting in stronger and slower compression.
+ * Special: value 0 means "use default strategy". */
+
+ ZSTD_c_targetCBlockSize=130, /* v1.5.6+
+ * Attempts to fit compressed block size into approximatively targetCBlockSize.
+ * Bound by ZSTD_TARGETCBLOCKSIZE_MIN and ZSTD_TARGETCBLOCKSIZE_MAX.
+ * Note that it's not a guarantee, just a convergence target (default:0).
+ * No target when targetCBlockSize == 0.
+ * This is helpful in low bandwidth streaming environments to improve end-to-end latency,
+ * when a client can make use of partial documents (a prominent example being Chrome).
+ * Note: this parameter is stable since v1.5.6.
+ * It was present as an experimental parameter in earlier versions,
+ * but it's not recommended using it with earlier library versions
+ * due to massive performance regressions.
+ */
+ /* LDM mode parameters */
+ ZSTD_c_enableLongDistanceMatching=160, /* Enable long distance matching.
+ * This parameter is designed to improve compression ratio
+ * for large inputs, by finding large matches at long distance.
+ * It increases memory usage and window size.
+ * Note: enabling this parameter increases default ZSTD_c_windowLog to 128 MB
+ * except when expressly set to a different value.
+ * Note: will be enabled by default if ZSTD_c_windowLog >= 128 MB and
+ * compression strategy >= ZSTD_btopt (== compression level 16+) */
+ ZSTD_c_ldmHashLog=161, /* Size of the table for long distance matching, as a power of 2.
+ * Larger values increase memory usage and compression ratio,
+ * but decrease compression speed.
+ * Must be clamped between ZSTD_HASHLOG_MIN and ZSTD_HASHLOG_MAX
+ * default: windowlog - 7.
+ * Special: value 0 means "automatically determine hashlog". */
+ ZSTD_c_ldmMinMatch=162, /* Minimum match size for long distance matcher.
+ * Larger/too small values usually decrease compression ratio.
+ * Must be clamped between ZSTD_LDM_MINMATCH_MIN and ZSTD_LDM_MINMATCH_MAX.
+ * Special: value 0 means "use default value" (default: 64). */
+ ZSTD_c_ldmBucketSizeLog=163, /* Log size of each bucket in the LDM hash table for collision resolution.
+ * Larger values improve collision resolution but decrease compression speed.
+ * The maximum value is ZSTD_LDM_BUCKETSIZELOG_MAX.
+ * Special: value 0 means "use default value" (default: 3). */
+ ZSTD_c_ldmHashRateLog=164, /* Frequency of inserting/looking up entries into the LDM hash table.
+ * Must be clamped between 0 and (ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN).
+ * Default is MAX(0, (windowLog - ldmHashLog)), optimizing hash table usage.
+ * Larger values improve compression speed.
+ * Deviating far from default value will likely result in a compression ratio decrease.
+ * Special: value 0 means "automatically determine hashRateLog". */
+
+ /* frame parameters */
+ ZSTD_c_contentSizeFlag=200, /* Content size will be written into frame header _whenever known_ (default:1)
+ * Content size must be known at the beginning of compression.
+ * This is automatically the case when using ZSTD_compress2(),
+ * For streaming scenarios, content size must be provided with ZSTD_CCtx_setPledgedSrcSize() */
+ ZSTD_c_checksumFlag=201, /* A 32-bits checksum of content is written at end of frame (default:0) */
+ ZSTD_c_dictIDFlag=202, /* When applicable, dictionary's ID is written into frame header (default:1) */
+
+ /* multi-threading parameters */
+ /* These parameters are only active if multi-threading is enabled (compiled with build macro ZSTD_MULTITHREAD).
+ * Otherwise, trying to set any other value than default (0) will be a no-op and return an error.
+ * In a situation where it's unknown if the linked library supports multi-threading or not,
+ * setting ZSTD_c_nbWorkers to any value >= 1 and consulting the return value provides a quick way to check this property.
+ */
+ ZSTD_c_nbWorkers=400, /* Select how many threads will be spawned to compress in parallel.
+ * When nbWorkers >= 1, triggers asynchronous mode when invoking ZSTD_compressStream*() :
+ * ZSTD_compressStream*() consumes input and flush output if possible, but immediately gives back control to caller,
+ * while compression is performed in parallel, within worker thread(s).
+ * (note : a strong exception to this rule is when first invocation of ZSTD_compressStream2() sets ZSTD_e_end :
+ * in which case, ZSTD_compressStream2() delegates to ZSTD_compress2(), which is always a blocking call).
+ * More workers improve speed, but also increase memory usage.
+ * Default value is `0`, aka "single-threaded mode" : no worker is spawned,
+ * compression is performed inside Caller's thread, and all invocations are blocking */
+ ZSTD_c_jobSize=401, /* Size of a compression job. This value is enforced only when nbWorkers >= 1.
+ * Each compression job is completed in parallel, so this value can indirectly impact the nb of active threads.
+ * 0 means default, which is dynamically determined based on compression parameters.
+ * Job size must be a minimum of overlap size, or ZSTDMT_JOBSIZE_MIN (= 512 KB), whichever is largest.
+ * The minimum size is automatically and transparently enforced. */
+ ZSTD_c_overlapLog=402, /* Control the overlap size, as a fraction of window size.
+ * The overlap size is an amount of data reloaded from previous job at the beginning of a new job.
+ * It helps preserve compression ratio, while each job is compressed in parallel.
+ * This value is enforced only when nbWorkers >= 1.
+ * Larger values increase compression ratio, but decrease speed.
+ * Possible values range from 0 to 9 :
+ * - 0 means "default" : value will be determined by the library, depending on strategy
+ * - 1 means "no overlap"
+ * - 9 means "full overlap", using a full window size.
+ * Each intermediate rank increases/decreases load size by a factor 2 :
+ * 9: full window; 8: w/2; 7: w/4; 6: w/8; 5:w/16; 4: w/32; 3:w/64; 2:w/128; 1:no overlap; 0:default
+ * default value varies between 6 and 9, depending on strategy */
+
+ /* note : additional experimental parameters are also available
+ * within the experimental section of the API.
+ * At the time of this writing, they include :
+ * ZSTD_c_rsyncable
+ * ZSTD_c_format
+ * ZSTD_c_forceMaxWindow
+ * ZSTD_c_forceAttachDict
+ * ZSTD_c_literalCompressionMode
+ * ZSTD_c_srcSizeHint
+ * ZSTD_c_enableDedicatedDictSearch
+ * ZSTD_c_stableInBuffer
+ * ZSTD_c_stableOutBuffer
+ * ZSTD_c_blockDelimiters
+ * ZSTD_c_validateSequences
+ * ZSTD_c_useBlockSplitter
+ * ZSTD_c_useRowMatchFinder
+ * ZSTD_c_prefetchCDictTables
+ * ZSTD_c_enableSeqProducerFallback
+ * ZSTD_c_maxBlockSize
+ * Because they are not stable, it's necessary to define ZSTD_STATIC_LINKING_ONLY to access them.
+ * note : never ever use experimentalParam? names directly;
+ * also, the enums values themselves are unstable and can still change.
+ */
+ ZSTD_c_experimentalParam1=500,
+ ZSTD_c_experimentalParam2=10,
+ ZSTD_c_experimentalParam3=1000,
+ ZSTD_c_experimentalParam4=1001,
+ ZSTD_c_experimentalParam5=1002,
+ /* was ZSTD_c_experimentalParam6=1003; is now ZSTD_c_targetCBlockSize */
+ ZSTD_c_experimentalParam7=1004,
+ ZSTD_c_experimentalParam8=1005,
+ ZSTD_c_experimentalParam9=1006,
+ ZSTD_c_experimentalParam10=1007,
+ ZSTD_c_experimentalParam11=1008,
+ ZSTD_c_experimentalParam12=1009,
+ ZSTD_c_experimentalParam13=1010,
+ ZSTD_c_experimentalParam14=1011,
+ ZSTD_c_experimentalParam15=1012,
+ ZSTD_c_experimentalParam16=1013,
+ ZSTD_c_experimentalParam17=1014,
+ ZSTD_c_experimentalParam18=1015,
+ ZSTD_c_experimentalParam19=1016
+} ZSTD_cParameter;
+
+typedef struct {
+ size_t error;
+ int lowerBound;
+ int upperBound;
+} ZSTD_bounds;
+
+/*! ZSTD_cParam_getBounds() :
+ * All parameters must belong to an interval with lower and upper bounds,
+ * otherwise they will either trigger an error or be automatically clamped.
+ * @return : a structure, ZSTD_bounds, which contains
+ * - an error status field, which must be tested using ZSTD_isError()
+ * - lower and upper bounds, both inclusive
+ */
+ZSTDLIB_API ZSTD_bounds ZSTD_cParam_getBounds(ZSTD_cParameter cParam);
+
+/*! ZSTD_CCtx_setParameter() :
+ * Set one compression parameter, selected by enum ZSTD_cParameter.
+ * All parameters have valid bounds. Bounds can be queried using ZSTD_cParam_getBounds().
+ * Providing a value beyond bound will either clamp it, or trigger an error (depending on parameter).
+ * Setting a parameter is generally only possible during frame initialization (before starting compression).
+ * Exception : when using multi-threading mode (nbWorkers >= 1),
+ * the following parameters can be updated _during_ compression (within same frame):
+ * => compressionLevel, hashLog, chainLog, searchLog, minMatch, targetLength and strategy.
+ * new parameters will be active for next job only (after a flush()).
+ * @return : an error code (which can be tested using ZSTD_isError()).
+ */
+ZSTDLIB_API size_t ZSTD_CCtx_setParameter(ZSTD_CCtx* cctx, ZSTD_cParameter param, int value);
+
+/*! ZSTD_CCtx_setPledgedSrcSize() :
+ * Total input data size to be compressed as a single frame.
+ * Value will be written in frame header, unless if explicitly forbidden using ZSTD_c_contentSizeFlag.
+ * This value will also be controlled at end of frame, and trigger an error if not respected.
+ * @result : 0, or an error code (which can be tested with ZSTD_isError()).
+ * Note 1 : pledgedSrcSize==0 actually means zero, aka an empty frame.
+ * In order to mean "unknown content size", pass constant ZSTD_CONTENTSIZE_UNKNOWN.
+ * ZSTD_CONTENTSIZE_UNKNOWN is default value for any new frame.
+ * Note 2 : pledgedSrcSize is only valid once, for the next frame.
+ * It's discarded at the end of the frame, and replaced by ZSTD_CONTENTSIZE_UNKNOWN.
+ * Note 3 : Whenever all input data is provided and consumed in a single round,
+ * for example with ZSTD_compress2(),
+ * or invoking immediately ZSTD_compressStream2(,,,ZSTD_e_end),
+ * this value is automatically overridden by srcSize instead.
+ */
+ZSTDLIB_API size_t ZSTD_CCtx_setPledgedSrcSize(ZSTD_CCtx* cctx, unsigned long long pledgedSrcSize);
+
+typedef enum {
+ ZSTD_reset_session_only = 1,
+ ZSTD_reset_parameters = 2,
+ ZSTD_reset_session_and_parameters = 3
+} ZSTD_ResetDirective;
+
+/*! ZSTD_CCtx_reset() :
+ * There are 2 different things that can be reset, independently or jointly :
+ * - The session : will stop compressing current frame, and make CCtx ready to start a new one.
+ * Useful after an error, or to interrupt any ongoing compression.
+ * Any internal data not yet flushed is cancelled.
+ * Compression parameters and dictionary remain unchanged.
+ * They will be used to compress next frame.
+ * Resetting session never fails.
+ * - The parameters : changes all parameters back to "default".
+ * This also removes any reference to any dictionary or external sequence producer.
+ * Parameters can only be changed between 2 sessions (i.e. no compression is currently ongoing)
+ * otherwise the reset fails, and function returns an error value (which can be tested using ZSTD_isError())
+ * - Both : similar to resetting the session, followed by resetting parameters.
+ */
+ZSTDLIB_API size_t ZSTD_CCtx_reset(ZSTD_CCtx* cctx, ZSTD_ResetDirective reset);
+
+/*! ZSTD_compress2() :
+ * Behave the same as ZSTD_compressCCtx(), but compression parameters are set using the advanced API.
+ * (note that this entry point doesn't even expose a compression level parameter).
+ * ZSTD_compress2() always starts a new frame.
+ * Should cctx hold data from a previously unfinished frame, everything about it is forgotten.
+ * - Compression parameters are pushed into CCtx before starting compression, using ZSTD_CCtx_set*()
+ * - The function is always blocking, returns when compression is completed.
+ * NOTE: Providing `dstCapacity >= ZSTD_compressBound(srcSize)` guarantees that zstd will have
+ * enough space to successfully compress the data, though it is possible it fails for other reasons.
+ * @return : compressed size written into `dst` (<= `dstCapacity),
+ * or an error code if it fails (which can be tested using ZSTD_isError()).
+ */
+ZSTDLIB_API size_t ZSTD_compress2( ZSTD_CCtx* cctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize);
+
+
+/***********************************************
+* Advanced decompression API (Requires v1.4.0+)
+************************************************/
+
+/* The advanced API pushes parameters one by one into an existing DCtx context.
+ * Parameters are sticky, and remain valid for all following frames
+ * using the same DCtx context.
+ * It's possible to reset parameters to default values using ZSTD_DCtx_reset().
+ * Note : This API is compatible with existing ZSTD_decompressDCtx() and ZSTD_decompressStream().
+ * Therefore, no new decompression function is necessary.
+ */
+
+typedef enum {
+
+ ZSTD_d_windowLogMax=100, /* Select a size limit (in power of 2) beyond which
+ * the streaming API will refuse to allocate memory buffer
+ * in order to protect the host from unreasonable memory requirements.
+ * This parameter is only useful in streaming mode, since no internal buffer is allocated in single-pass mode.
+ * By default, a decompression context accepts window sizes <= (1 << ZSTD_WINDOWLOG_LIMIT_DEFAULT).
+ * Special: value 0 means "use default maximum windowLog". */
+
+ /* note : additional experimental parameters are also available
+ * within the experimental section of the API.
+ * At the time of this writing, they include :
+ * ZSTD_d_format
+ * ZSTD_d_stableOutBuffer
+ * ZSTD_d_forceIgnoreChecksum
+ * ZSTD_d_refMultipleDDicts
+ * ZSTD_d_disableHuffmanAssembly
+ * ZSTD_d_maxBlockSize
+ * Because they are not stable, it's necessary to define ZSTD_STATIC_LINKING_ONLY to access them.
+ * note : never ever use experimentalParam? names directly
+ */
+ ZSTD_d_experimentalParam1=1000,
+ ZSTD_d_experimentalParam2=1001,
+ ZSTD_d_experimentalParam3=1002,
+ ZSTD_d_experimentalParam4=1003,
+ ZSTD_d_experimentalParam5=1004,
+ ZSTD_d_experimentalParam6=1005
+
+} ZSTD_dParameter;
+
+/*! ZSTD_dParam_getBounds() :
+ * All parameters must belong to an interval with lower and upper bounds,
+ * otherwise they will either trigger an error or be automatically clamped.
+ * @return : a structure, ZSTD_bounds, which contains
+ * - an error status field, which must be tested using ZSTD_isError()
+ * - both lower and upper bounds, inclusive
+ */
+ZSTDLIB_API ZSTD_bounds ZSTD_dParam_getBounds(ZSTD_dParameter dParam);
+
+/*! ZSTD_DCtx_setParameter() :
+ * Set one compression parameter, selected by enum ZSTD_dParameter.
+ * All parameters have valid bounds. Bounds can be queried using ZSTD_dParam_getBounds().
+ * Providing a value beyond bound will either clamp it, or trigger an error (depending on parameter).
+ * Setting a parameter is only possible during frame initialization (before starting decompression).
+ * @return : 0, or an error code (which can be tested using ZSTD_isError()).
+ */
+ZSTDLIB_API size_t ZSTD_DCtx_setParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int value);
+
+/*! ZSTD_DCtx_reset() :
+ * Return a DCtx to clean state.
+ * Session and parameters can be reset jointly or separately.
+ * Parameters can only be reset when no active frame is being decompressed.
+ * @return : 0, or an error code, which can be tested with ZSTD_isError()
+ */
+ZSTDLIB_API size_t ZSTD_DCtx_reset(ZSTD_DCtx* dctx, ZSTD_ResetDirective reset);
+
+
+/****************************
+* Streaming
+****************************/
+
+typedef struct ZSTD_inBuffer_s {
+ const void* src; /**< start of input buffer */
+ size_t size; /**< size of input buffer */
+ size_t pos; /**< position where reading stopped. Will be updated. Necessarily 0 <= pos <= size */
+} ZSTD_inBuffer;
+
+typedef struct ZSTD_outBuffer_s {
+ void* dst; /**< start of output buffer */
+ size_t size; /**< size of output buffer */
+ size_t pos; /**< position where writing stopped. Will be updated. Necessarily 0 <= pos <= size */
+} ZSTD_outBuffer;
+
+
+
+/*-***********************************************************************
+* Streaming compression - HowTo
+*
+* A ZSTD_CStream object is required to track streaming operation.
+* Use ZSTD_createCStream() and ZSTD_freeCStream() to create/release resources.
+* ZSTD_CStream objects can be reused multiple times on consecutive compression operations.
+* It is recommended to reuse ZSTD_CStream since it will play nicer with system's memory, by re-using already allocated memory.
+*
+* For parallel execution, use one separate ZSTD_CStream per thread.
+*
+* note : since v1.3.0, ZSTD_CStream and ZSTD_CCtx are the same thing.
+*
+* Parameters are sticky : when starting a new compression on the same context,
+* it will reuse the same sticky parameters as previous compression session.
+* When in doubt, it's recommended to fully initialize the context before usage.
+* Use ZSTD_CCtx_reset() to reset the context and ZSTD_CCtx_setParameter(),
+* ZSTD_CCtx_setPledgedSrcSize(), or ZSTD_CCtx_loadDictionary() and friends to
+* set more specific parameters, the pledged source size, or load a dictionary.
+*
+* Use ZSTD_compressStream2() with ZSTD_e_continue as many times as necessary to
+* consume input stream. The function will automatically update both `pos`
+* fields within `input` and `output`.
+* Note that the function may not consume the entire input, for example, because
+* the output buffer is already full, in which case `input.pos < input.size`.
+* The caller must check if input has been entirely consumed.
+* If not, the caller must make some room to receive more compressed data,
+* and then present again remaining input data.
+* note: ZSTD_e_continue is guaranteed to make some forward progress when called,
+* but doesn't guarantee maximal forward progress. This is especially relevant
+* when compressing with multiple threads. The call won't block if it can
+* consume some input, but if it can't it will wait for some, but not all,
+* output to be flushed.
+* @return : provides a minimum amount of data remaining to be flushed from internal buffers
+* or an error code, which can be tested using ZSTD_isError().
+*
+* At any moment, it's possible to flush whatever data might remain stuck within internal buffer,
+* using ZSTD_compressStream2() with ZSTD_e_flush. `output->pos` will be updated.
+* Note that, if `output->size` is too small, a single invocation with ZSTD_e_flush might not be enough (return code > 0).
+* In which case, make some room to receive more compressed data, and call again ZSTD_compressStream2() with ZSTD_e_flush.
+* You must continue calling ZSTD_compressStream2() with ZSTD_e_flush until it returns 0, at which point you can change the
+* operation.
+* note: ZSTD_e_flush will flush as much output as possible, meaning when compressing with multiple threads, it will
+* block until the flush is complete or the output buffer is full.
+* @return : 0 if internal buffers are entirely flushed,
+* >0 if some data still present within internal buffer (the value is minimal estimation of remaining size),
+* or an error code, which can be tested using ZSTD_isError().
+*
+* Calling ZSTD_compressStream2() with ZSTD_e_end instructs to finish a frame.
+* It will perform a flush and write frame epilogue.
+* The epilogue is required for decoders to consider a frame completed.
+* flush operation is the same, and follows same rules as calling ZSTD_compressStream2() with ZSTD_e_flush.
+* You must continue calling ZSTD_compressStream2() with ZSTD_e_end until it returns 0, at which point you are free to
+* start a new frame.
+* note: ZSTD_e_end will flush as much output as possible, meaning when compressing with multiple threads, it will
+* block until the flush is complete or the output buffer is full.
+* @return : 0 if frame fully completed and fully flushed,
+* >0 if some data still present within internal buffer (the value is minimal estimation of remaining size),
+* or an error code, which can be tested using ZSTD_isError().
+*
+* *******************************************************************/
+
+typedef ZSTD_CCtx ZSTD_CStream; /**< CCtx and CStream are now effectively same object (>= v1.3.0) */
+ /* Continue to distinguish them for compatibility with older versions <= v1.2.0 */
+/*===== ZSTD_CStream management functions =====*/
+ZSTDLIB_API ZSTD_CStream* ZSTD_createCStream(void);
+ZSTDLIB_API size_t ZSTD_freeCStream(ZSTD_CStream* zcs); /* accept NULL pointer */
+
+/*===== Streaming compression functions =====*/
+typedef enum {
+ ZSTD_e_continue=0, /* collect more data, encoder decides when to output compressed result, for optimal compression ratio */
+ ZSTD_e_flush=1, /* flush any data provided so far,
+ * it creates (at least) one new block, that can be decoded immediately on reception;
+ * frame will continue: any future data can still reference previously compressed data, improving compression.
+ * note : multithreaded compression will block to flush as much output as possible. */
+ ZSTD_e_end=2 /* flush any remaining data _and_ close current frame.
+ * note that frame is only closed after compressed data is fully flushed (return value == 0).
+ * After that point, any additional data starts a new frame.
+ * note : each frame is independent (does not reference any content from previous frame).
+ : note : multithreaded compression will block to flush as much output as possible. */
+} ZSTD_EndDirective;
+
+/*! ZSTD_compressStream2() : Requires v1.4.0+
+ * Behaves about the same as ZSTD_compressStream, with additional control on end directive.
+ * - Compression parameters are pushed into CCtx before starting compression, using ZSTD_CCtx_set*()
+ * - Compression parameters cannot be changed once compression is started (save a list of exceptions in multi-threading mode)
+ * - output->pos must be <= dstCapacity, input->pos must be <= srcSize
+ * - output->pos and input->pos will be updated. They are guaranteed to remain below their respective limit.
+ * - endOp must be a valid directive
+ * - When nbWorkers==0 (default), function is blocking : it completes its job before returning to caller.
+ * - When nbWorkers>=1, function is non-blocking : it copies a portion of input, distributes jobs to internal worker threads, flush to output whatever is available,
+ * and then immediately returns, just indicating that there is some data remaining to be flushed.
+ * The function nonetheless guarantees forward progress : it will return only after it reads or write at least 1+ byte.
+ * - Exception : if the first call requests a ZSTD_e_end directive and provides enough dstCapacity, the function delegates to ZSTD_compress2() which is always blocking.
+ * - @return provides a minimum amount of data remaining to be flushed from internal buffers
+ * or an error code, which can be tested using ZSTD_isError().
+ * if @return != 0, flush is not fully completed, there is still some data left within internal buffers.
+ * This is useful for ZSTD_e_flush, since in this case more flushes are necessary to empty all buffers.
+ * For ZSTD_e_end, @return == 0 when internal buffers are fully flushed and frame is completed.
+ * - after a ZSTD_e_end directive, if internal buffer is not fully flushed (@return != 0),
+ * only ZSTD_e_end or ZSTD_e_flush operations are allowed.
+ * Before starting a new compression job, or changing compression parameters,
+ * it is required to fully flush internal buffers.
+ * - note: if an operation ends with an error, it may leave @cctx in an undefined state.
+ * Therefore, it's UB to invoke ZSTD_compressStream2() of ZSTD_compressStream() on such a state.
+ * In order to be re-employed after an error, a state must be reset,
+ * which can be done explicitly (ZSTD_CCtx_reset()),
+ * or is sometimes implied by methods starting a new compression job (ZSTD_initCStream(), ZSTD_compressCCtx())
+ */
+ZSTDLIB_API size_t ZSTD_compressStream2( ZSTD_CCtx* cctx,
+ ZSTD_outBuffer* output,
+ ZSTD_inBuffer* input,
+ ZSTD_EndDirective endOp);
+
+
+/* These buffer sizes are softly recommended.
+ * They are not required : ZSTD_compressStream*() happily accepts any buffer size, for both input and output.
+ * Respecting the recommended size just makes it a bit easier for ZSTD_compressStream*(),
+ * reducing the amount of memory shuffling and buffering, resulting in minor performance savings.
+ *
+ * However, note that these recommendations are from the perspective of a C caller program.
+ * If the streaming interface is invoked from some other language,
+ * especially managed ones such as Java or Go, through a foreign function interface such as jni or cgo,
+ * a major performance rule is to reduce crossing such interface to an absolute minimum.
+ * It's not rare that performance ends being spent more into the interface, rather than compression itself.
+ * In which cases, prefer using large buffers, as large as practical,
+ * for both input and output, to reduce the nb of roundtrips.
+ */
+ZSTDLIB_API size_t ZSTD_CStreamInSize(void); /**< recommended size for input buffer */
+ZSTDLIB_API size_t ZSTD_CStreamOutSize(void); /**< recommended size for output buffer. Guarantee to successfully flush at least one complete compressed block. */
+
+
+/* *****************************************************************************
+ * This following is a legacy streaming API, available since v1.0+ .
+ * It can be replaced by ZSTD_CCtx_reset() and ZSTD_compressStream2().
+ * It is redundant, but remains fully supported.
+ ******************************************************************************/
+
+/*!
+ * Equivalent to:
+ *
+ * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only);
+ * ZSTD_CCtx_refCDict(zcs, NULL); // clear the dictionary (if any)
+ * ZSTD_CCtx_setParameter(zcs, ZSTD_c_compressionLevel, compressionLevel);
+ *
+ * Note that ZSTD_initCStream() clears any previously set dictionary. Use the new API
+ * to compress with a dictionary.
+ */
+ZSTDLIB_API size_t ZSTD_initCStream(ZSTD_CStream* zcs, int compressionLevel);
+/*!
+ * Alternative for ZSTD_compressStream2(zcs, output, input, ZSTD_e_continue).
+ * NOTE: The return value is different. ZSTD_compressStream() returns a hint for
+ * the next read size (if non-zero and not an error). ZSTD_compressStream2()
+ * returns the minimum nb of bytes left to flush (if non-zero and not an error).
+ */
+ZSTDLIB_API size_t ZSTD_compressStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output, ZSTD_inBuffer* input);
+/*! Equivalent to ZSTD_compressStream2(zcs, output, &emptyInput, ZSTD_e_flush). */
+ZSTDLIB_API size_t ZSTD_flushStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output);
+/*! Equivalent to ZSTD_compressStream2(zcs, output, &emptyInput, ZSTD_e_end). */
+ZSTDLIB_API size_t ZSTD_endStream(ZSTD_CStream* zcs, ZSTD_outBuffer* output);
+
+
+/*-***************************************************************************
+* Streaming decompression - HowTo
+*
+* A ZSTD_DStream object is required to track streaming operations.
+* Use ZSTD_createDStream() and ZSTD_freeDStream() to create/release resources.
+* ZSTD_DStream objects can be reused multiple times.
+*
+* Use ZSTD_initDStream() to start a new decompression operation.
+* @return : recommended first input size
+* Alternatively, use advanced API to set specific properties.
+*
+* Use ZSTD_decompressStream() repetitively to consume your input.
+* The function will update both `pos` fields.
+* If `input.pos < input.size`, some input has not been consumed.
+* It's up to the caller to present again remaining data.
+* The function tries to flush all data decoded immediately, respecting output buffer size.
+* If `output.pos < output.size`, decoder has flushed everything it could.
+* But if `output.pos == output.size`, there might be some data left within internal buffers.,
+* In which case, call ZSTD_decompressStream() again to flush whatever remains in the buffer.
+* Note : with no additional input provided, amount of data flushed is necessarily <= ZSTD_BLOCKSIZE_MAX.
+* @return : 0 when a frame is completely decoded and fully flushed,
+* or an error code, which can be tested using ZSTD_isError(),
+* or any other value > 0, which means there is still some decoding or flushing to do to complete current frame :
+* the return value is a suggested next input size (just a hint for better latency)
+* that will never request more than the remaining frame size.
+* *******************************************************************************/
+
+typedef ZSTD_DCtx ZSTD_DStream; /**< DCtx and DStream are now effectively same object (>= v1.3.0) */
+ /* For compatibility with versions <= v1.2.0, prefer differentiating them. */
+/*===== ZSTD_DStream management functions =====*/
+ZSTDLIB_API ZSTD_DStream* ZSTD_createDStream(void);
+ZSTDLIB_API size_t ZSTD_freeDStream(ZSTD_DStream* zds); /* accept NULL pointer */
+
+/*===== Streaming decompression functions =====*/
+
+/*! ZSTD_initDStream() :
+ * Initialize/reset DStream state for new decompression operation.
+ * Call before new decompression operation using same DStream.
+ *
+ * Note : This function is redundant with the advanced API and equivalent to:
+ * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only);
+ * ZSTD_DCtx_refDDict(zds, NULL);
+ */
+ZSTDLIB_API size_t ZSTD_initDStream(ZSTD_DStream* zds);
+
+/*! ZSTD_decompressStream() :
+ * Streaming decompression function.
+ * Call repetitively to consume full input updating it as necessary.
+ * Function will update both input and output `pos` fields exposing current state via these fields:
+ * - `input.pos < input.size`, some input remaining and caller should provide remaining input
+ * on the next call.
+ * - `output.pos < output.size`, decoder finished and flushed all remaining buffers.
+ * - `output.pos == output.size`, potentially uncflushed data present in the internal buffers,
+ * call ZSTD_decompressStream() again to flush remaining data to output.
+ * Note : with no additional input, amount of data flushed <= ZSTD_BLOCKSIZE_MAX.
+ *
+ * @return : 0 when a frame is completely decoded and fully flushed,
+ * or an error code, which can be tested using ZSTD_isError(),
+ * or any other value > 0, which means there is some decoding or flushing to do to complete current frame.
+ *
+ * Note: when an operation returns with an error code, the @zds state may be left in undefined state.
+ * It's UB to invoke `ZSTD_decompressStream()` on such a state.
+ * In order to re-use such a state, it must be first reset,
+ * which can be done explicitly (`ZSTD_DCtx_reset()`),
+ * or is implied for operations starting some new decompression job (`ZSTD_initDStream`, `ZSTD_decompressDCtx()`, `ZSTD_decompress_usingDict()`)
+ */
+ZSTDLIB_API size_t ZSTD_decompressStream(ZSTD_DStream* zds, ZSTD_outBuffer* output, ZSTD_inBuffer* input);
+
+ZSTDLIB_API size_t ZSTD_DStreamInSize(void); /*!< recommended size for input buffer */
+ZSTDLIB_API size_t ZSTD_DStreamOutSize(void); /*!< recommended size for output buffer. Guarantee to successfully flush at least one complete block in all circumstances. */
+
+
+/**************************
+* Simple dictionary API
+***************************/
+/*! ZSTD_compress_usingDict() :
+ * Compression at an explicit compression level using a Dictionary.
+ * A dictionary can be any arbitrary data segment (also called a prefix),
+ * or a buffer with specified information (see zdict.h).
+ * Note : This function loads the dictionary, resulting in significant startup delay.
+ * It's intended for a dictionary used only once.
+ * Note 2 : When `dict == NULL || dictSize < 8` no dictionary is used. */
+ZSTDLIB_API size_t ZSTD_compress_usingDict(ZSTD_CCtx* ctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize,
+ const void* dict,size_t dictSize,
+ int compressionLevel);
+
+/*! ZSTD_decompress_usingDict() :
+ * Decompression using a known Dictionary.
+ * Dictionary must be identical to the one used during compression.
+ * Note : This function loads the dictionary, resulting in significant startup delay.
+ * It's intended for a dictionary used only once.
+ * Note : When `dict == NULL || dictSize < 8` no dictionary is used. */
+ZSTDLIB_API size_t ZSTD_decompress_usingDict(ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize,
+ const void* dict,size_t dictSize);
+
+
+/***********************************
+ * Bulk processing dictionary API
+ **********************************/
+typedef struct ZSTD_CDict_s ZSTD_CDict;
+
+/*! ZSTD_createCDict() :
+ * When compressing multiple messages or blocks using the same dictionary,
+ * it's recommended to digest the dictionary only once, since it's a costly operation.
+ * ZSTD_createCDict() will create a state from digesting a dictionary.
+ * The resulting state can be used for future compression operations with very limited startup cost.
+ * ZSTD_CDict can be created once and shared by multiple threads concurrently, since its usage is read-only.
+ * @dictBuffer can be released after ZSTD_CDict creation, because its content is copied within CDict.
+ * Note 1 : Consider experimental function `ZSTD_createCDict_byReference()` if you prefer to not duplicate @dictBuffer content.
+ * Note 2 : A ZSTD_CDict can be created from an empty @dictBuffer,
+ * in which case the only thing that it transports is the @compressionLevel.
+ * This can be useful in a pipeline featuring ZSTD_compress_usingCDict() exclusively,
+ * expecting a ZSTD_CDict parameter with any data, including those without a known dictionary. */
+ZSTDLIB_API ZSTD_CDict* ZSTD_createCDict(const void* dictBuffer, size_t dictSize,
+ int compressionLevel);
+
+/*! ZSTD_freeCDict() :
+ * Function frees memory allocated by ZSTD_createCDict().
+ * If a NULL pointer is passed, no operation is performed. */
+ZSTDLIB_API size_t ZSTD_freeCDict(ZSTD_CDict* CDict);
+
+/*! ZSTD_compress_usingCDict() :
+ * Compression using a digested Dictionary.
+ * Recommended when same dictionary is used multiple times.
+ * Note : compression level is _decided at dictionary creation time_,
+ * and frame parameters are hardcoded (dictID=yes, contentSize=yes, checksum=no) */
+ZSTDLIB_API size_t ZSTD_compress_usingCDict(ZSTD_CCtx* cctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize,
+ const ZSTD_CDict* cdict);
+
+
+typedef struct ZSTD_DDict_s ZSTD_DDict;
+
+/*! ZSTD_createDDict() :
+ * Create a digested dictionary, ready to start decompression operation without startup delay.
+ * dictBuffer can be released after DDict creation, as its content is copied inside DDict. */
+ZSTDLIB_API ZSTD_DDict* ZSTD_createDDict(const void* dictBuffer, size_t dictSize);
+
+/*! ZSTD_freeDDict() :
+ * Function frees memory allocated with ZSTD_createDDict()
+ * If a NULL pointer is passed, no operation is performed. */
+ZSTDLIB_API size_t ZSTD_freeDDict(ZSTD_DDict* ddict);
+
+/*! ZSTD_decompress_usingDDict() :
+ * Decompression using a digested Dictionary.
+ * Recommended when same dictionary is used multiple times. */
+ZSTDLIB_API size_t ZSTD_decompress_usingDDict(ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize,
+ const ZSTD_DDict* ddict);
+
+
+/********************************
+ * Dictionary helper functions
+ *******************************/
+
+/*! ZSTD_getDictID_fromDict() : Requires v1.4.0+
+ * Provides the dictID stored within dictionary.
+ * if @return == 0, the dictionary is not conformant with Zstandard specification.
+ * It can still be loaded, but as a content-only dictionary. */
+ZSTDLIB_API unsigned ZSTD_getDictID_fromDict(const void* dict, size_t dictSize);
+
+/*! ZSTD_getDictID_fromCDict() : Requires v1.5.0+
+ * Provides the dictID of the dictionary loaded into `cdict`.
+ * If @return == 0, the dictionary is not conformant to Zstandard specification, or empty.
+ * Non-conformant dictionaries can still be loaded, but as content-only dictionaries. */
+ZSTDLIB_API unsigned ZSTD_getDictID_fromCDict(const ZSTD_CDict* cdict);
+
+/*! ZSTD_getDictID_fromDDict() : Requires v1.4.0+
+ * Provides the dictID of the dictionary loaded into `ddict`.
+ * If @return == 0, the dictionary is not conformant to Zstandard specification, or empty.
+ * Non-conformant dictionaries can still be loaded, but as content-only dictionaries. */
+ZSTDLIB_API unsigned ZSTD_getDictID_fromDDict(const ZSTD_DDict* ddict);
+
+/*! ZSTD_getDictID_fromFrame() : Requires v1.4.0+
+ * Provides the dictID required to decompressed the frame stored within `src`.
+ * If @return == 0, the dictID could not be decoded.
+ * This could for one of the following reasons :
+ * - The frame does not require a dictionary to be decoded (most common case).
+ * - The frame was built with dictID intentionally removed. Whatever dictionary is necessary is a hidden piece of information.
+ * Note : this use case also happens when using a non-conformant dictionary.
+ * - `srcSize` is too small, and as a result, the frame header could not be decoded (only possible if `srcSize < ZSTD_FRAMEHEADERSIZE_MAX`).
+ * - This is not a Zstandard frame.
+ * When identifying the exact failure cause, it's possible to use ZSTD_getFrameHeader(), which will provide a more precise error code. */
+ZSTDLIB_API unsigned ZSTD_getDictID_fromFrame(const void* src, size_t srcSize);
+
+
+/*******************************************************************************
+ * Advanced dictionary and prefix API (Requires v1.4.0+)
+ *
+ * This API allows dictionaries to be used with ZSTD_compress2(),
+ * ZSTD_compressStream2(), and ZSTD_decompressDCtx().
+ * Dictionaries are sticky, they remain valid when same context is reused,
+ * they only reset when the context is reset
+ * with ZSTD_reset_parameters or ZSTD_reset_session_and_parameters.
+ * In contrast, Prefixes are single-use.
+ ******************************************************************************/
+
+
+/*! ZSTD_CCtx_loadDictionary() : Requires v1.4.0+
+ * Create an internal CDict from `dict` buffer.
+ * Decompression will have to use same dictionary.
+ * @result : 0, or an error code (which can be tested with ZSTD_isError()).
+ * Special: Loading a NULL (or 0-size) dictionary invalidates previous dictionary,
+ * meaning "return to no-dictionary mode".
+ * Note 1 : Dictionary is sticky, it will be used for all future compressed frames,
+ * until parameters are reset, a new dictionary is loaded, or the dictionary
+ * is explicitly invalidated by loading a NULL dictionary.
+ * Note 2 : Loading a dictionary involves building tables.
+ * It's also a CPU consuming operation, with non-negligible impact on latency.
+ * Tables are dependent on compression parameters, and for this reason,
+ * compression parameters can no longer be changed after loading a dictionary.
+ * Note 3 :`dict` content will be copied internally.
+ * Use experimental ZSTD_CCtx_loadDictionary_byReference() to reference content instead.
+ * In such a case, dictionary buffer must outlive its users.
+ * Note 4 : Use ZSTD_CCtx_loadDictionary_advanced()
+ * to precisely select how dictionary content must be interpreted.
+ * Note 5 : This method does not benefit from LDM (long distance mode).
+ * If you want to employ LDM on some large dictionary content,
+ * prefer employing ZSTD_CCtx_refPrefix() described below.
+ */
+ZSTDLIB_API size_t ZSTD_CCtx_loadDictionary(ZSTD_CCtx* cctx, const void* dict, size_t dictSize);
+
+/*! ZSTD_CCtx_refCDict() : Requires v1.4.0+
+ * Reference a prepared dictionary, to be used for all future compressed frames.
+ * Note that compression parameters are enforced from within CDict,
+ * and supersede any compression parameter previously set within CCtx.
+ * The parameters ignored are labelled as "superseded-by-cdict" in the ZSTD_cParameter enum docs.
+ * The ignored parameters will be used again if the CCtx is returned to no-dictionary mode.
+ * The dictionary will remain valid for future compressed frames using same CCtx.
+ * @result : 0, or an error code (which can be tested with ZSTD_isError()).
+ * Special : Referencing a NULL CDict means "return to no-dictionary mode".
+ * Note 1 : Currently, only one dictionary can be managed.
+ * Referencing a new dictionary effectively "discards" any previous one.
+ * Note 2 : CDict is just referenced, its lifetime must outlive its usage within CCtx. */
+ZSTDLIB_API size_t ZSTD_CCtx_refCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict);
+
+/*! ZSTD_CCtx_refPrefix() : Requires v1.4.0+
+ * Reference a prefix (single-usage dictionary) for next compressed frame.
+ * A prefix is **only used once**. Tables are discarded at end of frame (ZSTD_e_end).
+ * Decompression will need same prefix to properly regenerate data.
+ * Compressing with a prefix is similar in outcome as performing a diff and compressing it,
+ * but performs much faster, especially during decompression (compression speed is tunable with compression level).
+ * This method is compatible with LDM (long distance mode).
+ * @result : 0, or an error code (which can be tested with ZSTD_isError()).
+ * Special: Adding any prefix (including NULL) invalidates any previous prefix or dictionary
+ * Note 1 : Prefix buffer is referenced. It **must** outlive compression.
+ * Its content must remain unmodified during compression.
+ * Note 2 : If the intention is to diff some large src data blob with some prior version of itself,
+ * ensure that the window size is large enough to contain the entire source.
+ * See ZSTD_c_windowLog.
+ * Note 3 : Referencing a prefix involves building tables, which are dependent on compression parameters.
+ * It's a CPU consuming operation, with non-negligible impact on latency.
+ * If there is a need to use the same prefix multiple times, consider loadDictionary instead.
+ * Note 4 : By default, the prefix is interpreted as raw content (ZSTD_dct_rawContent).
+ * Use experimental ZSTD_CCtx_refPrefix_advanced() to alter dictionary interpretation. */
+ZSTDLIB_API size_t ZSTD_CCtx_refPrefix(ZSTD_CCtx* cctx,
+ const void* prefix, size_t prefixSize);
+
+/*! ZSTD_DCtx_loadDictionary() : Requires v1.4.0+
+ * Create an internal DDict from dict buffer, to be used to decompress all future frames.
+ * The dictionary remains valid for all future frames, until explicitly invalidated, or
+ * a new dictionary is loaded.
+ * @result : 0, or an error code (which can be tested with ZSTD_isError()).
+ * Special : Adding a NULL (or 0-size) dictionary invalidates any previous dictionary,
+ * meaning "return to no-dictionary mode".
+ * Note 1 : Loading a dictionary involves building tables,
+ * which has a non-negligible impact on CPU usage and latency.
+ * It's recommended to "load once, use many times", to amortize the cost
+ * Note 2 :`dict` content will be copied internally, so `dict` can be released after loading.
+ * Use ZSTD_DCtx_loadDictionary_byReference() to reference dictionary content instead.
+ * Note 3 : Use ZSTD_DCtx_loadDictionary_advanced() to take control of
+ * how dictionary content is loaded and interpreted.
+ */
+ZSTDLIB_API size_t ZSTD_DCtx_loadDictionary(ZSTD_DCtx* dctx, const void* dict, size_t dictSize);
+
+/*! ZSTD_DCtx_refDDict() : Requires v1.4.0+
+ * Reference a prepared dictionary, to be used to decompress next frames.
+ * The dictionary remains active for decompression of future frames using same DCtx.
+ *
+ * If called with ZSTD_d_refMultipleDDicts enabled, repeated calls of this function
+ * will store the DDict references in a table, and the DDict used for decompression
+ * will be determined at decompression time, as per the dict ID in the frame.
+ * The memory for the table is allocated on the first call to refDDict, and can be
+ * freed with ZSTD_freeDCtx().
+ *
+ * If called with ZSTD_d_refMultipleDDicts disabled (the default), only one dictionary
+ * will be managed, and referencing a dictionary effectively "discards" any previous one.
+ *
+ * @result : 0, or an error code (which can be tested with ZSTD_isError()).
+ * Special: referencing a NULL DDict means "return to no-dictionary mode".
+ * Note 2 : DDict is just referenced, its lifetime must outlive its usage from DCtx.
+ */
+ZSTDLIB_API size_t ZSTD_DCtx_refDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict);
+
+/*! ZSTD_DCtx_refPrefix() : Requires v1.4.0+
+ * Reference a prefix (single-usage dictionary) to decompress next frame.
+ * This is the reverse operation of ZSTD_CCtx_refPrefix(),
+ * and must use the same prefix as the one used during compression.
+ * Prefix is **only used once**. Reference is discarded at end of frame.
+ * End of frame is reached when ZSTD_decompressStream() returns 0.
+ * @result : 0, or an error code (which can be tested with ZSTD_isError()).
+ * Note 1 : Adding any prefix (including NULL) invalidates any previously set prefix or dictionary
+ * Note 2 : Prefix buffer is referenced. It **must** outlive decompression.
+ * Prefix buffer must remain unmodified up to the end of frame,
+ * reached when ZSTD_decompressStream() returns 0.
+ * Note 3 : By default, the prefix is treated as raw content (ZSTD_dct_rawContent).
+ * Use ZSTD_CCtx_refPrefix_advanced() to alter dictMode (Experimental section)
+ * Note 4 : Referencing a raw content prefix has almost no cpu nor memory cost.
+ * A full dictionary is more costly, as it requires building tables.
+ */
+ZSTDLIB_API size_t ZSTD_DCtx_refPrefix(ZSTD_DCtx* dctx,
+ const void* prefix, size_t prefixSize);
+
+/* === Memory management === */
+
+/*! ZSTD_sizeof_*() : Requires v1.4.0+
+ * These functions give the _current_ memory usage of selected object.
+ * Note that object memory usage can evolve (increase or decrease) over time. */
+ZSTDLIB_API size_t ZSTD_sizeof_CCtx(const ZSTD_CCtx* cctx);
+ZSTDLIB_API size_t ZSTD_sizeof_DCtx(const ZSTD_DCtx* dctx);
+ZSTDLIB_API size_t ZSTD_sizeof_CStream(const ZSTD_CStream* zcs);
+ZSTDLIB_API size_t ZSTD_sizeof_DStream(const ZSTD_DStream* zds);
+ZSTDLIB_API size_t ZSTD_sizeof_CDict(const ZSTD_CDict* cdict);
+ZSTDLIB_API size_t ZSTD_sizeof_DDict(const ZSTD_DDict* ddict);
+
+#endif /* ZSTD_H_235446 */
+
+
+/* **************************************************************************************
+ * ADVANCED AND EXPERIMENTAL FUNCTIONS
+ ****************************************************************************************
+ * The definitions in the following section are considered experimental.
+ * They are provided for advanced scenarios.
+ * They should never be used with a dynamic library, as prototypes may change in the future.
+ * Use them only in association with static linking.
+ * ***************************************************************************************/
+
+#if defined(ZSTD_STATIC_LINKING_ONLY) && !defined(ZSTD_H_ZSTD_STATIC_LINKING_ONLY)
+#define ZSTD_H_ZSTD_STATIC_LINKING_ONLY
+
+/* This can be overridden externally to hide static symbols. */
+#ifndef ZSTDLIB_STATIC_API
+# if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1)
+# define ZSTDLIB_STATIC_API __declspec(dllexport) ZSTDLIB_VISIBLE
+# elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1)
+# define ZSTDLIB_STATIC_API __declspec(dllimport) ZSTDLIB_VISIBLE
+# else
+# define ZSTDLIB_STATIC_API ZSTDLIB_VISIBLE
+# endif
+#endif
+
+/****************************************************************************************
+ * experimental API (static linking only)
+ ****************************************************************************************
+ * The following symbols and constants
+ * are not planned to join "stable API" status in the near future.
+ * They can still change in future versions.
+ * Some of them are planned to remain in the static_only section indefinitely.
+ * Some of them might be removed in the future (especially when redundant with existing stable functions)
+ * ***************************************************************************************/
+
+#define ZSTD_FRAMEHEADERSIZE_PREFIX(format) ((format) == ZSTD_f_zstd1 ? 5 : 1) /* minimum input size required to query frame header size */
+#define ZSTD_FRAMEHEADERSIZE_MIN(format) ((format) == ZSTD_f_zstd1 ? 6 : 2)
+#define ZSTD_FRAMEHEADERSIZE_MAX 18 /* can be useful for static allocation */
+#define ZSTD_SKIPPABLEHEADERSIZE 8
+
+/* compression parameter bounds */
+#define ZSTD_WINDOWLOG_MAX_32 30
+#define ZSTD_WINDOWLOG_MAX_64 31
+#define ZSTD_WINDOWLOG_MAX ((int)(sizeof(size_t) == 4 ? ZSTD_WINDOWLOG_MAX_32 : ZSTD_WINDOWLOG_MAX_64))
+#define ZSTD_WINDOWLOG_MIN 10
+#define ZSTD_HASHLOG_MAX ((ZSTD_WINDOWLOG_MAX < 30) ? ZSTD_WINDOWLOG_MAX : 30)
+#define ZSTD_HASHLOG_MIN 6
+#define ZSTD_CHAINLOG_MAX_32 29
+#define ZSTD_CHAINLOG_MAX_64 30
+#define ZSTD_CHAINLOG_MAX ((int)(sizeof(size_t) == 4 ? ZSTD_CHAINLOG_MAX_32 : ZSTD_CHAINLOG_MAX_64))
+#define ZSTD_CHAINLOG_MIN ZSTD_HASHLOG_MIN
+#define ZSTD_SEARCHLOG_MAX (ZSTD_WINDOWLOG_MAX-1)
+#define ZSTD_SEARCHLOG_MIN 1
+#define ZSTD_MINMATCH_MAX 7 /* only for ZSTD_fast, other strategies are limited to 6 */
+#define ZSTD_MINMATCH_MIN 3 /* only for ZSTD_btopt+, faster strategies are limited to 4 */
+#define ZSTD_TARGETLENGTH_MAX ZSTD_BLOCKSIZE_MAX
+#define ZSTD_TARGETLENGTH_MIN 0 /* note : comparing this constant to an unsigned results in a tautological test */
+#define ZSTD_STRATEGY_MIN ZSTD_fast
+#define ZSTD_STRATEGY_MAX ZSTD_btultra2
+#define ZSTD_BLOCKSIZE_MAX_MIN (1 << 10) /* The minimum valid max blocksize. Maximum blocksizes smaller than this make compressBound() inaccurate. */
+
+
+#define ZSTD_OVERLAPLOG_MIN 0
+#define ZSTD_OVERLAPLOG_MAX 9
+
+#define ZSTD_WINDOWLOG_LIMIT_DEFAULT 27 /* by default, the streaming decoder will refuse any frame
+ * requiring larger than (1<<ZSTD_WINDOWLOG_LIMIT_DEFAULT) window size,
+ * to preserve host's memory from unreasonable requirements.
+ * This limit can be overridden using ZSTD_DCtx_setParameter(,ZSTD_d_windowLogMax,).
+ * The limit does not apply for one-pass decoders (such as ZSTD_decompress()), since no additional memory is allocated */
+
+
+/* LDM parameter bounds */
+#define ZSTD_LDM_HASHLOG_MIN ZSTD_HASHLOG_MIN
+#define ZSTD_LDM_HASHLOG_MAX ZSTD_HASHLOG_MAX
+#define ZSTD_LDM_MINMATCH_MIN 4
+#define ZSTD_LDM_MINMATCH_MAX 4096
+#define ZSTD_LDM_BUCKETSIZELOG_MIN 1
+#define ZSTD_LDM_BUCKETSIZELOG_MAX 8
+#define ZSTD_LDM_HASHRATELOG_MIN 0
+#define ZSTD_LDM_HASHRATELOG_MAX (ZSTD_WINDOWLOG_MAX - ZSTD_HASHLOG_MIN)
+
+/* Advanced parameter bounds */
+#define ZSTD_TARGETCBLOCKSIZE_MIN 1340 /* suitable to fit into an ethernet / wifi / 4G transport frame */
+#define ZSTD_TARGETCBLOCKSIZE_MAX ZSTD_BLOCKSIZE_MAX
+#define ZSTD_SRCSIZEHINT_MIN 0
+#define ZSTD_SRCSIZEHINT_MAX INT_MAX
+
+
+/* --- Advanced types --- */
+
+typedef struct ZSTD_CCtx_params_s ZSTD_CCtx_params;
+
+typedef struct {
+ unsigned int offset; /* The offset of the match. (NOT the same as the offset code)
+ * If offset == 0 and matchLength == 0, this sequence represents the last
+ * literals in the block of litLength size.
+ */
+
+ unsigned int litLength; /* Literal length of the sequence. */
+ unsigned int matchLength; /* Match length of the sequence. */
+
+ /* Note: Users of this API may provide a sequence with matchLength == litLength == offset == 0.
+ * In this case, we will treat the sequence as a marker for a block boundary.
+ */
+
+ unsigned int rep; /* Represents which repeat offset is represented by the field 'offset'.
+ * Ranges from [0, 3].
+ *
+ * Repeat offsets are essentially previous offsets from previous sequences sorted in
+ * recency order. For more detail, see doc/zstd_compression_format.md
+ *
+ * If rep == 0, then 'offset' does not contain a repeat offset.
+ * If rep > 0:
+ * If litLength != 0:
+ * rep == 1 --> offset == repeat_offset_1
+ * rep == 2 --> offset == repeat_offset_2
+ * rep == 3 --> offset == repeat_offset_3
+ * If litLength == 0:
+ * rep == 1 --> offset == repeat_offset_2
+ * rep == 2 --> offset == repeat_offset_3
+ * rep == 3 --> offset == repeat_offset_1 - 1
+ *
+ * Note: This field is optional. ZSTD_generateSequences() will calculate the value of
+ * 'rep', but repeat offsets do not necessarily need to be calculated from an external
+ * sequence provider's perspective. For example, ZSTD_compressSequences() does not
+ * use this 'rep' field at all (as of now).
+ */
+} ZSTD_Sequence;
+
+typedef struct {
+ unsigned windowLog; /**< largest match distance : larger == more compression, more memory needed during decompression */
+ unsigned chainLog; /**< fully searched segment : larger == more compression, slower, more memory (useless for fast) */
+ unsigned hashLog; /**< dispatch table : larger == faster, more memory */
+ unsigned searchLog; /**< nb of searches : larger == more compression, slower */
+ unsigned minMatch; /**< match length searched : larger == faster decompression, sometimes less compression */
+ unsigned targetLength; /**< acceptable match size for optimal parser (only) : larger == more compression, slower */
+ ZSTD_strategy strategy; /**< see ZSTD_strategy definition above */
+} ZSTD_compressionParameters;
+
+typedef struct {
+ int contentSizeFlag; /**< 1: content size will be in frame header (when known) */
+ int checksumFlag; /**< 1: generate a 32-bits checksum using XXH64 algorithm at end of frame, for error detection */
+ int noDictIDFlag; /**< 1: no dictID will be saved into frame header (dictID is only useful for dictionary compression) */
+} ZSTD_frameParameters;
+
+typedef struct {
+ ZSTD_compressionParameters cParams;
+ ZSTD_frameParameters fParams;
+} ZSTD_parameters;
+
+typedef enum {
+ ZSTD_dct_auto = 0, /* dictionary is "full" when starting with ZSTD_MAGIC_DICTIONARY, otherwise it is "rawContent" */
+ ZSTD_dct_rawContent = 1, /* ensures dictionary is always loaded as rawContent, even if it starts with ZSTD_MAGIC_DICTIONARY */
+ ZSTD_dct_fullDict = 2 /* refuses to load a dictionary if it does not respect Zstandard's specification, starting with ZSTD_MAGIC_DICTIONARY */
+} ZSTD_dictContentType_e;
+
+typedef enum {
+ ZSTD_dlm_byCopy = 0, /**< Copy dictionary content internally */
+ ZSTD_dlm_byRef = 1 /**< Reference dictionary content -- the dictionary buffer must outlive its users. */
+} ZSTD_dictLoadMethod_e;
+
+typedef enum {
+ ZSTD_f_zstd1 = 0, /* zstd frame format, specified in zstd_compression_format.md (default) */
+ ZSTD_f_zstd1_magicless = 1 /* Variant of zstd frame format, without initial 4-bytes magic number.
+ * Useful to save 4 bytes per generated frame.
+ * Decoder cannot recognise automatically this format, requiring this instruction. */
+} ZSTD_format_e;
+
+typedef enum {
+ /* Note: this enum controls ZSTD_d_forceIgnoreChecksum */
+ ZSTD_d_validateChecksum = 0,
+ ZSTD_d_ignoreChecksum = 1
+} ZSTD_forceIgnoreChecksum_e;
+
+typedef enum {
+ /* Note: this enum controls ZSTD_d_refMultipleDDicts */
+ ZSTD_rmd_refSingleDDict = 0,
+ ZSTD_rmd_refMultipleDDicts = 1
+} ZSTD_refMultipleDDicts_e;
+
+typedef enum {
+ /* Note: this enum and the behavior it controls are effectively internal
+ * implementation details of the compressor. They are expected to continue
+ * to evolve and should be considered only in the context of extremely
+ * advanced performance tuning.
+ *
+ * Zstd currently supports the use of a CDict in three ways:
+ *
+ * - The contents of the CDict can be copied into the working context. This
+ * means that the compression can search both the dictionary and input
+ * while operating on a single set of internal tables. This makes
+ * the compression faster per-byte of input. However, the initial copy of
+ * the CDict's tables incurs a fixed cost at the beginning of the
+ * compression. For small compressions (< 8 KB), that copy can dominate
+ * the cost of the compression.
+ *
+ * - The CDict's tables can be used in-place. In this model, compression is
+ * slower per input byte, because the compressor has to search two sets of
+ * tables. However, this model incurs no start-up cost (as long as the
+ * working context's tables can be reused). For small inputs, this can be
+ * faster than copying the CDict's tables.
+ *
+ * - The CDict's tables are not used at all, and instead we use the working
+ * context alone to reload the dictionary and use params based on the source
+ * size. See ZSTD_compress_insertDictionary() and ZSTD_compress_usingDict().
+ * This method is effective when the dictionary sizes are very small relative
+ * to the input size, and the input size is fairly large to begin with.
+ *
+ * Zstd has a simple internal heuristic that selects which strategy to use
+ * at the beginning of a compression. However, if experimentation shows that
+ * Zstd is making poor choices, it is possible to override that choice with
+ * this enum.
+ */
+ ZSTD_dictDefaultAttach = 0, /* Use the default heuristic. */
+ ZSTD_dictForceAttach = 1, /* Never copy the dictionary. */
+ ZSTD_dictForceCopy = 2, /* Always copy the dictionary. */
+ ZSTD_dictForceLoad = 3 /* Always reload the dictionary */
+} ZSTD_dictAttachPref_e;
+
+typedef enum {
+ ZSTD_lcm_auto = 0, /**< Automatically determine the compression mode based on the compression level.
+ * Negative compression levels will be uncompressed, and positive compression
+ * levels will be compressed. */
+ ZSTD_lcm_huffman = 1, /**< Always attempt Huffman compression. Uncompressed literals will still be
+ * emitted if Huffman compression is not profitable. */
+ ZSTD_lcm_uncompressed = 2 /**< Always emit uncompressed literals. */
+} ZSTD_literalCompressionMode_e;
+
+typedef enum {
+ /* Note: This enum controls features which are conditionally beneficial. Zstd typically will make a final
+ * decision on whether or not to enable the feature (ZSTD_ps_auto), but setting the switch to ZSTD_ps_enable
+ * or ZSTD_ps_disable allow for a force enable/disable the feature.
+ */
+ ZSTD_ps_auto = 0, /* Let the library automatically determine whether the feature shall be enabled */
+ ZSTD_ps_enable = 1, /* Force-enable the feature */
+ ZSTD_ps_disable = 2 /* Do not use the feature */
+} ZSTD_paramSwitch_e;
+
+/***************************************
+* Frame header and size functions
+***************************************/
+
+/*! ZSTD_findDecompressedSize() :
+ * `src` should point to the start of a series of ZSTD encoded and/or skippable frames
+ * `srcSize` must be the _exact_ size of this series
+ * (i.e. there should be a frame boundary at `src + srcSize`)
+ * @return : - decompressed size of all data in all successive frames
+ * - if the decompressed size cannot be determined: ZSTD_CONTENTSIZE_UNKNOWN
+ * - if an error occurred: ZSTD_CONTENTSIZE_ERROR
+ *
+ * note 1 : decompressed size is an optional field, that may not be present, especially in streaming mode.
+ * When `return==ZSTD_CONTENTSIZE_UNKNOWN`, data to decompress could be any size.
+ * In which case, it's necessary to use streaming mode to decompress data.
+ * note 2 : decompressed size is always present when compression is done with ZSTD_compress()
+ * note 3 : decompressed size can be very large (64-bits value),
+ * potentially larger than what local system can handle as a single memory segment.
+ * In which case, it's necessary to use streaming mode to decompress data.
+ * note 4 : If source is untrusted, decompressed size could be wrong or intentionally modified.
+ * Always ensure result fits within application's authorized limits.
+ * Each application can set its own limits.
+ * note 5 : ZSTD_findDecompressedSize handles multiple frames, and so it must traverse the input to
+ * read each contained frame header. This is fast as most of the data is skipped,
+ * however it does mean that all frame data must be present and valid. */
+ZSTDLIB_STATIC_API unsigned long long ZSTD_findDecompressedSize(const void* src, size_t srcSize);
+
+/*! ZSTD_decompressBound() :
+ * `src` should point to the start of a series of ZSTD encoded and/or skippable frames
+ * `srcSize` must be the _exact_ size of this series
+ * (i.e. there should be a frame boundary at `src + srcSize`)
+ * @return : - upper-bound for the decompressed size of all data in all successive frames
+ * - if an error occurred: ZSTD_CONTENTSIZE_ERROR
+ *
+ * note 1 : an error can occur if `src` contains an invalid or incorrectly formatted frame.
+ * note 2 : the upper-bound is exact when the decompressed size field is available in every ZSTD encoded frame of `src`.
+ * in this case, `ZSTD_findDecompressedSize` and `ZSTD_decompressBound` return the same value.
+ * note 3 : when the decompressed size field isn't available, the upper-bound for that frame is calculated by:
+ * upper-bound = # blocks * min(128 KB, Window_Size)
+ */
+ZSTDLIB_STATIC_API unsigned long long ZSTD_decompressBound(const void* src, size_t srcSize);
+
+/*! ZSTD_frameHeaderSize() :
+ * srcSize must be >= ZSTD_FRAMEHEADERSIZE_PREFIX.
+ * @return : size of the Frame Header,
+ * or an error code (if srcSize is too small) */
+ZSTDLIB_STATIC_API size_t ZSTD_frameHeaderSize(const void* src, size_t srcSize);
+
+typedef enum { ZSTD_frame, ZSTD_skippableFrame } ZSTD_frameType_e;
+typedef struct {
+ unsigned long long frameContentSize; /* if == ZSTD_CONTENTSIZE_UNKNOWN, it means this field is not available. 0 means "empty" */
+ unsigned long long windowSize; /* can be very large, up to <= frameContentSize */
+ unsigned blockSizeMax;
+ ZSTD_frameType_e frameType; /* if == ZSTD_skippableFrame, frameContentSize is the size of skippable content */
+ unsigned headerSize;
+ unsigned dictID;
+ unsigned checksumFlag;
+ unsigned _reserved1;
+ unsigned _reserved2;
+} ZSTD_frameHeader;
+
+/*! ZSTD_getFrameHeader() :
+ * decode Frame Header, or requires larger `srcSize`.
+ * @return : 0, `zfhPtr` is correctly filled,
+ * >0, `srcSize` is too small, value is wanted `srcSize` amount,
+ * or an error code, which can be tested using ZSTD_isError() */
+ZSTDLIB_STATIC_API size_t ZSTD_getFrameHeader(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize); /**< doesn't consume input */
+/*! ZSTD_getFrameHeader_advanced() :
+ * same as ZSTD_getFrameHeader(),
+ * with added capability to select a format (like ZSTD_f_zstd1_magicless) */
+ZSTDLIB_STATIC_API size_t ZSTD_getFrameHeader_advanced(ZSTD_frameHeader* zfhPtr, const void* src, size_t srcSize, ZSTD_format_e format);
+
+/*! ZSTD_decompressionMargin() :
+ * Zstd supports in-place decompression, where the input and output buffers overlap.
+ * In this case, the output buffer must be at least (Margin + Output_Size) bytes large,
+ * and the input buffer must be at the end of the output buffer.
+ *
+ * _______________________ Output Buffer ________________________
+ * | |
+ * | ____ Input Buffer ____|
+ * | | |
+ * v v v
+ * |---------------------------------------|-----------|----------|
+ * ^ ^ ^
+ * |___________________ Output_Size ___________________|_ Margin _|
+ *
+ * NOTE: See also ZSTD_DECOMPRESSION_MARGIN().
+ * NOTE: This applies only to single-pass decompression through ZSTD_decompress() or
+ * ZSTD_decompressDCtx().
+ * NOTE: This function supports multi-frame input.
+ *
+ * @param src The compressed frame(s)
+ * @param srcSize The size of the compressed frame(s)
+ * @returns The decompression margin or an error that can be checked with ZSTD_isError().
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_decompressionMargin(const void* src, size_t srcSize);
+
+/*! ZSTD_DECOMPRESS_MARGIN() :
+ * Similar to ZSTD_decompressionMargin(), but instead of computing the margin from
+ * the compressed frame, compute it from the original size and the blockSizeLog.
+ * See ZSTD_decompressionMargin() for details.
+ *
+ * WARNING: This macro does not support multi-frame input, the input must be a single
+ * zstd frame. If you need that support use the function, or implement it yourself.
+ *
+ * @param originalSize The original uncompressed size of the data.
+ * @param blockSize The block size == MIN(windowSize, ZSTD_BLOCKSIZE_MAX).
+ * Unless you explicitly set the windowLog smaller than
+ * ZSTD_BLOCKSIZELOG_MAX you can just use ZSTD_BLOCKSIZE_MAX.
+ */
+#define ZSTD_DECOMPRESSION_MARGIN(originalSize, blockSize) ((size_t)( \
+ ZSTD_FRAMEHEADERSIZE_MAX /* Frame header */ + \
+ 4 /* checksum */ + \
+ ((originalSize) == 0 ? 0 : 3 * (((originalSize) + (blockSize) - 1) / blockSize)) /* 3 bytes per block */ + \
+ (blockSize) /* One block of margin */ \
+ ))
+
+typedef enum {
+ ZSTD_sf_noBlockDelimiters = 0, /* Representation of ZSTD_Sequence has no block delimiters, sequences only */
+ ZSTD_sf_explicitBlockDelimiters = 1 /* Representation of ZSTD_Sequence contains explicit block delimiters */
+} ZSTD_sequenceFormat_e;
+
+/*! ZSTD_sequenceBound() :
+ * `srcSize` : size of the input buffer
+ * @return : upper-bound for the number of sequences that can be generated
+ * from a buffer of srcSize bytes
+ *
+ * note : returns number of sequences - to get bytes, multiply by sizeof(ZSTD_Sequence).
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_sequenceBound(size_t srcSize);
+
+/*! ZSTD_generateSequences() :
+ * WARNING: This function is meant for debugging and informational purposes ONLY!
+ * Its implementation is flawed, and it will be deleted in a future version.
+ * It is not guaranteed to succeed, as there are several cases where it will give
+ * up and fail. You should NOT use this function in production code.
+ *
+ * This function is deprecated, and will be removed in a future version.
+ *
+ * Generate sequences using ZSTD_compress2(), given a source buffer.
+ *
+ * @param zc The compression context to be used for ZSTD_compress2(). Set any
+ * compression parameters you need on this context.
+ * @param outSeqs The output sequences buffer of size @p outSeqsSize
+ * @param outSeqsSize The size of the output sequences buffer.
+ * ZSTD_sequenceBound(srcSize) is an upper bound on the number
+ * of sequences that can be generated.
+ * @param src The source buffer to generate sequences from of size @p srcSize.
+ * @param srcSize The size of the source buffer.
+ *
+ * Each block will end with a dummy sequence
+ * with offset == 0, matchLength == 0, and litLength == length of last literals.
+ * litLength may be == 0, and if so, then the sequence of (of: 0 ml: 0 ll: 0)
+ * simply acts as a block delimiter.
+ *
+ * @returns The number of sequences generated, necessarily less than
+ * ZSTD_sequenceBound(srcSize), or an error code that can be checked
+ * with ZSTD_isError().
+ */
+ZSTD_DEPRECATED("For debugging only, will be replaced by ZSTD_extractSequences()")
+ZSTDLIB_STATIC_API size_t
+ZSTD_generateSequences(ZSTD_CCtx* zc,
+ ZSTD_Sequence* outSeqs, size_t outSeqsSize,
+ const void* src, size_t srcSize);
+
+/*! ZSTD_mergeBlockDelimiters() :
+ * Given an array of ZSTD_Sequence, remove all sequences that represent block delimiters/last literals
+ * by merging them into the literals of the next sequence.
+ *
+ * As such, the final generated result has no explicit representation of block boundaries,
+ * and the final last literals segment is not represented in the sequences.
+ *
+ * The output of this function can be fed into ZSTD_compressSequences() with CCtx
+ * setting of ZSTD_c_blockDelimiters as ZSTD_sf_noBlockDelimiters
+ * @return : number of sequences left after merging
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_mergeBlockDelimiters(ZSTD_Sequence* sequences, size_t seqsSize);
+
+/*! ZSTD_compressSequences() :
+ * Compress an array of ZSTD_Sequence, associated with @src buffer, into dst.
+ * @src contains the entire input (not just the literals).
+ * If @srcSize > sum(sequence.length), the remaining bytes are considered all literals
+ * If a dictionary is included, then the cctx should reference the dict. (see: ZSTD_CCtx_refCDict(), ZSTD_CCtx_loadDictionary(), etc.)
+ * The entire source is compressed into a single frame.
+ *
+ * The compression behavior changes based on cctx params. In particular:
+ * If ZSTD_c_blockDelimiters == ZSTD_sf_noBlockDelimiters, the array of ZSTD_Sequence is expected to contain
+ * no block delimiters (defined in ZSTD_Sequence). Block boundaries are roughly determined based on
+ * the block size derived from the cctx, and sequences may be split. This is the default setting.
+ *
+ * If ZSTD_c_blockDelimiters == ZSTD_sf_explicitBlockDelimiters, the array of ZSTD_Sequence is expected to contain
+ * block delimiters (defined in ZSTD_Sequence). Behavior is undefined if no block delimiters are provided.
+ *
+ * If ZSTD_c_validateSequences == 0, this function will blindly accept the sequences provided. Invalid sequences cause undefined
+ * behavior. If ZSTD_c_validateSequences == 1, then if sequence is invalid (see doc/zstd_compression_format.md for
+ * specifics regarding offset/matchlength requirements) then the function will bail out and return an error.
+ *
+ * In addition to the two adjustable experimental params, there are other important cctx params.
+ * - ZSTD_c_minMatch MUST be set as less than or equal to the smallest match generated by the match finder. It has a minimum value of ZSTD_MINMATCH_MIN.
+ * - ZSTD_c_compressionLevel accordingly adjusts the strength of the entropy coder, as it would in typical compression.
+ * - ZSTD_c_windowLog affects offset validation: this function will return an error at higher debug levels if a provided offset
+ * is larger than what the spec allows for a given window log and dictionary (if present). See: doc/zstd_compression_format.md
+ *
+ * Note: Repcodes are, as of now, always re-calculated within this function, so ZSTD_Sequence::rep is unused.
+ * Note 2: Once we integrate ability to ingest repcodes, the explicit block delims mode must respect those repcodes exactly,
+ * and cannot emit an RLE block that disagrees with the repcode history
+ * @return : final compressed size, or a ZSTD error code.
+ */
+ZSTDLIB_STATIC_API size_t
+ZSTD_compressSequences( ZSTD_CCtx* cctx, void* dst, size_t dstSize,
+ const ZSTD_Sequence* inSeqs, size_t inSeqsSize,
+ const void* src, size_t srcSize);
+
+
+/*! ZSTD_writeSkippableFrame() :
+ * Generates a zstd skippable frame containing data given by src, and writes it to dst buffer.
+ *
+ * Skippable frames begin with a 4-byte magic number. There are 16 possible choices of magic number,
+ * ranging from ZSTD_MAGIC_SKIPPABLE_START to ZSTD_MAGIC_SKIPPABLE_START+15.
+ * As such, the parameter magicVariant controls the exact skippable frame magic number variant used, so
+ * the magic number used will be ZSTD_MAGIC_SKIPPABLE_START + magicVariant.
+ *
+ * Returns an error if destination buffer is not large enough, if the source size is not representable
+ * with a 4-byte unsigned int, or if the parameter magicVariant is greater than 15 (and therefore invalid).
+ *
+ * @return : number of bytes written or a ZSTD error.
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_writeSkippableFrame(void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize, unsigned magicVariant);
+
+/*! ZSTD_readSkippableFrame() :
+ * Retrieves a zstd skippable frame containing data given by src, and writes it to dst buffer.
+ *
+ * The parameter magicVariant will receive the magicVariant that was supplied when the frame was written,
+ * i.e. magicNumber - ZSTD_MAGIC_SKIPPABLE_START. This can be NULL if the caller is not interested
+ * in the magicVariant.
+ *
+ * Returns an error if destination buffer is not large enough, or if the frame is not skippable.
+ *
+ * @return : number of bytes written or a ZSTD error.
+ */
+ZSTDLIB_API size_t ZSTD_readSkippableFrame(void* dst, size_t dstCapacity, unsigned* magicVariant,
+ const void* src, size_t srcSize);
+
+/*! ZSTD_isSkippableFrame() :
+ * Tells if the content of `buffer` starts with a valid Frame Identifier for a skippable frame.
+ */
+ZSTDLIB_API unsigned ZSTD_isSkippableFrame(const void* buffer, size_t size);
+
+
+
+/***************************************
+* Memory management
+***************************************/
+
+/*! ZSTD_estimate*() :
+ * These functions make it possible to estimate memory usage
+ * of a future {D,C}Ctx, before its creation.
+ * This is useful in combination with ZSTD_initStatic(),
+ * which makes it possible to employ a static buffer for ZSTD_CCtx* state.
+ *
+ * ZSTD_estimateCCtxSize() will provide a memory budget large enough
+ * to compress data of any size using one-shot compression ZSTD_compressCCtx() or ZSTD_compress2()
+ * associated with any compression level up to max specified one.
+ * The estimate will assume the input may be arbitrarily large,
+ * which is the worst case.
+ *
+ * Note that the size estimation is specific for one-shot compression,
+ * it is not valid for streaming (see ZSTD_estimateCStreamSize*())
+ * nor other potential ways of using a ZSTD_CCtx* state.
+ *
+ * When srcSize can be bound by a known and rather "small" value,
+ * this knowledge can be used to provide a tighter budget estimation
+ * because the ZSTD_CCtx* state will need less memory for small inputs.
+ * This tighter estimation can be provided by employing more advanced functions
+ * ZSTD_estimateCCtxSize_usingCParams(), which can be used in tandem with ZSTD_getCParams(),
+ * and ZSTD_estimateCCtxSize_usingCCtxParams(), which can be used in tandem with ZSTD_CCtxParams_setParameter().
+ * Both can be used to estimate memory using custom compression parameters and arbitrary srcSize limits.
+ *
+ * Note : only single-threaded compression is supported.
+ * ZSTD_estimateCCtxSize_usingCCtxParams() will return an error code if ZSTD_c_nbWorkers is >= 1.
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_estimateCCtxSize(int maxCompressionLevel);
+ZSTDLIB_STATIC_API size_t ZSTD_estimateCCtxSize_usingCParams(ZSTD_compressionParameters cParams);
+ZSTDLIB_STATIC_API size_t ZSTD_estimateCCtxSize_usingCCtxParams(const ZSTD_CCtx_params* params);
+ZSTDLIB_STATIC_API size_t ZSTD_estimateDCtxSize(void);
+
+/*! ZSTD_estimateCStreamSize() :
+ * ZSTD_estimateCStreamSize() will provide a memory budget large enough for streaming compression
+ * using any compression level up to the max specified one.
+ * It will also consider src size to be arbitrarily "large", which is a worst case scenario.
+ * If srcSize is known to always be small, ZSTD_estimateCStreamSize_usingCParams() can provide a tighter estimation.
+ * ZSTD_estimateCStreamSize_usingCParams() can be used in tandem with ZSTD_getCParams() to create cParams from compressionLevel.
+ * ZSTD_estimateCStreamSize_usingCCtxParams() can be used in tandem with ZSTD_CCtxParams_setParameter(). Only single-threaded compression is supported. This function will return an error code if ZSTD_c_nbWorkers is >= 1.
+ * Note : CStream size estimation is only correct for single-threaded compression.
+ * ZSTD_estimateCStreamSize_usingCCtxParams() will return an error code if ZSTD_c_nbWorkers is >= 1.
+ * Note 2 : ZSTD_estimateCStreamSize* functions are not compatible with the Block-Level Sequence Producer API at this time.
+ * Size estimates assume that no external sequence producer is registered.
+ *
+ * ZSTD_DStream memory budget depends on frame's window Size.
+ * This information can be passed manually, using ZSTD_estimateDStreamSize,
+ * or deducted from a valid frame Header, using ZSTD_estimateDStreamSize_fromFrame();
+ * Any frame requesting a window size larger than max specified one will be rejected.
+ * Note : if streaming is init with function ZSTD_init?Stream_usingDict(),
+ * an internal ?Dict will be created, which additional size is not estimated here.
+ * In this case, get total size by adding ZSTD_estimate?DictSize
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize(int maxCompressionLevel);
+ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize_usingCParams(ZSTD_compressionParameters cParams);
+ZSTDLIB_STATIC_API size_t ZSTD_estimateCStreamSize_usingCCtxParams(const ZSTD_CCtx_params* params);
+ZSTDLIB_STATIC_API size_t ZSTD_estimateDStreamSize(size_t maxWindowSize);
+ZSTDLIB_STATIC_API size_t ZSTD_estimateDStreamSize_fromFrame(const void* src, size_t srcSize);
+
+/*! ZSTD_estimate?DictSize() :
+ * ZSTD_estimateCDictSize() will bet that src size is relatively "small", and content is copied, like ZSTD_createCDict().
+ * ZSTD_estimateCDictSize_advanced() makes it possible to control compression parameters precisely, like ZSTD_createCDict_advanced().
+ * Note : dictionaries created by reference (`ZSTD_dlm_byRef`) are logically smaller.
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_estimateCDictSize(size_t dictSize, int compressionLevel);
+ZSTDLIB_STATIC_API size_t ZSTD_estimateCDictSize_advanced(size_t dictSize, ZSTD_compressionParameters cParams, ZSTD_dictLoadMethod_e dictLoadMethod);
+ZSTDLIB_STATIC_API size_t ZSTD_estimateDDictSize(size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod);
+
+/*! ZSTD_initStatic*() :
+ * Initialize an object using a pre-allocated fixed-size buffer.
+ * workspace: The memory area to emplace the object into.
+ * Provided pointer *must be 8-bytes aligned*.
+ * Buffer must outlive object.
+ * workspaceSize: Use ZSTD_estimate*Size() to determine
+ * how large workspace must be to support target scenario.
+ * @return : pointer to object (same address as workspace, just different type),
+ * or NULL if error (size too small, incorrect alignment, etc.)
+ * Note : zstd will never resize nor malloc() when using a static buffer.
+ * If the object requires more memory than available,
+ * zstd will just error out (typically ZSTD_error_memory_allocation).
+ * Note 2 : there is no corresponding "free" function.
+ * Since workspace is allocated externally, it must be freed externally too.
+ * Note 3 : cParams : use ZSTD_getCParams() to convert a compression level
+ * into its associated cParams.
+ * Limitation 1 : currently not compatible with internal dictionary creation, triggered by
+ * ZSTD_CCtx_loadDictionary(), ZSTD_initCStream_usingDict() or ZSTD_initDStream_usingDict().
+ * Limitation 2 : static cctx currently not compatible with multi-threading.
+ * Limitation 3 : static dctx is incompatible with legacy support.
+ */
+ZSTDLIB_STATIC_API ZSTD_CCtx* ZSTD_initStaticCCtx(void* workspace, size_t workspaceSize);
+ZSTDLIB_STATIC_API ZSTD_CStream* ZSTD_initStaticCStream(void* workspace, size_t workspaceSize); /**< same as ZSTD_initStaticCCtx() */
+
+ZSTDLIB_STATIC_API ZSTD_DCtx* ZSTD_initStaticDCtx(void* workspace, size_t workspaceSize);
+ZSTDLIB_STATIC_API ZSTD_DStream* ZSTD_initStaticDStream(void* workspace, size_t workspaceSize); /**< same as ZSTD_initStaticDCtx() */
+
+ZSTDLIB_STATIC_API const ZSTD_CDict* ZSTD_initStaticCDict(
+ void* workspace, size_t workspaceSize,
+ const void* dict, size_t dictSize,
+ ZSTD_dictLoadMethod_e dictLoadMethod,
+ ZSTD_dictContentType_e dictContentType,
+ ZSTD_compressionParameters cParams);
+
+ZSTDLIB_STATIC_API const ZSTD_DDict* ZSTD_initStaticDDict(
+ void* workspace, size_t workspaceSize,
+ const void* dict, size_t dictSize,
+ ZSTD_dictLoadMethod_e dictLoadMethod,
+ ZSTD_dictContentType_e dictContentType);
+
+
+/*! Custom memory allocation :
+ * These prototypes make it possible to pass your own allocation/free functions.
+ * ZSTD_customMem is provided at creation time, using ZSTD_create*_advanced() variants listed below.
+ * All allocation/free operations will be completed using these custom variants instead of regular <stdlib.h> ones.
+ */
+typedef void* (*ZSTD_allocFunction) (void* opaque, size_t size);
+typedef void (*ZSTD_freeFunction) (void* opaque, void* address);
+typedef struct { ZSTD_allocFunction customAlloc; ZSTD_freeFunction customFree; void* opaque; } ZSTD_customMem;
+static
+#ifdef __GNUC__
+__attribute__((__unused__))
+#endif
+ZSTD_customMem const ZSTD_defaultCMem = { NULL, NULL, NULL }; /**< this constant defers to stdlib's functions */
+
+ZSTDLIB_STATIC_API ZSTD_CCtx* ZSTD_createCCtx_advanced(ZSTD_customMem customMem);
+ZSTDLIB_STATIC_API ZSTD_CStream* ZSTD_createCStream_advanced(ZSTD_customMem customMem);
+ZSTDLIB_STATIC_API ZSTD_DCtx* ZSTD_createDCtx_advanced(ZSTD_customMem customMem);
+ZSTDLIB_STATIC_API ZSTD_DStream* ZSTD_createDStream_advanced(ZSTD_customMem customMem);
+
+ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_createCDict_advanced(const void* dict, size_t dictSize,
+ ZSTD_dictLoadMethod_e dictLoadMethod,
+ ZSTD_dictContentType_e dictContentType,
+ ZSTD_compressionParameters cParams,
+ ZSTD_customMem customMem);
+
+/*! Thread pool :
+ * These prototypes make it possible to share a thread pool among multiple compression contexts.
+ * This can limit resources for applications with multiple threads where each one uses
+ * a threaded compression mode (via ZSTD_c_nbWorkers parameter).
+ * ZSTD_createThreadPool creates a new thread pool with a given number of threads.
+ * Note that the lifetime of such pool must exist while being used.
+ * ZSTD_CCtx_refThreadPool assigns a thread pool to a context (use NULL argument value
+ * to use an internal thread pool).
+ * ZSTD_freeThreadPool frees a thread pool, accepts NULL pointer.
+ */
+typedef struct POOL_ctx_s ZSTD_threadPool;
+ZSTDLIB_STATIC_API ZSTD_threadPool* ZSTD_createThreadPool(size_t numThreads);
+ZSTDLIB_STATIC_API void ZSTD_freeThreadPool (ZSTD_threadPool* pool); /* accept NULL pointer */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtx_refThreadPool(ZSTD_CCtx* cctx, ZSTD_threadPool* pool);
+
+
+/*
+ * This API is temporary and is expected to change or disappear in the future!
+ */
+ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_createCDict_advanced2(
+ const void* dict, size_t dictSize,
+ ZSTD_dictLoadMethod_e dictLoadMethod,
+ ZSTD_dictContentType_e dictContentType,
+ const ZSTD_CCtx_params* cctxParams,
+ ZSTD_customMem customMem);
+
+ZSTDLIB_STATIC_API ZSTD_DDict* ZSTD_createDDict_advanced(
+ const void* dict, size_t dictSize,
+ ZSTD_dictLoadMethod_e dictLoadMethod,
+ ZSTD_dictContentType_e dictContentType,
+ ZSTD_customMem customMem);
+
+
+/***************************************
+* Advanced compression functions
+***************************************/
+
+/*! ZSTD_createCDict_byReference() :
+ * Create a digested dictionary for compression
+ * Dictionary content is just referenced, not duplicated.
+ * As a consequence, `dictBuffer` **must** outlive CDict,
+ * and its content must remain unmodified throughout the lifetime of CDict.
+ * note: equivalent to ZSTD_createCDict_advanced(), with dictLoadMethod==ZSTD_dlm_byRef */
+ZSTDLIB_STATIC_API ZSTD_CDict* ZSTD_createCDict_byReference(const void* dictBuffer, size_t dictSize, int compressionLevel);
+
+/*! ZSTD_getCParams() :
+ * @return ZSTD_compressionParameters structure for a selected compression level and estimated srcSize.
+ * `estimatedSrcSize` value is optional, select 0 if not known */
+ZSTDLIB_STATIC_API ZSTD_compressionParameters ZSTD_getCParams(int compressionLevel, unsigned long long estimatedSrcSize, size_t dictSize);
+
+/*! ZSTD_getParams() :
+ * same as ZSTD_getCParams(), but @return a full `ZSTD_parameters` object instead of sub-component `ZSTD_compressionParameters`.
+ * All fields of `ZSTD_frameParameters` are set to default : contentSize=1, checksum=0, noDictID=0 */
+ZSTDLIB_STATIC_API ZSTD_parameters ZSTD_getParams(int compressionLevel, unsigned long long estimatedSrcSize, size_t dictSize);
+
+/*! ZSTD_checkCParams() :
+ * Ensure param values remain within authorized range.
+ * @return 0 on success, or an error code (can be checked with ZSTD_isError()) */
+ZSTDLIB_STATIC_API size_t ZSTD_checkCParams(ZSTD_compressionParameters params);
+
+/*! ZSTD_adjustCParams() :
+ * optimize params for a given `srcSize` and `dictSize`.
+ * `srcSize` can be unknown, in which case use ZSTD_CONTENTSIZE_UNKNOWN.
+ * `dictSize` must be `0` when there is no dictionary.
+ * cPar can be invalid : all parameters will be clamped within valid range in the @return struct.
+ * This function never fails (wide contract) */
+ZSTDLIB_STATIC_API ZSTD_compressionParameters ZSTD_adjustCParams(ZSTD_compressionParameters cPar, unsigned long long srcSize, size_t dictSize);
+
+/*! ZSTD_CCtx_setCParams() :
+ * Set all parameters provided within @p cparams into the working @p cctx.
+ * Note : if modifying parameters during compression (MT mode only),
+ * note that changes to the .windowLog parameter will be ignored.
+ * @return 0 on success, or an error code (can be checked with ZSTD_isError()).
+ * On failure, no parameters are updated.
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setCParams(ZSTD_CCtx* cctx, ZSTD_compressionParameters cparams);
+
+/*! ZSTD_CCtx_setFParams() :
+ * Set all parameters provided within @p fparams into the working @p cctx.
+ * @return 0 on success, or an error code (can be checked with ZSTD_isError()).
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setFParams(ZSTD_CCtx* cctx, ZSTD_frameParameters fparams);
+
+/*! ZSTD_CCtx_setParams() :
+ * Set all parameters provided within @p params into the working @p cctx.
+ * @return 0 on success, or an error code (can be checked with ZSTD_isError()).
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setParams(ZSTD_CCtx* cctx, ZSTD_parameters params);
+
+/*! ZSTD_compress_advanced() :
+ * Note : this function is now DEPRECATED.
+ * It can be replaced by ZSTD_compress2(), in combination with ZSTD_CCtx_setParameter() and other parameter setters.
+ * This prototype will generate compilation warnings. */
+ZSTD_DEPRECATED("use ZSTD_compress2")
+ZSTDLIB_STATIC_API
+size_t ZSTD_compress_advanced(ZSTD_CCtx* cctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize,
+ const void* dict,size_t dictSize,
+ ZSTD_parameters params);
+
+/*! ZSTD_compress_usingCDict_advanced() :
+ * Note : this function is now DEPRECATED.
+ * It can be replaced by ZSTD_compress2(), in combination with ZSTD_CCtx_loadDictionary() and other parameter setters.
+ * This prototype will generate compilation warnings. */
+ZSTD_DEPRECATED("use ZSTD_compress2 with ZSTD_CCtx_loadDictionary")
+ZSTDLIB_STATIC_API
+size_t ZSTD_compress_usingCDict_advanced(ZSTD_CCtx* cctx,
+ void* dst, size_t dstCapacity,
+ const void* src, size_t srcSize,
+ const ZSTD_CDict* cdict,
+ ZSTD_frameParameters fParams);
+
+
+/*! ZSTD_CCtx_loadDictionary_byReference() :
+ * Same as ZSTD_CCtx_loadDictionary(), but dictionary content is referenced, instead of being copied into CCtx.
+ * It saves some memory, but also requires that `dict` outlives its usage within `cctx` */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtx_loadDictionary_byReference(ZSTD_CCtx* cctx, const void* dict, size_t dictSize);
+
+/*! ZSTD_CCtx_loadDictionary_advanced() :
+ * Same as ZSTD_CCtx_loadDictionary(), but gives finer control over
+ * how to load the dictionary (by copy ? by reference ?)
+ * and how to interpret it (automatic ? force raw mode ? full mode only ?) */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtx_loadDictionary_advanced(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType);
+
+/*! ZSTD_CCtx_refPrefix_advanced() :
+ * Same as ZSTD_CCtx_refPrefix(), but gives finer control over
+ * how to interpret prefix content (automatic ? force raw mode (default) ? full mode only ?) */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtx_refPrefix_advanced(ZSTD_CCtx* cctx, const void* prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType);
+
+/* === experimental parameters === */
+/* these parameters can be used with ZSTD_setParameter()
+ * they are not guaranteed to remain supported in the future */
+
+ /* Enables rsyncable mode,
+ * which makes compressed files more rsync friendly
+ * by adding periodic synchronization points to the compressed data.
+ * The target average block size is ZSTD_c_jobSize / 2.
+ * It's possible to modify the job size to increase or decrease
+ * the granularity of the synchronization point.
+ * Once the jobSize is smaller than the window size,
+ * it will result in compression ratio degradation.
+ * NOTE 1: rsyncable mode only works when multithreading is enabled.
+ * NOTE 2: rsyncable performs poorly in combination with long range mode,
+ * since it will decrease the effectiveness of synchronization points,
+ * though mileage may vary.
+ * NOTE 3: Rsyncable mode limits maximum compression speed to ~400 MB/s.
+ * If the selected compression level is already running significantly slower,
+ * the overall speed won't be significantly impacted.
+ */
+ #define ZSTD_c_rsyncable ZSTD_c_experimentalParam1
+
+/* Select a compression format.
+ * The value must be of type ZSTD_format_e.
+ * See ZSTD_format_e enum definition for details */
+#define ZSTD_c_format ZSTD_c_experimentalParam2
+
+/* Force back-reference distances to remain < windowSize,
+ * even when referencing into Dictionary content (default:0) */
+#define ZSTD_c_forceMaxWindow ZSTD_c_experimentalParam3
+
+/* Controls whether the contents of a CDict
+ * are used in place, or copied into the working context.
+ * Accepts values from the ZSTD_dictAttachPref_e enum.
+ * See the comments on that enum for an explanation of the feature. */
+#define ZSTD_c_forceAttachDict ZSTD_c_experimentalParam4
+
+/* Controlled with ZSTD_paramSwitch_e enum.
+ * Default is ZSTD_ps_auto.
+ * Set to ZSTD_ps_disable to never compress literals.
+ * Set to ZSTD_ps_enable to always compress literals. (Note: uncompressed literals
+ * may still be emitted if huffman is not beneficial to use.)
+ *
+ * By default, in ZSTD_ps_auto, the library will decide at runtime whether to use
+ * literals compression based on the compression parameters - specifically,
+ * negative compression levels do not use literal compression.
+ */
+#define ZSTD_c_literalCompressionMode ZSTD_c_experimentalParam5
+
+/* User's best guess of source size.
+ * Hint is not valid when srcSizeHint == 0.
+ * There is no guarantee that hint is close to actual source size,
+ * but compression ratio may regress significantly if guess considerably underestimates */
+#define ZSTD_c_srcSizeHint ZSTD_c_experimentalParam7
+
+/* Controls whether the new and experimental "dedicated dictionary search
+ * structure" can be used. This feature is still rough around the edges, be
+ * prepared for surprising behavior!
+ *
+ * How to use it:
+ *
+ * When using a CDict, whether to use this feature or not is controlled at
+ * CDict creation, and it must be set in a CCtxParams set passed into that
+ * construction (via ZSTD_createCDict_advanced2()). A compression will then
+ * use the feature or not based on how the CDict was constructed; the value of
+ * this param, set in the CCtx, will have no effect.
+ *
+ * However, when a dictionary buffer is passed into a CCtx, such as via
+ * ZSTD_CCtx_loadDictionary(), this param can be set on the CCtx to control
+ * whether the CDict that is created internally can use the feature or not.
+ *
+ * What it does:
+ *
+ * Normally, the internal data structures of the CDict are analogous to what
+ * would be stored in a CCtx after compressing the contents of a dictionary.
+ * To an approximation, a compression using a dictionary can then use those
+ * data structures to simply continue what is effectively a streaming
+ * compression where the simulated compression of the dictionary left off.
+ * Which is to say, the search structures in the CDict are normally the same
+ * format as in the CCtx.
+ *
+ * It is possible to do better, since the CDict is not like a CCtx: the search
+ * structures are written once during CDict creation, and then are only read
+ * after that, while the search structures in the CCtx are both read and
+ * written as the compression goes along. This means we can choose a search
+ * structure for the dictionary that is read-optimized.
+ *
+ * This feature enables the use of that different structure.
+ *
+ * Note that some of the members of the ZSTD_compressionParameters struct have
+ * different semantics and constraints in the dedicated search structure. It is
+ * highly recommended that you simply set a compression level in the CCtxParams
+ * you pass into the CDict creation call, and avoid messing with the cParams
+ * directly.
+ *
+ * Effects:
+ *
+ * This will only have any effect when the selected ZSTD_strategy
+ * implementation supports this feature. Currently, that's limited to
+ * ZSTD_greedy, ZSTD_lazy, and ZSTD_lazy2.
+ *
+ * Note that this means that the CDict tables can no longer be copied into the
+ * CCtx, so the dict attachment mode ZSTD_dictForceCopy will no longer be
+ * usable. The dictionary can only be attached or reloaded.
+ *
+ * In general, you should expect compression to be faster--sometimes very much
+ * so--and CDict creation to be slightly slower. Eventually, we will probably
+ * make this mode the default.
+ */
+#define ZSTD_c_enableDedicatedDictSearch ZSTD_c_experimentalParam8
+
+/* ZSTD_c_stableInBuffer
+ * Experimental parameter.
+ * Default is 0 == disabled. Set to 1 to enable.
+ *
+ * Tells the compressor that input data presented with ZSTD_inBuffer
+ * will ALWAYS be the same between calls.
+ * Technically, the @src pointer must never be changed,
+ * and the @pos field can only be updated by zstd.
+ * However, it's possible to increase the @size field,
+ * allowing scenarios where more data can be appended after compressions starts.
+ * These conditions are checked by the compressor,
+ * and compression will fail if they are not respected.
+ * Also, data in the ZSTD_inBuffer within the range [src, src + pos)
+ * MUST not be modified during compression or it will result in data corruption.
+ *
+ * When this flag is enabled zstd won't allocate an input window buffer,
+ * because the user guarantees it can reference the ZSTD_inBuffer until
+ * the frame is complete. But, it will still allocate an output buffer
+ * large enough to fit a block (see ZSTD_c_stableOutBuffer). This will also
+ * avoid the memcpy() from the input buffer to the input window buffer.
+ *
+ * NOTE: So long as the ZSTD_inBuffer always points to valid memory, using
+ * this flag is ALWAYS memory safe, and will never access out-of-bounds
+ * memory. However, compression WILL fail if conditions are not respected.
+ *
+ * WARNING: The data in the ZSTD_inBuffer in the range [src, src + pos) MUST
+ * not be modified during compression or it will result in data corruption.
+ * This is because zstd needs to reference data in the ZSTD_inBuffer to find
+ * matches. Normally zstd maintains its own window buffer for this purpose,
+ * but passing this flag tells zstd to rely on user provided buffer instead.
+ */
+#define ZSTD_c_stableInBuffer ZSTD_c_experimentalParam9
+
+/* ZSTD_c_stableOutBuffer
+ * Experimental parameter.
+ * Default is 0 == disabled. Set to 1 to enable.
+ *
+ * Tells he compressor that the ZSTD_outBuffer will not be resized between
+ * calls. Specifically: (out.size - out.pos) will never grow. This gives the
+ * compressor the freedom to say: If the compressed data doesn't fit in the
+ * output buffer then return ZSTD_error_dstSizeTooSmall. This allows us to
+ * always decompress directly into the output buffer, instead of decompressing
+ * into an internal buffer and copying to the output buffer.
+ *
+ * When this flag is enabled zstd won't allocate an output buffer, because
+ * it can write directly to the ZSTD_outBuffer. It will still allocate the
+ * input window buffer (see ZSTD_c_stableInBuffer).
+ *
+ * Zstd will check that (out.size - out.pos) never grows and return an error
+ * if it does. While not strictly necessary, this should prevent surprises.
+ */
+#define ZSTD_c_stableOutBuffer ZSTD_c_experimentalParam10
+
+/* ZSTD_c_blockDelimiters
+ * Default is 0 == ZSTD_sf_noBlockDelimiters.
+ *
+ * For use with sequence compression API: ZSTD_compressSequences().
+ *
+ * Designates whether or not the given array of ZSTD_Sequence contains block delimiters
+ * and last literals, which are defined as sequences with offset == 0 and matchLength == 0.
+ * See the definition of ZSTD_Sequence for more specifics.
+ */
+#define ZSTD_c_blockDelimiters ZSTD_c_experimentalParam11
+
+/* ZSTD_c_validateSequences
+ * Default is 0 == disabled. Set to 1 to enable sequence validation.
+ *
+ * For use with sequence compression API: ZSTD_compressSequences().
+ * Designates whether or not we validate sequences provided to ZSTD_compressSequences()
+ * during function execution.
+ *
+ * Without validation, providing a sequence that does not conform to the zstd spec will cause
+ * undefined behavior, and may produce a corrupted block.
+ *
+ * With validation enabled, if sequence is invalid (see doc/zstd_compression_format.md for
+ * specifics regarding offset/matchlength requirements) then the function will bail out and
+ * return an error.
+ *
+ */
+#define ZSTD_c_validateSequences ZSTD_c_experimentalParam12
+
+/* ZSTD_c_useBlockSplitter
+ * Controlled with ZSTD_paramSwitch_e enum.
+ * Default is ZSTD_ps_auto.
+ * Set to ZSTD_ps_disable to never use block splitter.
+ * Set to ZSTD_ps_enable to always use block splitter.
+ *
+ * By default, in ZSTD_ps_auto, the library will decide at runtime whether to use
+ * block splitting based on the compression parameters.
+ */
+#define ZSTD_c_useBlockSplitter ZSTD_c_experimentalParam13
+
+/* ZSTD_c_useRowMatchFinder
+ * Controlled with ZSTD_paramSwitch_e enum.
+ * Default is ZSTD_ps_auto.
+ * Set to ZSTD_ps_disable to never use row-based matchfinder.
+ * Set to ZSTD_ps_enable to force usage of row-based matchfinder.
+ *
+ * By default, in ZSTD_ps_auto, the library will decide at runtime whether to use
+ * the row-based matchfinder based on support for SIMD instructions and the window log.
+ * Note that this only pertains to compression strategies: greedy, lazy, and lazy2
+ */
+#define ZSTD_c_useRowMatchFinder ZSTD_c_experimentalParam14
+
+/* ZSTD_c_deterministicRefPrefix
+ * Default is 0 == disabled. Set to 1 to enable.
+ *
+ * Zstd produces different results for prefix compression when the prefix is
+ * directly adjacent to the data about to be compressed vs. when it isn't.
+ * This is because zstd detects that the two buffers are contiguous and it can
+ * use a more efficient match finding algorithm. However, this produces different
+ * results than when the two buffers are non-contiguous. This flag forces zstd
+ * to always load the prefix in non-contiguous mode, even if it happens to be
+ * adjacent to the data, to guarantee determinism.
+ *
+ * If you really care about determinism when using a dictionary or prefix,
+ * like when doing delta compression, you should select this option. It comes
+ * at a speed penalty of about ~2.5% if the dictionary and data happened to be
+ * contiguous, and is free if they weren't contiguous. We don't expect that
+ * intentionally making the dictionary and data contiguous will be worth the
+ * cost to memcpy() the data.
+ */
+#define ZSTD_c_deterministicRefPrefix ZSTD_c_experimentalParam15
+
+/* ZSTD_c_prefetchCDictTables
+ * Controlled with ZSTD_paramSwitch_e enum. Default is ZSTD_ps_auto.
+ *
+ * In some situations, zstd uses CDict tables in-place rather than copying them
+ * into the working context. (See docs on ZSTD_dictAttachPref_e above for details).
+ * In such situations, compression speed is seriously impacted when CDict tables are
+ * "cold" (outside CPU cache). This parameter instructs zstd to prefetch CDict tables
+ * when they are used in-place.
+ *
+ * For sufficiently small inputs, the cost of the prefetch will outweigh the benefit.
+ * For sufficiently large inputs, zstd will by default memcpy() CDict tables
+ * into the working context, so there is no need to prefetch. This parameter is
+ * targeted at a middle range of input sizes, where a prefetch is cheap enough to be
+ * useful but memcpy() is too expensive. The exact range of input sizes where this
+ * makes sense is best determined by careful experimentation.
+ *
+ * Note: for this parameter, ZSTD_ps_auto is currently equivalent to ZSTD_ps_disable,
+ * but in the future zstd may conditionally enable this feature via an auto-detection
+ * heuristic for cold CDicts.
+ * Use ZSTD_ps_disable to opt out of prefetching under any circumstances.
+ */
+#define ZSTD_c_prefetchCDictTables ZSTD_c_experimentalParam16
+
+/* ZSTD_c_enableSeqProducerFallback
+ * Allowed values are 0 (disable) and 1 (enable). The default setting is 0.
+ *
+ * Controls whether zstd will fall back to an internal sequence producer if an
+ * external sequence producer is registered and returns an error code. This fallback
+ * is block-by-block: the internal sequence producer will only be called for blocks
+ * where the external sequence producer returns an error code. Fallback parsing will
+ * follow any other cParam settings, such as compression level, the same as in a
+ * normal (fully-internal) compression operation.
+ *
+ * The user is strongly encouraged to read the full Block-Level Sequence Producer API
+ * documentation (below) before setting this parameter. */
+#define ZSTD_c_enableSeqProducerFallback ZSTD_c_experimentalParam17
+
+/* ZSTD_c_maxBlockSize
+ * Allowed values are between 1KB and ZSTD_BLOCKSIZE_MAX (128KB).
+ * The default is ZSTD_BLOCKSIZE_MAX, and setting to 0 will set to the default.
+ *
+ * This parameter can be used to set an upper bound on the blocksize
+ * that overrides the default ZSTD_BLOCKSIZE_MAX. It cannot be used to set upper
+ * bounds greater than ZSTD_BLOCKSIZE_MAX or bounds lower than 1KB (will make
+ * compressBound() inaccurate). Only currently meant to be used for testing.
+ *
+ */
+#define ZSTD_c_maxBlockSize ZSTD_c_experimentalParam18
+
+/* ZSTD_c_searchForExternalRepcodes
+ * This parameter affects how zstd parses external sequences, such as sequences
+ * provided through the compressSequences() API or from an external block-level
+ * sequence producer.
+ *
+ * If set to ZSTD_ps_enable, the library will check for repeated offsets in
+ * external sequences, even if those repcodes are not explicitly indicated in
+ * the "rep" field. Note that this is the only way to exploit repcode matches
+ * while using compressSequences() or an external sequence producer, since zstd
+ * currently ignores the "rep" field of external sequences.
+ *
+ * If set to ZSTD_ps_disable, the library will not exploit repeated offsets in
+ * external sequences, regardless of whether the "rep" field has been set. This
+ * reduces sequence compression overhead by about 25% while sacrificing some
+ * compression ratio.
+ *
+ * The default value is ZSTD_ps_auto, for which the library will enable/disable
+ * based on compression level.
+ *
+ * Note: for now, this param only has an effect if ZSTD_c_blockDelimiters is
+ * set to ZSTD_sf_explicitBlockDelimiters. That may change in the future.
+ */
+#define ZSTD_c_searchForExternalRepcodes ZSTD_c_experimentalParam19
+
+/*! ZSTD_CCtx_getParameter() :
+ * Get the requested compression parameter value, selected by enum ZSTD_cParameter,
+ * and store it into int* value.
+ * @return : 0, or an error code (which can be tested with ZSTD_isError()).
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtx_getParameter(const ZSTD_CCtx* cctx, ZSTD_cParameter param, int* value);
+
+
+/*! ZSTD_CCtx_params :
+ * Quick howto :
+ * - ZSTD_createCCtxParams() : Create a ZSTD_CCtx_params structure
+ * - ZSTD_CCtxParams_setParameter() : Push parameters one by one into
+ * an existing ZSTD_CCtx_params structure.
+ * This is similar to
+ * ZSTD_CCtx_setParameter().
+ * - ZSTD_CCtx_setParametersUsingCCtxParams() : Apply parameters to
+ * an existing CCtx.
+ * These parameters will be applied to
+ * all subsequent frames.
+ * - ZSTD_compressStream2() : Do compression using the CCtx.
+ * - ZSTD_freeCCtxParams() : Free the memory, accept NULL pointer.
+ *
+ * This can be used with ZSTD_estimateCCtxSize_advanced_usingCCtxParams()
+ * for static allocation of CCtx for single-threaded compression.
+ */
+ZSTDLIB_STATIC_API ZSTD_CCtx_params* ZSTD_createCCtxParams(void);
+ZSTDLIB_STATIC_API size_t ZSTD_freeCCtxParams(ZSTD_CCtx_params* params); /* accept NULL pointer */
+
+/*! ZSTD_CCtxParams_reset() :
+ * Reset params to default values.
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtxParams_reset(ZSTD_CCtx_params* params);
+
+/*! ZSTD_CCtxParams_init() :
+ * Initializes the compression parameters of cctxParams according to
+ * compression level. All other parameters are reset to their default values.
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtxParams_init(ZSTD_CCtx_params* cctxParams, int compressionLevel);
+
+/*! ZSTD_CCtxParams_init_advanced() :
+ * Initializes the compression and frame parameters of cctxParams according to
+ * params. All other parameters are reset to their default values.
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtxParams_init_advanced(ZSTD_CCtx_params* cctxParams, ZSTD_parameters params);
+
+/*! ZSTD_CCtxParams_setParameter() : Requires v1.4.0+
+ * Similar to ZSTD_CCtx_setParameter.
+ * Set one compression parameter, selected by enum ZSTD_cParameter.
+ * Parameters must be applied to a ZSTD_CCtx using
+ * ZSTD_CCtx_setParametersUsingCCtxParams().
+ * @result : a code representing success or failure (which can be tested with
+ * ZSTD_isError()).
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtxParams_setParameter(ZSTD_CCtx_params* params, ZSTD_cParameter param, int value);
+
+/*! ZSTD_CCtxParams_getParameter() :
+ * Similar to ZSTD_CCtx_getParameter.
+ * Get the requested value of one compression parameter, selected by enum ZSTD_cParameter.
+ * @result : 0, or an error code (which can be tested with ZSTD_isError()).
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtxParams_getParameter(const ZSTD_CCtx_params* params, ZSTD_cParameter param, int* value);
+
+/*! ZSTD_CCtx_setParametersUsingCCtxParams() :
+ * Apply a set of ZSTD_CCtx_params to the compression context.
+ * This can be done even after compression is started,
+ * if nbWorkers==0, this will have no impact until a new compression is started.
+ * if nbWorkers>=1, new parameters will be picked up at next job,
+ * with a few restrictions (windowLog, pledgedSrcSize, nbWorkers, jobSize, and overlapLog are not updated).
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_CCtx_setParametersUsingCCtxParams(
+ ZSTD_CCtx* cctx, const ZSTD_CCtx_params* params);
+
+/*! ZSTD_compressStream2_simpleArgs() :
+ * Same as ZSTD_compressStream2(),
+ * but using only integral types as arguments.
+ * This variant might be helpful for binders from dynamic languages
+ * which have troubles handling structures containing memory pointers.
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_compressStream2_simpleArgs (
+ ZSTD_CCtx* cctx,
+ void* dst, size_t dstCapacity, size_t* dstPos,
+ const void* src, size_t srcSize, size_t* srcPos,
+ ZSTD_EndDirective endOp);
+
+
+/***************************************
+* Advanced decompression functions
+***************************************/
+
+/*! ZSTD_isFrame() :
+ * Tells if the content of `buffer` starts with a valid Frame Identifier.
+ * Note : Frame Identifier is 4 bytes. If `size < 4`, @return will always be 0.
+ * Note 2 : Legacy Frame Identifiers are considered valid only if Legacy Support is enabled.
+ * Note 3 : Skippable Frame Identifiers are considered valid. */
+ZSTDLIB_STATIC_API unsigned ZSTD_isFrame(const void* buffer, size_t size);
+
+/*! ZSTD_createDDict_byReference() :
+ * Create a digested dictionary, ready to start decompression operation without startup delay.
+ * Dictionary content is referenced, and therefore stays in dictBuffer.
+ * It is important that dictBuffer outlives DDict,
+ * it must remain read accessible throughout the lifetime of DDict */
+ZSTDLIB_STATIC_API ZSTD_DDict* ZSTD_createDDict_byReference(const void* dictBuffer, size_t dictSize);
+
+/*! ZSTD_DCtx_loadDictionary_byReference() :
+ * Same as ZSTD_DCtx_loadDictionary(),
+ * but references `dict` content instead of copying it into `dctx`.
+ * This saves memory if `dict` remains around.,
+ * However, it's imperative that `dict` remains accessible (and unmodified) while being used, so it must outlive decompression. */
+ZSTDLIB_STATIC_API size_t ZSTD_DCtx_loadDictionary_byReference(ZSTD_DCtx* dctx, const void* dict, size_t dictSize);
+
+/*! ZSTD_DCtx_loadDictionary_advanced() :
+ * Same as ZSTD_DCtx_loadDictionary(),
+ * but gives direct control over
+ * how to load the dictionary (by copy ? by reference ?)
+ * and how to interpret it (automatic ? force raw mode ? full mode only ?). */
+ZSTDLIB_STATIC_API size_t ZSTD_DCtx_loadDictionary_advanced(ZSTD_DCtx* dctx, const void* dict, size_t dictSize, ZSTD_dictLoadMethod_e dictLoadMethod, ZSTD_dictContentType_e dictContentType);
+
+/*! ZSTD_DCtx_refPrefix_advanced() :
+ * Same as ZSTD_DCtx_refPrefix(), but gives finer control over
+ * how to interpret prefix content (automatic ? force raw mode (default) ? full mode only ?) */
+ZSTDLIB_STATIC_API size_t ZSTD_DCtx_refPrefix_advanced(ZSTD_DCtx* dctx, const void* prefix, size_t prefixSize, ZSTD_dictContentType_e dictContentType);
+
+/*! ZSTD_DCtx_setMaxWindowSize() :
+ * Refuses allocating internal buffers for frames requiring a window size larger than provided limit.
+ * This protects a decoder context from reserving too much memory for itself (potential attack scenario).
+ * This parameter is only useful in streaming mode, since no internal buffer is allocated in single-pass mode.
+ * By default, a decompression context accepts all window sizes <= (1 << ZSTD_WINDOWLOG_LIMIT_DEFAULT)
+ * @return : 0, or an error code (which can be tested using ZSTD_isError()).
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_DCtx_setMaxWindowSize(ZSTD_DCtx* dctx, size_t maxWindowSize);
+
+/*! ZSTD_DCtx_getParameter() :
+ * Get the requested decompression parameter value, selected by enum ZSTD_dParameter,
+ * and store it into int* value.
+ * @return : 0, or an error code (which can be tested with ZSTD_isError()).
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_DCtx_getParameter(ZSTD_DCtx* dctx, ZSTD_dParameter param, int* value);
+
+/* ZSTD_d_format
+ * experimental parameter,
+ * allowing selection between ZSTD_format_e input compression formats
+ */
+#define ZSTD_d_format ZSTD_d_experimentalParam1
+/* ZSTD_d_stableOutBuffer
+ * Experimental parameter.
+ * Default is 0 == disabled. Set to 1 to enable.
+ *
+ * Tells the decompressor that the ZSTD_outBuffer will ALWAYS be the same
+ * between calls, except for the modifications that zstd makes to pos (the
+ * caller must not modify pos). This is checked by the decompressor, and
+ * decompression will fail if it ever changes. Therefore the ZSTD_outBuffer
+ * MUST be large enough to fit the entire decompressed frame. This will be
+ * checked when the frame content size is known. The data in the ZSTD_outBuffer
+ * in the range [dst, dst + pos) MUST not be modified during decompression
+ * or you will get data corruption.
+ *
+ * When this flag is enabled zstd won't allocate an output buffer, because
+ * it can write directly to the ZSTD_outBuffer, but it will still allocate
+ * an input buffer large enough to fit any compressed block. This will also
+ * avoid the memcpy() from the internal output buffer to the ZSTD_outBuffer.
+ * If you need to avoid the input buffer allocation use the buffer-less
+ * streaming API.
+ *
+ * NOTE: So long as the ZSTD_outBuffer always points to valid memory, using
+ * this flag is ALWAYS memory safe, and will never access out-of-bounds
+ * memory. However, decompression WILL fail if you violate the preconditions.
+ *
+ * WARNING: The data in the ZSTD_outBuffer in the range [dst, dst + pos) MUST
+ * not be modified during decompression or you will get data corruption. This
+ * is because zstd needs to reference data in the ZSTD_outBuffer to regenerate
+ * matches. Normally zstd maintains its own buffer for this purpose, but passing
+ * this flag tells zstd to use the user provided buffer.
+ */
+#define ZSTD_d_stableOutBuffer ZSTD_d_experimentalParam2
+
+/* ZSTD_d_forceIgnoreChecksum
+ * Experimental parameter.
+ * Default is 0 == disabled. Set to 1 to enable
+ *
+ * Tells the decompressor to skip checksum validation during decompression, regardless
+ * of whether checksumming was specified during compression. This offers some
+ * slight performance benefits, and may be useful for debugging.
+ * Param has values of type ZSTD_forceIgnoreChecksum_e
+ */
+#define ZSTD_d_forceIgnoreChecksum ZSTD_d_experimentalParam3
+
+/* ZSTD_d_refMultipleDDicts
+ * Experimental parameter.
+ * Default is 0 == disabled. Set to 1 to enable
+ *
+ * If enabled and dctx is allocated on the heap, then additional memory will be allocated
+ * to store references to multiple ZSTD_DDict. That is, multiple calls of ZSTD_refDDict()
+ * using a given ZSTD_DCtx, rather than overwriting the previous DDict reference, will instead
+ * store all references. At decompression time, the appropriate dictID is selected
+ * from the set of DDicts based on the dictID in the frame.
+ *
+ * Usage is simply calling ZSTD_refDDict() on multiple dict buffers.
+ *
+ * Param has values of byte ZSTD_refMultipleDDicts_e
+ *
+ * WARNING: Enabling this parameter and calling ZSTD_DCtx_refDDict(), will trigger memory
+ * allocation for the hash table. ZSTD_freeDCtx() also frees this memory.
+ * Memory is allocated as per ZSTD_DCtx::customMem.
+ *
+ * Although this function allocates memory for the table, the user is still responsible for
+ * memory management of the underlying ZSTD_DDict* themselves.
+ */
+#define ZSTD_d_refMultipleDDicts ZSTD_d_experimentalParam4
+
+/* ZSTD_d_disableHuffmanAssembly
+ * Set to 1 to disable the Huffman assembly implementation.
+ * The default value is 0, which allows zstd to use the Huffman assembly
+ * implementation if available.
+ *
+ * This parameter can be used to disable Huffman assembly at runtime.
+ * If you want to disable it at compile time you can define the macro
+ * ZSTD_DISABLE_ASM.
+ */
+#define ZSTD_d_disableHuffmanAssembly ZSTD_d_experimentalParam5
+
+/* ZSTD_d_maxBlockSize
+ * Allowed values are between 1KB and ZSTD_BLOCKSIZE_MAX (128KB).
+ * The default is ZSTD_BLOCKSIZE_MAX, and setting to 0 will set to the default.
+ *
+ * Forces the decompressor to reject blocks whose content size is
+ * larger than the configured maxBlockSize. When maxBlockSize is
+ * larger than the windowSize, the windowSize is used instead.
+ * This saves memory on the decoder when you know all blocks are small.
+ *
+ * This option is typically used in conjunction with ZSTD_c_maxBlockSize.
+ *
+ * WARNING: This causes the decoder to reject otherwise valid frames
+ * that have block sizes larger than the configured maxBlockSize.
+ */
+#define ZSTD_d_maxBlockSize ZSTD_d_experimentalParam6
+
+
+/*! ZSTD_DCtx_setFormat() :
+ * This function is REDUNDANT. Prefer ZSTD_DCtx_setParameter().
+ * Instruct the decoder context about what kind of data to decode next.
+ * This instruction is mandatory to decode data without a fully-formed header,
+ * such ZSTD_f_zstd1_magicless for example.
+ * @return : 0, or an error code (which can be tested using ZSTD_isError()). */
+ZSTD_DEPRECATED("use ZSTD_DCtx_setParameter() instead")
+ZSTDLIB_STATIC_API
+size_t ZSTD_DCtx_setFormat(ZSTD_DCtx* dctx, ZSTD_format_e format);
+
+/*! ZSTD_decompressStream_simpleArgs() :
+ * Same as ZSTD_decompressStream(),
+ * but using only integral types as arguments.
+ * This can be helpful for binders from dynamic languages
+ * which have troubles handling structures containing memory pointers.
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_decompressStream_simpleArgs (
+ ZSTD_DCtx* dctx,
+ void* dst, size_t dstCapacity, size_t* dstPos,
+ const void* src, size_t srcSize, size_t* srcPos);
+
+
+/********************************************************************
+* Advanced streaming functions
+* Warning : most of these functions are now redundant with the Advanced API.
+* Once Advanced API reaches "stable" status,
+* redundant functions will be deprecated, and then at some point removed.
+********************************************************************/
+
+/*===== Advanced Streaming compression functions =====*/
+
+/*! ZSTD_initCStream_srcSize() :
+ * This function is DEPRECATED, and equivalent to:
+ * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only);
+ * ZSTD_CCtx_refCDict(zcs, NULL); // clear the dictionary (if any)
+ * ZSTD_CCtx_setParameter(zcs, ZSTD_c_compressionLevel, compressionLevel);
+ * ZSTD_CCtx_setPledgedSrcSize(zcs, pledgedSrcSize);
+ *
+ * pledgedSrcSize must be correct. If it is not known at init time, use
+ * ZSTD_CONTENTSIZE_UNKNOWN. Note that, for compatibility with older programs,
+ * "0" also disables frame content size field. It may be enabled in the future.
+ * This prototype will generate compilation warnings.
+ */
+ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions")
+ZSTDLIB_STATIC_API
+size_t ZSTD_initCStream_srcSize(ZSTD_CStream* zcs,
+ int compressionLevel,
+ unsigned long long pledgedSrcSize);
+
+/*! ZSTD_initCStream_usingDict() :
+ * This function is DEPRECATED, and is equivalent to:
+ * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only);
+ * ZSTD_CCtx_setParameter(zcs, ZSTD_c_compressionLevel, compressionLevel);
+ * ZSTD_CCtx_loadDictionary(zcs, dict, dictSize);
+ *
+ * Creates of an internal CDict (incompatible with static CCtx), except if
+ * dict == NULL or dictSize < 8, in which case no dict is used.
+ * Note: dict is loaded with ZSTD_dct_auto (treated as a full zstd dictionary if
+ * it begins with ZSTD_MAGIC_DICTIONARY, else as raw content) and ZSTD_dlm_byCopy.
+ * This prototype will generate compilation warnings.
+ */
+ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions")
+ZSTDLIB_STATIC_API
+size_t ZSTD_initCStream_usingDict(ZSTD_CStream* zcs,
+ const void* dict, size_t dictSize,
+ int compressionLevel);
+
+/*! ZSTD_initCStream_advanced() :
+ * This function is DEPRECATED, and is equivalent to:
+ * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only);
+ * ZSTD_CCtx_setParams(zcs, params);
+ * ZSTD_CCtx_setPledgedSrcSize(zcs, pledgedSrcSize);
+ * ZSTD_CCtx_loadDictionary(zcs, dict, dictSize);
+ *
+ * dict is loaded with ZSTD_dct_auto and ZSTD_dlm_byCopy.
+ * pledgedSrcSize must be correct.
+ * If srcSize is not known at init time, use value ZSTD_CONTENTSIZE_UNKNOWN.
+ * This prototype will generate compilation warnings.
+ */
+ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions")
+ZSTDLIB_STATIC_API
+size_t ZSTD_initCStream_advanced(ZSTD_CStream* zcs,
+ const void* dict, size_t dictSize,
+ ZSTD_parameters params,
+ unsigned long long pledgedSrcSize);
+
+/*! ZSTD_initCStream_usingCDict() :
+ * This function is DEPRECATED, and equivalent to:
+ * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only);
+ * ZSTD_CCtx_refCDict(zcs, cdict);
+ *
+ * note : cdict will just be referenced, and must outlive compression session
+ * This prototype will generate compilation warnings.
+ */
+ZSTD_DEPRECATED("use ZSTD_CCtx_reset and ZSTD_CCtx_refCDict, see zstd.h for detailed instructions")
+ZSTDLIB_STATIC_API
+size_t ZSTD_initCStream_usingCDict(ZSTD_CStream* zcs, const ZSTD_CDict* cdict);
+
+/*! ZSTD_initCStream_usingCDict_advanced() :
+ * This function is DEPRECATED, and is equivalent to:
+ * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only);
+ * ZSTD_CCtx_setFParams(zcs, fParams);
+ * ZSTD_CCtx_setPledgedSrcSize(zcs, pledgedSrcSize);
+ * ZSTD_CCtx_refCDict(zcs, cdict);
+ *
+ * same as ZSTD_initCStream_usingCDict(), with control over frame parameters.
+ * pledgedSrcSize must be correct. If srcSize is not known at init time, use
+ * value ZSTD_CONTENTSIZE_UNKNOWN.
+ * This prototype will generate compilation warnings.
+ */
+ZSTD_DEPRECATED("use ZSTD_CCtx_reset and ZSTD_CCtx_refCDict, see zstd.h for detailed instructions")
+ZSTDLIB_STATIC_API
+size_t ZSTD_initCStream_usingCDict_advanced(ZSTD_CStream* zcs,
+ const ZSTD_CDict* cdict,
+ ZSTD_frameParameters fParams,
+ unsigned long long pledgedSrcSize);
+
+/*! ZSTD_resetCStream() :
+ * This function is DEPRECATED, and is equivalent to:
+ * ZSTD_CCtx_reset(zcs, ZSTD_reset_session_only);
+ * ZSTD_CCtx_setPledgedSrcSize(zcs, pledgedSrcSize);
+ * Note: ZSTD_resetCStream() interprets pledgedSrcSize == 0 as ZSTD_CONTENTSIZE_UNKNOWN, but
+ * ZSTD_CCtx_setPledgedSrcSize() does not do the same, so ZSTD_CONTENTSIZE_UNKNOWN must be
+ * explicitly specified.
+ *
+ * start a new frame, using same parameters from previous frame.
+ * This is typically useful to skip dictionary loading stage, since it will reuse it in-place.
+ * Note that zcs must be init at least once before using ZSTD_resetCStream().
+ * If pledgedSrcSize is not known at reset time, use macro ZSTD_CONTENTSIZE_UNKNOWN.
+ * If pledgedSrcSize > 0, its value must be correct, as it will be written in header, and controlled at the end.
+ * For the time being, pledgedSrcSize==0 is interpreted as "srcSize unknown" for compatibility with older programs,
+ * but it will change to mean "empty" in future version, so use macro ZSTD_CONTENTSIZE_UNKNOWN instead.
+ * @return : 0, or an error code (which can be tested using ZSTD_isError())
+ * This prototype will generate compilation warnings.
+ */
+ZSTD_DEPRECATED("use ZSTD_CCtx_reset, see zstd.h for detailed instructions")
+ZSTDLIB_STATIC_API
+size_t ZSTD_resetCStream(ZSTD_CStream* zcs, unsigned long long pledgedSrcSize);
+
+
+typedef struct {
+ unsigned long long ingested; /* nb input bytes read and buffered */
+ unsigned long long consumed; /* nb input bytes actually compressed */
+ unsigned long long produced; /* nb of compressed bytes generated and buffered */
+ unsigned long long flushed; /* nb of compressed bytes flushed : not provided; can be tracked from caller side */
+ unsigned currentJobID; /* MT only : latest started job nb */
+ unsigned nbActiveWorkers; /* MT only : nb of workers actively compressing at probe time */
+} ZSTD_frameProgression;
+
+/* ZSTD_getFrameProgression() :
+ * tells how much data has been ingested (read from input)
+ * consumed (input actually compressed) and produced (output) for current frame.
+ * Note : (ingested - consumed) is amount of input data buffered internally, not yet compressed.
+ * Aggregates progression inside active worker threads.
+ */
+ZSTDLIB_STATIC_API ZSTD_frameProgression ZSTD_getFrameProgression(const ZSTD_CCtx* cctx);
+
+/*! ZSTD_toFlushNow() :
+ * Tell how many bytes are ready to be flushed immediately.
+ * Useful for multithreading scenarios (nbWorkers >= 1).
+ * Probe the oldest active job, defined as oldest job not yet entirely flushed,
+ * and check its output buffer.
+ * @return : amount of data stored in oldest job and ready to be flushed immediately.
+ * if @return == 0, it means either :
+ * + there is no active job (could be checked with ZSTD_frameProgression()), or
+ * + oldest job is still actively compressing data,
+ * but everything it has produced has also been flushed so far,
+ * therefore flush speed is limited by production speed of oldest job
+ * irrespective of the speed of concurrent (and newer) jobs.
+ */
+ZSTDLIB_STATIC_API size_t ZSTD_toFlushNow(ZSTD_CCtx* cctx);
+
+
+/*===== Advanced Streaming decompression functions =====*/
+
+/*!
+ * This function is deprecated, and is equivalent to:
+ *
+ * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only);
+ * ZSTD_DCtx_loadDictionary(zds, dict, dictSize);
+ *
+ * note: no dictionary will be used if dict == NULL or dictSize < 8
+ */
+ZSTD_DEPRECATED("use ZSTD_DCtx_reset + ZSTD_DCtx_loadDictionary, see zstd.h for detailed instructions")
+ZSTDLIB_STATIC_API size_t ZSTD_initDStream_usingDict(ZSTD_DStream* zds, const void* dict, size_t dictSize);
+
+/*!
+ * This function is deprecated, and is equivalent to:
+ *
+ * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only);
+ * ZSTD_DCtx_refDDict(zds, ddict);
+ *
+ * note : ddict is referenced, it must outlive decompression session
+ */
+ZSTD_DEPRECATED("use ZSTD_DCtx_reset + ZSTD_DCtx_refDDict, see zstd.h for detailed instructions")
+ZSTDLIB_STATIC_API size_t ZSTD_initDStream_usingDDict(ZSTD_DStream* zds, const ZSTD_DDict* ddict);
+
+/*!
+ * This function is deprecated, and is equivalent to:
+ *
+ * ZSTD_DCtx_reset(zds, ZSTD_reset_session_only);
+ *
+ * reuse decompression parameters from previous init; saves dictionary loading
+ */
+ZSTD_DEPRECATED("use ZSTD_DCtx_reset, see zstd.h for detailed instructions")
+ZSTDLIB_STATIC_API size_t ZSTD_resetDStream(ZSTD_DStream* zds);
+
+
+/* ********************* BLOCK-LEVEL SEQUENCE PRODUCER API *********************
+ *
+ * *** OVERVIEW ***
+ * The Block-Level Sequence Producer API allows users to provide their own custom
+ * sequence producer which libzstd invokes to process each block. The produced list
+ * of sequences (literals and matches) is then post-processed by libzstd to produce
+ * valid compressed blocks.
+ *
+ * This block-level offload API is a more granular complement of the existing
+ * frame-level offload API compressSequences() (introduced in v1.5.1). It offers
+ * an easier migration story for applications already integrated with libzstd: the
+ * user application continues to invoke the same compression functions
+ * ZSTD_compress2() or ZSTD_compressStream2() as usual, and transparently benefits
+ * from the specific advantages of the external sequence producer. For example,
+ * the sequence producer could be tuned to take advantage of known characteristics
+ * of the input, to offer better speed / ratio, or could leverage hardware
+ * acceleration not available within libzstd itself.
+ *
+ * See contrib/externalSequenceProducer for an example program employing the
+ * Block-Level Sequence Producer API.
+ *
+ * *** USAGE ***
+ * The user is responsible for implementing a function of type
+ * ZSTD_sequenceProducer_F. For each block, zstd will pass the following
+ * arguments to the user-provided function:
+ *
+ * - sequenceProducerState: a pointer to a user-managed state for the sequence
+ * producer.
+ *
+ * - outSeqs, outSeqsCapacity: an output buffer for the sequence producer.
+ * outSeqsCapacity is guaranteed >= ZSTD_sequenceBound(srcSize). The memory
+ * backing outSeqs is managed by the CCtx.
+ *
+ * - src, srcSize: an input buffer for the sequence producer to parse.
+ * srcSize is guaranteed to be <= ZSTD_BLOCKSIZE_MAX.
+ *
+ * - dict, dictSize: a history buffer, which may be empty, which the sequence
+ * producer may reference as it parses the src buffer. Currently, zstd will
+ * always pass dictSize == 0 into external sequence producers, but this will
+ * change in the future.
+ *
+ * - compressionLevel: a signed integer representing the zstd compression level
+ * set by the user for the current operation. The sequence producer may choose
+ * to use this information to change its compression strategy and speed/ratio
+ * tradeoff. Note: the compression level does not reflect zstd parameters set
+ * through the advanced API.
+ *
+ * - windowSize: a size_t representing the maximum allowed offset for external
+ * sequences. Note that sequence offsets are sometimes allowed to exceed the
+ * windowSize if a dictionary is present, see doc/zstd_compression_format.md
+ * for details.
+ *
+ * The user-provided function shall return a size_t representing the number of
+ * sequences written to outSeqs. This return value will be treated as an error
+ * code if it is greater than outSeqsCapacity. The return value must be non-zero
+ * if srcSize is non-zero. The ZSTD_SEQUENCE_PRODUCER_ERROR macro is provided
+ * for convenience, but any value greater than outSeqsCapacity will be treated as
+ * an error code.
+ *
+ * If the user-provided function does not return an error code, the sequences
+ * written to outSeqs must be a valid parse of the src buffer. Data corruption may
+ * occur if the parse is not valid. A parse is defined to be valid if the
+ * following conditions hold:
+ * - The sum of matchLengths and literalLengths must equal srcSize.
+ * - All sequences in the parse, except for the final sequence, must have
+ * matchLength >= ZSTD_MINMATCH_MIN. The final sequence must have
+ * matchLength >= ZSTD_MINMATCH_MIN or matchLength == 0.
+ * - All offsets must respect the windowSize parameter as specified in
+ * doc/zstd_compression_format.md.
+ * - If the final sequence has matchLength == 0, it must also have offset == 0.
+ *
+ * zstd will only validate these conditions (and fail compression if they do not
+ * hold) if the ZSTD_c_validateSequences cParam is enabled. Note that sequence
+ * validation has a performance cost.
+ *
+ * If the user-provided function returns an error, zstd will either fall back
+ * to an internal sequence producer or fail the compression operation. The user can
+ * choose between the two behaviors by setting the ZSTD_c_enableSeqProducerFallback
+ * cParam. Fallback compression will follow any other cParam settings, such as
+ * compression level, the same as in a normal compression operation.
+ *
+ * The user shall instruct zstd to use a particular ZSTD_sequenceProducer_F
+ * function by calling
+ * ZSTD_registerSequenceProducer(cctx,
+ * sequenceProducerState,
+ * sequenceProducer)
+ * This setting will persist until the next parameter reset of the CCtx.
+ *
+ * The sequenceProducerState must be initialized by the user before calling
+ * ZSTD_registerSequenceProducer(). The user is responsible for destroying the
+ * sequenceProducerState.
+ *
+ * *** LIMITATIONS ***
+ * This API is compatible with all zstd compression APIs which respect advanced parameters.
+ * However, there are three limitations:
+ *
+ * First, the ZSTD_c_enableLongDistanceMatching cParam is not currently supported.
+ * COMPRESSION WILL FAIL if it is enabled and the user tries to compress with a block-level
+ * external sequence producer.
+ * - Note that ZSTD_c_enableLongDistanceMatching is auto-enabled by default in some
+ * cases (see its documentation for details). Users must explicitly set
+ * ZSTD_c_enableLongDistanceMatching to ZSTD_ps_disable in such cases if an external
+ * sequence producer is registered.
+ * - As of this writing, ZSTD_c_enableLongDistanceMatching is disabled by default
+ * whenever ZSTD_c_windowLog < 128MB, but that's subject to change. Users should
+ * check the docs on ZSTD_c_enableLongDistanceMatching whenever the Block-Level Sequence
+ * Producer API is used in conjunction with advanced settings (like ZSTD_c_windowLog).
+ *
+ * Second, history buffers are not currently supported. Concretely, zstd will always pass
+ * dictSize == 0 to the external sequence producer (for now). This has two implications:
+ * - Dictionaries are not currently supported. Compression will *not* fail if the user
+ * references a dictionary, but the dictionary won't have any effect.
+ * - Stream history is not currently supported. All advanced compression APIs, including
+ * streaming APIs, work with external sequence producers, but each block is treated as
+ * an independent chunk without history from previous blocks.
+ *
+ * Third, multi-threading within a single compression is not currently supported. In other words,
+ * COMPRESSION WILL FAIL if ZSTD_c_nbWorkers > 0 and an external sequence producer is registered.
+ * Multi-threading across compressions is fine: simply create one CCtx per thread.
+ *
+ * Long-term, we plan to overcome all three limitations. There is no technical blocker to
+ * overcoming them. It is purely a question of engineering effort.
+ */
+
+#define ZSTD_SEQUENCE_PRODUCER_ERROR ((size_t)(-1))
+
+typedef size_t (*ZSTD_sequenceProducer_F) (
+ void* sequenceProducerState,
+ ZSTD_Sequence* outSeqs, size_t outSeqsCapacity,
+ const void* src, size_t srcSize,
+ const void* dict, size_t dictSize,
+ int compressionLevel,
+ size_t windowSize
+);
+
+/*! ZSTD_registerSequenceProducer() :
+ * Instruct zstd to use a block-level external sequence producer function.
+ *
+ * The sequenceProducerState must be initialized by the caller, and the caller is
+ * responsible for managing its lifetime. This parameter is sticky across
+ * compressions. It will remain set until the user explicitly resets compression
+ * parameters.
+ *
+ * Sequence producer registration is considered to be an "advanced parameter",
+ * part of the "advanced API". This means it will only have an effect on compression
+ * APIs which respect advanced parameters, such as compress2() and compressStream2().
+ * Older compression APIs such as compressCCtx(), which predate the introduction of
+ * "advanced parameters", will ignore any external sequence producer setting.
+ *
+ * The sequence producer can be "cleared" by registering a NULL function pointer. This
+ * removes all limitations described above in the "LIMITATIONS" section of the API docs.
+ *
+ * The user is strongly encouraged to read the full API documentation (above) before
+ * calling this function. */
+ZSTDLIB_STATIC_API void
+ZSTD_registerSequenceProducer(
+ ZSTD_CCtx* cctx,
+ void* sequenceProducerState,
+ ZSTD_sequenceProducer_F sequenceProducer
+);
+
+/*! ZSTD_CCtxParams_registerSequenceProducer() :
+ * Same as ZSTD_registerSequenceProducer(), but operates on ZSTD_CCtx_params.
+ * This is used for accurate size estimation with ZSTD_estimateCCtxSize_usingCCtxParams(),
+ * which is needed when creating a ZSTD_CCtx with ZSTD_initStaticCCtx().
+ *
+ * If you are using the external sequence producer API in a scenario where ZSTD_initStaticCCtx()
+ * is required, then this function is for you. Otherwise, you probably don't need it.
+ *
+ * See tests/zstreamtest.c for example usage. */
+ZSTDLIB_STATIC_API void
+ZSTD_CCtxParams_registerSequenceProducer(
+ ZSTD_CCtx_params* params,
+ void* sequenceProducerState,
+ ZSTD_sequenceProducer_F sequenceProducer
+);
+
+
+/*********************************************************************
+* Buffer-less and synchronous inner streaming functions (DEPRECATED)
+*
+* This API is deprecated, and will be removed in a future version.
+* It allows streaming (de)compression with user allocated buffers.
+* However, it is hard to use, and not as well tested as the rest of
+* our API.
+*
+* Please use the normal streaming API instead: ZSTD_compressStream2,
+* and ZSTD_decompressStream.
+* If there is functionality that you need, but it doesn't provide,
+* please open an issue on our GitHub.
+********************************************************************* */
+
+/**
+ Buffer-less streaming compression (synchronous mode)
+
+ A ZSTD_CCtx object is required to track streaming operations.
+ Use ZSTD_createCCtx() / ZSTD_freeCCtx() to manage resource.
+ ZSTD_CCtx object can be reused multiple times within successive compression operations.
+
+ Start by initializing a context.
+ Use ZSTD_compressBegin(), or ZSTD_compressBegin_usingDict() for dictionary compression.
+
+ Then, consume your input using ZSTD_compressContinue().
+ There are some important considerations to keep in mind when using this advanced function :
+ - ZSTD_compressContinue() has no internal buffer. It uses externally provided buffers only.
+ - Interface is synchronous : input is consumed entirely and produces 1+ compressed blocks.
+ - Caller must ensure there is enough space in `dst` to store compressed data under worst case scenario.
+ Worst case evaluation is provided by ZSTD_compressBound().
+ ZSTD_compressContinue() doesn't guarantee recover after a failed compression.
+ - ZSTD_compressContinue() presumes prior input ***is still accessible and unmodified*** (up to maximum distance size, see WindowLog).
+ It remembers all previous contiguous blocks, plus one separated memory segment (which can itself consists of multiple contiguous blocks)
+ - ZSTD_compressContinue() detects that prior input has been overwritten when `src` buffer overlaps.
+ In which case, it will "discard" the relevant memory section from its history.
+
+ Finish a frame with ZSTD_compressEnd(), which will write the last block(s) and optional checksum.
+ It's possible to use srcSize==0, in which case, it will write a final empty block to end the frame.
+ Without last block mark, frames are considered unfinished (hence corrupted) by compliant decoders.
+
+ `ZSTD_CCtx` object can be reused (ZSTD_compressBegin()) to compress again.
+*/
+
+/*===== Buffer-less streaming compression functions =====*/
+ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.")
+ZSTDLIB_STATIC_API size_t ZSTD_compressBegin(ZSTD_CCtx* cctx, int compressionLevel);
+ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.")
+ZSTDLIB_STATIC_API size_t ZSTD_compressBegin_usingDict(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel);
+ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.")
+ZSTDLIB_STATIC_API size_t ZSTD_compressBegin_usingCDict(ZSTD_CCtx* cctx, const ZSTD_CDict* cdict); /**< note: fails if cdict==NULL */
+
+ZSTD_DEPRECATED("This function will likely be removed in a future release. It is misleading and has very limited utility.")
+ZSTDLIB_STATIC_API
+size_t ZSTD_copyCCtx(ZSTD_CCtx* cctx, const ZSTD_CCtx* preparedCCtx, unsigned long long pledgedSrcSize); /**< note: if pledgedSrcSize is not known, use ZSTD_CONTENTSIZE_UNKNOWN */
+
+ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.")
+ZSTDLIB_STATIC_API size_t ZSTD_compressContinue(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize);
+ZSTD_DEPRECATED("The buffer-less API is deprecated in favor of the normal streaming API. See docs.")
+ZSTDLIB_STATIC_API size_t ZSTD_compressEnd(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize);
+
+/* The ZSTD_compressBegin_advanced() and ZSTD_compressBegin_usingCDict_advanced() are now DEPRECATED and will generate a compiler warning */
+ZSTD_DEPRECATED("use advanced API to access custom parameters")
+ZSTDLIB_STATIC_API
+size_t ZSTD_compressBegin_advanced(ZSTD_CCtx* cctx, const void* dict, size_t dictSize, ZSTD_parameters params, unsigned long long pledgedSrcSize); /**< pledgedSrcSize : If srcSize is not known at init time, use ZSTD_CONTENTSIZE_UNKNOWN */
+ZSTD_DEPRECATED("use advanced API to access custom parameters")
+ZSTDLIB_STATIC_API
+size_t ZSTD_compressBegin_usingCDict_advanced(ZSTD_CCtx* const cctx, const ZSTD_CDict* const cdict, ZSTD_frameParameters const fParams, unsigned long long const pledgedSrcSize); /* compression parameters are already set within cdict. pledgedSrcSize must be correct. If srcSize is not known, use macro ZSTD_CONTENTSIZE_UNKNOWN */
+/**
+ Buffer-less streaming decompression (synchronous mode)
+
+ A ZSTD_DCtx object is required to track streaming operations.
+ Use ZSTD_createDCtx() / ZSTD_freeDCtx() to manage it.
+ A ZSTD_DCtx object can be reused multiple times.
+
+ First typical operation is to retrieve frame parameters, using ZSTD_getFrameHeader().
+ Frame header is extracted from the beginning of compressed frame, so providing only the frame's beginning is enough.
+ Data fragment must be large enough to ensure successful decoding.
+ `ZSTD_frameHeaderSize_max` bytes is guaranteed to always be large enough.
+ result : 0 : successful decoding, the `ZSTD_frameHeader` structure is correctly filled.
+ >0 : `srcSize` is too small, please provide at least result bytes on next attempt.
+ errorCode, which can be tested using ZSTD_isError().
+
+ It fills a ZSTD_frameHeader structure with important information to correctly decode the frame,
+ such as the dictionary ID, content size, or maximum back-reference distance (`windowSize`).
+ Note that these values could be wrong, either because of data corruption, or because a 3rd party deliberately spoofs false information.
+ As a consequence, check that values remain within valid application range.
+ For example, do not allocate memory blindly, check that `windowSize` is within expectation.
+ Each application can set its own limits, depending on local restrictions.
+ For extended interoperability, it is recommended to support `windowSize` of at least 8 MB.
+
+ ZSTD_decompressContinue() needs previous data blocks during decompression, up to `windowSize` bytes.
+ ZSTD_decompressContinue() is very sensitive to contiguity,
+ if 2 blocks don't follow each other, make sure that either the compressor breaks contiguity at the same place,
+ or that previous contiguous segment is large enough to properly handle maximum back-reference distance.
+ There are multiple ways to guarantee this condition.
+
+ The most memory efficient way is to use a round buffer of sufficient size.
+ Sufficient size is determined by invoking ZSTD_decodingBufferSize_min(),
+ which can return an error code if required value is too large for current system (in 32-bits mode).
+ In a round buffer methodology, ZSTD_decompressContinue() decompresses each block next to previous one,
+ up to the moment there is not enough room left in the buffer to guarantee decoding another full block,
+ which maximum size is provided in `ZSTD_frameHeader` structure, field `blockSizeMax`.
+ At which point, decoding can resume from the beginning of the buffer.
+ Note that already decoded data stored in the buffer should be flushed before being overwritten.
+
+ There are alternatives possible, for example using two or more buffers of size `windowSize` each, though they consume more memory.
+
+ Finally, if you control the compression process, you can also ignore all buffer size rules,
+ as long as the encoder and decoder progress in "lock-step",
+ aka use exactly the same buffer sizes, break contiguity at the same place, etc.
+
+ Once buffers are setup, start decompression, with ZSTD_decompressBegin().
+ If decompression requires a dictionary, use ZSTD_decompressBegin_usingDict() or ZSTD_decompressBegin_usingDDict().
+
+ Then use ZSTD_nextSrcSizeToDecompress() and ZSTD_decompressContinue() alternatively.
+ ZSTD_nextSrcSizeToDecompress() tells how many bytes to provide as 'srcSize' to ZSTD_decompressContinue().
+ ZSTD_decompressContinue() requires this _exact_ amount of bytes, or it will fail.
+
+ result of ZSTD_decompressContinue() is the number of bytes regenerated within 'dst' (necessarily <= dstCapacity).
+ It can be zero : it just means ZSTD_decompressContinue() has decoded some metadata item.
+ It can also be an error code, which can be tested with ZSTD_isError().
+
+ A frame is fully decoded when ZSTD_nextSrcSizeToDecompress() returns zero.
+ Context can then be reset to start a new decompression.
+
+ Note : it's possible to know if next input to present is a header or a block, using ZSTD_nextInputType().
+ This information is not required to properly decode a frame.
+
+ == Special case : skippable frames ==
+
+ Skippable frames allow integration of user-defined data into a flow of concatenated frames.
+ Skippable frames will be ignored (skipped) by decompressor.
+ The format of skippable frames is as follows :
+ a) Skippable frame ID - 4 Bytes, Little endian format, any value from 0x184D2A50 to 0x184D2A5F
+ b) Frame Size - 4 Bytes, Little endian format, unsigned 32-bits
+ c) Frame Content - any content (User Data) of length equal to Frame Size
+ For skippable frames ZSTD_getFrameHeader() returns zfhPtr->frameType==ZSTD_skippableFrame.
+ For skippable frames ZSTD_decompressContinue() always returns 0 : it only skips the content.
+*/
+
+/*===== Buffer-less streaming decompression functions =====*/
+
+ZSTDLIB_STATIC_API size_t ZSTD_decodingBufferSize_min(unsigned long long windowSize, unsigned long long frameContentSize); /**< when frame content size is not known, pass in frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN */
+
+ZSTDLIB_STATIC_API size_t ZSTD_decompressBegin(ZSTD_DCtx* dctx);
+ZSTDLIB_STATIC_API size_t ZSTD_decompressBegin_usingDict(ZSTD_DCtx* dctx, const void* dict, size_t dictSize);
+ZSTDLIB_STATIC_API size_t ZSTD_decompressBegin_usingDDict(ZSTD_DCtx* dctx, const ZSTD_DDict* ddict);
+
+ZSTDLIB_STATIC_API size_t ZSTD_nextSrcSizeToDecompress(ZSTD_DCtx* dctx);
+ZSTDLIB_STATIC_API size_t ZSTD_decompressContinue(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize);
+
+/* misc */
+ZSTD_DEPRECATED("This function will likely be removed in the next minor release. It is misleading and has very limited utility.")
+ZSTDLIB_STATIC_API void ZSTD_copyDCtx(ZSTD_DCtx* dctx, const ZSTD_DCtx* preparedDCtx);
+typedef enum { ZSTDnit_frameHeader, ZSTDnit_blockHeader, ZSTDnit_block, ZSTDnit_lastBlock, ZSTDnit_checksum, ZSTDnit_skippableFrame } ZSTD_nextInputType_e;
+ZSTDLIB_STATIC_API ZSTD_nextInputType_e ZSTD_nextInputType(ZSTD_DCtx* dctx);
+
+
+
+
+/* ========================================= */
+/** Block level API (DEPRECATED) */
+/* ========================================= */
+
+/*!
+
+ This API is deprecated in favor of the regular compression API.
+ You can get the frame header down to 2 bytes by setting:
+ - ZSTD_c_format = ZSTD_f_zstd1_magicless
+ - ZSTD_c_contentSizeFlag = 0
+ - ZSTD_c_checksumFlag = 0
+ - ZSTD_c_dictIDFlag = 0
+
+ This API is not as well tested as our normal API, so we recommend not using it.
+ We will be removing it in a future version. If the normal API doesn't provide
+ the functionality you need, please open a GitHub issue.
+
+ Block functions produce and decode raw zstd blocks, without frame metadata.
+ Frame metadata cost is typically ~12 bytes, which can be non-negligible for very small blocks (< 100 bytes).
+ But users will have to take in charge needed metadata to regenerate data, such as compressed and content sizes.
+
+ A few rules to respect :
+ - Compressing and decompressing require a context structure
+ + Use ZSTD_createCCtx() and ZSTD_createDCtx()
+ - It is necessary to init context before starting
+ + compression : any ZSTD_compressBegin*() variant, including with dictionary
+ + decompression : any ZSTD_decompressBegin*() variant, including with dictionary
+ - Block size is limited, it must be <= ZSTD_getBlockSize() <= ZSTD_BLOCKSIZE_MAX == 128 KB
+ + If input is larger than a block size, it's necessary to split input data into multiple blocks
+ + For inputs larger than a single block, consider using regular ZSTD_compress() instead.
+ Frame metadata is not that costly, and quickly becomes negligible as source size grows larger than a block.
+ - When a block is considered not compressible enough, ZSTD_compressBlock() result will be 0 (zero) !
+ ===> In which case, nothing is produced into `dst` !
+ + User __must__ test for such outcome and deal directly with uncompressed data
+ + A block cannot be declared incompressible if ZSTD_compressBlock() return value was != 0.
+ Doing so would mess up with statistics history, leading to potential data corruption.
+ + ZSTD_decompressBlock() _doesn't accept uncompressed data as input_ !!
+ + In case of multiple successive blocks, should some of them be uncompressed,
+ decoder must be informed of their existence in order to follow proper history.
+ Use ZSTD_insertBlock() for such a case.
+*/
+
+/*===== Raw zstd block functions =====*/
+ZSTD_DEPRECATED("The block API is deprecated in favor of the normal compression API. See docs.")
+ZSTDLIB_STATIC_API size_t ZSTD_getBlockSize (const ZSTD_CCtx* cctx);
+ZSTD_DEPRECATED("The block API is deprecated in favor of the normal compression API. See docs.")
+ZSTDLIB_STATIC_API size_t ZSTD_compressBlock (ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize);
+ZSTD_DEPRECATED("The block API is deprecated in favor of the normal compression API. See docs.")
+ZSTDLIB_STATIC_API size_t ZSTD_decompressBlock(ZSTD_DCtx* dctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize);
+ZSTD_DEPRECATED("The block API is deprecated in favor of the normal compression API. See docs.")
+ZSTDLIB_STATIC_API size_t ZSTD_insertBlock (ZSTD_DCtx* dctx, const void* blockStart, size_t blockSize); /**< insert uncompressed block into `dctx` history. Useful for multi-blocks decompression. */
+
+#endif /* ZSTD_H_ZSTD_STATIC_LINKING_ONLY */
+
+#if defined (__cplusplus)
+}
+#endif
diff --git a/third_party/zstd/lib/zstd_errors.h b/third_party/zstd/lib/zstd_errors.h
new file mode 100644
index 0000000000..dc75eeebad
--- /dev/null
+++ b/third_party/zstd/lib/zstd_errors.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * All rights reserved.
+ *
+ * This source code is licensed under both the BSD-style license (found in the
+ * LICENSE file in the root directory of this source tree) and the GPLv2 (found
+ * in the COPYING file in the root directory of this source tree).
+ * You may select, at your option, one of the above-listed licenses.
+ */
+
+#ifndef ZSTD_ERRORS_H_398273423
+#define ZSTD_ERRORS_H_398273423
+
+#if defined (__cplusplus)
+extern "C" {
+#endif
+
+/*===== dependency =====*/
+#include <stddef.h> /* size_t */
+
+
+/* ===== ZSTDERRORLIB_API : control library symbols visibility ===== */
+#ifndef ZSTDERRORLIB_VISIBLE
+ /* Backwards compatibility with old macro name */
+# ifdef ZSTDERRORLIB_VISIBILITY
+# define ZSTDERRORLIB_VISIBLE ZSTDERRORLIB_VISIBILITY
+# elif defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__)
+# define ZSTDERRORLIB_VISIBLE __attribute__ ((visibility ("default")))
+# else
+# define ZSTDERRORLIB_VISIBLE
+# endif
+#endif
+
+#ifndef ZSTDERRORLIB_HIDDEN
+# if defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__MINGW32__)
+# define ZSTDERRORLIB_HIDDEN __attribute__ ((visibility ("hidden")))
+# else
+# define ZSTDERRORLIB_HIDDEN
+# endif
+#endif
+
+#if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1)
+# define ZSTDERRORLIB_API __declspec(dllexport) ZSTDERRORLIB_VISIBLE
+#elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1)
+# define ZSTDERRORLIB_API __declspec(dllimport) ZSTDERRORLIB_VISIBLE /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/
+#else
+# define ZSTDERRORLIB_API ZSTDERRORLIB_VISIBLE
+#endif
+
+/*-*********************************************
+ * Error codes list
+ *-*********************************************
+ * Error codes _values_ are pinned down since v1.3.1 only.
+ * Therefore, don't rely on values if you may link to any version < v1.3.1.
+ *
+ * Only values < 100 are considered stable.
+ *
+ * note 1 : this API shall be used with static linking only.
+ * dynamic linking is not yet officially supported.
+ * note 2 : Prefer relying on the enum than on its value whenever possible
+ * This is the only supported way to use the error list < v1.3.1
+ * note 3 : ZSTD_isError() is always correct, whatever the library version.
+ **********************************************/
+typedef enum {
+ ZSTD_error_no_error = 0,
+ ZSTD_error_GENERIC = 1,
+ ZSTD_error_prefix_unknown = 10,
+ ZSTD_error_version_unsupported = 12,
+ ZSTD_error_frameParameter_unsupported = 14,
+ ZSTD_error_frameParameter_windowTooLarge = 16,
+ ZSTD_error_corruption_detected = 20,
+ ZSTD_error_checksum_wrong = 22,
+ ZSTD_error_literals_headerWrong = 24,
+ ZSTD_error_dictionary_corrupted = 30,
+ ZSTD_error_dictionary_wrong = 32,
+ ZSTD_error_dictionaryCreation_failed = 34,
+ ZSTD_error_parameter_unsupported = 40,
+ ZSTD_error_parameter_combination_unsupported = 41,
+ ZSTD_error_parameter_outOfBound = 42,
+ ZSTD_error_tableLog_tooLarge = 44,
+ ZSTD_error_maxSymbolValue_tooLarge = 46,
+ ZSTD_error_maxSymbolValue_tooSmall = 48,
+ ZSTD_error_stabilityCondition_notRespected = 50,
+ ZSTD_error_stage_wrong = 60,
+ ZSTD_error_init_missing = 62,
+ ZSTD_error_memory_allocation = 64,
+ ZSTD_error_workSpace_tooSmall= 66,
+ ZSTD_error_dstSize_tooSmall = 70,
+ ZSTD_error_srcSize_wrong = 72,
+ ZSTD_error_dstBuffer_null = 74,
+ ZSTD_error_noForwardProgress_destFull = 80,
+ ZSTD_error_noForwardProgress_inputEmpty = 82,
+ /* following error codes are __NOT STABLE__, they can be removed or changed in future versions */
+ ZSTD_error_frameIndex_tooLarge = 100,
+ ZSTD_error_seekableIO = 102,
+ ZSTD_error_dstBuffer_wrong = 104,
+ ZSTD_error_srcBuffer_wrong = 105,
+ ZSTD_error_sequenceProducer_failed = 106,
+ ZSTD_error_externalSequences_invalid = 107,
+ ZSTD_error_maxCode = 120 /* never EVER use this value directly, it can change in future versions! Use ZSTD_isError() instead */
+} ZSTD_ErrorCode;
+
+/*! ZSTD_getErrorCode() :
+ convert a `size_t` function result into a `ZSTD_ErrorCode` enum type,
+ which can be used to compare with enum list published above */
+ZSTDERRORLIB_API ZSTD_ErrorCode ZSTD_getErrorCode(size_t functionResult);
+ZSTDERRORLIB_API const char* ZSTD_getErrorString(ZSTD_ErrorCode code); /**< Same as ZSTD_getErrorName, but using a `ZSTD_ErrorCode` enum argument */
+
+
+#if defined (__cplusplus)
+}
+#endif
+
+#endif /* ZSTD_ERRORS_H_398273423 */
diff --git a/third_party/zstd/moz.build b/third_party/zstd/moz.build
new file mode 100644
index 0000000000..6bcc264981
--- /dev/null
+++ b/third_party/zstd/moz.build
@@ -0,0 +1,107 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files('**'):
+ BUG_COMPONENT = ('Core', 'Networking')
+
+EXPORTS.zstd += [
+ 'lib/zstd.h',
+]
+
+UNIFIED_SOURCES += [
+ 'lib/common/debug.c',
+ 'lib/common/entropy_common.c',
+ 'lib/common/error_private.c',
+ 'lib/common/fse_decompress.c',
+ 'lib/common/pool.c',
+ 'lib/common/threading.c',
+ 'lib/common/xxhash.c',
+ 'lib/common/zstd_common.c',
+ 'lib/decompress/huf_decompress.c',
+ 'lib/decompress/zstd_ddict.c',
+ 'lib/decompress/zstd_decompress.c',
+ 'lib/decompress/zstd_decompress_block.c',
+]
+
+# we aren't using these currently
+#COMPRESS_SOURCES = [
+# 'lib/compress/fse_compress.c',
+# 'lib/compress/hist.c',
+# 'lib/compress/huf_compress.c',
+# 'lib/compress/zstd_compress.c',
+# 'lib/compress/zstd_compress_literals.c',
+# 'lib/compress/zstd_compress_sequences.c',
+# 'lib/compress/zstd_compress_superblock.c',
+# 'lib/compress/zstd_double_fast.c',
+# 'lib/compress/zstd_fast.c',
+# 'lib/compress/zstd_lazy.c',
+# 'lib/compress/zstd_ldm.c',
+# 'lib/compress/zstd_opt.c',
+# 'lib/compress/zstdmt_compress.c',
+#]
+
+CFLAGS += [
+ '-Wall',
+ '-Wextra',
+ '-Wcast-qual',
+ '-Wcast-align',
+ '-Wshadow',
+ '-Wstrict-aliasing=1',
+ '-Wswitch-enum',
+ '-Wdeclaration-after-statement',
+ '-Wstrict-prototypes',
+ '-Wundef',
+ '-Wpointer-arith',
+ '-Wvla',
+ '-Wformat=2',
+ '-Winit-self',
+ '-Wfloat-equal',
+ '-Wwrite-strings',
+ '-Wredundant-decls',
+ '-Wmissing-prototypes',
+ '-Wc++-compat'
+]
+
+CXXFLAGS += [
+ '-DXXH_NAMESPACE=ZSTD_',
+ '-DDEBUGLEVEL=0',
+ '-DZSTD_MULTITHREAD=1',
+]
+
+LOCAL_INCLUDES += [ 'lib/decompress' ]
+
+# .S assembler format is incompatible with Microsoft MASM, disable for now
+if CONFIG["OS_ARCH"] == "WINNT":
+ CXXFLAGS += [ '-DZSTD_DISABLE_ASM=1' ]
+else:
+ # If the assembler format is made compatible, we still need to preprocess it
+ if CONFIG["TARGET_CPU"] == "x86_64" and CONFIG["OS_ARCH"] == "WINNT":
+ if CONFIG["CC_TYPE"] == "clang-cl":
+ # libffi asm needs to be preprocessed for MSVC's assembler
+ GeneratedFile("win64_intel.asm",
+ inputs=[
+ "lib/decompress/huf_decompress_amd64.S",
+ ],
+ script="preprocess_asm.py",
+ flags=["$(DEFINES)", "$(LOCAL_INCLUDES)"],
+ )
+ SOURCES += ["!win64_intel.asm"]
+ else:
+ SOURCES += [
+ 'lib/decompress/huf_decompress_amd64.S',
+ ]
+ else:
+ SOURCES += [
+ 'lib/decompress/huf_decompress_amd64.S',
+ ]
+
+# We allow warnings for third-party code that can be updated from upstream.
+AllowCompilerWarnings()
+
+Library('zstd')
+
+FINAL_LIBRARY = 'xul'
+
diff --git a/third_party/zstd/moz.yaml b/third_party/zstd/moz.yaml
new file mode 100644
index 0000000000..52accb395a
--- /dev/null
+++ b/third_party/zstd/moz.yaml
@@ -0,0 +1,44 @@
+schema: 1
+
+bugzilla:
+ product: Toolkit
+ component: "General"
+
+origin:
+ name: zstd
+ description: generic-purpose lossless compression algorithm
+
+ url: https://github.com/facebook/zstd
+
+ release: 72c16b187d27016b7634f5c6b7290e7c66ba44b3 (2024-04-01T16:53:08Z).
+ revision: 72c16b187d27016b7634f5c6b7290e7c66ba44b3
+
+ license: "BSD-3-Clause"
+ license-file: "LICENSE"
+
+vendoring:
+ url: https://github.com/facebook/zstd
+ source-hosting: github
+ tracking: commit
+
+ exclude:
+ - ".*"
+ - ".circleci"
+ - "*"
+ - build
+ - examples
+ - tests
+ - zlibWrapper
+ - programs
+ - contrib
+ - doc
+ - lib/legacy
+ - lib/dll
+ - lib/deprecated
+ - lib/dictBuilder
+ - lib/compress
+
+ include:
+ - lib/
+ - LICENSE
+ - COPYING
diff --git a/third_party/zstd/preprocess_asm.py b/third_party/zstd/preprocess_asm.py
new file mode 100644
index 0000000000..1ce269c387
--- /dev/null
+++ b/third_party/zstd/preprocess_asm.py
@@ -0,0 +1,25 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 distibuted with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import shlex
+import subprocess
+
+import buildconfig
+import mozpack.path as mozpath
+
+
+def main(output, input_asm, defines, includes):
+ defines = shlex.split(defines)
+ includes = shlex.split(includes)
+ # CPP uses -E which generates #line directives. -EP suppresses them.
+ # -TC forces the compiler to treat the input as C.
+ cpp = buildconfig.substs["CPP"] + ["-EP"] + ["-TC"]
+ input_asm = mozpath.relpath(input_asm, os.getcwd())
+ args = cpp + defines + includes + [input_asm]
+ print(" ".join(args))
+ preprocessed = subprocess.check_output(args)
+ output.write(preprocessed)