diff --git a/CLOBBER b/CLOBBER index 2a966c8a83f8..06eff4071185 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Merge day clobber 2024-09-02 \ No newline at end of file +Modified build files in third_party/libwebrtc - Bug 1912989 - Vendor libwebrtc from f6a804826c diff --git a/dom/media/webrtc/third_party_build/default_config_env b/dom/media/webrtc/third_party_build/default_config_env index 679482607f5a..eee2a93d4fd4 100644 --- a/dom/media/webrtc/third_party_build/default_config_env +++ b/dom/media/webrtc/third_party_build/default_config_env @@ -5,41 +5,41 @@ export MOZ_LIBWEBRTC_SRC=$STATE_DIR/moz-libwebrtc # The previous fast-forward bug number is used for some error messaging. -export MOZ_PRIOR_FASTFORWARD_BUG="1903098" +export MOZ_PRIOR_FASTFORWARD_BUG="1909234" # Fast-forwarding each Chromium version of libwebrtc should be done # under a separate bugzilla bug. This bug number is used when crafting # the commit summary as each upstream commit is vendored into the # mercurial repository. The bug used for the v106 fast-forward was # 1800920. -export MOZ_FASTFORWARD_BUG="1909234" +export MOZ_FASTFORWARD_BUG="1912989" # MOZ_NEXT_LIBWEBRTC_MILESTONE and MOZ_NEXT_FIREFOX_REL_TARGET are # not used during fast-forward processing, but facilitate generating this # default config. To generate an default config for the next update, run # bash dom/media/webrtc/third_party_build/update_default_config_env.sh -export MOZ_NEXT_LIBWEBRTC_MILESTONE=127 -export MOZ_NEXT_FIREFOX_REL_TARGET=131 +export MOZ_NEXT_LIBWEBRTC_MILESTONE=128 +export MOZ_NEXT_FIREFOX_REL_TARGET=132 # For Chromium release branches, see: # https://chromiumdash.appspot.com/branches -# Chromium's v126 release branch was 6478. This is used to pre-stack +# Chromium's v127 release branch was 6533. This is used to pre-stack # the previous release branch's commits onto the appropriate base commit # (the first common commit between trunk and the release branch). -export MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM="6478" +export MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM="6533" -# New target release branch for v127 is branch-heads/6533. This is used +# New target release branch for v128 is branch-heads/6613. This is used # to calculate the next upstream commit. -export MOZ_TARGET_UPSTREAM_BRANCH_HEAD="branch-heads/6533" +export MOZ_TARGET_UPSTREAM_BRANCH_HEAD="branch-heads/6613" # For local development 'mozpatches' is fine for a branch name, but when # pushing the patch stack to github, it should be named something like -# 'moz-mods-chr127-for-rel131'. +# 'moz-mods-chr128-for-rel132'. export MOZ_LIBWEBRTC_BRANCH="mozpatches" # After elm has been merged to mozilla-central, the patch stack in # moz-libwebrtc should be pushed to github. The script # push_official_branch.sh uses this branch name when pushing to the # public repo. -export MOZ_LIBWEBRTC_OFFICIAL_BRANCH="moz-mods-chr127-for-rel131" +export MOZ_LIBWEBRTC_OFFICIAL_BRANCH="moz-mods-chr128-for-rel132" diff --git a/third_party/libwebrtc/.gn b/third_party/libwebrtc/.gn index 23da4f21b9fb..907da535dba4 100644 --- a/third_party/libwebrtc/.gn +++ b/third_party/libwebrtc/.gn @@ -27,9 +27,6 @@ secondary_source = "//build/secondary/" no_check_targets = [ "//third_party/icu/*", - # TODO(crbug.com/1151236) Remove once fixed. - "//base/allocator/partition_allocator:partition_alloc", - # TODO: crbug/326607005 - GTEST_HAS_ABSL is broken "//third_party/googletest:gmock", "//third_party/googletest:gtest", @@ -41,6 +38,10 @@ no_check_targets = [ exec_script_whitelist = build_dotfile_settings.exec_script_whitelist + [ "//build_overrides/build.gni" ] +# Normally, we'd use 'if (!build_with_mozilla)', but that flag isn't +# available yet. +#export_compile_commands = [ "*" ] + default_args = { # Webrtc does not support component builds because we are not using the # template "component" but we rely directly on "rtc_static_library" and diff --git a/third_party/libwebrtc/AUTHORS b/third_party/libwebrtc/AUTHORS index e7592cf88754..7b16d7c38ae7 100644 --- a/third_party/libwebrtc/AUTHORS +++ b/third_party/libwebrtc/AUTHORS @@ -21,6 +21,7 @@ Andrew MacDonald Andrey Efremov Andrew Johnson Anil Kumar +Anton Barkov Ben Strong Berthold Herrmann Bob Withers @@ -70,6 +71,7 @@ Keiichi Enomoto Kiran Thind Korniltsev Anatoly Kyutae Lee +lauren n. liberda Lennart Grahl Luke Weber Maksim Khobat @@ -186,6 +188,7 @@ The WebRTC Authors <*@webrtc.org> Threema GmbH <*@threema.ch> Tuple, LLC <*@tuple.app> Twilio, Inc. <*@twilio.com> +Twitch Interactive, Inc. <*@justin.tv> Vewd Software AS <*@vewd.com> Videona Socialmedia <*@videona.com> Videxio AS <*@videxio.com> diff --git a/third_party/libwebrtc/BUILD.gn b/third_party/libwebrtc/BUILD.gn index 9e3ab0758b16..48dd68844285 100644 --- a/third_party/libwebrtc/BUILD.gn +++ b/third_party/libwebrtc/BUILD.gn @@ -139,10 +139,6 @@ config("common_inherited_config") { cflags = [] ldflags = [] - if (rtc_jni_generator_legacy_symbols) { - defines += [ "RTC_JNI_GENERATOR_LEGACY_SYMBOLS" ] - } - if (rtc_objc_prefix != "") { defines += [ "RTC_OBJC_TYPE_PREFIX=${rtc_objc_prefix}" ] } diff --git a/third_party/libwebrtc/DEPS b/third_party/libwebrtc/DEPS index c04bf73c8333..871bac2ec334 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': 'ab2dcf34af863484e003e22685445d00401ca183', + 'chromium_revision': 'ba1ae79f58162c2a16454c1672eb636d8e197687', # Fetch the prebuilt binaries for llvm-cov and llvm-profdata. Needed to # process the raw profiles produced by instrumented targets (built with @@ -30,7 +30,7 @@ vars = { # By default, download the fuchsia sdk from the public sdk directory. 'fuchsia_sdk_cipd_prefix': 'fuchsia/sdk/core/', - 'fuchsia_version': 'version:20.20240522.3.1', + 'fuchsia_version': 'version:21.20240620.2.1', # By default, download the fuchsia images from the fuchsia GCS bucket. 'fuchsia_images_bucket': 'fuchsia', 'checkout_fuchsia': False, @@ -45,11 +45,11 @@ 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.143.0.518e369-gomaip', + 'reclient_version': 're_client_version:0.148.0.41b09b51-gomaip', # ninja CIPD package version # https://chrome-infra-packages.appspot.com/p/infra/3pp/tools/ninja - 'ninja_version': 'version:2@1.11.1.chromium.6', + 'ninja_version': 'version:2@1.11.1.chromium.2', # condition to allowlist deps for non-git-source processing. 'non_git_source': 'True', @@ -58,30 +58,30 @@ vars = { deps = { # TODO(kjellander): Move this to be Android-only. 'src/base': - 'https://chromium.googlesource.com/chromium/src/base@e8aad0129094fdb0ab55858b7a20b5d574283897', + 'https://chromium.googlesource.com/chromium/src/base@aa6dbe6d6a68e6503360a7e0e18b8464c56fc159', 'src/build': - 'https://chromium.googlesource.com/chromium/src/build@e3c3f5e5f5643bb5099ddd192dfd90ff699142c6', + 'https://chromium.googlesource.com/chromium/src/build@5bce81deee6d30ac58c45f6cd53e859c62780687', 'src/buildtools': - 'https://chromium.googlesource.com/chromium/src/buildtools@efa920ce144e4dc1c1841e73179cd7e23b9f0d5e', + 'https://chromium.googlesource.com/chromium/src/buildtools@94d7b86a83537f8a7db7dccb0bf885739f7a81aa', # 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@4213b31aef7d13b5b581c882e0bc870b3eca35e1', + 'url': 'https://chromium.googlesource.com/chromium/src/ios@9e33110a5d6d22342302264579598e42c4623ebb', 'condition': 'checkout_ios', }, 'src/testing': - 'https://chromium.googlesource.com/chromium/src/testing@0ffe26f09e6524ee9851a2b2799ad5ff0974aab4', + 'https://chromium.googlesource.com/chromium/src/testing@a1b47952f3737c536f14a74ff70bc12ed2c1ac7d', 'src/third_party': - 'https://chromium.googlesource.com/chromium/src/third_party@65ef38d36813d1ad9d4cbf8a18b32ac3691f418b', + 'https://chromium.googlesource.com/chromium/src/third_party@91945cadc24feea7b44e1682d17844b6ab508d6d', 'src/buildtools/linux64': { 'packages': [ { 'package': 'gn/gn/linux-${{arch}}', - 'version': 'git_revision:df98b86690c83b81aedc909ded18857296406159', + 'version': 'git_revision:b2afae122eeb6ce09c52d63f67dc53fc517dbdc8', } ], 'dep_type': 'cipd', @@ -91,7 +91,7 @@ deps = { 'packages': [ { 'package': 'gn/gn/mac-${{arch}}', - 'version': 'git_revision:df98b86690c83b81aedc909ded18857296406159', + 'version': 'git_revision:b2afae122eeb6ce09c52d63f67dc53fc517dbdc8', } ], 'dep_type': 'cipd', @@ -101,7 +101,7 @@ deps = { 'packages': [ { 'package': 'gn/gn/windows-amd64', - 'version': 'git_revision:df98b86690c83b81aedc909ded18857296406159', + 'version': 'git_revision:b2afae122eeb6ce09c52d63f67dc53fc517dbdc8', } ], 'dep_type': 'cipd', @@ -123,11 +123,11 @@ deps = { 'src/third_party/clang-format/script': 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/clang/tools/clang-format.git@3c0acd2d4e73dd911309d9e970ba09d58bf23a62', 'src/third_party/libc++/src': - 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxx.git@852bc6746f45add53fec19f3a29280e69e358d44', + 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxx.git@09b99fd8ab300c93ff7b8df6688cafb27bd3db28', 'src/third_party/libc++abi/src': - 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxxabi.git@ba370858669b1e905db5ded82c8887095b61dc14', + 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxxabi.git@bac941ca447afdea824073f3138ef49869e675db', 'src/third_party/libunwind/src': - 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libunwind.git@0906c4a31502cc7a46b5cf1794e21664ffa23be1', + 'https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libunwind.git@dcc1ffafb55a188d09e46c51dc9cd2f091521ea8', 'src/third_party/test_fonts/test_fonts': { 'dep_type': 'gcs', @@ -146,7 +146,7 @@ deps = { 'src/third_party/ninja': { 'packages': [ { - 'package': 'infra/3pp/tools/ninja/${{platform}}', + 'package': 'infra/3pp/build_support/ninja-1_11_1/${{platform}}', 'version': Var('ninja_version'), } ], @@ -207,11 +207,11 @@ deps = { }, 'src/third_party/boringssl/src': - 'https://boringssl.googlesource.com/boringssl.git@2db0eb3f96a5756298dcd7f9319e56a98585bd10', + 'https://boringssl.googlesource.com/boringssl.git@f01108e4761e1d4189cb134322c3cb01dc71ef87', 'src/third_party/breakpad/breakpad': 'https://chromium.googlesource.com/breakpad/breakpad.git@76788faa4ef163081f82273bfca7fae8a734b971', 'src/third_party/catapult': - 'https://chromium.googlesource.com/catapult.git@1f97358483207a44e5dcec67433fb570eeceb3c4', + 'https://chromium.googlesource.com/catapult.git@022cd349fe146c3dd0ba31f2789c630fc40e76a0', 'src/third_party/ced/src': { 'url': 'https://chromium.googlesource.com/external/github.com/google/compact_enc_det.git@ba412eaaacd3186085babcd901679a48863c7dd5', }, @@ -224,11 +224,11 @@ 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@062ecac69f7149d88a467eef91f707f441d62b1d', + 'https://chromium.googlesource.com/chromium/tools/depot_tools.git@f1c7c96958b849668e62799e74b204c9fe9fe17c', 'src/third_party/ffmpeg': - 'https://chromium.googlesource.com/chromium/third_party/ffmpeg.git@092f84b6141055bfab609b6b2666b724eee2e130', + 'https://chromium.googlesource.com/chromium/third_party/ffmpeg.git@d941d9677bb4802f01750fd908ec284fb72c84df', 'src/third_party/flatbuffers/src': - 'https://chromium.googlesource.com/external/github.com/google/flatbuffers.git@150644d7f4d030a0629c564fd90dc3becab77636', + 'https://chromium.googlesource.com/external/github.com/google/flatbuffers.git@6ede1ccc9e24e00d5b19c19d6df0f09fdf1a64fe', 'src/third_party/grpc/src': { 'url': 'https://chromium.googlesource.com/external/github.com/grpc/grpc.git@822dab21d9995c5cf942476b35ca12a1aa9d2737', }, @@ -238,7 +238,7 @@ deps = { 'condition': 'checkout_linux', }, 'src/third_party/freetype/src': - 'https://chromium.googlesource.com/chromium/src/third_party/freetype2.git@5f131cfd20135ac5a1609854a1c2bde425741d3e', + 'https://chromium.googlesource.com/chromium/src/third_party/freetype2.git@73720c7c9958e87b3d134a7574d1720ad2d24442', 'src/third_party/harfbuzz-ng/src': 'https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git@1da053e87f0487382404656edca98b85fe51f2fd', 'src/third_party/google_benchmark/src': { @@ -252,15 +252,15 @@ deps = { 'condition': 'checkout_android', }, 'src/third_party/googletest/src': - 'https://chromium.googlesource.com/external/github.com/google/googletest.git@9b4993ca7d1279dec5c5d41ba327cb11a77bdc00', + 'https://chromium.googlesource.com/external/github.com/google/googletest.git@1d17ea141d2c11b8917d2c7d029f1c4e2b9769b2', 'src/third_party/icu': { - 'url': 'https://chromium.googlesource.com/chromium/deps/icu.git@98f2494518c2dbb9c488e83e507b070ea5910e95', + 'url': 'https://chromium.googlesource.com/chromium/deps/icu.git@163e29159ecb39d4c165c48272e565614a1e024a', }, - 'src/third_party/jdk': { + 'src/third_party/jdk/current': { 'packages': [ { 'package': 'chromium/third_party/jdk', - 'version': 'tUJrCBvDNDE9jFvgkuOwX8tU6oCWT8CtI2_JxpGlTJIC', + 'version': 'BXZwbslDFpYhPRuG8hBh2z7ApP36ZG-ZfkBWrkpnPl4C', }, ], 'condition': 'host_os == "linux" and checkout_android', @@ -300,7 +300,7 @@ deps = { 'packages': [ { 'package': 'chromium/third_party/kotlinc', - 'version': '0jpbSygC1gCOFyv-hsyNVfvxPLhDSXnTCSnxHY_mjKoC', + 'version': '9KgLQsrBWX4kePu9T7eDB1JknSYIPKmmumTEE70lyHYC', }, ], 'condition': 'checkout_android', @@ -312,23 +312,23 @@ deps = { 'src/third_party/fuzztest/src': 'https://chromium.googlesource.com/external/github.com/google/fuzztest.git@32eb84a95951fa3a0148fb3e6a1a02f830ded136', 'src/third_party/libjpeg_turbo': - 'https://chromium.googlesource.com/chromium/deps/libjpeg_turbo.git@9b894306ec3b28cea46e84c32b56773a98c483da', + 'https://chromium.googlesource.com/chromium/deps/libjpeg_turbo.git@ccfbe1c82a3b6dbe8647ceb36a3f9ee711fba3cf', 'src/third_party/libsrtp': 'https://chromium.googlesource.com/chromium/deps/libsrtp.git@7a7e64c8b5a632f55929cb3bb7d3e6fb48c3205a', 'src/third_party/dav1d/libdav1d': - 'https://chromium.googlesource.com/external/github.com/videolan/dav1d.git@006ca01d387ac6652825d6cce1a57b2de67dbf8d', + 'https://chromium.googlesource.com/external/github.com/videolan/dav1d.git@92f592ed104ba92ad35c781ee93f354525eef503', 'src/third_party/libaom/source/libaom': - 'https://aomedia.googlesource.com/aom.git@0f766c1101fa146bfe2aeb7eca23e076bbf631de', + 'https://aomedia.googlesource.com/aom.git@afedaf9da5a13c372b8c7a645ab1bf18f80b56cd', 'src/third_party/libunwindstack': { 'url': 'https://chromium.googlesource.com/chromium/src/third_party/libunwindstack.git@a3bb4cd02e0e984a235069f812cbef2b37c389e5', 'condition': 'checkout_android', }, 'src/third_party/perfetto': - 'https://android.googlesource.com/platform/external/perfetto.git@f235f50590e878113c2fc6eec85ebb5ac47b98dd', + 'https://android.googlesource.com/platform/external/perfetto.git@f9098afffa2c70acecfec66ba520f59b3864be83', 'src/third_party/protobuf-javascript/src': Var('chromium_git') + '/external/github.com/protocolbuffers/protobuf-javascript' + '@' + 'e34549db516f8712f678fcd4bc411613b5cc5295', 'src/third_party/libvpx/source/libvpx': - 'https://chromium.googlesource.com/webm/libvpx.git@5b4cfe88e45a42fbdf22f7210ed253faec9c0fe6', + 'https://chromium.googlesource.com/webm/libvpx.git@253d6365e3f134f4aca6f5fc312336cb501a1c6f', 'src/third_party/libyuv': 'https://chromium.googlesource.com/libyuv/libyuv.git@a6a2ec654b1be1166b376476a7555c89eca0c275', 'src/third_party/lss': { @@ -340,7 +340,7 @@ deps = { 'condition': 'checkout_android', }, 'src/third_party/instrumented_libs': { - 'url': Var('chromium_git') + '/chromium/third_party/instrumented_libraries.git' + '@' + '0172d67d98df2d30bd2241959d0e9569ada25abe', + 'url': Var('chromium_git') + '/chromium/third_party/instrumented_libraries.git' + '@' + 'bb6dbcf2df7a9beb34c3773ef4df161800e3aed9', 'condition': 'checkout_instrumented_libraries', }, @@ -353,13 +353,13 @@ deps = { '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@a771d3fbe7c432dc4db68360c6c0004fdde5646b', + 'https://chromium.googlesource.com/external/github.com/google/re2.git@6144b62bece50a4af8bcdb166f04f6ec5af3d6d8', 'src/third_party/r8': { 'packages': [ { 'package': 'chromium/third_party/r8', - 'version': 'WbqEJ5OsG7ZZ0tJWEj3-SoY215emnCb3V88u0L6O1t4C', + 'version': 'M8rc1oybTkWXWpoImSQ8gAwv6mdEyvAPnQNs6Dus_28C', }, ], 'condition': 'checkout_android', @@ -383,7 +383,7 @@ deps = { 'condition': 'checkout_android', }, 'src/tools': - 'https://chromium.googlesource.com/chromium/src/tools@fbfc57567750705dea8c3dde82e4408abef31f05', + 'https://chromium.googlesource.com/chromium/src/tools@a8fe86b922a84de686c3b15c87e2a9ac84d06db3', 'src/third_party/espresso': { 'packages': [ @@ -422,7 +422,7 @@ deps = { 'packages': [ { 'package': 'chromium/third_party/androidx', - 'version': 'XfjjEUcD39PJCZHKqeWU90_Esp5GFEetk4kpIPZWRZsC', + 'version': '-zomVY2T8V3NRjAUbZNAZpFp8dAPuNdu3oQsnmhhIHEC', }, ], 'condition': 'checkout_android', @@ -433,7 +433,7 @@ deps = { 'packages': [ { 'package': 'chromium/third_party/android_build_tools/manifest_merger', - 'version': 'let00MLFVBLhc9quEKtUuTMxC_vL8cvLoxiRSF2M4nkC', + 'version': '8yUA9fKPOvtc2p3lVEA3l885a1V4-CXZuKt6xAbdR4AC', }, ], 'condition': 'checkout_android', @@ -493,7 +493,7 @@ deps = { 'packages': [ { 'package': 'chromium/third_party/robolectric', - 'version': '5jhUsMBjUweTrDRCZAJB6a0IUPt6cINwH4ZM1rbdLEkC', + 'version': 'Y1B0M_fCpPZ058xErMX6GQOJEVRBWR342juuxNLpVnkC', }, ], 'condition': 'checkout_android', @@ -515,7 +515,7 @@ deps = { 'packages': [ { 'package': 'chromium/third_party/turbine', - 'version': 'JA8o86DtHkYnsW4v8F9pdcvi7uqN1WB-L1XFLggZdtAC', + 'version': 'xWEBZuFKl1Dvw_zOpabeMkGVYlEllIxK06D-RoC6wUsC', }, ], 'condition': 'checkout_android', @@ -526,11 +526,11 @@ deps = { 'packages': [ { 'package': 'infra/tools/luci/isolate/${{platform}}', - 'version': 'git_revision:4967d21f2b92546ac3747086cdcbb046b6db52fb', + 'version': 'git_revision:6fb4d5d26773ebddeac2c57506324493e4220007', }, { 'package': 'infra/tools/luci/swarming/${{platform}}', - 'version': 'git_revision:4967d21f2b92546ac3747086cdcbb046b6db52fb', + 'version': 'git_revision:6fb4d5d26773ebddeac2c57506324493e4220007', }, ], 'dep_type': 'cipd', @@ -2602,4 +2602,4 @@ specific_include_rules = { "+modules/audio_device", "+modules/audio_processing", ] -} +} \ No newline at end of file diff --git a/third_party/libwebrtc/README.moz-ff-commit b/third_party/libwebrtc/README.moz-ff-commit index eaadde96dc04..addd13ae57f2 100644 --- a/third_party/libwebrtc/README.moz-ff-commit +++ b/third_party/libwebrtc/README.moz-ff-commit @@ -31143,3 +31143,639 @@ c24ccd866e # MOZ_LIBWEBRTC_SRC=/home/bcampen/checkouts/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh # base of lastest vendoring e0b28a6a81 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +41b934fe37 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +403220e111 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +546d15ae20 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +6f3103f23d +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +03ebfdf044 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +3f91288883 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +633a41ff8e +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c2c581753a +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +6dfb8c131a +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +ff2bf4b195 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +6e37ee34d1 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +b2c4f5469c +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +7ee37cf839 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +b0a1d8b609 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +f79120a5f8 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +ed1801492d +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +8c0e6286c8 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +94a6b92645 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c3aeffd776 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +6724f1b573 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +b244727265 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +f9f631c48b +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +2da85bc19a +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +da485a1b46 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +093824c4d2 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +3252f5d8e4 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +6118951cdc +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +f13a0e9ec5 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +025d69b4d0 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +da9ef00b61 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +94fa6bf9f4 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +469e69800f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +537543b188 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +504f323437 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +77ffbd3099 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +08b649b6b7 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +72302cc5e4 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +04dd95fcac +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +05c6e745db +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c24b2d508c +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +a0b22af9e1 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +e19ce9b3db +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +6056976709 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +da4d496103 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +6948d84f63 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +feea82fad5 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +7115de6c5c +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +eed94222ea +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +a93d5a00b1 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +799c8e6422 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +418bcf2acb +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +fc6df056b6 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +578905e7ca +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +0f862520dc +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +d4a6c3f76f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +e7a305d3c7 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +aefed55c25 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +d5238b0998 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +defafcb86e +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +85c1db046f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +0fd67312ea +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +dedb03e782 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +3069c60ada +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +e226676a16 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +3a45801d34 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +af1f3a86da +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +9603aa1f7e +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c8b857f1c5 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +26d3e569be +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c47f649e67 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +0adf97372b +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +12b861ef29 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +46b43e0072 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +e71fa4e8b9 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +d03ce76147 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +eaea3e26d7 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +27abd690cc +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +3fede875fe +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +2086ff5d33 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +3be745a20a +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +889402ee1f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +accef6ad5d +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +f86c247185 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +a6c34d10cb +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +0592d2b3c4 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +eb3da2b1ec +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +1030eaaffe +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +0bbc8ce12b +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +81a3d95332 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +20b8e33a3f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c2c6442ad5 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +2d6c397795 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +e6ad337d63 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +769ef20309 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +7235dd0e2b +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +529707576a +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c429517927 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +975334439a +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +86ff48adae +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +479e066495 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +e19896abec +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c7bac48b60 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +2360208f7d +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +216cce5f49 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +f5ccb396b1 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +4157f3def0 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +fb2c7bc8bd +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +93788b68c6 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +b58937316b +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +84af278666 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +6bbbc08747 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +e0287f2797 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +3ae2bfa262 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +7e59d264f1 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +f101f3f803 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +a58047d4e9 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +445d403eca +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +fe4c1dd6dc +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +aab34560cf +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +51ad7c1277 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +fbf754b581 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +257a7b6aea +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +666ef50459 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +881c1a73ad +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +5d56b7cdf0 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +32c3398e1a +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +55c3600781 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +7218d0f304 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +0471a1963f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +10bd257dbe +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +5e49544a76 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +d6ef33e59b +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +3b15e46a4c +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +7f30dd11eb +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +82c8e674ae +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c592257953 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +6ed0a3c3c6 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +db65fda82f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +508e20f92b +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +121eeedc14 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c02488f0f9 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +ea61f0ec82 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +383870faf4 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +ffca3241a0 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +41ffc51e45 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +b43cd86e64 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +0849764539 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +db519e75b7 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +7a6053ae62 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +06b782cb72 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +6502bad4d6 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +8ac4a464f0 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +3172d16ea0 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +b1ebcfbfd6 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +187a4363c0 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +06af5b5c64 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +44a7550acc +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +38e3466837 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +01cba58478 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +9ebf0921ea +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +5d24544378 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +76960dfdb6 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +9fcaa034bc +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +756f58963f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +f7a1506703 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +7fac89f3f9 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +954e72b654 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +be2f8f6ec8 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c2f0260894 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +3d20dce85f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +4f3d660f4f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +4643fb7e01 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +cd8b36bb9f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c0a32fe01b +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +e9810a8adb +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +83e34e3fea +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +cfac4fb9f6 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +55d328dc25 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +3f9589ae64 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +108c94b1d4 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +c14e2cc4ca +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +ea615affcc +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +1bb68532dd +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +5ccec98826 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +1869616223 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +1766a3dbce +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +f6a804826c +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +bdfe6ae801 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +1b78a7eb3f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +faf5b0308c +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +9e1460f9a3 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +83671efdb9 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +4dedf5efae +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +b27ac6bc83 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +45e5e385f3 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +09f03be548 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +3753c8190e +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +ede05c35e4 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +5fe85d23a2 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +1a33aa4a8e +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +4bded9601b +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +02f375da42 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +69720eda3d +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +175e0c95e3 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +74c761384f +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +36b548b31a +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +ac15a137ac +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +7b61b84ab1 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +1ac162ee20 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +bef5d63112 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +e7686023a1 +# MOZ_LIBWEBRTC_SRC=/Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc MOZ_LIBWEBRTC_BRANCH=mozpatches bash dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh +# base of lastest vendoring +f237dc146d diff --git a/third_party/libwebrtc/README.mozilla b/third_party/libwebrtc/README.mozilla index bb12756f2225..ffdd17b4f470 100644 --- a/third_party/libwebrtc/README.mozilla +++ b/third_party/libwebrtc/README.mozilla @@ -20786,3 +20786,427 @@ libwebrtc updated from /home/bcampen/checkouts/elm/.moz-fast-forward/moz-libwebr libwebrtc updated from /home/bcampen/checkouts/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-07-29T17:53:00.327522. # ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /home/bcampen/checkouts/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc libwebrtc updated from /home/bcampen/checkouts/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-07-29T17:54:04.799952. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T18:24:18.055577. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T18:27:36.061988. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T18:29:44.107217. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T18:31:57.389880. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T18:34:09.022995. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T18:37:29.396450. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T18:39:24.327110. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T18:41:51.311968. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T18:43:46.487015. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T18:45:43.755937. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T18:47:47.798112. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T18:49:43.709746. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:11:35.712443. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:14:18.492225. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:16:25.600829. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:18:30.871099. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:20:39.416095. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:22:39.666134. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:24:36.624058. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:27:05.355658. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:29:10.066989. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:31:06.625363. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:33:07.871209. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:35:13.963953. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:38:51.709932. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:42:07.474986. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:44:35.492234. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:46:29.808259. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:48:30.697864. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:50:57.381864. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:52:52.211401. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:54:47.035780. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T20:58:38.085302. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:00:34.838288. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:02:32.341685. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:05:17.682181. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:09:05.951070. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:13:56.597594. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:16:18.425304. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:18:17.720684. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:20:19.361835. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:22:47.739898. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:25:07.333892. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:28:34.327869. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:31:38.503013. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:35:10.044660. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:37:27.058088. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:39:35.868544. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:41:46.542872. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:43:52.960550. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:45:57.812199. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:48:24.422224. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:51:19.583520. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:53:51.658584. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T21:57:34.717308. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:00:12.468466. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:02:22.879432. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:16:23.990253. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:18:25.351780. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:20:33.104314. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:22:40.769914. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:25:11.431667. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:28:41.405250. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:31:18.137737. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:33:11.790995. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:36:13.405916. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:38:07.315080. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:40:01.029706. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:42:00.712277. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:43:58.626181. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:46:18.531394. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:48:13.020322. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:50:11.538177. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:52:35.055066. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:55:08.697018. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:57:15.760485. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T22:59:11.609060. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T23:01:22.685049. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T23:05:08.659827. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T23:07:39.389593. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T23:10:02.523590. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T23:11:56.899784. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T23:13:55.408065. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T23:15:56.223500. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-26T23:19:25.222183. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:04:49.710201. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:08:44.191014. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:12:38.933120. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:14:45.528929. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:16:58.221244. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:20:53.007576. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:23:17.298689. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:25:26.608316. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:28:16.219403. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:31:43.114681. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:34:13.080743. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:36:54.816819. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:39:04.070071. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:41:50.850733. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:44:20.656347. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:47:49.684347. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:49:41.346870. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T19:51:33.698633. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T20:47:21.694748. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T20:50:01.890543. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T21:26:05.107155. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T21:28:46.758707. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T21:31:24.042894. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T21:34:05.443907. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T21:36:51.305778. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T21:38:57.344663. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T21:48:28.721692. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T21:50:24.776459. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T21:52:27.369915. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T21:54:21.422979. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T21:56:25.343743. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T21:59:27.712170. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:01:22.855711. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:03:46.797381. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:06:05.871949. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:08:06.946352. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:10:05.795752. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:11:56.623354. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:13:50.316526. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:16:09.014948. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:18:02.416007. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:19:55.861904. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:23:18.246033. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:25:05.614109. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:26:59.368270. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:28:45.787670. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:30:37.888361. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:32:40.391967. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:34:38.669128. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:36:44.707478. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:39:53.530861. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:49:18.254936. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:51:11.960269. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:53:33.709620. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:55:27.932293. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:57:23.383675. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T22:59:47.235200. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:01:45.955633. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:03:42.716935. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:05:39.752571. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:07:35.164937. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:09:31.255563. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:12:56.766964. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:14:53.973887. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:17:41.303908. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:19:37.001442. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:21:36.300756. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:23:34.689992. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:27:13.685527. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:29:36.339505. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:32:45.832200. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:35:57.790600. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:37:47.054910. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:39:40.684247. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:41:37.079818. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:45:07.811567. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:46:58.480534. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:48:50.914077. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:51:05.099314. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:53:27.227407. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:56:36.748649. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-27T23:58:27.391848. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:02:00.684356. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:03:56.345527. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:06:17.921683. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:09:40.781731. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:11:58.678197. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:14:24.531137. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:16:15.741018. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:18:36.870808. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:20:30.396517. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:22:25.655853. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:24:18.839063. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:26:09.670366. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:28:22.931709. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:30:19.215991. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:32:12.166293. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:35:45.799510. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:37:44.250558. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:40:03.458486. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:41:58.331224. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:43:52.427227. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:53:19.011251. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:55:15.776040. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T00:57:11.371734. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:00:37.809784. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:02:33.061314. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:04:55.450005. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:07:17.398047. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:10:32.027281. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:13:04.434073. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:15:26.604646. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:17:22.565385. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:19:48.412561. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:23:08.357099. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:25:11.469040. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:27:32.768170. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:29:28.757467. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:31:48.400019. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:33:42.894979. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T01:36:04.815430. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T17:14:21.726594. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T17:16:53.448691. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T17:18:47.807257. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T17:21:13.694405. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T17:23:39.728003. +# ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2024-08-28T17:27:24.493750. diff --git a/third_party/libwebrtc/api/BUILD.gn b/third_party/libwebrtc/api/BUILD.gn index 89faaaa63fb4..2710b2476e2d 100644 --- a/third_party/libwebrtc/api/BUILD.gn +++ b/third_party/libwebrtc/api/BUILD.gn @@ -34,7 +34,11 @@ rtc_source_set("enable_media") { ] deps = [ ":libjingle_peerconnection_api", + ":scoped_refptr", "../call", + "../call:call_interfaces", + "../call:call_interfaces", + "../media:media_engine", "../media:rtc_audio_video", "../pc:media_factory", "../rtc_base/system:rtc_export", @@ -59,13 +63,19 @@ rtc_source_set("enable_media_with_defaults") { deps = [ ":enable_media", ":libjingle_peerconnection_api", + ":scoped_refptr", "../rtc_base/system:rtc_export", "audio:audio_processing", + "audio_codecs:audio_codecs_api", + "audio_codecs:audio_codecs_api", "audio_codecs:builtin_audio_decoder_factory", "audio_codecs:builtin_audio_encoder_factory", "task_queue:default_task_queue_factory", + "task_queue:task_queue", "video_codecs:builtin_video_decoder_factory", "video_codecs:builtin_video_encoder_factory", + "video_codecs:video_codecs_api", + "video_codecs:video_codecs_api", ] } @@ -85,6 +95,7 @@ if (!build_with_chromium && !build_with_mozilla) { "../api/rtc_event_log:rtc_event_log_factory", "../pc:peer_connection_factory", "../pc:webrtc_sdp", + "../rtc_base:socket_server", "../rtc_base:threading", "../rtc_base/system:rtc_export", "../stats:rtc_stats", @@ -116,6 +127,8 @@ rtc_library("rtp_headers") { ] deps = [ ":array_view", + "../rtc_base:checks", + "../rtc_base/system:rtc_export", "units:timestamp", "video:video_rtp_headers", "//third_party/abseil-cpp/absl/types:optional", @@ -237,6 +250,7 @@ if (!build_with_mozilla) { ":scoped_refptr", "../rtc_base:ssl", "../rtc_base/system:rtc_export", + "//third_party/abseil-cpp/absl/base:core_headers", "//third_party/abseil-cpp/absl/types:optional", ] } @@ -366,14 +380,19 @@ if (!build_with_mozilla) { "../pc:media_factory", "../rtc_base:copy_on_write_buffer", "../rtc_base:logging", + "../rtc_base:macromagic", "../rtc_base:network", "../rtc_base:network_constants", "../rtc_base:rtc_certificate_generator", + "../rtc_base:socket_factory", "../rtc_base:ssl", "../rtc_base:ssl_adapter", "../rtc_base:stringutils", "adaptation:resource_adaptation_api", + "audio:audio_device", + "audio:audio_frame_processor", "audio:audio_mixer_api", + "audio:audio_processing", "audio_codecs:audio_codecs_api", "crypto:frame_decryptor_interface", "crypto:frame_encryptor_interface", @@ -389,6 +408,7 @@ if (!build_with_mozilla) { "transport:sctp_transport_factory_interface", "transport/rtp:rtp_source", "units:data_rate", + "units:time_delta", "units:timestamp", "video:encoded_image", "video:video_bitrate_allocator_factory", @@ -798,6 +818,7 @@ rtc_library("rtc_event_log_output_file") { "../rtc_base:logging", "../rtc_base/system:file_wrapper", "rtc_event_log", + "//third_party/abseil-cpp/absl/strings:string_view", ] } @@ -970,12 +991,14 @@ rtc_library("ice_transport_factory") { ":make_ref_counted", ":packet_socket_factory", ":scoped_refptr", + ":sequence_checker", "../p2p:connection", "../p2p:ice_transport_internal", "../p2p:p2p_constants", "../p2p:p2p_transport_channel", "../p2p:port_allocator", "../p2p:rtc_p2p", + "../rtc_base:macromagic", "../rtc_base:threading", "../rtc_base/system:rtc_export", "rtc_event_log:rtc_event_log", @@ -1005,6 +1028,7 @@ rtc_source_set("sequence_checker") { "../rtc_base:checks", "../rtc_base:macromagic", "../rtc_base/synchronization:sequence_checker_internal", + "task_queue:task_queue", ] } @@ -1488,6 +1512,8 @@ if (rtc_include_tests) { ":peer_connection_quality_test_fixture_api", ":rtc_error", ":rtc_event_log_output_file", + ":rtp_headers", + ":rtp_headers", ":rtp_packet_info", ":rtp_parameters", ":scoped_refptr", @@ -1497,11 +1523,14 @@ if (rtc_include_tests) { "../rtc_base:buffer", "../rtc_base:checks", "../rtc_base:gunit_helpers", + "../rtc_base:macromagic", "../rtc_base:platform_thread", "../rtc_base:rtc_event", + "../rtc_base:socket_address", "../rtc_base:ssl", "../rtc_base:task_queue_for_test", "../rtc_base/containers:flat_set", + "../rtc_base/synchronization:sequence_checker_internal", "../rtc_base/task_utils:repeating_task", "../system_wrappers:field_trial", "../test:field_trial", @@ -1519,6 +1548,9 @@ if (rtc_include_tests) { "video:frame_buffer_unittest", "video:rtp_video_frame_assembler_unittests", "video:video_frame_metadata_unittest", + "//testing/gtest", + "//third_party/abseil-cpp/absl/functional:any_invocable", + "//third_party/abseil-cpp/absl/strings", "//third_party/abseil-cpp/absl/strings:string_view", "//third_party/abseil-cpp/absl/types:optional", ] @@ -1621,6 +1653,8 @@ rtc_library("frame_transformer_factory") { ":scoped_refptr", "../audio:audio", "../modules/rtp_rtcp", + "../rtc_base:checks", + "../rtc_base/system:rtc_export", "video:encoded_frame", "video:video_frame_metadata", ] diff --git a/third_party/libwebrtc/api/DEPS b/third_party/libwebrtc/api/DEPS index eecde257182f..b07180e763a9 100644 --- a/third_party/libwebrtc/api/DEPS +++ b/third_party/libwebrtc/api/DEPS @@ -116,6 +116,7 @@ specific_include_rules = { "+rtc_base/rtc_certificate.h", "+rtc_base/rtc_certificate_generator.h", "+rtc_base/socket_address.h", + "+rtc_base/socket_factory.h", "+rtc_base/ssl_certificate.h", "+rtc_base/ssl_stream_adapter.h", "+rtc_base/thread.h", @@ -140,6 +141,7 @@ specific_include_rules = { ], "legacy_stats_types\.h": [ + "+rtc_base/thread_annotations.h", "+rtc_base/thread_checker.h", ], @@ -193,14 +195,6 @@ specific_include_rules = { "+rtc_base/thread_annotations.h", ], - "wrapping_async_dns_resolver\.h": [ - "+rtc_base/async_resolver.h", - "+rtc_base/async_resolver_interface.h", - "+rtc_base/socket_address.h", - "+rtc_base/third_party/sigslot/sigslot.h", - "+rtc_base/thread_annotations.h", - ], - "video_encoder_factory_template.*\.h": [ "+modules/video_coding", ], diff --git a/third_party/libwebrtc/api/array_view.h b/third_party/libwebrtc/api/array_view.h index 7e01959b01a4..4abd2bf89584 100644 --- a/third_party/libwebrtc/api/array_view.h +++ b/third_party/libwebrtc/api/array_view.h @@ -13,6 +13,7 @@ #include #include +#include #include #include diff --git a/third_party/libwebrtc/api/array_view_unittest.cc b/third_party/libwebrtc/api/array_view_unittest.cc index 97267df006df..194d8aaf016e 100644 --- a/third_party/libwebrtc/api/array_view_unittest.cc +++ b/third_party/libwebrtc/api/array_view_unittest.cc @@ -10,16 +10,18 @@ #include "api/array_view.h" -#include +#include + #include +#include #include #include #include #include "rtc_base/buffer.h" #include "rtc_base/checks.h" -#include "rtc_base/gunit.h" #include "test/gmock.h" +#include "test/gtest.h" namespace rtc { diff --git a/third_party/libwebrtc/api/audio/BUILD.gn b/third_party/libwebrtc/api/audio/BUILD.gn index 10c96d082b84..1f9e43d47285 100644 --- a/third_party/libwebrtc/api/audio/BUILD.gn +++ b/third_party/libwebrtc/api/audio/BUILD.gn @@ -41,6 +41,7 @@ rtc_library("audio_frame_api") { "../../rtc_base:logging", "../../rtc_base:macromagic", "../../rtc_base:timeutils", + "//third_party/abseil-cpp/absl/types:optional", ] } @@ -124,6 +125,7 @@ rtc_library("aec3_factory") { ":echo_control", "../../modules/audio_processing/aec3", "../../rtc_base/system:rtc_export", + "//third_party/abseil-cpp/absl/types:optional", ] } diff --git a/third_party/libwebrtc/api/audio/audio_frame.cc b/third_party/libwebrtc/api/audio/audio_frame.cc index bee4be67ec75..a2aa7749276b 100644 --- a/third_party/libwebrtc/api/audio/audio_frame.cc +++ b/third_party/libwebrtc/api/audio/audio_frame.cc @@ -12,6 +12,13 @@ #include +#include + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/audio/audio_view.h" +#include "api/audio/channel_layout.h" +#include "api/rtp_packet_infos.h" #include "rtc_base/checks.h" #include "rtc_base/time_utils.h" diff --git a/third_party/libwebrtc/api/audio/audio_frame.h b/third_party/libwebrtc/api/audio/audio_frame.h index 40475c771dc7..5683e8b6e641 100644 --- a/third_party/libwebrtc/api/audio/audio_frame.h +++ b/third_party/libwebrtc/api/audio/audio_frame.h @@ -16,6 +16,7 @@ #include +#include "absl/types/optional.h" #include "api/array_view.h" #include "api/audio/audio_view.h" #include "api/audio/channel_layout.h" diff --git a/third_party/libwebrtc/api/audio/echo_canceller3_factory.cc b/third_party/libwebrtc/api/audio/echo_canceller3_factory.cc index 284b117bea2d..8422a3e0e57e 100644 --- a/third_party/libwebrtc/api/audio/echo_canceller3_factory.cc +++ b/third_party/libwebrtc/api/audio/echo_canceller3_factory.cc @@ -11,6 +11,9 @@ #include +#include "absl/types/optional.h" +#include "api/audio/echo_canceller3_config.h" +#include "api/audio/echo_control.h" #include "modules/audio_processing/aec3/echo_canceller3.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/audio_codecs/BUILD.gn b/third_party/libwebrtc/api/audio_codecs/BUILD.gn index c57f53723298..1a90815c0ac6 100644 --- a/third_party/libwebrtc/api/audio_codecs/BUILD.gn +++ b/third_party/libwebrtc/api/audio_codecs/BUILD.gn @@ -42,9 +42,11 @@ rtc_library("audio_codecs_api") { "../../rtc_base:refcount", "../../rtc_base:sanitizer", "../../rtc_base/system:rtc_export", + "../environment", "../units:data_rate", "../units:time_delta", "//third_party/abseil-cpp/absl/base:core_headers", + "//third_party/abseil-cpp/absl/base:nullability", "//third_party/abseil-cpp/absl/strings", "//third_party/abseil-cpp/absl/strings:string_view", "//third_party/abseil-cpp/absl/types:optional", @@ -64,6 +66,7 @@ rtc_library("builtin_audio_decoder_factory") { "L16:audio_decoder_L16", "g711:audio_decoder_g711", "g722:audio_decoder_g722", + "//third_party/abseil-cpp/absl/types:optional", ] defines = [] if (rtc_include_ilbc) { @@ -92,20 +95,27 @@ rtc_library("builtin_audio_encoder_factory") { ] deps = [ ":audio_codecs_api", + "..:field_trials_view", "..:scoped_refptr", "L16:audio_encoder_L16", "g711:audio_encoder_g711", "g722:audio_encoder_g722", + "//third_party/abseil-cpp/absl/types:optional", ] defines = [] if (rtc_include_ilbc) { - deps += [ "ilbc:audio_encoder_ilbc" ] + deps += [ + "..:field_trials_view", + "ilbc:audio_encoder_ilbc", + "//third_party/abseil-cpp/absl/types:optional", + ] defines += [ "WEBRTC_USE_BUILTIN_ILBC=1" ] } else { defines += [ "WEBRTC_USE_BUILTIN_ILBC=0" ] } if (rtc_include_opus) { deps += [ + "..:field_trials_view", "opus:audio_encoder_multiopus", "opus:audio_encoder_opus", ] @@ -127,6 +137,7 @@ rtc_library("opus_audio_decoder_factory") { "..:scoped_refptr", "opus:audio_decoder_multiopus", "opus:audio_decoder_opus", + "//third_party/abseil-cpp/absl/types:optional", ] } @@ -139,8 +150,10 @@ rtc_library("opus_audio_encoder_factory") { ] deps = [ ":audio_codecs_api", + "..:field_trials_view", "..:scoped_refptr", "opus:audio_encoder_multiopus", "opus:audio_encoder_opus", + "//third_party/abseil-cpp/absl/types:optional", ] } diff --git a/third_party/libwebrtc/api/audio_codecs/L16/BUILD.gn b/third_party/libwebrtc/api/audio_codecs/L16/BUILD.gn index 9d67f8df674b..f2cefbdb9103 100644 --- a/third_party/libwebrtc/api/audio_codecs/L16/BUILD.gn +++ b/third_party/libwebrtc/api/audio_codecs/L16/BUILD.gn @@ -23,6 +23,7 @@ rtc_library("audio_encoder_L16") { "..:audio_codecs_api", "../../../api:field_trials_view", "../../../modules/audio_coding:pcm16b", + "../../../rtc_base:checks", "../../../rtc_base:safe_conversions", "../../../rtc_base:safe_minmax", "../../../rtc_base:stringutils", diff --git a/third_party/libwebrtc/api/audio_codecs/L16/audio_decoder_L16.cc b/third_party/libwebrtc/api/audio_codecs/L16/audio_decoder_L16.cc index a03abe26f74a..b392523473b4 100644 --- a/third_party/libwebrtc/api/audio_codecs/L16/audio_decoder_L16.cc +++ b/third_party/libwebrtc/api/audio_codecs/L16/audio_decoder_L16.cc @@ -11,8 +11,14 @@ #include "api/audio_codecs/L16/audio_decoder_L16.h" #include +#include #include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_decoder.h" +#include "api/audio_codecs/audio_format.h" +#include "api/field_trials_view.h" #include "modules/audio_coding/codecs/pcm16b/audio_decoder_pcm16b.h" #include "modules/audio_coding/codecs/pcm16b/pcm16b_common.h" #include "rtc_base/numerics/safe_conversions.h" diff --git a/third_party/libwebrtc/api/audio_codecs/L16/audio_encoder_L16.cc b/third_party/libwebrtc/api/audio_codecs/L16/audio_encoder_L16.cc index 20259b9ad817..6435cd0993c6 100644 --- a/third_party/libwebrtc/api/audio_codecs/L16/audio_encoder_L16.cc +++ b/third_party/libwebrtc/api/audio_codecs/L16/audio_encoder_L16.cc @@ -10,11 +10,22 @@ #include "api/audio_codecs/L16/audio_encoder_L16.h" +#include + +#include #include +#include +#include #include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_encoder.h" +#include "api/audio_codecs/audio_format.h" +#include "api/field_trials_view.h" #include "modules/audio_coding/codecs/pcm16b/audio_encoder_pcm16b.h" #include "modules/audio_coding/codecs/pcm16b/pcm16b_common.h" +#include "rtc_base/checks.h" #include "rtc_base/numerics/safe_conversions.h" #include "rtc_base/numerics/safe_minmax.h" #include "rtc_base/string_to_number.h" diff --git a/third_party/libwebrtc/api/audio_codecs/audio_decoder.cc b/third_party/libwebrtc/api/audio_codecs/audio_decoder.cc index 0a131f15bca4..84a3eb275062 100644 --- a/third_party/libwebrtc/api/audio_codecs/audio_decoder.cc +++ b/third_party/libwebrtc/api/audio_codecs/audio_decoder.cc @@ -10,10 +10,15 @@ #include "api/audio_codecs/audio_decoder.h" +#include +#include #include #include +#include +#include "absl/types/optional.h" #include "api/array_view.h" +#include "rtc_base/buffer.h" #include "rtc_base/checks.h" #include "rtc_base/sanitizer.h" #include "rtc_base/trace_event.h" diff --git a/third_party/libwebrtc/api/audio_codecs/audio_decoder_factory_template.h b/third_party/libwebrtc/api/audio_codecs/audio_decoder_factory_template.h index 7ea0c913723e..0c026564dc70 100644 --- a/third_party/libwebrtc/api/audio_codecs/audio_decoder_factory_template.h +++ b/third_party/libwebrtc/api/audio_codecs/audio_decoder_factory_template.h @@ -12,9 +12,14 @@ #define API_AUDIO_CODECS_AUDIO_DECODER_FACTORY_TEMPLATE_H_ #include +#include #include +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_decoder.h" #include "api/audio_codecs/audio_decoder_factory.h" +#include "api/audio_codecs/audio_format.h" #include "api/field_trials_view.h" #include "api/make_ref_counted.h" #include "api/scoped_refptr.h" diff --git a/third_party/libwebrtc/api/audio_codecs/audio_encoder.cc b/third_party/libwebrtc/api/audio_codecs/audio_encoder.cc index 31bb8739f745..bb558be38c42 100644 --- a/third_party/libwebrtc/api/audio_codecs/audio_encoder.cc +++ b/third_party/libwebrtc/api/audio_codecs/audio_encoder.cc @@ -10,6 +10,15 @@ #include "api/audio_codecs/audio_encoder.h" +#include +#include +#include +#include + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/call/bitrate_allocation.h" +#include "rtc_base/buffer.h" #include "rtc_base/checks.h" #include "rtc_base/trace_event.h" diff --git a/third_party/libwebrtc/api/audio_codecs/audio_encoder.h b/third_party/libwebrtc/api/audio_codecs/audio_encoder.h index 7b5ee7a86691..552319b940b9 100644 --- a/third_party/libwebrtc/api/audio_codecs/audio_encoder.h +++ b/third_party/libwebrtc/api/audio_codecs/audio_encoder.h @@ -11,6 +11,9 @@ #ifndef API_AUDIO_CODECS_AUDIO_ENCODER_H_ #define API_AUDIO_CODECS_AUDIO_ENCODER_H_ +#include +#include + #include #include #include diff --git a/third_party/libwebrtc/api/audio_codecs/audio_encoder_factory.h b/third_party/libwebrtc/api/audio_codecs/audio_encoder_factory.h index b7c4482d112a..52716939e564 100644 --- a/third_party/libwebrtc/api/audio_codecs/audio_encoder_factory.h +++ b/third_party/libwebrtc/api/audio_codecs/audio_encoder_factory.h @@ -18,6 +18,8 @@ #include "api/audio_codecs/audio_codec_pair_id.h" #include "api/audio_codecs/audio_encoder.h" #include "api/audio_codecs/audio_format.h" +#include "api/environment/environment.h" +#include "rtc_base/checks.h" #include "rtc_base/ref_count.h" namespace webrtc { @@ -25,6 +27,13 @@ namespace webrtc { // A factory that creates AudioEncoders. class AudioEncoderFactory : public RefCountInterface { public: + struct Options { + // TODO(ossu): Try to avoid audio encoders having to know their payload + // type. + int payload_type = -1; + absl::optional codec_pair_id; + }; + // Returns a prioritized list of audio codecs, to use for signaling etc. virtual std::vector GetSupportedEncoders() = 0; @@ -49,14 +58,40 @@ class AudioEncoderFactory : public RefCountInterface { // Note: Implementations need to be robust against combinations other than // one encoder, one decoder getting the same ID; such encoders must still // work. - // - // TODO(ossu): Try to avoid audio encoders having to know their payload type. + // TODO: bugs.webrtc.org/343086059 - make pure virtual when all + // implementations of the `AudioEncoderFactory` are updated. + virtual absl::Nullable> + Create(const Environment& env, const SdpAudioFormat& format, Options options); + + // TODO: bugs.webrtc.org/343086059 - Update all callers to use `Create` + // instead, update implementations not to override it, then delete. virtual std::unique_ptr MakeAudioEncoder( int payload_type, const SdpAudioFormat& format, - absl::optional codec_pair_id) = 0; + absl::optional codec_pair_id); }; +//------------------------------------------------------------------------------ +// Implementation details follow +//------------------------------------------------------------------------------ + +inline absl::Nullable> +AudioEncoderFactory::Create(const Environment& env, + const SdpAudioFormat& format, + Options options) { + return MakeAudioEncoder(options.payload_type, format, options.codec_pair_id); +} + +inline absl::Nullable> +AudioEncoderFactory::MakeAudioEncoder( + int payload_type, + const SdpAudioFormat& format, + absl::optional codec_pair_id) { + // Newer shouldn't call it. + // Older code should implement it. + RTC_CHECK_NOTREACHED(); +} + } // namespace webrtc #endif // API_AUDIO_CODECS_AUDIO_ENCODER_FACTORY_H_ diff --git a/third_party/libwebrtc/api/audio_codecs/audio_encoder_factory_template.h b/third_party/libwebrtc/api/audio_codecs/audio_encoder_factory_template.h index 8a70ba22681d..14b1c5ea1758 100644 --- a/third_party/libwebrtc/api/audio_codecs/audio_encoder_factory_template.h +++ b/third_party/libwebrtc/api/audio_codecs/audio_encoder_factory_template.h @@ -12,9 +12,16 @@ #define API_AUDIO_CODECS_AUDIO_ENCODER_FACTORY_TEMPLATE_H_ #include +#include #include +#include "absl/base/nullability.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_encoder.h" #include "api/audio_codecs/audio_encoder_factory.h" +#include "api/audio_codecs/audio_format.h" +#include "api/environment/environment.h" #include "api/field_trials_view.h" #include "api/make_ref_counted.h" #include "api/scoped_refptr.h" @@ -37,12 +44,76 @@ struct Helper<> { static std::unique_ptr MakeAudioEncoder( int payload_type, const SdpAudioFormat& format, - absl::optional codec_pair_id, - const FieldTrialsView* field_trials) { + absl::optional codec_pair_id) { + return nullptr; + } + static absl::Nullable> CreateAudioEncoder( + const Environment& env, + const SdpAudioFormat& format, + const AudioEncoderFactory::Options& options) { return nullptr; } }; +// Use ranked overloads (abseil.io/tips/229) for dispatching. +struct Rank0 {}; +struct Rank1 : Rank0 {}; + +template (), + std::declval(), + std::declval())), + std::unique_ptr>>> +absl::Nullable> CreateEncoder( + Rank1, + const Environment& env, + const typename Trait::Config& config, + const AudioEncoderFactory::Options& options) { + return Trait::MakeAudioEncoder(env, config, options); +} + +template (), + int{}, + std::declval>())), + std::unique_ptr>>> +absl::Nullable> CreateEncoder( + Rank0, + const Environment& env, + const typename Trait::Config& config, + const AudioEncoderFactory::Options& options) { + return Trait::MakeAudioEncoder(config, options.payload_type, + options.codec_pair_id); +} + +template (), + int{}, + std::declval>())), + std::unique_ptr>>> +absl::Nullable> LegacyCreateEncoder( + Rank1, + const typename Trait::Config& config, + int payload_type, + absl::optional codec_pair_ids) { + return Trait::MakeAudioEncoder(config, payload_type, codec_pair_ids); +} + +template +absl::Nullable> LegacyCreateEncoder( + Rank0, + const typename Trait::Config& config, + int payload_type, + absl::optional codec_pair_ids) { + RTC_CHECK_NOTREACHED(); +} + // Inductive case: Called with n + 1 template parameters; calls subroutines // with n template parameters. template @@ -65,25 +136,31 @@ struct Helper { static std::unique_ptr MakeAudioEncoder( int payload_type, const SdpAudioFormat& format, - absl::optional codec_pair_id, - const FieldTrialsView* field_trials) { + absl::optional codec_pair_id) { auto opt_config = T::SdpToConfig(format); if (opt_config) { - return T::MakeAudioEncoder(*opt_config, payload_type, codec_pair_id); + return LegacyCreateEncoder(Rank1{}, *opt_config, payload_type, + codec_pair_id); } else { return Helper::MakeAudioEncoder(payload_type, format, - codec_pair_id, field_trials); + codec_pair_id); } } + + static absl::Nullable> CreateAudioEncoder( + const Environment& env, + const SdpAudioFormat& format, + const AudioEncoderFactory::Options& options) { + if (auto opt_config = T::SdpToConfig(format); opt_config.has_value()) { + return CreateEncoder(Rank1{}, env, *opt_config, options); + } + return Helper::CreateAudioEncoder(env, format, options); + } }; template class AudioEncoderFactoryT : public AudioEncoderFactory { public: - explicit AudioEncoderFactoryT(const FieldTrialsView* field_trials) { - field_trials_ = field_trials; - } - std::vector GetSupportedEncoders() override { std::vector specs; Helper::AppendSupportedEncoders(&specs); @@ -99,11 +176,15 @@ class AudioEncoderFactoryT : public AudioEncoderFactory { int payload_type, const SdpAudioFormat& format, absl::optional codec_pair_id) override { - return Helper::MakeAudioEncoder(payload_type, format, codec_pair_id, - field_trials_); + return Helper::MakeAudioEncoder(payload_type, format, codec_pair_id); } - const FieldTrialsView* field_trials_; + absl::Nullable> Create( + const Environment& env, + const SdpAudioFormat& format, + Options options) override { + return Helper::CreateAudioEncoder(env, format, options); + } }; } // namespace audio_encoder_factory_template_impl @@ -127,8 +208,13 @@ class AudioEncoderFactoryT : public AudioEncoderFactory { // AudioCodecInfo QueryAudioEncoder(const ConfigType& config); // // // Creates an AudioEncoder for the specified format. Used to implement -// // AudioEncoderFactory::MakeAudioEncoder(). -// std::unique_ptr MakeAudioEncoder( +// // AudioEncoderFactory::Create. +// std::unique_ptr MakeAudioEncoder( +// const Environment& env, +// const ConfigType& config, +// const AudioEncoderFactory::Options& options); +// or +// std::unique_ptr MakeAudioEncoder( // const ConfigType& config, // int payload_type, // absl::optional codec_pair_id); @@ -136,6 +222,12 @@ class AudioEncoderFactoryT : public AudioEncoderFactory { // ConfigType should be a type that encapsulates all the settings needed to // create an AudioEncoder. T::Config (where T is the encoder struct) should // either be the config type, or an alias for it. +// When both MakeAudioEncoder signatures are present, 1st one is preferred. +// +// Note: AudioEncoderFactory::MakeAudioEncoder factory can't use 1st signature, +// and thus uses 2nd signature when available, crashes otherwise. +// TODO: bugs.webrtc.org/343086059 - Remove the note above when MakeAudioEncoder +// is removed. // // Whenever it tries to do something, the new factory will try each of the // encoders in the order they were specified in the template argument list, @@ -144,8 +236,7 @@ class AudioEncoderFactoryT : public AudioEncoderFactory { // TODO(kwiberg): Point at CreateBuiltinAudioEncoderFactory() for an example of // how it is used. template -rtc::scoped_refptr CreateAudioEncoderFactory( - const FieldTrialsView* field_trials = nullptr) { +rtc::scoped_refptr CreateAudioEncoderFactory() { // There's no technical reason we couldn't allow zero template parameters, // but such a factory couldn't create any encoders, and callers can do this // by mistake by simply forgetting the <> altogether. So we forbid it in @@ -154,8 +245,14 @@ rtc::scoped_refptr CreateAudioEncoderFactory( "Caller must give at least one template parameter"); return rtc::make_ref_counted< - audio_encoder_factory_template_impl::AudioEncoderFactoryT>( - field_trials); + audio_encoder_factory_template_impl::AudioEncoderFactoryT>(); +} + +// TODO: bugs.webrtc.org/343086059 - Delete after 2024-07-24 +template +[[deprecated]] rtc::scoped_refptr +CreateAudioEncoderFactory(const FieldTrialsView*) { + return CreateAudioEncoderFactory(); } } // namespace webrtc diff --git a/third_party/libwebrtc/api/audio_codecs/audio_format.cc b/third_party/libwebrtc/api/audio_codecs/audio_format.cc index 8dc11fd80fe1..c83e631e8442 100644 --- a/third_party/libwebrtc/api/audio_codecs/audio_format.cc +++ b/third_party/libwebrtc/api/audio_codecs/audio_format.cc @@ -10,9 +10,13 @@ #include "api/audio_codecs/audio_format.h" +#include #include #include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "api/rtp_parameters.h" +#include "rtc_base/checks.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/audio_codecs/builtin_audio_decoder_factory.cc b/third_party/libwebrtc/api/audio_codecs/builtin_audio_decoder_factory.cc index 881113d985e9..25be26efb4fb 100644 --- a/third_party/libwebrtc/api/audio_codecs/builtin_audio_decoder_factory.cc +++ b/third_party/libwebrtc/api/audio_codecs/builtin_audio_decoder_factory.cc @@ -13,10 +13,16 @@ #include #include +#include "absl/types/optional.h" #include "api/audio_codecs/L16/audio_decoder_L16.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_decoder.h" +#include "api/audio_codecs/audio_decoder_factory.h" #include "api/audio_codecs/audio_decoder_factory_template.h" +#include "api/audio_codecs/audio_format.h" #include "api/audio_codecs/g711/audio_decoder_g711.h" #include "api/audio_codecs/g722/audio_decoder_g722.h" +#include "api/scoped_refptr.h" #if WEBRTC_USE_BUILTIN_ILBC #include "api/audio_codecs/ilbc/audio_decoder_ilbc.h" // nogncheck #endif diff --git a/third_party/libwebrtc/api/audio_codecs/builtin_audio_encoder_factory.cc b/third_party/libwebrtc/api/audio_codecs/builtin_audio_encoder_factory.cc index 4546a2eaee37..864d1a68beb9 100644 --- a/third_party/libwebrtc/api/audio_codecs/builtin_audio_encoder_factory.cc +++ b/third_party/libwebrtc/api/audio_codecs/builtin_audio_encoder_factory.cc @@ -13,10 +13,17 @@ #include #include +#include "absl/types/optional.h" #include "api/audio_codecs/L16/audio_encoder_L16.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_encoder.h" +#include "api/audio_codecs/audio_encoder_factory.h" #include "api/audio_codecs/audio_encoder_factory_template.h" +#include "api/audio_codecs/audio_format.h" #include "api/audio_codecs/g711/audio_encoder_g711.h" #include "api/audio_codecs/g722/audio_encoder_g722.h" +#include "api/field_trials_view.h" +#include "api/scoped_refptr.h" #if WEBRTC_USE_BUILTIN_ILBC #include "api/audio_codecs/ilbc/audio_encoder_ilbc.h" // nogncheck #endif diff --git a/third_party/libwebrtc/api/audio_codecs/g711/BUILD.gn b/third_party/libwebrtc/api/audio_codecs/g711/BUILD.gn index 6e3c2324e2cf..df377e264bf9 100644 --- a/third_party/libwebrtc/api/audio_codecs/g711/BUILD.gn +++ b/third_party/libwebrtc/api/audio_codecs/g711/BUILD.gn @@ -23,6 +23,7 @@ rtc_library("audio_encoder_g711") { "..:audio_codecs_api", "../../../api:field_trials_view", "../../../modules/audio_coding:g711", + "../../../rtc_base:checks", "../../../rtc_base:safe_conversions", "../../../rtc_base:safe_minmax", "../../../rtc_base:stringutils", @@ -43,6 +44,7 @@ rtc_library("audio_decoder_g711") { "..:audio_codecs_api", "../../../api:field_trials_view", "../../../modules/audio_coding:g711", + "../../../rtc_base:checks", "../../../rtc_base:safe_conversions", "../../../rtc_base/system:rtc_export", "//third_party/abseil-cpp/absl/strings", diff --git a/third_party/libwebrtc/api/audio_codecs/g711/audio_decoder_g711.cc b/third_party/libwebrtc/api/audio_codecs/g711/audio_decoder_g711.cc index 838f7e9624b6..28963c53f024 100644 --- a/third_party/libwebrtc/api/audio_codecs/g711/audio_decoder_g711.cc +++ b/third_party/libwebrtc/api/audio_codecs/g711/audio_decoder_g711.cc @@ -10,11 +10,18 @@ #include "api/audio_codecs/g711/audio_decoder_g711.h" +#include #include #include #include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_decoder.h" +#include "api/audio_codecs/audio_format.h" +#include "api/field_trials_view.h" #include "modules/audio_coding/codecs/g711/audio_decoder_pcm.h" +#include "rtc_base/checks.h" #include "rtc_base/numerics/safe_conversions.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/audio_codecs/g711/audio_encoder_g711.cc b/third_party/libwebrtc/api/audio_codecs/g711/audio_encoder_g711.cc index 1dca3b80d31b..e32aa28d3f3e 100644 --- a/third_party/libwebrtc/api/audio_codecs/g711/audio_encoder_g711.cc +++ b/third_party/libwebrtc/api/audio_codecs/g711/audio_encoder_g711.cc @@ -10,11 +10,22 @@ #include "api/audio_codecs/g711/audio_encoder_g711.h" +#include + +#include +#include #include +#include #include #include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_encoder.h" +#include "api/audio_codecs/audio_format.h" +#include "api/field_trials_view.h" #include "modules/audio_coding/codecs/g711/audio_encoder_pcm.h" +#include "rtc_base/checks.h" #include "rtc_base/numerics/safe_conversions.h" #include "rtc_base/numerics/safe_minmax.h" #include "rtc_base/string_to_number.h" diff --git a/third_party/libwebrtc/api/audio_codecs/g722/BUILD.gn b/third_party/libwebrtc/api/audio_codecs/g722/BUILD.gn index a3a06b5246fd..58b6e249859b 100644 --- a/third_party/libwebrtc/api/audio_codecs/g722/BUILD.gn +++ b/third_party/libwebrtc/api/audio_codecs/g722/BUILD.gn @@ -30,6 +30,7 @@ rtc_library("audio_encoder_g722") { "..:audio_codecs_api", "../../../api:field_trials_view", "../../../modules/audio_coding:g722", + "../../../rtc_base:checks", "../../../rtc_base:safe_conversions", "../../../rtc_base:safe_minmax", "../../../rtc_base:stringutils", @@ -50,6 +51,7 @@ rtc_library("audio_decoder_g722") { "..:audio_codecs_api", "../../../api:field_trials_view", "../../../modules/audio_coding:g722", + "../../../rtc_base:checks", "../../../rtc_base:safe_conversions", "../../../rtc_base/system:rtc_export", "//third_party/abseil-cpp/absl/strings", diff --git a/third_party/libwebrtc/api/audio_codecs/g722/audio_decoder_g722.cc b/third_party/libwebrtc/api/audio_codecs/g722/audio_decoder_g722.cc index ed7163471a91..b5e5f429d1b4 100644 --- a/third_party/libwebrtc/api/audio_codecs/g722/audio_decoder_g722.cc +++ b/third_party/libwebrtc/api/audio_codecs/g722/audio_decoder_g722.cc @@ -14,7 +14,13 @@ #include #include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_decoder.h" +#include "api/audio_codecs/audio_format.h" +#include "api/field_trials_view.h" #include "modules/audio_coding/codecs/g722/audio_decoder_g722.h" +#include "rtc_base/checks.h" #include "rtc_base/numerics/safe_conversions.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/audio_codecs/g722/audio_encoder_g722.cc b/third_party/libwebrtc/api/audio_codecs/g722/audio_encoder_g722.cc index 56a6c4da6a67..d7a1c896c5b0 100644 --- a/third_party/libwebrtc/api/audio_codecs/g722/audio_encoder_g722.cc +++ b/third_party/libwebrtc/api/audio_codecs/g722/audio_encoder_g722.cc @@ -10,11 +10,22 @@ #include "api/audio_codecs/g722/audio_encoder_g722.h" +#include + +#include #include +#include #include #include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_encoder.h" +#include "api/audio_codecs/audio_format.h" +#include "api/audio_codecs/g722/audio_encoder_g722_config.h" +#include "api/field_trials_view.h" #include "modules/audio_coding/codecs/g722/audio_encoder_g722.h" +#include "rtc_base/checks.h" #include "rtc_base/numerics/safe_conversions.h" #include "rtc_base/numerics/safe_minmax.h" #include "rtc_base/string_to_number.h" diff --git a/third_party/libwebrtc/api/audio_codecs/ilbc/BUILD.gn b/third_party/libwebrtc/api/audio_codecs/ilbc/BUILD.gn index f2cb397730ca..a71867a32618 100644 --- a/third_party/libwebrtc/api/audio_codecs/ilbc/BUILD.gn +++ b/third_party/libwebrtc/api/audio_codecs/ilbc/BUILD.gn @@ -29,6 +29,7 @@ rtc_library("audio_encoder_ilbc") { "..:audio_codecs_api", "../../../api:field_trials_view", "../../../modules/audio_coding:ilbc", + "../../../rtc_base:checks", "../../../rtc_base:safe_conversions", "../../../rtc_base:safe_minmax", "../../../rtc_base:stringutils", diff --git a/third_party/libwebrtc/api/audio_codecs/ilbc/audio_decoder_ilbc.cc b/third_party/libwebrtc/api/audio_codecs/ilbc/audio_decoder_ilbc.cc index c58316903a45..6b3c0cb378dd 100644 --- a/third_party/libwebrtc/api/audio_codecs/ilbc/audio_decoder_ilbc.cc +++ b/third_party/libwebrtc/api/audio_codecs/ilbc/audio_decoder_ilbc.cc @@ -14,6 +14,11 @@ #include #include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_decoder.h" +#include "api/audio_codecs/audio_format.h" +#include "api/field_trials_view.h" #include "modules/audio_coding/codecs/ilbc/audio_decoder_ilbc.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/audio_codecs/ilbc/audio_encoder_ilbc.cc b/third_party/libwebrtc/api/audio_codecs/ilbc/audio_encoder_ilbc.cc index b4979484918d..73455910a438 100644 --- a/third_party/libwebrtc/api/audio_codecs/ilbc/audio_encoder_ilbc.cc +++ b/third_party/libwebrtc/api/audio_codecs/ilbc/audio_encoder_ilbc.cc @@ -10,12 +10,21 @@ #include "api/audio_codecs/ilbc/audio_encoder_ilbc.h" +#include #include +#include +#include #include #include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_encoder.h" +#include "api/audio_codecs/audio_format.h" +#include "api/audio_codecs/ilbc/audio_encoder_ilbc_config.h" +#include "api/field_trials_view.h" #include "modules/audio_coding/codecs/ilbc/audio_encoder_ilbc.h" -#include "rtc_base/numerics/safe_conversions.h" +#include "rtc_base/checks.h" #include "rtc_base/numerics/safe_minmax.h" #include "rtc_base/string_to_number.h" diff --git a/third_party/libwebrtc/api/audio_codecs/opus/BUILD.gn b/third_party/libwebrtc/api/audio_codecs/opus/BUILD.gn index cde56e1f23b5..cbf03c229ad4 100644 --- a/third_party/libwebrtc/api/audio_codecs/opus/BUILD.gn +++ b/third_party/libwebrtc/api/audio_codecs/opus/BUILD.gn @@ -48,7 +48,10 @@ rtc_library("audio_encoder_opus") { "..:audio_codecs_api", "../../../api:field_trials_view", "../../../modules/audio_coding:webrtc_opus", + "../../../rtc_base:checks", "../../../rtc_base/system:rtc_export", + "../../environment", + "//third_party/abseil-cpp/absl/memory", "//third_party/abseil-cpp/absl/types:optional", ] } @@ -64,6 +67,7 @@ rtc_library("audio_decoder_opus") { "..:audio_codecs_api", "../../../api:field_trials_view", "../../../modules/audio_coding:webrtc_opus", + "../../../rtc_base:checks", "../../../rtc_base/system:rtc_export", "//third_party/abseil-cpp/absl/strings", "//third_party/abseil-cpp/absl/types:optional", diff --git a/third_party/libwebrtc/api/audio_codecs/opus/audio_decoder_multi_channel_opus.cc b/third_party/libwebrtc/api/audio_codecs/opus/audio_decoder_multi_channel_opus.cc index 0fb4e055114b..497e212cd033 100644 --- a/third_party/libwebrtc/api/audio_codecs/opus/audio_decoder_multi_channel_opus.cc +++ b/third_party/libwebrtc/api/audio_codecs/opus/audio_decoder_multi_channel_opus.cc @@ -14,8 +14,12 @@ #include #include -#include "absl/memory/memory.h" -#include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_decoder.h" +#include "api/audio_codecs/audio_format.h" +#include "api/audio_codecs/opus/audio_decoder_multi_channel_opus_config.h" +#include "api/field_trials_view.h" #include "modules/audio_coding/codecs/opus/audio_decoder_multi_channel_opus_impl.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/audio_codecs/opus/audio_decoder_opus.cc b/third_party/libwebrtc/api/audio_codecs/opus/audio_decoder_opus.cc index efc9a73546ef..3c8635d6c84b 100644 --- a/third_party/libwebrtc/api/audio_codecs/opus/audio_decoder_opus.cc +++ b/third_party/libwebrtc/api/audio_codecs/opus/audio_decoder_opus.cc @@ -10,12 +10,20 @@ #include "api/audio_codecs/opus/audio_decoder_opus.h" +#include #include +#include #include #include #include "absl/strings/match.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_decoder.h" +#include "api/audio_codecs/audio_format.h" +#include "api/field_trials_view.h" #include "modules/audio_coding/codecs/opus/audio_decoder_opus.h" +#include "rtc_base/checks.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_multi_channel_opus_config.h b/third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_multi_channel_opus_config.h index 9b51246c1579..ff513abf42ac 100644 --- a/third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_multi_channel_opus_config.h +++ b/third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_multi_channel_opus_config.h @@ -12,11 +12,8 @@ #define API_AUDIO_CODECS_OPUS_AUDIO_ENCODER_MULTI_CHANNEL_OPUS_CONFIG_H_ #include - #include -#include "absl/types/optional.h" -#include "api/audio_codecs/opus/audio_encoder_opus_config.h" #include "rtc_base/system/rtc_export.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_opus.cc b/third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_opus.cc index 5b6322da4c0e..6a6d5664b607 100644 --- a/third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_opus.cc +++ b/third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_opus.cc @@ -10,7 +10,19 @@ #include "api/audio_codecs/opus/audio_encoder_opus.h" +#include +#include + +#include "absl/memory/memory.h" +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_encoder.h" +#include "api/audio_codecs/audio_encoder_factory.h" +#include "api/audio_codecs/audio_format.h" +#include "api/audio_codecs/opus/audio_encoder_opus_config.h" +#include "api/field_trials_view.h" #include "modules/audio_coding/codecs/opus/audio_encoder_opus.h" +#include "rtc_base/checks.h" namespace webrtc { @@ -38,7 +50,23 @@ std::unique_ptr AudioEncoderOpus::MakeAudioEncoder( RTC_DCHECK_NOTREACHED(); return nullptr; } - return AudioEncoderOpusImpl::MakeAudioEncoder(config, payload_type); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + // Use WrapUnique to call deprecated constructor. + return absl::WrapUnique(new AudioEncoderOpusImpl(config, payload_type)); +#pragma clang diagnostic pop +} + +std::unique_ptr AudioEncoderOpus::MakeAudioEncoder( + const Environment& env, + const AudioEncoderOpusConfig& config, + const AudioEncoderFactory::Options& options) { + if (!config.IsOk()) { + RTC_DCHECK_NOTREACHED(); + return nullptr; + } + return std::make_unique(env, config, + options.payload_type); } } // namespace webrtc diff --git a/third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_opus.h b/third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_opus.h index df93ae530300..414fe450121b 100644 --- a/third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_opus.h +++ b/third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_opus.h @@ -17,8 +17,10 @@ #include "absl/types/optional.h" #include "api/audio_codecs/audio_codec_pair_id.h" #include "api/audio_codecs/audio_encoder.h" +#include "api/audio_codecs/audio_encoder_factory.h" #include "api/audio_codecs/audio_format.h" #include "api/audio_codecs/opus/audio_encoder_opus_config.h" +#include "api/environment/environment.h" #include "api/field_trials_view.h" #include "rtc_base/system/rtc_export.h" @@ -32,6 +34,10 @@ struct RTC_EXPORT AudioEncoderOpus { const SdpAudioFormat& audio_format); static void AppendSupportedEncoders(std::vector* specs); static AudioCodecInfo QueryAudioEncoder(const AudioEncoderOpusConfig& config); + static std::unique_ptr MakeAudioEncoder( + const Environment& env, + const AudioEncoderOpusConfig& config, + const AudioEncoderFactory::Options& options); static std::unique_ptr MakeAudioEncoder( const AudioEncoderOpusConfig& config, int payload_type, diff --git a/third_party/libwebrtc/api/audio_codecs/opus_audio_decoder_factory.cc b/third_party/libwebrtc/api/audio_codecs/opus_audio_decoder_factory.cc index ed68f2584e02..cf7bda2ff207 100644 --- a/third_party/libwebrtc/api/audio_codecs/opus_audio_decoder_factory.cc +++ b/third_party/libwebrtc/api/audio_codecs/opus_audio_decoder_factory.cc @@ -13,9 +13,15 @@ #include #include +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_decoder.h" +#include "api/audio_codecs/audio_decoder_factory.h" #include "api/audio_codecs/audio_decoder_factory_template.h" +#include "api/audio_codecs/audio_format.h" #include "api/audio_codecs/opus/audio_decoder_multi_channel_opus.h" #include "api/audio_codecs/opus/audio_decoder_opus.h" +#include "api/scoped_refptr.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/audio_codecs/opus_audio_encoder_factory.cc b/third_party/libwebrtc/api/audio_codecs/opus_audio_encoder_factory.cc index 8c286f21e18e..8d1d150e7ea3 100644 --- a/third_party/libwebrtc/api/audio_codecs/opus_audio_encoder_factory.cc +++ b/third_party/libwebrtc/api/audio_codecs/opus_audio_encoder_factory.cc @@ -13,9 +13,16 @@ #include #include +#include "absl/types/optional.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_encoder.h" +#include "api/audio_codecs/audio_encoder_factory.h" #include "api/audio_codecs/audio_encoder_factory_template.h" +#include "api/audio_codecs/audio_format.h" #include "api/audio_codecs/opus/audio_encoder_multi_channel_opus.h" #include "api/audio_codecs/opus/audio_encoder_opus.h" +#include "api/field_trials_view.h" +#include "api/scoped_refptr.h" namespace webrtc { namespace { diff --git a/third_party/libwebrtc/api/audio_codecs/test/BUILD.gn b/third_party/libwebrtc/api/audio_codecs/test/BUILD.gn index 89f5fef1eafe..702772c57cce 100644 --- a/third_party/libwebrtc/api/audio_codecs/test/BUILD.gn +++ b/third_party/libwebrtc/api/audio_codecs/test/BUILD.gn @@ -21,9 +21,13 @@ if (rtc_include_tests) { ] deps = [ "..:audio_codecs_api", + "../..:make_ref_counted", + "../..:scoped_refptr", "../../../test:audio_codec_mocks", "../../../test:scoped_key_value_config", "../../../test:test_support", + "../../environment", + "../../environment:environment_factory", "../L16:audio_decoder_L16", "../L16:audio_encoder_L16", "../g711:audio_decoder_g711", @@ -34,6 +38,7 @@ if (rtc_include_tests) { "../ilbc:audio_encoder_ilbc", "../opus:audio_decoder_opus", "../opus:audio_encoder_opus", + "//third_party/abseil-cpp/absl/types:optional", ] } } diff --git a/third_party/libwebrtc/api/audio_codecs/test/audio_decoder_factory_template_unittest.cc b/third_party/libwebrtc/api/audio_codecs/test/audio_decoder_factory_template_unittest.cc index 0b18cf934a99..dd112c945977 100644 --- a/third_party/libwebrtc/api/audio_codecs/test/audio_decoder_factory_template_unittest.cc +++ b/third_party/libwebrtc/api/audio_codecs/test/audio_decoder_factory_template_unittest.cc @@ -11,12 +11,21 @@ #include "api/audio_codecs/audio_decoder_factory_template.h" #include +#include +#include +#include "absl/types/optional.h" #include "api/audio_codecs/L16/audio_decoder_L16.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_decoder.h" +#include "api/audio_codecs/audio_decoder_factory.h" +#include "api/audio_codecs/audio_format.h" #include "api/audio_codecs/g711/audio_decoder_g711.h" #include "api/audio_codecs/g722/audio_decoder_g722.h" #include "api/audio_codecs/ilbc/audio_decoder_ilbc.h" #include "api/audio_codecs/opus/audio_decoder_opus.h" +#include "api/make_ref_counted.h" +#include "api/scoped_refptr.h" #include "test/gmock.h" #include "test/gtest.h" #include "test/mock_audio_decoder.h" diff --git a/third_party/libwebrtc/api/audio_codecs/test/audio_encoder_factory_template_unittest.cc b/third_party/libwebrtc/api/audio_codecs/test/audio_encoder_factory_template_unittest.cc index dbba38772448..5b8e35dc8d47 100644 --- a/third_party/libwebrtc/api/audio_codecs/test/audio_encoder_factory_template_unittest.cc +++ b/third_party/libwebrtc/api/audio_codecs/test/audio_encoder_factory_template_unittest.cc @@ -11,21 +11,37 @@ #include "api/audio_codecs/audio_encoder_factory_template.h" #include +#include +#include +#include "absl/types/optional.h" #include "api/audio_codecs/L16/audio_encoder_L16.h" +#include "api/audio_codecs/audio_codec_pair_id.h" +#include "api/audio_codecs/audio_encoder.h" +#include "api/audio_codecs/audio_encoder_factory.h" +#include "api/audio_codecs/audio_format.h" #include "api/audio_codecs/g711/audio_encoder_g711.h" #include "api/audio_codecs/g722/audio_encoder_g722.h" #include "api/audio_codecs/ilbc/audio_encoder_ilbc.h" #include "api/audio_codecs/opus/audio_encoder_opus.h" +#include "api/environment/environment.h" +#include "api/environment/environment_factory.h" +#include "api/make_ref_counted.h" +#include "api/scoped_refptr.h" #include "test/gmock.h" #include "test/gtest.h" #include "test/mock_audio_encoder.h" #include "test/scoped_key_value_config.h" namespace webrtc { - namespace { +using ::testing::IsNull; +using ::testing::NiceMock; +using ::testing::Pointer; +using ::testing::Property; +using ::testing::Return; + struct BogusParams { static SdpAudioFormat AudioFormat() { return {"bogus", 8000, 1}; } static AudioCodecInfo CodecInfo() { return {8000, 1, 12345}; } @@ -73,21 +89,139 @@ struct AudioEncoderFakeApi { } }; -} // namespace +// Trait to pass as template parameter to `CreateAudioEncoderFactory` with +// all the functions except the functions to create the audio encoder. +struct BaseAudioEncoderApi { + // Create Encoders with different sample rates depending if it is created + // through V1 or V2 method so that a test may detect which method was used. + static constexpr int kV1SameRate = 10'000; + static constexpr int kV2SameRate = 20'000; + + struct Config {}; + + static SdpAudioFormat AudioFormat() { return {"fake", 16'000, 2, {}}; } + static AudioCodecInfo CodecInfo() { return {16'000, 2, 23456}; } + + static absl::optional SdpToConfig( + const SdpAudioFormat& audio_format) { + return Config(); + } + + static void AppendSupportedEncoders(std::vector* specs) { + specs->push_back({AudioFormat(), CodecInfo()}); + } + + static AudioCodecInfo QueryAudioEncoder(const Config&) { return CodecInfo(); } +}; + +struct AudioEncoderApiWithV1Make : BaseAudioEncoderApi { + static std::unique_ptr MakeAudioEncoder( + const Config&, + int payload_type, + absl::optional codec_pair_id) { + auto encoder = std::make_unique>(); + ON_CALL(*encoder, SampleRateHz).WillByDefault(Return(kV1SameRate)); + return encoder; + } +}; + +struct AudioEncoderApiWithV2Make : BaseAudioEncoderApi { + static std::unique_ptr MakeAudioEncoder( + const Environment& env, + const Config& config, + const AudioEncoderFactory::Options& options) { + auto encoder = std::make_unique>(); + ON_CALL(*encoder, SampleRateHz).WillByDefault(Return(kV2SameRate)); + return encoder; + } +}; + +struct AudioEncoderApiWithBothV1AndV2Make : BaseAudioEncoderApi { + static std::unique_ptr MakeAudioEncoder( + const Config&, + int payload_type, + absl::optional codec_pair_id) { + auto encoder = std::make_unique>(); + ON_CALL(*encoder, SampleRateHz).WillByDefault(Return(kV1SameRate)); + return encoder; + } + + static std::unique_ptr MakeAudioEncoder( + const Environment& env, + const Config& config, + const AudioEncoderFactory::Options& options) { + auto encoder = std::make_unique>(); + ON_CALL(*encoder, SampleRateHz).WillByDefault(Return(kV2SameRate)); + return encoder; + } +}; + +TEST(AudioEncoderFactoryTemplateTest, + UsesV1MakeAudioEncoderWhenV2IsNotAvailable) { + const Environment env = CreateEnvironment(); + auto factory = CreateAudioEncoderFactory(); + + EXPECT_THAT( + factory->MakeAudioEncoder(17, BaseAudioEncoderApi::AudioFormat(), {}), + Pointer(Property(&AudioEncoder::SampleRateHz, + BaseAudioEncoderApi::kV1SameRate))); + + EXPECT_THAT(factory->Create(env, BaseAudioEncoderApi::AudioFormat(), {}), + Pointer(Property(&AudioEncoder::SampleRateHz, + BaseAudioEncoderApi::kV1SameRate))); +} + +TEST(AudioEncoderFactoryTemplateTest, + PreferV2MakeAudioEncoderWhenBothAreAvailable) { + const Environment env = CreateEnvironment(); + auto factory = + CreateAudioEncoderFactory(); + + EXPECT_THAT(factory->Create(env, BaseAudioEncoderApi::AudioFormat(), {}), + Pointer(Property(&AudioEncoder::SampleRateHz, + BaseAudioEncoderApi::kV2SameRate))); + + // For backward compatibility legacy AudioEncoderFactory::MakeAudioEncoder + // still can be used, and uses older signature of the Trait::MakeAudioEncoder. + EXPECT_THAT( + factory->MakeAudioEncoder(17, BaseAudioEncoderApi::AudioFormat(), {}), + Pointer(Property(&AudioEncoder::SampleRateHz, + BaseAudioEncoderApi::kV1SameRate))); +} + +TEST(AudioEncoderFactoryTemplateTest, CanUseTraitWithOnlyV2MakeAudioEncoder) { + const Environment env = CreateEnvironment(); + auto factory = CreateAudioEncoderFactory(); + EXPECT_THAT(factory->Create(env, BaseAudioEncoderApi::AudioFormat(), {}), + Pointer(Property(&AudioEncoder::SampleRateHz, + BaseAudioEncoderApi::kV2SameRate))); +} + +#if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +TEST(AudioEncoderFactoryTemplateTest, CrashesWhenV2OnlyTraitUsedWithOlderApi) { + auto factory = CreateAudioEncoderFactory(); + // V2 signature requires Environment that + // AudioEncoderFactory::MakeAudioEncoder doesn't provide. + EXPECT_DEATH( + factory->MakeAudioEncoder(17, BaseAudioEncoderApi::AudioFormat(), {}), + ""); +} +#endif TEST(AudioEncoderFactoryTemplateTest, NoEncoderTypes) { test::ScopedKeyValueConfig field_trials; + const Environment env = CreateEnvironment(&field_trials); rtc::scoped_refptr factory( rtc::make_ref_counted< - audio_encoder_factory_template_impl::AudioEncoderFactoryT<>>( - &field_trials)); + audio_encoder_factory_template_impl::AudioEncoderFactoryT<>>()); EXPECT_THAT(factory->GetSupportedEncoders(), ::testing::IsEmpty()); EXPECT_EQ(absl::nullopt, factory->QueryAudioEncoder({"foo", 8000, 1})); - EXPECT_EQ(nullptr, - factory->MakeAudioEncoder(17, {"bar", 16000, 1}, absl::nullopt)); + + EXPECT_THAT(factory->Create(env, {"bar", 16000, 1}, {}), IsNull()); } TEST(AudioEncoderFactoryTemplateTest, OneEncoderType) { + const Environment env = CreateEnvironment(); auto factory = CreateAudioEncoderFactory>(); EXPECT_THAT(factory->GetSupportedEncoders(), ::testing::ElementsAre( @@ -95,14 +229,14 @@ TEST(AudioEncoderFactoryTemplateTest, OneEncoderType) { EXPECT_EQ(absl::nullopt, factory->QueryAudioEncoder({"foo", 8000, 1})); EXPECT_EQ(AudioCodecInfo(8000, 1, 12345), factory->QueryAudioEncoder({"bogus", 8000, 1})); - EXPECT_EQ(nullptr, - factory->MakeAudioEncoder(17, {"bar", 16000, 1}, absl::nullopt)); - auto enc = factory->MakeAudioEncoder(17, {"bogus", 8000, 1}, absl::nullopt); - ASSERT_NE(nullptr, enc); - EXPECT_EQ(8000, enc->SampleRateHz()); + + EXPECT_THAT(factory->Create(env, {"bar", 16000, 1}, {}), IsNull()); + EXPECT_THAT(factory->Create(env, {"bogus", 8000, 1}, {}), + Pointer(Property(&AudioEncoder::SampleRateHz, 8000))); } TEST(AudioEncoderFactoryTemplateTest, TwoEncoderTypes) { + const Environment env = CreateEnvironment(); auto factory = CreateAudioEncoderFactory, AudioEncoderFakeApi>(); EXPECT_THAT(factory->GetSupportedEncoders(), @@ -116,20 +250,18 @@ TEST(AudioEncoderFactoryTemplateTest, TwoEncoderTypes) { EXPECT_EQ( AudioCodecInfo(16000, 2, 23456), factory->QueryAudioEncoder({"sham", 16000, 2, {{"param", "value"}}})); - EXPECT_EQ(nullptr, - factory->MakeAudioEncoder(17, {"bar", 16000, 1}, absl::nullopt)); - auto enc1 = factory->MakeAudioEncoder(17, {"bogus", 8000, 1}, absl::nullopt); - ASSERT_NE(nullptr, enc1); - EXPECT_EQ(8000, enc1->SampleRateHz()); - EXPECT_EQ(nullptr, - factory->MakeAudioEncoder(17, {"sham", 16000, 2}, absl::nullopt)); - auto enc2 = factory->MakeAudioEncoder( - 17, {"sham", 16000, 2, {{"param", "value"}}}, absl::nullopt); - ASSERT_NE(nullptr, enc2); - EXPECT_EQ(16000, enc2->SampleRateHz()); + + EXPECT_THAT(factory->Create(env, {"bar", 16000, 1}, {}), IsNull()); + EXPECT_THAT(factory->Create(env, {"bogus", 8000, 1}, {}), + Pointer(Property(&AudioEncoder::SampleRateHz, 8000))); + EXPECT_THAT(factory->Create(env, {"sham", 16000, 2}, {}), IsNull()); + EXPECT_THAT( + factory->Create(env, {"sham", 16000, 2, {{"param", "value"}}}, {}), + Pointer(Property(&AudioEncoder::SampleRateHz, 16000))); } TEST(AudioEncoderFactoryTemplateTest, G711) { + const Environment env = CreateEnvironment(); auto factory = CreateAudioEncoderFactory(); EXPECT_THAT(factory->GetSupportedEncoders(), ::testing::ElementsAre( @@ -138,17 +270,16 @@ TEST(AudioEncoderFactoryTemplateTest, G711) { EXPECT_EQ(absl::nullopt, factory->QueryAudioEncoder({"PCMA", 16000, 1})); EXPECT_EQ(AudioCodecInfo(8000, 1, 64000), factory->QueryAudioEncoder({"PCMA", 8000, 1})); - EXPECT_EQ(nullptr, - factory->MakeAudioEncoder(17, {"PCMU", 16000, 1}, absl::nullopt)); - auto enc1 = factory->MakeAudioEncoder(17, {"PCMU", 8000, 1}, absl::nullopt); - ASSERT_NE(nullptr, enc1); - EXPECT_EQ(8000, enc1->SampleRateHz()); - auto enc2 = factory->MakeAudioEncoder(17, {"PCMA", 8000, 1}, absl::nullopt); - ASSERT_NE(nullptr, enc2); - EXPECT_EQ(8000, enc2->SampleRateHz()); + + EXPECT_THAT(factory->Create(env, {"PCMU", 16000, 1}, {}), IsNull()); + EXPECT_THAT(factory->Create(env, {"PCMU", 8000, 1}, {}), + Pointer(Property(&AudioEncoder::SampleRateHz, 8000))); + EXPECT_THAT(factory->Create(env, {"PCMA", 8000, 1}, {}), + Pointer(Property(&AudioEncoder::SampleRateHz, 8000))); } TEST(AudioEncoderFactoryTemplateTest, G722) { + const Environment env = CreateEnvironment(); auto factory = CreateAudioEncoderFactory(); EXPECT_THAT(factory->GetSupportedEncoders(), ::testing::ElementsAre( @@ -156,14 +287,14 @@ TEST(AudioEncoderFactoryTemplateTest, G722) { EXPECT_EQ(absl::nullopt, factory->QueryAudioEncoder({"foo", 8000, 1})); EXPECT_EQ(AudioCodecInfo(16000, 1, 64000), factory->QueryAudioEncoder({"G722", 8000, 1})); - EXPECT_EQ(nullptr, - factory->MakeAudioEncoder(17, {"bar", 16000, 1}, absl::nullopt)); - auto enc = factory->MakeAudioEncoder(17, {"G722", 8000, 1}, absl::nullopt); - ASSERT_NE(nullptr, enc); - EXPECT_EQ(16000, enc->SampleRateHz()); + + EXPECT_THAT(factory->Create(env, {"bar", 16000, 1}, {}), IsNull()); + EXPECT_THAT(factory->Create(env, {"G722", 8000, 1}, {}), + Pointer(Property(&AudioEncoder::SampleRateHz, 16000))); } TEST(AudioEncoderFactoryTemplateTest, Ilbc) { + const Environment env = CreateEnvironment(); auto factory = CreateAudioEncoderFactory(); EXPECT_THAT(factory->GetSupportedEncoders(), ::testing::ElementsAre( @@ -171,14 +302,14 @@ TEST(AudioEncoderFactoryTemplateTest, Ilbc) { EXPECT_EQ(absl::nullopt, factory->QueryAudioEncoder({"foo", 8000, 1})); EXPECT_EQ(AudioCodecInfo(8000, 1, 13333), factory->QueryAudioEncoder({"ilbc", 8000, 1})); - EXPECT_EQ(nullptr, - factory->MakeAudioEncoder(17, {"bar", 8000, 1}, absl::nullopt)); - auto enc = factory->MakeAudioEncoder(17, {"ilbc", 8000, 1}, absl::nullopt); - ASSERT_NE(nullptr, enc); - EXPECT_EQ(8000, enc->SampleRateHz()); + + EXPECT_THAT(factory->Create(env, {"bar", 8000, 1}, {}), IsNull()); + EXPECT_THAT(factory->Create(env, {"ilbc", 8000, 1}, {}), + Pointer(Property(&AudioEncoder::SampleRateHz, 8000))); } TEST(AudioEncoderFactoryTemplateTest, L16) { + const Environment env = CreateEnvironment(); auto factory = CreateAudioEncoderFactory(); EXPECT_THAT( factory->GetSupportedEncoders(), @@ -192,14 +323,14 @@ TEST(AudioEncoderFactoryTemplateTest, L16) { EXPECT_EQ(absl::nullopt, factory->QueryAudioEncoder({"L16", 8000, 0})); EXPECT_EQ(AudioCodecInfo(48000, 1, 48000 * 16), factory->QueryAudioEncoder({"L16", 48000, 1})); - EXPECT_EQ(nullptr, - factory->MakeAudioEncoder(17, {"L16", 8000, 0}, absl::nullopt)); - auto enc = factory->MakeAudioEncoder(17, {"L16", 48000, 2}, absl::nullopt); - ASSERT_NE(nullptr, enc); - EXPECT_EQ(48000, enc->SampleRateHz()); + + EXPECT_THAT(factory->Create(env, {"L16", 8000, 0}, {}), IsNull()); + EXPECT_THAT(factory->Create(env, {"L16", 48000, 2}, {}), + Pointer(Property(&AudioEncoder::SampleRateHz, 48000))); } TEST(AudioEncoderFactoryTemplateTest, Opus) { + const Environment env = CreateEnvironment(); auto factory = CreateAudioEncoderFactory(); AudioCodecInfo info = {48000, 1, 32000, 6000, 510000}; info.allow_comfort_noise = false; @@ -214,11 +345,12 @@ TEST(AudioEncoderFactoryTemplateTest, Opus) { info, factory->QueryAudioEncoder( {"opus", 48000, 2, {{"minptime", "10"}, {"useinbandfec", "1"}}})); - EXPECT_EQ(nullptr, - factory->MakeAudioEncoder(17, {"bar", 16000, 1}, absl::nullopt)); - auto enc = factory->MakeAudioEncoder(17, {"opus", 48000, 2}, absl::nullopt); - ASSERT_NE(nullptr, enc); - EXPECT_EQ(48000, enc->SampleRateHz()); + + EXPECT_THAT(factory->Create(env, {"bar", 16000, 1}, {.payload_type = 17}), + IsNull()); + EXPECT_THAT(factory->Create(env, {"opus", 48000, 2}, {.payload_type = 17}), + Pointer(Property(&AudioEncoder::SampleRateHz, 48000))); } +} // namespace } // namespace webrtc diff --git a/third_party/libwebrtc/api/audio_options.h b/third_party/libwebrtc/api/audio_options.h index 3ab3b3c98ce2..2162471c6ad2 100644 --- a/third_party/libwebrtc/api/audio_options.h +++ b/third_party/libwebrtc/api/audio_options.h @@ -11,8 +11,6 @@ #ifndef API_AUDIO_OPTIONS_H_ #define API_AUDIO_OPTIONS_H_ -#include - #include #include "absl/types/optional.h" diff --git a/third_party/libwebrtc/api/call/transport.cc b/third_party/libwebrtc/api/call/transport.cc index bcadc762de4d..0a9dd5bcc7e1 100644 --- a/third_party/libwebrtc/api/call/transport.cc +++ b/third_party/libwebrtc/api/call/transport.cc @@ -10,8 +10,6 @@ #include "api/call/transport.h" -#include - namespace webrtc { PacketOptions::PacketOptions() = default; diff --git a/third_party/libwebrtc/api/call/transport.h b/third_party/libwebrtc/api/call/transport.h index c27377f17f90..7240a8b8b1f1 100644 --- a/third_party/libwebrtc/api/call/transport.h +++ b/third_party/libwebrtc/api/call/transport.h @@ -11,7 +11,6 @@ #ifndef API_CALL_TRANSPORT_H_ #define API_CALL_TRANSPORT_H_ -#include #include #include "api/array_view.h" diff --git a/third_party/libwebrtc/api/candidate.cc b/third_party/libwebrtc/api/candidate.cc index 4e462dda91a4..77113299ab98 100644 --- a/third_party/libwebrtc/api/candidate.cc +++ b/third_party/libwebrtc/api/candidate.cc @@ -10,12 +10,19 @@ #include "api/candidate.h" -#include "absl/base/attributes.h" +#include // IWYU pragma: keep +#include +#include + +#include "absl/strings/string_view.h" #include "p2p/base/p2p_constants.h" +#include "rtc_base/checks.h" #include "rtc_base/crc32.h" #include "rtc_base/crypto_random.h" #include "rtc_base/ip_address.h" -#include "rtc_base/logging.h" +#include "rtc_base/network_constants.h" +#include "rtc_base/socket_address.h" +#include "rtc_base/string_encode.h" #include "rtc_base/strings/string_builder.h" using webrtc::IceCandidateType; diff --git a/third_party/libwebrtc/api/candidate.h b/third_party/libwebrtc/api/candidate.h index 5f6f7cdcddce..180b1c915b26 100644 --- a/third_party/libwebrtc/api/candidate.h +++ b/third_party/libwebrtc/api/candidate.h @@ -11,13 +11,11 @@ #ifndef API_CANDIDATE_H_ #define API_CANDIDATE_H_ -#include +#include #include -#include #include -#include "absl/base/attributes.h" #include "absl/strings/string_view.h" #include "rtc_base/checks.h" #include "rtc_base/network_constants.h" diff --git a/third_party/libwebrtc/api/candidate_unittest.cc b/third_party/libwebrtc/api/candidate_unittest.cc index 4743610729ce..30916299a688 100644 --- a/third_party/libwebrtc/api/candidate_unittest.cc +++ b/third_party/libwebrtc/api/candidate_unittest.cc @@ -13,7 +13,8 @@ #include #include "p2p/base/p2p_constants.h" -#include "rtc_base/gunit.h" +#include "rtc_base/socket_address.h" +#include "test/gtest.h" using webrtc::IceCandidateType; diff --git a/third_party/libwebrtc/api/create_peerconnection_factory.h b/third_party/libwebrtc/api/create_peerconnection_factory.h index 18febb69f1b6..6c37124d49b2 100644 --- a/third_party/libwebrtc/api/create_peerconnection_factory.h +++ b/third_party/libwebrtc/api/create_peerconnection_factory.h @@ -10,6 +10,7 @@ #ifndef API_CREATE_PEERCONNECTION_FACTORY_H_ #define API_CREATE_PEERCONNECTION_FACTORY_H_ +// IWYU pragma: no_include "rtc_base/thread.h" #include @@ -29,11 +30,9 @@ namespace rtc { // TODO(bugs.webrtc.org/9987): Move rtc::Thread to api/ or expose a better // type. At the moment, rtc::Thread is not part of api/ so it cannot be // included in order to avoid to leak internal types. -class Thread; +class Thread; // IWYU pragma: keep } // namespace rtc - namespace webrtc { - class AudioFrameProcessor; // Create a new instance of PeerConnectionFactoryInterface with optional video diff --git a/third_party/libwebrtc/api/crypto/BUILD.gn b/third_party/libwebrtc/api/crypto/BUILD.gn index 5064aff82fe5..d1f9b13db8fd 100644 --- a/third_party/libwebrtc/api/crypto/BUILD.gn +++ b/third_party/libwebrtc/api/crypto/BUILD.gn @@ -23,6 +23,7 @@ rtc_library("options") { "crypto_options.h", ] deps = [ + "../../rtc_base:checks", "../../rtc_base:ssl_adapter", "../../rtc_base/system:rtc_export", ] diff --git a/third_party/libwebrtc/api/crypto/crypto_options.cc b/third_party/libwebrtc/api/crypto/crypto_options.cc index 22c5dd464b17..37df0d561e30 100644 --- a/third_party/libwebrtc/api/crypto/crypto_options.cc +++ b/third_party/libwebrtc/api/crypto/crypto_options.cc @@ -10,6 +10,9 @@ #include "api/crypto/crypto_options.h" +#include + +#include "rtc_base/checks.h" #include "rtc_base/ssl_stream_adapter.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/crypto/options_gn/moz.build b/third_party/libwebrtc/api/crypto/options_gn/moz.build index fae93b139253..a9e9b0cd9473 100644 --- a/third_party/libwebrtc/api/crypto/options_gn/moz.build +++ b/third_party/libwebrtc/api/crypto/options_gn/moz.build @@ -58,6 +58,10 @@ 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 diff --git a/third_party/libwebrtc/api/dtls_transport_interface.cc b/third_party/libwebrtc/api/dtls_transport_interface.cc index faebc0972fb3..8133525e4e37 100644 --- a/third_party/libwebrtc/api/dtls_transport_interface.cc +++ b/third_party/libwebrtc/api/dtls_transport_interface.cc @@ -10,6 +10,12 @@ #include "api/dtls_transport_interface.h" +#include +#include + +#include "absl/types/optional.h" +#include "rtc_base/ssl_certificate.h" + namespace webrtc { DtlsTransportInformation::DtlsTransportInformation() diff --git a/third_party/libwebrtc/api/dtls_transport_interface.h b/third_party/libwebrtc/api/dtls_transport_interface.h index fe64fb194745..66ae4d4ecc39 100644 --- a/third_party/libwebrtc/api/dtls_transport_interface.h +++ b/third_party/libwebrtc/api/dtls_transport_interface.h @@ -12,8 +12,8 @@ #define API_DTLS_TRANSPORT_INTERFACE_H_ #include -#include +#include "absl/base/attributes.h" #include "absl/types/optional.h" #include "api/ice_transport_interface.h" #include "api/ref_count.h" diff --git a/third_party/libwebrtc/api/enable_media.cc b/third_party/libwebrtc/api/enable_media.cc index 91938cc32000..e9c115b2fd57 100644 --- a/third_party/libwebrtc/api/enable_media.cc +++ b/third_party/libwebrtc/api/enable_media.cc @@ -15,8 +15,11 @@ #include "api/environment/environment.h" #include "api/peer_connection_interface.h" +#include "api/scoped_refptr.h" +#include "call/call.h" +#include "call/call_config.h" #include "call/create_call.h" -#include "media/engine/webrtc_media_engine.h" +#include "media/base/media_engine.h" #include "media/engine/webrtc_video_engine.h" #include "media/engine/webrtc_voice_engine.h" #include "pc/media_factory.h" diff --git a/third_party/libwebrtc/api/enable_media_with_defaults.cc b/third_party/libwebrtc/api/enable_media_with_defaults.cc index ace224707f90..7dccfa22f411 100644 --- a/third_party/libwebrtc/api/enable_media_with_defaults.cc +++ b/third_party/libwebrtc/api/enable_media_with_defaults.cc @@ -10,10 +10,14 @@ #include "api/enable_media_with_defaults.h" +#include + #include "api/audio/audio_processing.h" #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "api/enable_media.h" +#include "api/peer_connection_interface.h" +#include "api/scoped_refptr.h" #include "api/task_queue/default_task_queue_factory.h" #include "api/video_codecs/builtin_video_decoder_factory.h" #include "api/video_codecs/builtin_video_encoder_factory.h" diff --git a/third_party/libwebrtc/api/environment/BUILD.gn b/third_party/libwebrtc/api/environment/BUILD.gn index 70ffdecbdfe6..10d008f417ad 100644 --- a/third_party/libwebrtc/api/environment/BUILD.gn +++ b/third_party/libwebrtc/api/environment/BUILD.gn @@ -28,6 +28,7 @@ rtc_library("environment_factory") { ] deps = [ ":environment", + "..:field_trials_view", "..:make_ref_counted", "..:refcountedbase", "..:scoped_refptr", @@ -36,6 +37,7 @@ rtc_library("environment_factory") { "../../system_wrappers", "../rtc_event_log", "../task_queue:default_task_queue_factory", + "../task_queue:task_queue", "../transport:field_trial_based_config", "//third_party/abseil-cpp/absl/base:nullability", ] @@ -55,6 +57,7 @@ if (rtc_include_tests) { "../task_queue", "../units:timestamp", "//third_party/abseil-cpp/absl/functional:any_invocable", + "//third_party/abseil-cpp/absl/strings:string_view", "//third_party/abseil-cpp/absl/types:optional", ] } diff --git a/third_party/libwebrtc/api/environment/environment_factory.cc b/third_party/libwebrtc/api/environment/environment_factory.cc index 6f0ec40dbee9..b9cb6b4f5341 100644 --- a/third_party/libwebrtc/api/environment/environment_factory.cc +++ b/third_party/libwebrtc/api/environment/environment_factory.cc @@ -12,11 +12,16 @@ #include #include -#include +#include "absl/base/nullability.h" +#include "api/environment/environment.h" +#include "api/field_trials_view.h" #include "api/make_ref_counted.h" +#include "api/ref_counted_base.h" #include "api/rtc_event_log/rtc_event_log.h" +#include "api/scoped_refptr.h" #include "api/task_queue/default_task_queue_factory.h" +#include "api/task_queue/task_queue_factory.h" #include "api/transport/field_trial_based_config.h" #include "rtc_base/checks.h" #include "system_wrappers/include/clock.h" diff --git a/third_party/libwebrtc/api/environment/environment_factory.h b/third_party/libwebrtc/api/environment/environment_factory.h index a0fc3effdbee..0d37ea9aaf2a 100644 --- a/third_party/libwebrtc/api/environment/environment_factory.h +++ b/third_party/libwebrtc/api/environment/environment_factory.h @@ -24,11 +24,12 @@ namespace webrtc { // These classes are forward declared to reduce amount of headers exposed // through api header. +// IWYU pragma: begin_keep class Clock; class TaskQueueFactory; class FieldTrialsView; class RtcEventLog; - +// IWYU pragma: end_keep // Constructs `Environment`. // Individual utilities are provided using one of the `Set` functions. // `Set` functions do nothing when nullptr value is passed. diff --git a/third_party/libwebrtc/api/environment/environment_unittest.cc b/third_party/libwebrtc/api/environment/environment_unittest.cc index 07bd8793bc53..17bc66839138 100644 --- a/third_party/libwebrtc/api/environment/environment_unittest.cc +++ b/third_party/libwebrtc/api/environment/environment_unittest.cc @@ -16,10 +16,13 @@ #include #include "absl/functional/any_invocable.h" +#include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "api/environment/environment_factory.h" #include "api/field_trials_view.h" +#include "api/rtc_event_log/rtc_event.h" #include "api/rtc_event_log/rtc_event_log.h" +#include "api/task_queue/task_queue_base.h" #include "api/task_queue/task_queue_factory.h" #include "api/units/timestamp.h" #include "system_wrappers/include/clock.h" diff --git a/third_party/libwebrtc/api/field_trials.cc b/third_party/libwebrtc/api/field_trials.cc index 4bd11271dcdc..2643e1285c6c 100644 --- a/third_party/libwebrtc/api/field_trials.cc +++ b/third_party/libwebrtc/api/field_trials.cc @@ -11,8 +11,13 @@ #include "api/field_trials.h" #include +#include +#include +#include +#include "absl/strings/string_view.h" #include "rtc_base/checks.h" +#include "rtc_base/containers/flat_map.h" #include "system_wrappers/include/field_trial.h" namespace { diff --git a/third_party/libwebrtc/api/field_trials_registry.cc b/third_party/libwebrtc/api/field_trials_registry.cc index 61d31512ce21..11259cc6af0c 100644 --- a/third_party/libwebrtc/api/field_trials_registry.cc +++ b/third_party/libwebrtc/api/field_trials_registry.cc @@ -11,12 +11,13 @@ #include -#include "absl/algorithm/container.h" #include "absl/strings/string_view.h" +// IWYU pragma: begin_keep +#include "absl/algorithm/container.h" #include "experiments/registered_field_trials.h" #include "rtc_base/checks.h" -#include "rtc_base/containers/flat_set.h" #include "rtc_base/logging.h" +// IWYU pragma: end_keep namespace webrtc { diff --git a/third_party/libwebrtc/api/field_trials_unittest.cc b/third_party/libwebrtc/api/field_trials_unittest.cc index 804b52a81861..8144f11c3807 100644 --- a/third_party/libwebrtc/api/field_trials_unittest.cc +++ b/third_party/libwebrtc/api/field_trials_unittest.cc @@ -11,9 +11,7 @@ #include "api/field_trials.h" #include -#include -#include "absl/strings/string_view.h" #include "api/transport/field_trial_based_config.h" #include "rtc_base/containers/flat_set.h" #include "system_wrappers/include/field_trial.h" diff --git a/third_party/libwebrtc/api/frame_transformer_factory.cc b/third_party/libwebrtc/api/frame_transformer_factory.cc index 841ab0f941bb..88073011856d 100644 --- a/third_party/libwebrtc/api/frame_transformer_factory.cc +++ b/third_party/libwebrtc/api/frame_transformer_factory.cc @@ -10,8 +10,12 @@ #include "api/frame_transformer_factory.h" +#include + +#include "api/frame_transformer_interface.h" #include "audio/channel_send_frame_transformer_delegate.h" #include "modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h" +#include "rtc_base/checks.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/frame_transformer_factory.h b/third_party/libwebrtc/api/frame_transformer_factory.h index a73ff6295610..fda61173149a 100644 --- a/third_party/libwebrtc/api/frame_transformer_factory.h +++ b/third_party/libwebrtc/api/frame_transformer_factory.h @@ -12,12 +12,9 @@ #define API_FRAME_TRANSFORMER_FACTORY_H_ #include -#include #include "api/frame_transformer_interface.h" -#include "api/scoped_refptr.h" -#include "api/video/encoded_frame.h" -#include "api/video/video_frame_metadata.h" +#include "rtc_base/system/rtc_export.h" // This file contains EXPERIMENTAL functions to create video frames from // either an old video frame or directly from parameters. diff --git a/third_party/libwebrtc/api/function_view.h b/third_party/libwebrtc/api/function_view.h index 5ae1bd6cfe19..b191a058f6e8 100644 --- a/third_party/libwebrtc/api/function_view.h +++ b/third_party/libwebrtc/api/function_view.h @@ -11,6 +11,7 @@ #ifndef API_FUNCTION_VIEW_H_ #define API_FUNCTION_VIEW_H_ +#include #include #include diff --git a/third_party/libwebrtc/api/ice_transport_factory.cc b/third_party/libwebrtc/api/ice_transport_factory.cc index e88ac183fac9..13c4017609df 100644 --- a/third_party/libwebrtc/api/ice_transport_factory.cc +++ b/third_party/libwebrtc/api/ice_transport_factory.cc @@ -13,12 +13,15 @@ #include #include +#include "api/ice_transport_interface.h" #include "api/make_ref_counted.h" +#include "api/scoped_refptr.h" +#include "api/sequence_checker.h" #include "p2p/base/ice_transport_internal.h" #include "p2p/base/p2p_constants.h" #include "p2p/base/p2p_transport_channel.h" #include "p2p/base/port_allocator.h" -#include "rtc_base/thread.h" +#include "rtc_base/thread_annotations.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/ice_transport_factory.h b/third_party/libwebrtc/api/ice_transport_factory.h index 2268ea5e1265..a5cee1276b1b 100644 --- a/third_party/libwebrtc/api/ice_transport_factory.h +++ b/third_party/libwebrtc/api/ice_transport_factory.h @@ -16,7 +16,7 @@ #include "rtc_base/system/rtc_export.h" namespace cricket { -class PortAllocator; +class PortAllocator; // IWYU pragma: keep } // namespace cricket namespace webrtc { diff --git a/third_party/libwebrtc/api/legacy_stats_types.h b/third_party/libwebrtc/api/legacy_stats_types.h index 70f21d4ad9fd..2b54614b3acf 100644 --- a/third_party/libwebrtc/api/legacy_stats_types.h +++ b/third_party/libwebrtc/api/legacy_stats_types.h @@ -14,7 +14,9 @@ #ifndef API_LEGACY_STATS_TYPES_H_ #define API_LEGACY_STATS_TYPES_H_ -#include +#include +#include + #include #include #include @@ -23,7 +25,9 @@ #include "api/ref_count.h" #include "api/scoped_refptr.h" #include "api/sequence_checker.h" +#include "rtc_base/checks.h" #include "rtc_base/system/rtc_export.h" +#include "rtc_base/thread_annotations.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/media_stream_interface.h b/third_party/libwebrtc/api/media_stream_interface.h index d050fe72f6a3..1597b8d3fd18 100644 --- a/third_party/libwebrtc/api/media_stream_interface.h +++ b/third_party/libwebrtc/api/media_stream_interface.h @@ -17,6 +17,7 @@ #define API_MEDIA_STREAM_INTERFACE_H_ #include +#include #include #include @@ -31,6 +32,7 @@ #include "api/video/video_sink_interface.h" #include "api/video/video_source_interface.h" #include "api/video_track_source_constraints.h" +#include "rtc_base/checks.h" #include "rtc_base/system/rtc_export.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/metronome/test/fake_metronome.cc b/third_party/libwebrtc/api/metronome/test/fake_metronome.cc index bd54d5b0bacd..563bd7ef98fd 100644 --- a/third_party/libwebrtc/api/metronome/test/fake_metronome.cc +++ b/third_party/libwebrtc/api/metronome/test/fake_metronome.cc @@ -10,6 +10,7 @@ #include "api/metronome/test/fake_metronome.h" +#include #include #include diff --git a/third_party/libwebrtc/api/neteq/BUILD.gn b/third_party/libwebrtc/api/neteq/BUILD.gn index c29660d3a071..b995b22fefc2 100644 --- a/third_party/libwebrtc/api/neteq/BUILD.gn +++ b/third_party/libwebrtc/api/neteq/BUILD.gn @@ -17,6 +17,7 @@ rtc_source_set("neteq_api") { ] deps = [ + "..:array_view", "..:rtp_headers", "..:rtp_packet_info", "..:scoped_refptr", diff --git a/third_party/libwebrtc/api/neteq/custom_neteq_factory.h b/third_party/libwebrtc/api/neteq/custom_neteq_factory.h index d080f68e8ef2..1e9a1fca9da4 100644 --- a/third_party/libwebrtc/api/neteq/custom_neteq_factory.h +++ b/third_party/libwebrtc/api/neteq/custom_neteq_factory.h @@ -14,6 +14,7 @@ #include #include "api/audio_codecs/audio_decoder_factory.h" +#include "api/neteq/neteq.h" #include "api/neteq/neteq_controller_factory.h" #include "api/neteq/neteq_factory.h" #include "api/scoped_refptr.h" diff --git a/third_party/libwebrtc/api/neteq/default_neteq_controller_factory.h b/third_party/libwebrtc/api/neteq/default_neteq_controller_factory.h index 611afc2586a5..4df307fd0fd1 100644 --- a/third_party/libwebrtc/api/neteq/default_neteq_controller_factory.h +++ b/third_party/libwebrtc/api/neteq/default_neteq_controller_factory.h @@ -13,6 +13,7 @@ #include +#include "api/neteq/neteq_controller.h" #include "api/neteq/neteq_controller_factory.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/neteq/neteq.h b/third_party/libwebrtc/api/neteq/neteq.h index d16f7ba9dd80..77ccb3157656 100644 --- a/third_party/libwebrtc/api/neteq/neteq.h +++ b/third_party/libwebrtc/api/neteq/neteq.h @@ -12,25 +12,23 @@ #define API_NETEQ_NETEQ_H_ #include // Provide access to size_t. +#include #include #include #include #include "absl/types/optional.h" +#include "api/array_view.h" #include "api/audio_codecs/audio_codec_pair_id.h" -#include "api/audio_codecs/audio_decoder.h" #include "api/audio_codecs/audio_format.h" #include "api/rtp_headers.h" -#include "api/scoped_refptr.h" #include "api/units/timestamp.h" namespace webrtc { // Forward declarations. class AudioFrame; -class AudioDecoderFactory; -class Clock; struct NetEqNetworkStatistics { uint16_t current_buffer_size_ms; // Current jitter buffer size in ms. @@ -94,6 +92,7 @@ struct NetEqLifetimeStatistics { int32_t total_interruption_duration_ms = 0; // Total number of comfort noise samples generated during DTX. uint64_t generated_noise_samples = 0; + uint64_t total_processing_delay_us = 0; }; // Metrics that describe the operations performed in NetEq, and the internal diff --git a/third_party/libwebrtc/api/neteq/tick_timer_unittest.cc b/third_party/libwebrtc/api/neteq/tick_timer_unittest.cc index 863c0117f46b..25d3a8abf16a 100644 --- a/third_party/libwebrtc/api/neteq/tick_timer_unittest.cc +++ b/third_party/libwebrtc/api/neteq/tick_timer_unittest.cc @@ -10,9 +10,9 @@ #include "api/neteq/tick_timer.h" +#include #include -#include "test/gmock.h" #include "test/gtest.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/numerics/samples_stats_counter.h b/third_party/libwebrtc/api/numerics/samples_stats_counter.h index c4eabffa2fa4..57a53a27ecbf 100644 --- a/third_party/libwebrtc/api/numerics/samples_stats_counter.h +++ b/third_party/libwebrtc/api/numerics/samples_stats_counter.h @@ -11,6 +11,9 @@ #ifndef API_NUMERICS_SAMPLES_STATS_COUNTER_H_ #define API_NUMERICS_SAMPLES_STATS_COUNTER_H_ +#include +#include + #include #include #include diff --git a/third_party/libwebrtc/api/peer_connection_interface.h b/third_party/libwebrtc/api/peer_connection_interface.h index ca6baacd0ddc..a422eae17e5b 100644 --- a/third_party/libwebrtc/api/peer_connection_interface.h +++ b/third_party/libwebrtc/api/peer_connection_interface.h @@ -66,6 +66,7 @@ #ifndef API_PEER_CONNECTION_INTERFACE_H_ #define API_PEER_CONNECTION_INTERFACE_H_ +// IWYU pragma: no_include "pc/media_factory.h" #include #include @@ -80,7 +81,9 @@ #include "absl/types/optional.h" #include "api/adaptation/resource.h" #include "api/async_dns_resolver.h" +#include "api/audio/audio_device.h" #include "api/audio/audio_mixer.h" +#include "api/audio/audio_processing.h" #include "api/audio_codecs/audio_decoder_factory.h" #include "api/audio_codecs/audio_encoder_factory.h" #include "api/audio_options.h" @@ -123,32 +126,36 @@ #include "api/video_codecs/video_encoder_factory.h" #include "call/rtp_transport_controller_send_factory_interface.h" #include "media/base/media_config.h" -#include "media/base/media_engine.h" // TODO(bugs.webrtc.org/7447): We plan to provide a way to let applications // inject a PacketSocketFactory and/or NetworkManager, and not expose // PortAllocator in the PeerConnection api. +#include "api/audio/audio_frame_processor.h" #include "api/ref_count.h" +#include "api/units/time_delta.h" +#include "p2p/base/port.h" #include "p2p/base/port_allocator.h" +#include "rtc_base/checks.h" #include "rtc_base/network.h" #include "rtc_base/network_constants.h" #include "rtc_base/network_monitor_factory.h" #include "rtc_base/rtc_certificate.h" #include "rtc_base/rtc_certificate_generator.h" -#include "rtc_base/socket_address.h" +#include "rtc_base/socket_factory.h" #include "rtc_base/ssl_certificate.h" #include "rtc_base/ssl_stream_adapter.h" #include "rtc_base/system/rtc_export.h" #include "rtc_base/thread.h" namespace rtc { -class Thread; +class Thread; // IWYU pragma: keep } // namespace rtc namespace webrtc { - +// IWYU pragma: begin_keep // MediaFactory class definition is not part of the api. class MediaFactory; +// IWYU pragma: end_keep // MediaStream container interface. class StreamCollectionInterface : public webrtc::RefCountInterface { public: @@ -1221,6 +1228,10 @@ class RTC_EXPORT PeerConnectionInterface : public webrtc::RefCountInterface { // pointers. virtual rtc::Thread* signaling_thread() const = 0; + // NetworkController instance being used by this PeerConnection, to be used + // to identify instances when using a custom NetworkControllerFactory. + virtual NetworkControllerInterface* GetNetworkController() = 0; + protected: // Dtor protected as objects shouldn't be deleted via this interface. ~PeerConnectionInterface() override = default; diff --git a/third_party/libwebrtc/api/rtc_error.h b/third_party/libwebrtc/api/rtc_error.h index 7adf30eacfe8..8579ff637fbd 100644 --- a/third_party/libwebrtc/api/rtc_error.h +++ b/third_party/libwebrtc/api/rtc_error.h @@ -11,9 +11,8 @@ #ifndef API_RTC_ERROR_H_ #define API_RTC_ERROR_H_ -#ifdef WEBRTC_UNIT_TEST -#include -#endif // WEBRTC_UNIT_TEST +#include + #include #include // For std::move. @@ -162,20 +161,6 @@ class RTC_EXPORT RTCError { RTC_EXPORT absl::string_view ToString(RTCErrorType error); RTC_EXPORT absl::string_view ToString(RTCErrorDetailType error); -#ifdef WEBRTC_UNIT_TEST -inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) - std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) - RTCErrorType error) { - return stream << ToString(error); -} - -inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) - std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) - RTCErrorDetailType error) { - return stream << ToString(error); -} -#endif // WEBRTC_UNIT_TEST - // Helper macro that can be used by implementations to create an error with a // message and log it. `message` should be a string literal or movable // std::string. diff --git a/third_party/libwebrtc/api/rtc_event_log/rtc_event_log.cc b/third_party/libwebrtc/api/rtc_event_log/rtc_event_log.cc index 56189c0ff7ee..9437a234080f 100644 --- a/third_party/libwebrtc/api/rtc_event_log/rtc_event_log.cc +++ b/third_party/libwebrtc/api/rtc_event_log/rtc_event_log.cc @@ -10,6 +10,11 @@ #include "api/rtc_event_log/rtc_event_log.h" +#include +#include + +#include "api/rtc_event_log_output.h" + namespace webrtc { bool RtcEventLogNull::StartLogging( diff --git a/third_party/libwebrtc/api/rtc_event_log/rtc_event_log.h b/third_party/libwebrtc/api/rtc_event_log/rtc_event_log.h index 7b42cdc028bf..3bd255371de6 100644 --- a/third_party/libwebrtc/api/rtc_event_log/rtc_event_log.h +++ b/third_party/libwebrtc/api/rtc_event_log/rtc_event_log.h @@ -18,7 +18,6 @@ #include "api/rtc_event_log/rtc_event.h" #include "api/rtc_event_log_output.h" -#include "api/task_queue/task_queue_factory.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/rtc_event_log_output.h b/third_party/libwebrtc/api/rtc_event_log_output.h index f1f84a5f3a5b..9b55f299accc 100644 --- a/third_party/libwebrtc/api/rtc_event_log_output.h +++ b/third_party/libwebrtc/api/rtc_event_log_output.h @@ -11,7 +11,6 @@ #ifndef API_RTC_EVENT_LOG_OUTPUT_H_ #define API_RTC_EVENT_LOG_OUTPUT_H_ -#include #include "absl/strings/string_view.h" diff --git a/third_party/libwebrtc/api/rtc_event_log_output_file.cc b/third_party/libwebrtc/api/rtc_event_log_output_file.cc index e1d4c7c711e5..b31e39a3e777 100644 --- a/third_party/libwebrtc/api/rtc_event_log_output_file.cc +++ b/third_party/libwebrtc/api/rtc_event_log_output_file.cc @@ -10,12 +10,17 @@ #include "api/rtc_event_log_output_file.h" +#include +#include #include +#include #include +#include "absl/strings/string_view.h" #include "api/rtc_event_log/rtc_event_log.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" +#include "rtc_base/system/file_wrapper.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/rtc_event_log_output_file.h b/third_party/libwebrtc/api/rtc_event_log_output_file.h index c9ae0a8ede19..1f94b0d20ad9 100644 --- a/third_party/libwebrtc/api/rtc_event_log_output_file.h +++ b/third_party/libwebrtc/api/rtc_event_log_output_file.h @@ -16,6 +16,7 @@ #include +#include "absl/strings/string_view.h" #include "api/rtc_event_log_output.h" #include "rtc_base/system/file_wrapper.h" diff --git a/third_party/libwebrtc/api/rtc_event_log_output_file_unittest.cc b/third_party/libwebrtc/api/rtc_event_log_output_file_unittest.cc index d2f1e1c6b5da..b1441c7cf7a7 100644 --- a/third_party/libwebrtc/api/rtc_event_log_output_file_unittest.cc +++ b/third_party/libwebrtc/api/rtc_event_log_output_file_unittest.cc @@ -10,7 +10,11 @@ #include "api/rtc_event_log_output_file.h" +#include + +#include #include +#include #include #include #include diff --git a/third_party/libwebrtc/api/rtp_headers.h b/third_party/libwebrtc/api/rtp_headers.h index bf1660d934d7..5a867bbd7748 100644 --- a/third_party/libwebrtc/api/rtp_headers.h +++ b/third_party/libwebrtc/api/rtp_headers.h @@ -17,12 +17,13 @@ #include #include "absl/types/optional.h" -#include "api/array_view.h" #include "api/units/timestamp.h" #include "api/video/color_space.h" #include "api/video/video_content_type.h" #include "api/video/video_rotation.h" #include "api/video/video_timing.h" +#include "rtc_base/checks.h" +#include "rtc_base/system/rtc_export.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/rtp_packet_info.cc b/third_party/libwebrtc/api/rtp_packet_info.cc index cfe6a24a0ccb..90cd27515ca0 100644 --- a/third_party/libwebrtc/api/rtp_packet_info.cc +++ b/third_party/libwebrtc/api/rtp_packet_info.cc @@ -10,8 +10,15 @@ #include "api/rtp_packet_info.h" +#include + #include +#include #include +#include + +#include "api/rtp_headers.h" +#include "api/units/timestamp.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/rtp_packet_info_unittest.cc b/third_party/libwebrtc/api/rtp_packet_info_unittest.cc index d35edf75db87..49ed3379d2fd 100644 --- a/third_party/libwebrtc/api/rtp_packet_info_unittest.cc +++ b/third_party/libwebrtc/api/rtp_packet_info_unittest.cc @@ -8,9 +8,15 @@ * be found in the AUTHORS file in the root of the source tree. */ -#include "api/rtp_packet_infos.h" +#include "api/rtp_packet_info.h" + +#include +#include + +#include "absl/types/optional.h" +#include "api/rtp_headers.h" #include "api/units/time_delta.h" -#include "test/gmock.h" +#include "api/units/timestamp.h" #include "test/gtest.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/rtp_packet_infos.h b/third_party/libwebrtc/api/rtp_packet_infos.h index 7445729fbb2f..dfb4e1eeaa5a 100644 --- a/third_party/libwebrtc/api/rtp_packet_infos.h +++ b/third_party/libwebrtc/api/rtp_packet_infos.h @@ -11,7 +11,6 @@ #ifndef API_RTP_PACKET_INFOS_H_ #define API_RTP_PACKET_INFOS_H_ -#include #include #include diff --git a/third_party/libwebrtc/api/rtp_packet_infos_unittest.cc b/third_party/libwebrtc/api/rtp_packet_infos_unittest.cc index a90cfa03e25d..544bb93b380c 100644 --- a/third_party/libwebrtc/api/rtp_packet_infos_unittest.cc +++ b/third_party/libwebrtc/api/rtp_packet_infos_unittest.cc @@ -10,6 +10,11 @@ #include "api/rtp_packet_infos.h" +#include + +#include "api/rtp_headers.h" +#include "api/rtp_packet_info.h" +#include "api/units/timestamp.h" #include "test/gmock.h" #include "test/gtest.h" diff --git a/third_party/libwebrtc/api/rtp_parameters.cc b/third_party/libwebrtc/api/rtp_parameters.cc index ad0f3c9396b4..da06138ce817 100644 --- a/third_party/libwebrtc/api/rtp_parameters.cc +++ b/third_party/libwebrtc/api/rtp_parameters.cc @@ -10,12 +10,16 @@ #include "api/rtp_parameters.h" #include +#include #include #include -#include +#include +#include "absl/strings/string_view.h" #include "api/array_view.h" +#include "api/rtp_transceiver_direction.h" #include "media/base/media_constants.h" +#include "rtc_base/checks.h" #include "rtc_base/strings/string_builder.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/rtp_transceiver_interface.h b/third_party/libwebrtc/api/rtp_transceiver_interface.h index 940264ef5163..555566d6d620 100644 --- a/third_party/libwebrtc/api/rtp_transceiver_interface.h +++ b/third_party/libwebrtc/api/rtp_transceiver_interface.h @@ -19,6 +19,7 @@ #include "api/array_view.h" #include "api/media_types.h" #include "api/ref_count.h" +#include "api/rtc_error.h" #include "api/rtp_parameters.h" #include "api/rtp_receiver_interface.h" #include "api/rtp_sender_interface.h" diff --git a/third_party/libwebrtc/api/scoped_refptr.h b/third_party/libwebrtc/api/scoped_refptr.h index 61b2eb1f200d..35ad2fea9495 100644 --- a/third_party/libwebrtc/api/scoped_refptr.h +++ b/third_party/libwebrtc/api/scoped_refptr.h @@ -63,7 +63,7 @@ #ifndef API_SCOPED_REFPTR_H_ #define API_SCOPED_REFPTR_H_ -#include +#include #include namespace webrtc { diff --git a/third_party/libwebrtc/api/scoped_refptr_unittest.cc b/third_party/libwebrtc/api/scoped_refptr_unittest.cc index 22b61209cdcf..490b61d1d083 100644 --- a/third_party/libwebrtc/api/scoped_refptr_unittest.cc +++ b/third_party/libwebrtc/api/scoped_refptr_unittest.cc @@ -10,6 +10,7 @@ #include "api/scoped_refptr.h" +#include #include #include diff --git a/third_party/libwebrtc/api/sctp_transport_interface.h b/third_party/libwebrtc/api/sctp_transport_interface.h index cfd7e130d221..25c2ba03af3a 100644 --- a/third_party/libwebrtc/api/sctp_transport_interface.h +++ b/third_party/libwebrtc/api/sctp_transport_interface.h @@ -14,8 +14,8 @@ #include "absl/types/optional.h" #include "api/dtls_transport_interface.h" #include "api/ref_count.h" -#include "api/rtc_error.h" #include "api/scoped_refptr.h" +#include "rtc_base/system/rtc_export.h" namespace webrtc { diff --git a/third_party/libwebrtc/api/sequence_checker.h b/third_party/libwebrtc/api/sequence_checker.h index 5ff586037194..2c352f701cf7 100644 --- a/third_party/libwebrtc/api/sequence_checker.h +++ b/third_party/libwebrtc/api/sequence_checker.h @@ -10,6 +10,7 @@ #ifndef API_SEQUENCE_CHECKER_H_ #define API_SEQUENCE_CHECKER_H_ +#include "api/task_queue/task_queue_base.h" #include "rtc_base/checks.h" #include "rtc_base/synchronization/sequence_checker_internal.h" #include "rtc_base/thread_annotations.h" diff --git a/third_party/libwebrtc/api/sequence_checker_unittest.cc b/third_party/libwebrtc/api/sequence_checker_unittest.cc index e54f33dba9ad..3579fa11a605 100644 --- a/third_party/libwebrtc/api/sequence_checker_unittest.cc +++ b/third_party/libwebrtc/api/sequence_checker_unittest.cc @@ -10,14 +10,18 @@ #include "api/sequence_checker.h" +#include #include -#include +#include "absl/functional/any_invocable.h" #include "api/function_view.h" #include "api/units/time_delta.h" +#include "rtc_base/checks.h" #include "rtc_base/event.h" #include "rtc_base/platform_thread.h" +#include "rtc_base/synchronization/sequence_checker_internal.h" #include "rtc_base/task_queue_for_test.h" +#include "rtc_base/thread_annotations.h" #include "test/gmock.h" #include "test/gtest.h" diff --git a/third_party/libwebrtc/api/test/audioproc_float.cc b/third_party/libwebrtc/api/test/audioproc_float.cc index c8d7ff719390..2e0f15c4fbce 100644 --- a/third_party/libwebrtc/api/test/audioproc_float.cc +++ b/third_party/libwebrtc/api/test/audioproc_float.cc @@ -31,14 +31,5 @@ int AudioprocFloat(std::unique_ptr ap_builder, /*processed_capture_samples=*/nullptr); } -int AudioprocFloat(std::unique_ptr ap_builder, - int argc, - char* argv[], - absl::string_view input_aecdump, - std::vector* processed_capture_samples) { - return AudioprocFloatImpl(std::move(ap_builder), argc, argv, input_aecdump, - processed_capture_samples); -} - } // namespace test } // namespace webrtc diff --git a/third_party/libwebrtc/api/test/audioproc_float.h b/third_party/libwebrtc/api/test/audioproc_float.h index 021bc732e1bc..dbdd9348e741 100644 --- a/third_party/libwebrtc/api/test/audioproc_float.h +++ b/third_party/libwebrtc/api/test/audioproc_float.h @@ -53,18 +53,6 @@ int AudioprocFloat(std::unique_ptr ap_builder, int argc, char* argv[]); -// Interface for the audio processing simulation utility, which is similar to -// the one above, but which adds the option of receiving the input as a string -// and returning the output as an array. The first three arguments fulfill the -// same purpose as above. Pass the `input_aecdump` to provide the content of an -// AEC dump file as a string. After the simulation is completed, -// `processed_capture_samples` will contain the the samples processed on the -// capture side. -int AudioprocFloat(std::unique_ptr ap_builder, - int argc, - char* argv[], - absl::string_view input_aecdump, - std::vector* processed_capture_samples); } // namespace test } // namespace webrtc diff --git a/third_party/libwebrtc/api/test/mock_peerconnectioninterface.h b/third_party/libwebrtc/api/test/mock_peerconnectioninterface.h index 814d1b142017..30822930bb2f 100644 --- a/third_party/libwebrtc/api/test/mock_peerconnectioninterface.h +++ b/third_party/libwebrtc/api/test/mock_peerconnectioninterface.h @@ -215,6 +215,10 @@ class MockPeerConnectionInterface : public webrtc::PeerConnectionInterface { MOCK_METHOD(void, StopRtcEventLog, (), (override)); MOCK_METHOD(void, Close, (), (override)); MOCK_METHOD(rtc::Thread*, signaling_thread, (), (const override)); + MOCK_METHOD(NetworkControllerInterface*, + GetNetworkController, + (), + (override)); }; static_assert( diff --git a/third_party/libwebrtc/api/test/network_emulation/BUILD.gn b/third_party/libwebrtc/api/test/network_emulation/BUILD.gn index 2ee14dfe19a4..ac07325bbf27 100644 --- a/third_party/libwebrtc/api/test/network_emulation/BUILD.gn +++ b/third_party/libwebrtc/api/test/network_emulation/BUILD.gn @@ -26,6 +26,8 @@ if (rtc_enable_protobuf) { ":network_config_schedule_proto", "../..:network_emulation_manager_api", "../../../test/network:schedulable_network_behavior", + "../../units:timestamp", + "//third_party/abseil-cpp/absl/functional:any_invocable", ] } } diff --git a/third_party/libwebrtc/api/test/network_emulation/schedulable_network_node_builder.cc b/third_party/libwebrtc/api/test/network_emulation/schedulable_network_node_builder.cc index fd5a26c4fa0a..41c66d8581d0 100644 --- a/third_party/libwebrtc/api/test/network_emulation/schedulable_network_node_builder.cc +++ b/third_party/libwebrtc/api/test/network_emulation/schedulable_network_node_builder.cc @@ -10,10 +10,13 @@ #include "api/test/network_emulation/schedulable_network_node_builder.h" #include +#include #include +#include "absl/functional/any_invocable.h" #include "api/test/network_emulation/network_config_schedule.pb.h" #include "api/test/network_emulation_manager.h" +#include "api/units/timestamp.h" #include "test/network/schedulable_network_behavior.h" namespace webrtc { @@ -21,10 +24,22 @@ namespace webrtc { SchedulableNetworkNodeBuilder::SchedulableNetworkNodeBuilder( webrtc::NetworkEmulationManager& net, network_behaviour::NetworkConfigSchedule schedule) - : net_(net), schedule_(std::move(schedule)) {} + : net_(net), + schedule_(std::move(schedule)), + start_condition_([](webrtc::Timestamp) { return true; }) {} -webrtc::EmulatedNetworkNode* SchedulableNetworkNodeBuilder::Build() { +void SchedulableNetworkNodeBuilder::set_start_condition( + absl::AnyInvocable start_condition) { + start_condition_ = std::move(start_condition); +} + +webrtc::EmulatedNetworkNode* SchedulableNetworkNodeBuilder::Build( + std::optional random_seed) { + uint64_t seed = random_seed.has_value() + ? *random_seed + : static_cast(rtc::TimeNanos()); return net_.CreateEmulatedNode(std::make_unique( - std::move(schedule_), *net_.time_controller()->GetClock())); + std::move(schedule_), seed, *net_.time_controller()->GetClock(), + std::move(start_condition_))); } } // namespace webrtc diff --git a/third_party/libwebrtc/api/test/network_emulation/schedulable_network_node_builder.h b/third_party/libwebrtc/api/test/network_emulation/schedulable_network_node_builder.h index 5e705c95fecd..9ac6547a323b 100644 --- a/third_party/libwebrtc/api/test/network_emulation/schedulable_network_node_builder.h +++ b/third_party/libwebrtc/api/test/network_emulation/schedulable_network_node_builder.h @@ -10,8 +10,13 @@ #ifndef API_TEST_NETWORK_EMULATION_SCHEDULABLE_NETWORK_NODE_BUILDER_H_ #define API_TEST_NETWORK_EMULATION_SCHEDULABLE_NETWORK_NODE_BUILDER_H_ +#include +#include + +#include "absl/functional/any_invocable.h" #include "api/test/network_emulation/network_config_schedule.pb.h" #include "api/test/network_emulation_manager.h" +#include "api/units/timestamp.h" namespace webrtc { @@ -20,12 +25,23 @@ class SchedulableNetworkNodeBuilder { SchedulableNetworkNodeBuilder( webrtc::NetworkEmulationManager& net, network_behaviour::NetworkConfigSchedule schedule); + // set_start_condition allows a test to control when the schedule start. + // `start_condition` is invoked every time a packet is enqueued on the network + // until the first time `start_condition` returns true. Until then, the first + // NetworkConfigScheduleItem is used. There is no guarantee on which + // thread/task queue that will be used. + void set_start_condition( + absl::AnyInvocable start_condition); - webrtc::EmulatedNetworkNode* Build(); + // If no random seed is provided, one will be created. + // The random seed is required for loss rate and to delay standard deviation. + webrtc::EmulatedNetworkNode* Build( + std::optional random_seed = std::nullopt); private: webrtc::NetworkEmulationManager& net_; network_behaviour::NetworkConfigSchedule schedule_; + absl::AnyInvocable start_condition_; }; } // namespace webrtc diff --git a/third_party/libwebrtc/api/test/pclf/BUILD.gn b/third_party/libwebrtc/api/test/pclf/BUILD.gn index 6a42f95cf1cf..f88ce19a9d6d 100644 --- a/third_party/libwebrtc/api/test/pclf/BUILD.gn +++ b/third_party/libwebrtc/api/test/pclf/BUILD.gn @@ -39,7 +39,7 @@ rtc_source_set("media_configuration") { "../../../rtc_base:stringutils", "../../../rtc_base:threading", "../../../test:fileutils", - "../../../test:video_test_support", + "../../../test:video_frame_writer", "../../../test/pc/e2e/analyzer/video:video_dumping", "../../audio:audio_mixer_api", "../../audio:audio_processing", diff --git a/third_party/libwebrtc/api/test/video/BUILD.gn b/third_party/libwebrtc/api/test/video/BUILD.gn index ab9da974a55f..61af6b030245 100644 --- a/third_party/libwebrtc/api/test/video/BUILD.gn +++ b/third_party/libwebrtc/api/test/video/BUILD.gn @@ -25,7 +25,6 @@ rtc_library("function_video_factory") { rtc_library("video_frame_writer") { visibility = [ "*" ] - testonly = true sources = [ "video_frame_writer.h" ] deps = [ "../../video:video_frame" ] diff --git a/third_party/libwebrtc/api/units/data_rate.h b/third_party/libwebrtc/api/units/data_rate.h index d813c61156df..63bf39a3c3ff 100644 --- a/third_party/libwebrtc/api/units/data_rate.h +++ b/third_party/libwebrtc/api/units/data_rate.h @@ -11,10 +11,6 @@ #ifndef API_UNITS_DATA_RATE_H_ #define API_UNITS_DATA_RATE_H_ -#ifdef WEBRTC_UNIT_TEST -#include // no-presubmit-check TODO(webrtc:8982) -#endif // WEBRTC_UNIT_TEST - #include #include #include @@ -142,14 +138,6 @@ inline std::string ToLogString(DataRate value) { return ToString(value); } -#ifdef WEBRTC_UNIT_TEST -inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) - std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) - DataRate value) { - return stream << ToString(value); -} -#endif // WEBRTC_UNIT_TEST - } // namespace webrtc #endif // API_UNITS_DATA_RATE_H_ diff --git a/third_party/libwebrtc/api/units/data_size.h b/third_party/libwebrtc/api/units/data_size.h index 9df6434fb9a0..937513f326f0 100644 --- a/third_party/libwebrtc/api/units/data_size.h +++ b/third_party/libwebrtc/api/units/data_size.h @@ -11,10 +11,6 @@ #ifndef API_UNITS_DATA_SIZE_H_ #define API_UNITS_DATA_SIZE_H_ -#ifdef WEBRTC_UNIT_TEST -#include // no-presubmit-check TODO(webrtc:8982) -#endif // WEBRTC_UNIT_TEST - #include #include @@ -53,14 +49,6 @@ inline std::string ToLogString(DataSize value) { return ToString(value); } -#ifdef WEBRTC_UNIT_TEST -inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) - std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) - DataSize value) { - return stream << ToString(value); -} -#endif // WEBRTC_UNIT_TEST - } // namespace webrtc #endif // API_UNITS_DATA_SIZE_H_ diff --git a/third_party/libwebrtc/api/units/frequency.h b/third_party/libwebrtc/api/units/frequency.h index 06081e4c0d22..e0ce2aa2d949 100644 --- a/third_party/libwebrtc/api/units/frequency.h +++ b/third_party/libwebrtc/api/units/frequency.h @@ -10,10 +10,6 @@ #ifndef API_UNITS_FREQUENCY_H_ #define API_UNITS_FREQUENCY_H_ -#ifdef WEBRTC_UNIT_TEST -#include // no-presubmit-check TODO(webrtc:8982) -#endif // WEBRTC_UNIT_TEST - #include #include #include @@ -89,13 +85,5 @@ inline std::string ToLogString(Frequency value) { return ToString(value); } -#ifdef WEBRTC_UNIT_TEST -inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) - std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) - Frequency value) { - return stream << ToString(value); -} -#endif // WEBRTC_UNIT_TEST - } // namespace webrtc #endif // API_UNITS_FREQUENCY_H_ diff --git a/third_party/libwebrtc/api/units/time_delta.h b/third_party/libwebrtc/api/units/time_delta.h index 5981e32dce9f..bc4c28541197 100644 --- a/third_party/libwebrtc/api/units/time_delta.h +++ b/third_party/libwebrtc/api/units/time_delta.h @@ -11,10 +11,6 @@ #ifndef API_UNITS_TIME_DELTA_H_ #define API_UNITS_TIME_DELTA_H_ -#ifdef WEBRTC_UNIT_TEST -#include // no-presubmit-check TODO(webrtc:8982) -#endif // WEBRTC_UNIT_TEST - #include #include #include @@ -97,14 +93,6 @@ inline std::string ToLogString(TimeDelta value) { return ToString(value); } -#ifdef WEBRTC_UNIT_TEST -inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) - std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) - TimeDelta value) { - return stream << ToString(value); -} -#endif // WEBRTC_UNIT_TEST - } // namespace webrtc #endif // API_UNITS_TIME_DELTA_H_ diff --git a/third_party/libwebrtc/api/units/timestamp.h b/third_party/libwebrtc/api/units/timestamp.h index 8aabe05cad50..91acc6c2540a 100644 --- a/third_party/libwebrtc/api/units/timestamp.h +++ b/third_party/libwebrtc/api/units/timestamp.h @@ -11,10 +11,6 @@ #ifndef API_UNITS_TIMESTAMP_H_ #define API_UNITS_TIMESTAMP_H_ -#ifdef WEBRTC_UNIT_TEST -#include // no-presubmit-check TODO(webrtc:8982) -#endif // WEBRTC_UNIT_TEST - #include #include @@ -126,14 +122,6 @@ inline std::string ToLogString(Timestamp value) { return ToString(value); } -#ifdef WEBRTC_UNIT_TEST -inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) - std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) - Timestamp value) { - return stream << ToString(value); -} -#endif // WEBRTC_UNIT_TEST - } // namespace webrtc #endif // API_UNITS_TIMESTAMP_H_ diff --git a/third_party/libwebrtc/api/video/encoded_image.h b/third_party/libwebrtc/api/video/encoded_image.h index 3958c1672fb3..3d0dd90f28f5 100644 --- a/third_party/libwebrtc/api/video/encoded_image.h +++ b/third_party/libwebrtc/api/video/encoded_image.h @@ -200,6 +200,16 @@ class RTC_EXPORT EncodedImage { at_target_quality_ = at_target_quality; } + // Returns whether the frame that was encoded is a steady-state refresh frame + // intended to improve the visual quality. + bool IsSteadyStateRefreshFrame() const { + return is_steady_state_refresh_frame_; + } + + void SetIsSteadyStateRefreshFrame(bool refresh_frame) { + is_steady_state_refresh_frame_ = refresh_frame; + } + webrtc::VideoFrameType FrameType() const { return _frameType; } void SetFrameType(webrtc::VideoFrameType frame_type) { @@ -260,6 +270,9 @@ class RTC_EXPORT EncodedImage { bool retransmission_allowed_ = true; // True if the encoded image can be considered to be of target quality. bool at_target_quality_ = false; + // True if the frame that was encoded is a steady-state refresh frame intended + // to improve the visual quality. + bool is_steady_state_refresh_frame_ = false; }; } // namespace webrtc diff --git a/third_party/libwebrtc/api/video/rtp_video_frame_assembler.cc b/third_party/libwebrtc/api/video/rtp_video_frame_assembler.cc index a19fcdcb973f..c53789a959ae 100644 --- a/third_party/libwebrtc/api/video/rtp_video_frame_assembler.cc +++ b/third_party/libwebrtc/api/video/rtp_video_frame_assembler.cc @@ -83,6 +83,7 @@ class RtpVideoFrameAssembler::Impl { void ClearOldData(uint16_t incoming_seq_num); std::unique_ptr video_structure_; + SeqNumUnwrapper rtp_sequence_number_unwrapper_; SeqNumUnwrapper frame_id_unwrapper_; absl::optional video_structure_frame_id_; std::unique_ptr depacketizer_; @@ -124,7 +125,9 @@ RtpVideoFrameAssembler::FrameVector RtpVideoFrameAssembler::Impl::InsertPacket( parsed_payload->video_header.is_last_packet_in_frame |= rtp_packet.Marker(); auto packet = std::make_unique( - rtp_packet, parsed_payload->video_header); + rtp_packet, + rtp_sequence_number_unwrapper_.Unwrap(rtp_packet.SequenceNumber()), + parsed_payload->video_header); packet->video_payload = std::move(parsed_payload->video_payload); ClearOldData(rtp_packet.SequenceNumber()); @@ -163,8 +166,8 @@ RtpVideoFrameAssembler::Impl::AssembleFrames( const video_coding::PacketBuffer::Packet& last_packet = *packet; result.push_back(std::make_unique( - first_packet->seq_num, // - last_packet.seq_num, // + first_packet->seq_num(), // + last_packet.seq_num(), // last_packet.marker_bit, // /*times_nacked=*/0, // /*first_packet_received_time=*/0, // diff --git a/third_party/libwebrtc/api/video/video_frame_metadata_unittest.cc b/third_party/libwebrtc/api/video/video_frame_metadata_unittest.cc index 0f730f741026..4ebf7435dd4a 100644 --- a/third_party/libwebrtc/api/video/video_frame_metadata_unittest.cc +++ b/third_party/libwebrtc/api/video/video_frame_metadata_unittest.cc @@ -28,8 +28,7 @@ RTPVideoHeaderH264 ExampleHeaderH264() { RTPVideoHeaderH264 header; header.nalu_type = 4; header.packetization_type = H264PacketizationTypes::kH264StapA; - header.nalus[0] = nalu_info; - header.nalus_length = 1; + header.nalus = {nalu_info}; header.packetization_mode = H264PacketizationMode::SingleNalUnit; return header; } diff --git a/third_party/libwebrtc/api/video_codecs/BUILD.gn b/third_party/libwebrtc/api/video_codecs/BUILD.gn index f71fa08503ff..b911e6b608a9 100644 --- a/third_party/libwebrtc/api/video_codecs/BUILD.gn +++ b/third_party/libwebrtc/api/video_codecs/BUILD.gn @@ -381,6 +381,7 @@ rtc_library("libaom_av1_encoder_factory_test") { "../../rtc_base:logging", "../../test:fileutils", "../../test:test_support", + "../../test:video_frame_writer", "../../test:video_test_support", "//third_party/abseil-cpp/absl/types:variant", ] diff --git a/third_party/libwebrtc/api/video_codecs/video_encoder.h b/third_party/libwebrtc/api/video_codecs/video_encoder.h index 49ea6e1c0e53..ff84acf5921b 100644 --- a/third_party/libwebrtc/api/video_codecs/video_encoder.h +++ b/third_party/libwebrtc/api/video_codecs/video_encoder.h @@ -259,6 +259,11 @@ class RTC_EXPORT VideoEncoder { // Indicates whether or not QP value encoder writes into frame/slice/tile // header can be interpreted as average frame/slice/tile QP. absl::optional is_qp_trusted; + + // The minimum QP that the encoder is expected to use with the current + // configuration. This may be used to determine if the encoder has reached + // its target video quality for static screenshare content. + absl::optional min_qp; }; struct RTC_EXPORT RateControlParameters { diff --git a/third_party/libwebrtc/api/voip/BUILD.gn b/third_party/libwebrtc/api/voip/BUILD.gn index 6ed1ea8a9072..b28d7ce5c6eb 100644 --- a/third_party/libwebrtc/api/voip/BUILD.gn +++ b/third_party/libwebrtc/api/voip/BUILD.gn @@ -30,6 +30,7 @@ rtc_source_set("voip_api") { rtc_library("voip_engine_factory") { visibility = [ "*" ] + allow_poison = [ "environment_construction" ] sources = [ "voip_engine_factory.cc", "voip_engine_factory.h", @@ -42,6 +43,7 @@ rtc_library("voip_engine_factory") { "../audio:audio_device", "../audio:audio_processing", "../audio_codecs:audio_codecs_api", + "../environment:environment_factory", "../task_queue", ] } diff --git a/third_party/libwebrtc/api/voip/test/compile_all_headers.cc b/third_party/libwebrtc/api/voip/test/compile_all_headers.cc index 73a0f0d1c4a0..c95ba78b1aca 100644 --- a/third_party/libwebrtc/api/voip/test/compile_all_headers.cc +++ b/third_party/libwebrtc/api/voip/test/compile_all_headers.cc @@ -11,4 +11,3 @@ // This file verifies that all include files in this directory can be // compiled without errors or other required includes. -#include "api/voip/test/mock_voip_engine.h" diff --git a/third_party/libwebrtc/api/voip/voip_engine_factory.cc b/third_party/libwebrtc/api/voip/voip_engine_factory.cc index 8da53cef74bd..2465ec950223 100644 --- a/third_party/libwebrtc/api/voip/voip_engine_factory.cc +++ b/third_party/libwebrtc/api/voip/voip_engine_factory.cc @@ -12,6 +12,7 @@ #include +#include "api/environment/environment_factory.h" #include "audio/voip/voip_core.h" #include "rtc_base/logging.h" @@ -27,11 +28,11 @@ std::unique_ptr CreateVoipEngine(VoipEngineConfig config) { RTC_DLOG(LS_INFO) << "No audio processing functionality provided."; } - return std::make_unique(std::move(config.encoder_factory), - std::move(config.decoder_factory), - std::move(config.task_queue_factory), - std::move(config.audio_device_module), - std::move(config.audio_processing)); + return std::make_unique( + CreateEnvironment(std::move(config.task_queue_factory)), + std::move(config.encoder_factory), std::move(config.decoder_factory), + std::move(config.audio_device_module), + std::move(config.audio_processing)); } } // namespace webrtc diff --git a/third_party/libwebrtc/api/wrapping_async_dns_resolver.cc b/third_party/libwebrtc/api/wrapping_async_dns_resolver.cc deleted file mode 100644 index 866cb0076d5c..000000000000 --- a/third_party/libwebrtc/api/wrapping_async_dns_resolver.cc +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2021 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/wrapping_async_dns_resolver.h" - -namespace webrtc { - -bool WrappingAsyncDnsResolverResult::GetResolvedAddress( - int family, - rtc::SocketAddress* addr) const { - if (!owner_->wrapped()) { - return false; - } - return owner_->wrapped()->GetResolvedAddress(family, addr); -} - -int WrappingAsyncDnsResolverResult::GetError() const { - if (!owner_->wrapped()) { - return -1; // FIXME: Find a code that makes sense. - } - return owner_->wrapped()->GetError(); -} - -} // namespace webrtc diff --git a/third_party/libwebrtc/audio/audio_receive_stream.cc b/third_party/libwebrtc/audio/audio_receive_stream.cc index a677623843b8..5aabcf10d34c 100644 --- a/third_party/libwebrtc/audio/audio_receive_stream.cc +++ b/third_party/libwebrtc/audio/audio_receive_stream.cc @@ -324,6 +324,9 @@ webrtc::AudioReceiveStreamInterface::Stats AudioReceiveStreamImpl::GetStats( static_cast(rtc::kNumMillisecsPerSec); stats.inserted_samples_for_deceleration = ns.insertedSamplesForDeceleration; stats.removed_samples_for_acceleration = ns.removedSamplesForAcceleration; + stats.total_processing_delay_seconds = + static_cast(ns.totalProcessingDelayUs) / + static_cast(rtc::kNumMicrosecsPerSec); stats.expand_rate = Q14ToFloat(ns.currentExpandRate); stats.speech_expand_rate = Q14ToFloat(ns.currentSpeechExpandRate); stats.secondary_decoded_rate = Q14ToFloat(ns.currentSecondaryDecodedRate); diff --git a/third_party/libwebrtc/audio/audio_receive_stream_unittest.cc b/third_party/libwebrtc/audio/audio_receive_stream_unittest.cc index 25e7e7bfa6c6..1860e844d015 100644 --- a/third_party/libwebrtc/audio/audio_receive_stream_unittest.cc +++ b/third_party/libwebrtc/audio/audio_receive_stream_unittest.cc @@ -82,6 +82,7 @@ const NetworkStatistics kNetworkStats = { /*removedSamplesForAcceleration=*/321, /*fecPacketsReceived=*/123, /*fecPacketsDiscarded=*/101, + /*totalProcessingDelayMs=*/154, /*packetsDiscarded=*/989, /*currentExpandRate=*/789, /*currentSpeechExpandRate=*/12, @@ -287,6 +288,9 @@ TEST(AudioReceiveStreamTest, GetStats) { stats.removed_samples_for_acceleration); EXPECT_EQ(kNetworkStats.fecPacketsReceived, stats.fec_packets_received); EXPECT_EQ(kNetworkStats.fecPacketsDiscarded, stats.fec_packets_discarded); + EXPECT_EQ(static_cast(kNetworkStats.totalProcessingDelayUs) / + static_cast(rtc::kNumMicrosecsPerSec), + stats.total_processing_delay_seconds); EXPECT_EQ(kNetworkStats.packetsDiscarded, stats.packets_discarded); EXPECT_EQ(Q14ToFloat(kNetworkStats.currentExpandRate), stats.expand_rate); EXPECT_EQ(Q14ToFloat(kNetworkStats.currentSpeechExpandRate), diff --git a/third_party/libwebrtc/audio/audio_send_stream.cc b/third_party/libwebrtc/audio/audio_send_stream.cc index 04f0f02a5d4b..cfe194920c67 100644 --- a/third_party/libwebrtc/audio/audio_send_stream.cc +++ b/third_party/libwebrtc/audio/audio_send_stream.cc @@ -559,9 +559,10 @@ bool AudioSendStream::SetupSendCodec(const Config& new_config) { const auto& spec = *new_config.send_codec_spec; RTC_DCHECK(new_config.encoder_factory); - std::unique_ptr encoder = - new_config.encoder_factory->MakeAudioEncoder( - spec.payload_type, spec.format, new_config.codec_pair_id); + std::unique_ptr encoder = new_config.encoder_factory->Create( + env_, spec.format, + {.payload_type = spec.payload_type, + .codec_pair_id = new_config.codec_pair_id}); if (!encoder) { RTC_DLOG(LS_ERROR) << "Unable to create encoder for " diff --git a/third_party/libwebrtc/audio/audio_send_stream_unittest.cc b/third_party/libwebrtc/audio/audio_send_stream_unittest.cc index 7c74a115d49e..45f539137324 100644 --- a/third_party/libwebrtc/audio/audio_send_stream_unittest.cc +++ b/third_party/libwebrtc/audio/audio_send_stream_unittest.cc @@ -51,6 +51,7 @@ using ::testing::Ne; using ::testing::NiceMock; using ::testing::Return; using ::testing::StrEq; +using ::testing::WithArg; static const float kTolerance = 0.0001f; @@ -100,21 +101,19 @@ class MockLimitObserver : public BitrateAllocator::LimitObserver { }; std::unique_ptr SetupAudioEncoderMock( - int payload_type, const SdpAudioFormat& format) { for (const auto& spec : kCodecSpecs) { if (format == spec.format) { - std::unique_ptr encoder( - new ::testing::NiceMock()); - ON_CALL(*encoder.get(), SampleRateHz()) + auto encoder = std::make_unique>(); + ON_CALL(*encoder, SampleRateHz) .WillByDefault(Return(spec.info.sample_rate_hz)); - ON_CALL(*encoder.get(), NumChannels()) + ON_CALL(*encoder, NumChannels) .WillByDefault(Return(spec.info.num_channels)); - ON_CALL(*encoder.get(), RtpTimestampRateHz()) + ON_CALL(*encoder, RtpTimestampRateHz) .WillByDefault(Return(spec.format.clockrate_hz)); - ON_CALL(*encoder.get(), GetFrameLengthRange()) - .WillByDefault(Return(absl::optional>{ - {TimeDelta::Millis(20), TimeDelta::Millis(120)}})); + ON_CALL(*encoder, GetFrameLengthRange) + .WillByDefault(Return( + std::make_pair(TimeDelta::Millis(20), TimeDelta::Millis(120)))); return encoder; } } @@ -124,11 +123,11 @@ std::unique_ptr SetupAudioEncoderMock( rtc::scoped_refptr SetupEncoderFactoryMock() { rtc::scoped_refptr factory = rtc::make_ref_counted(); - ON_CALL(*factory.get(), GetSupportedEncoders()) + ON_CALL(*factory, GetSupportedEncoders) .WillByDefault(Return(std::vector( std::begin(kCodecSpecs), std::end(kCodecSpecs)))); - ON_CALL(*factory.get(), QueryAudioEncoder(_)) - .WillByDefault(Invoke( + ON_CALL(*factory, QueryAudioEncoder) + .WillByDefault( [](const SdpAudioFormat& format) -> absl::optional { for (const auto& spec : kCodecSpecs) { if (format == spec.format) { @@ -136,13 +135,8 @@ rtc::scoped_refptr SetupEncoderFactoryMock() { } } return absl::nullopt; - })); - ON_CALL(*factory.get(), MakeAudioEncoderMock(_, _, _, _)) - .WillByDefault(Invoke([](int payload_type, const SdpAudioFormat& format, - absl::optional codec_pair_id, - std::unique_ptr* return_value) { - *return_value = SetupAudioEncoderMock(payload_type, format); - })); + }); + ON_CALL(*factory, Create).WillByDefault(WithArg<1>(&SetupAudioEncoderMock)); return factory; } @@ -518,19 +512,17 @@ TEST(AudioSendStreamTest, SendCodecAppliesAudioNetworkAdaptor) { helper.config().audio_network_adaptor_config = kAnaConfigString; - EXPECT_CALL(helper.mock_encoder_factory(), MakeAudioEncoderMock(_, _, _, _)) - .WillOnce(Invoke([&kAnaConfigString, &kAnaReconfigString]( - int payload_type, const SdpAudioFormat& format, - absl::optional codec_pair_id, - std::unique_ptr* return_value) { - auto mock_encoder = SetupAudioEncoderMock(payload_type, format); + EXPECT_CALL(helper.mock_encoder_factory(), Create) + .WillOnce(WithArg<1>([&kAnaConfigString, &kAnaReconfigString]( + const SdpAudioFormat& format) { + auto mock_encoder = SetupAudioEncoderMock(format); EXPECT_CALL(*mock_encoder, EnableAudioNetworkAdaptor(StrEq(kAnaConfigString), _)) .WillOnce(Return(true)); EXPECT_CALL(*mock_encoder, EnableAudioNetworkAdaptor(StrEq(kAnaReconfigString), _)) .WillOnce(Return(true)); - *return_value = std::move(mock_encoder); + return mock_encoder; })); auto send_stream = helper.CreateAudioSendStream(); @@ -549,12 +541,10 @@ TEST(AudioSendStreamTest, AudioNetworkAdaptorReceivesOverhead) { AudioSendStream::Config::SendCodecSpec(0, kOpusFormat); const std::string kAnaConfigString = "abcde"; - EXPECT_CALL(helper.mock_encoder_factory(), MakeAudioEncoderMock(_, _, _, _)) - .WillOnce(Invoke( - [&kAnaConfigString](int payload_type, const SdpAudioFormat& format, - absl::optional codec_pair_id, - std::unique_ptr* return_value) { - auto mock_encoder = SetupAudioEncoderMock(payload_type, format); + EXPECT_CALL(helper.mock_encoder_factory(), Create) + .WillOnce( + WithArg<1>([&kAnaConfigString](const SdpAudioFormat& format) { + auto mock_encoder = SetupAudioEncoderMock(format); InSequence s; EXPECT_CALL( *mock_encoder, @@ -565,9 +555,8 @@ TEST(AudioSendStreamTest, AudioNetworkAdaptorReceivesOverhead) { // Note: Overhead is received AFTER ANA has been enabled. EXPECT_CALL( *mock_encoder, - OnReceivedOverhead(Eq(kOverheadPerPacket.bytes()))) - .WillOnce(Return()); - *return_value = std::move(mock_encoder); + OnReceivedOverhead(Eq(kOverheadPerPacket.bytes()))); + return mock_encoder; })); EXPECT_CALL(*helper.rtp_rtcp(), ExpectedPerPacketOverhead) .WillRepeatedly(Return(kOverheadPerPacket.bytes())); @@ -960,14 +949,12 @@ TEST(AudioSendStreamTest, UseEncoderBitrateRange) { ConfigHelper helper(true, true, true); std::pair bitrate_range{DataRate::BitsPerSec(5000), DataRate::BitsPerSec(10000)}; - EXPECT_CALL(helper.mock_encoder_factory(), MakeAudioEncoderMock(_, _, _, _)) - .WillOnce(Invoke([&](int payload_type, const SdpAudioFormat& format, - absl::optional codec_pair_id, - std::unique_ptr* return_value) { - auto mock_encoder = SetupAudioEncoderMock(payload_type, format); - EXPECT_CALL(*mock_encoder, GetBitrateRange()) + EXPECT_CALL(helper.mock_encoder_factory(), Create) + .WillOnce(WithArg<1>([&](const SdpAudioFormat& format) { + auto mock_encoder = SetupAudioEncoderMock(format); + EXPECT_CALL(*mock_encoder, GetBitrateRange) .WillRepeatedly(Return(bitrate_range)); - *return_value = std::move(mock_encoder); + return mock_encoder; })); auto send_stream = helper.CreateAudioSendStream(); EXPECT_CALL(*helper.bitrate_allocator(), AddObserver(send_stream.get(), _)) diff --git a/third_party/libwebrtc/audio/audio_transport_impl.cc b/third_party/libwebrtc/audio/audio_transport_impl.cc index d7bdd400b022..b9d83321b9cc 100644 --- a/third_party/libwebrtc/audio/audio_transport_impl.cc +++ b/third_party/libwebrtc/audio/audio_transport_impl.cc @@ -38,8 +38,8 @@ void InitializeCaptureFrame(int input_sample_rate, RTC_DCHECK(audio_frame); int min_processing_rate_hz = std::min(input_sample_rate, send_sample_rate_hz); for (int native_rate_hz : AudioProcessing::kNativeSampleRatesHz) { - audio_frame->sample_rate_hz_ = native_rate_hz; - if (audio_frame->sample_rate_hz_ >= min_processing_rate_hz) { + audio_frame->SetSampleRateAndChannelSize(native_rate_hz); + if (native_rate_hz >= min_processing_rate_hz) { break; } } @@ -81,9 +81,6 @@ int Resample(const AudioFrame& frame, RTC_CHECK_EQ(destination.data().size(), frame.num_channels_ * target_number_of_samples_per_channel); - resampler->InitializeIfNeeded(frame.sample_rate_hz_, destination_sample_rate, - static_cast(frame.num_channels())); - // TODO(yujo): Add special case handling of muted frames. return resampler->Resample(frame.data_view(), destination); } @@ -149,6 +146,9 @@ int32_t AudioTransportImpl::RecordedDataIsAvailable( RTC_DCHECK_LE(bytes_per_sample * number_of_frames * number_of_channels, AudioFrame::kMaxDataSizeBytes); + InterleavedView source(static_cast(audio_data), + number_of_frames, number_of_channels); + int send_sample_rate_hz = 0; size_t send_num_channels = 0; bool swap_stereo_channels = false; @@ -162,9 +162,8 @@ int32_t AudioTransportImpl::RecordedDataIsAvailable( std::unique_ptr audio_frame(new AudioFrame()); InitializeCaptureFrame(sample_rate, send_sample_rate_hz, number_of_channels, send_num_channels, audio_frame.get()); - voe::RemixAndResample(static_cast(audio_data), - number_of_frames, number_of_channels, sample_rate, - &capture_resampler_, audio_frame.get()); + voe::RemixAndResample(source, sample_rate, &capture_resampler_, + audio_frame.get()); ProcessCaptureFrame(audio_delay_milliseconds, key_pressed, swap_stereo_channels, audio_processing_, audio_frame.get()); diff --git a/third_party/libwebrtc/audio/channel_send.cc b/third_party/libwebrtc/audio/channel_send.cc index 9a15793ed08d..d753613a9037 100644 --- a/third_party/libwebrtc/audio/channel_send.cc +++ b/third_party/libwebrtc/audio/channel_send.cc @@ -22,6 +22,7 @@ #include "api/crypto/frame_encryptor_interface.h" #include "api/rtc_event_log/rtc_event_log.h" #include "api/sequence_checker.h" +#include "api/task_queue/task_queue_factory.h" #include "audio/channel_send_frame_transformer_delegate.h" #include "audio/utility/audio_frame_operations.h" #include "call/rtp_transport_controller_send_interface.h" diff --git a/third_party/libwebrtc/audio/channel_send_unittest.cc b/third_party/libwebrtc/audio/channel_send_unittest.cc index 3d67e8a8e427..57557763977a 100644 --- a/third_party/libwebrtc/audio/channel_send_unittest.cc +++ b/third_party/libwebrtc/audio/channel_send_unittest.cc @@ -67,7 +67,7 @@ class ChannelSendTest : public ::testing::Test { encoder_factory_ = CreateBuiltinAudioEncoderFactory(); SdpAudioFormat opus = SdpAudioFormat("opus", kRtpRateHz, 2); std::unique_ptr encoder = - encoder_factory_->MakeAudioEncoder(kPayloadType, opus, {}); + encoder_factory_->Create(env_, opus, {.payload_type = kPayloadType}); channel_->SetEncoder(kPayloadType, opus, std::move(encoder)); transport_controller_.EnsureStarted(); channel_->RegisterSenderCongestionControlObjects(&transport_controller_); diff --git a/third_party/libwebrtc/audio/remix_resample.cc b/third_party/libwebrtc/audio/remix_resample.cc index fdea62767506..93e86517aed3 100644 --- a/third_party/libwebrtc/audio/remix_resample.cc +++ b/third_party/libwebrtc/audio/remix_resample.cc @@ -24,48 +24,41 @@ namespace voe { void RemixAndResample(const AudioFrame& src_frame, PushResampler* resampler, AudioFrame* dst_frame) { - RemixAndResample(src_frame.data(), src_frame.samples_per_channel_, - src_frame.num_channels_, src_frame.sample_rate_hz_, - resampler, dst_frame); + RemixAndResample(src_frame.data_view(), src_frame.sample_rate_hz_, resampler, + dst_frame); dst_frame->timestamp_ = src_frame.timestamp_; dst_frame->elapsed_time_ms_ = src_frame.elapsed_time_ms_; dst_frame->ntp_time_ms_ = src_frame.ntp_time_ms_; dst_frame->packet_infos_ = src_frame.packet_infos_; } -// TODO: b/335805780 - Accept ArrayView. -void RemixAndResample(const int16_t* src_data, - size_t samples_per_channel, - size_t num_channels, +void RemixAndResample(InterleavedView src_data, int sample_rate_hz, PushResampler* resampler, AudioFrame* dst_frame) { - const int16_t* audio_ptr = src_data; - size_t audio_ptr_num_channels = num_channels; + // The `samples_per_channel_` members must have been set correctly based on + // the associated sample rate and the assumed 10ms buffer size. + // TODO(tommi): Remove the `sample_rate_hz` param. + RTC_DCHECK_EQ(SampleRateToDefaultChannelSize(sample_rate_hz), + src_data.samples_per_channel()); + RTC_DCHECK_EQ(SampleRateToDefaultChannelSize(dst_frame->sample_rate_hz_), + dst_frame->samples_per_channel()); + + // Temporary buffer in case downmixing is required. std::array downmixed_audio; // Downmix before resampling. - if (num_channels > dst_frame->num_channels_) { - RTC_DCHECK(num_channels == 2 || num_channels == 4) - << "num_channels: " << num_channels; + if (src_data.num_channels() > dst_frame->num_channels_) { + RTC_DCHECK(src_data.num_channels() == 2 || src_data.num_channels() == 4) + << "num_channels: " << src_data.num_channels(); RTC_DCHECK(dst_frame->num_channels_ == 1 || dst_frame->num_channels_ == 2) << "dst_frame->num_channels_: " << dst_frame->num_channels_; - AudioFrameOperations::DownmixChannels( - InterleavedView(src_data, samples_per_channel, - num_channels), - InterleavedView(&downmixed_audio[0], samples_per_channel, - dst_frame->num_channels_)); - audio_ptr = downmixed_audio.data(); - audio_ptr_num_channels = dst_frame->num_channels_; - } - - if (resampler->InitializeIfNeeded(sample_rate_hz, dst_frame->sample_rate_hz_, - audio_ptr_num_channels) == -1) { - RTC_FATAL() << "InitializeIfNeeded failed: sample_rate_hz = " - << sample_rate_hz << ", dst_frame->sample_rate_hz_ = " - << dst_frame->sample_rate_hz_ - << ", audio_ptr_num_channels = " << audio_ptr_num_channels; + InterleavedView downmixed(downmixed_audio.data(), + src_data.samples_per_channel(), + dst_frame->num_channels_); + AudioFrameOperations::DownmixChannels(src_data, downmixed); + src_data = downmixed; } // TODO(yujo): for muted input frames, don't resample. Either 1) allow @@ -73,28 +66,19 @@ void RemixAndResample(const int16_t* src_data, // how much to zero here; or 2) make resampler accept a hint that the input is // zeroed. - // Ensure the `samples_per_channel_` member is set correctly based on the - // destination sample rate, number of channels and assumed 10ms buffer size. - // TODO(tommi): Could we rather assume that this has been done by the caller? - dst_frame->SetSampleRateAndChannelSize(dst_frame->sample_rate_hz_); - - InterleavedView src_view(audio_ptr, samples_per_channel, - audio_ptr_num_channels); // Stash away the originally requested number of channels. Then provide // `dst_frame` as a target buffer with the same number of channels as the // source. auto original_dst_number_of_channels = dst_frame->num_channels_; int out_length = resampler->Resample( - src_view, dst_frame->mutable_data(dst_frame->samples_per_channel_, - src_view.num_channels())); - RTC_CHECK_NE(out_length, -1) << "Resample failed: audio_ptr = " << audio_ptr - << ", src_length = " << src_view.data().size(); - + src_data, dst_frame->mutable_data(dst_frame->samples_per_channel_, + src_data.num_channels())); + RTC_CHECK_NE(out_length, -1) << "src_data.size=" << src_data.size(); RTC_DCHECK_EQ(dst_frame->samples_per_channel(), - out_length / audio_ptr_num_channels); + out_length / src_data.num_channels()); // Upmix after resampling. - if (num_channels == 1 && original_dst_number_of_channels == 2) { + if (src_data.num_channels() == 1 && original_dst_number_of_channels == 2) { // The audio in dst_frame really is mono at this point; MonoToStereo will // set this back to stereo. RTC_DCHECK_EQ(dst_frame->num_channels_, 1); diff --git a/third_party/libwebrtc/audio/remix_resample.h b/third_party/libwebrtc/audio/remix_resample.h index 580ba40310a8..d2f34686dd28 100644 --- a/third_party/libwebrtc/audio/remix_resample.h +++ b/third_party/libwebrtc/audio/remix_resample.h @@ -12,6 +12,7 @@ #define AUDIO_REMIX_RESAMPLE_H_ #include "api/audio/audio_frame.h" +#include "api/audio/audio_view.h" #include "common_audio/resampler/include/push_resampler.h" namespace webrtc { @@ -30,12 +31,9 @@ void RemixAndResample(const AudioFrame& src_frame, PushResampler* resampler, AudioFrame* dst_frame); -// This version has a pointer to the samples `src_data` as input and receives -// `samples_per_channel`, `num_channels` and `sample_rate_hz` of the data as -// parameters. -void RemixAndResample(const int16_t* src_data, - size_t samples_per_channel, - size_t num_channels, +// TODO(tommi): The `sample_rate_hz` argument can probably be removed since it's +// always related to `src_data.samples_per_frame()'. +void RemixAndResample(InterleavedView src_data, int sample_rate_hz, PushResampler* resampler, AudioFrame* dst_frame); diff --git a/third_party/libwebrtc/audio/voip/BUILD.gn b/third_party/libwebrtc/audio/voip/BUILD.gn index 8042ec99fa42..a491ec43ee65 100644 --- a/third_party/libwebrtc/audio/voip/BUILD.gn +++ b/third_party/libwebrtc/audio/voip/BUILD.gn @@ -20,6 +20,7 @@ rtc_library("voip_core") { "../../api/audio:audio_device", "../../api/audio:audio_processing", "../../api/audio_codecs:audio_codecs_api", + "../../api/environment", "../../api/task_queue", "../../api/voip:voip_api", "../../modules/audio_mixer:audio_mixer_impl", diff --git a/third_party/libwebrtc/audio/voip/test/BUILD.gn b/third_party/libwebrtc/audio/voip/test/BUILD.gn index 088a7a56075e..7f120065b5f7 100644 --- a/third_party/libwebrtc/audio/voip/test/BUILD.gn +++ b/third_party/libwebrtc/audio/voip/test/BUILD.gn @@ -28,6 +28,7 @@ if (rtc_include_tests) { "..:voip_core", "../../../api/audio_codecs:builtin_audio_decoder_factory", "../../../api/audio_codecs:builtin_audio_encoder_factory", + "../../../api/environment:environment_factory", "../../../api/task_queue:default_task_queue_factory", "../../../modules/audio_device:mock_audio_device", "../../../modules/audio_processing:mocks", @@ -48,6 +49,8 @@ if (rtc_include_tests) { "../../../api:transport_api", "../../../api/audio_codecs:builtin_audio_decoder_factory", "../../../api/audio_codecs:builtin_audio_encoder_factory", + "../../../api/environment", + "../../../api/environment:environment_factory", "../../../api/task_queue:task_queue", "../../../modules/audio_mixer:audio_mixer_impl", "../../../modules/audio_mixer:audio_mixer_test_utils", @@ -69,6 +72,8 @@ if (rtc_include_tests) { "../../../api:transport_api", "../../../api/audio_codecs:builtin_audio_decoder_factory", "../../../api/audio_codecs:builtin_audio_encoder_factory", + "../../../api/environment", + "../../../api/environment:environment_factory", "../../../api/task_queue:default_task_queue_factory", "../../../api/units:time_delta", "../../../api/units:timestamp", @@ -90,6 +95,8 @@ if (rtc_include_tests) { "..:audio_egress", "../../../api:transport_api", "../../../api/audio_codecs:builtin_audio_encoder_factory", + "../../../api/environment", + "../../../api/environment:environment_factory", "../../../api/task_queue:default_task_queue_factory", "../../../api/units:time_delta", "../../../api/units:timestamp", diff --git a/third_party/libwebrtc/audio/voip/test/audio_channel_unittest.cc b/third_party/libwebrtc/audio/voip/test/audio_channel_unittest.cc index 0c8312b73825..08ca47d12ded 100644 --- a/third_party/libwebrtc/audio/voip/test/audio_channel_unittest.cc +++ b/third_party/libwebrtc/audio/voip/test/audio_channel_unittest.cc @@ -14,6 +14,8 @@ #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "api/call/transport.h" +#include "api/environment/environment.h" +#include "api/environment/environment_factory.h" #include "api/task_queue/task_queue_base.h" #include "api/task_queue/task_queue_factory.h" #include "audio/voip/test/mock_task_queue.h" @@ -44,8 +46,11 @@ class AudioChannelTest : public ::testing::Test { const SdpAudioFormat kPcmuFormat = {"pcmu", 8000, 1}; AudioChannelTest() - : fake_clock_(kStartTime), wave_generator_(1000.0, kAudioLevel) { - task_queue_factory_ = std::make_unique(&task_queue_); + : fake_clock_(kStartTime), + wave_generator_(1000.0, kAudioLevel), + env_(CreateEnvironment( + &fake_clock_, + std::make_unique(&task_queue_))) { audio_mixer_ = AudioMixerImpl::Create(); encoder_factory_ = CreateBuiltinAudioEncoderFactory(); decoder_factory_ = CreateBuiltinAudioDecoderFactory(); @@ -68,11 +73,12 @@ class AudioChannelTest : public ::testing::Test { // simplify network routing logic. rtc::scoped_refptr audio_channel = rtc::make_ref_counted( - &transport_, ssrc, task_queue_factory_.get(), audio_mixer_.get(), + &transport_, ssrc, &env_.task_queue_factory(), audio_mixer_.get(), decoder_factory_); - audio_channel->SetEncoder(kPcmuPayload, kPcmuFormat, - encoder_factory_->MakeAudioEncoder( - kPcmuPayload, kPcmuFormat, absl::nullopt)); + audio_channel->SetEncoder( + kPcmuPayload, kPcmuFormat, + encoder_factory_->Create(env_, kPcmuFormat, + {.payload_type = kPcmuPayload})); audio_channel->SetReceiveCodecs({{kPcmuPayload, kPcmuFormat}}); audio_channel->StartSend(); audio_channel->StartPlay(); @@ -93,7 +99,7 @@ class AudioChannelTest : public ::testing::Test { SineWaveGenerator wave_generator_; NiceMock transport_; NiceMock task_queue_; - std::unique_ptr task_queue_factory_; + const Environment env_; rtc::scoped_refptr audio_mixer_; rtc::scoped_refptr decoder_factory_; rtc::scoped_refptr encoder_factory_; diff --git a/third_party/libwebrtc/audio/voip/test/audio_egress_unittest.cc b/third_party/libwebrtc/audio/voip/test/audio_egress_unittest.cc index 83df26eef1df..c42b78f2e6ce 100644 --- a/third_party/libwebrtc/audio/voip/test/audio_egress_unittest.cc +++ b/third_party/libwebrtc/audio/voip/test/audio_egress_unittest.cc @@ -12,7 +12,8 @@ #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "api/call/transport.h" -#include "api/task_queue/default_task_queue_factory.h" +#include "api/environment/environment.h" +#include "api/environment/environment_factory.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "modules/audio_mixer/sine_wave_generator.h" @@ -74,8 +75,8 @@ class AudioEgressTest : public ::testing::Test { time_controller_.GetTaskQueueFactory()); constexpr int kPcmuPayload = 0; egress_->SetEncoder(kPcmuPayload, kPcmuFormat, - encoder_factory_->MakeAudioEncoder( - kPcmuPayload, kPcmuFormat, absl::nullopt)); + encoder_factory_->Create( + env_, kPcmuFormat, {.payload_type = kPcmuPayload})); egress_->StartSend(); rtp_rtcp_->SetSequenceNumber(kSeqNum); rtp_rtcp_->SetSendingStatus(true); @@ -104,6 +105,9 @@ class AudioEgressTest : public ::testing::Test { } GlobalSimulatedTimeController time_controller_{Timestamp::Micros(kStartTime)}; + const Environment env_ = + CreateEnvironment(time_controller_.GetClock(), + time_controller_.GetTaskQueueFactory()); NiceMock transport_; SineWaveGenerator wave_generator_; std::unique_ptr rtp_rtcp_; @@ -235,8 +239,8 @@ TEST_F(AudioEgressTest, ChangeEncoderFromPcmuToOpus) { const SdpAudioFormat kOpusFormat = {"opus", 48000, 2}; egress_->SetEncoder(kOpusPayload, kOpusFormat, - encoder_factory_->MakeAudioEncoder( - kOpusPayload, kOpusFormat, absl::nullopt)); + encoder_factory_->Create(env_, kOpusFormat, + {.payload_type = kOpusPayload})); absl::optional opus = egress_->GetEncoderFormat(); EXPECT_TRUE(opus); diff --git a/third_party/libwebrtc/audio/voip/test/audio_ingress_unittest.cc b/third_party/libwebrtc/audio/voip/test/audio_ingress_unittest.cc index c7736b247a53..194d97a55c5d 100644 --- a/third_party/libwebrtc/audio/voip/test/audio_ingress_unittest.cc +++ b/third_party/libwebrtc/audio/voip/test/audio_ingress_unittest.cc @@ -13,6 +13,8 @@ #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "api/call/transport.h" +#include "api/environment/environment.h" +#include "api/environment/environment_factory.h" #include "api/task_queue/default_task_queue_factory.h" #include "api/units/time_delta.h" #include "audio/voip/audio_egress.h" @@ -70,8 +72,8 @@ class AudioIngressTest : public ::testing::Test { rtp_rtcp_.get(), time_controller_.GetClock(), time_controller_.GetTaskQueueFactory()); egress_->SetEncoder(kPcmuPayload, kPcmuFormat, - encoder_factory_->MakeAudioEncoder( - kPcmuPayload, kPcmuFormat, absl::nullopt)); + encoder_factory_->Create( + env_, kPcmuFormat, {.payload_type = kPcmuPayload})); egress_->StartSend(); ingress_->StartPlay(); rtp_rtcp_->SetSendingStatus(true); @@ -96,6 +98,9 @@ class AudioIngressTest : public ::testing::Test { } GlobalSimulatedTimeController time_controller_{Timestamp::Micros(123456789)}; + const Environment env_ = + CreateEnvironment(time_controller_.GetClock(), + time_controller_.GetTaskQueueFactory()); SineWaveGenerator wave_generator_; NiceMock transport_; std::unique_ptr receive_statistics_; diff --git a/third_party/libwebrtc/audio/voip/test/voip_core_unittest.cc b/third_party/libwebrtc/audio/voip/test/voip_core_unittest.cc index b432506b128a..2838f5e61d4c 100644 --- a/third_party/libwebrtc/audio/voip/test/voip_core_unittest.cc +++ b/third_party/libwebrtc/audio/voip/test/voip_core_unittest.cc @@ -12,7 +12,7 @@ #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" -#include "api/task_queue/default_task_queue_factory.h" +#include "api/environment/environment_factory.h" #include "modules/audio_device/include/mock_audio_device.h" #include "modules/audio_processing/include/mock_audio_processing.h" #include "test/gtest.h" @@ -43,9 +43,8 @@ class VoipCoreTest : public ::testing::Test { rtc::make_ref_counted>(); voip_core_ = std::make_unique( - std::move(encoder_factory), std::move(decoder_factory), - CreateDefaultTaskQueueFactory(), audio_device_, - std::move(audio_processing)); + CreateEnvironment(), std::move(encoder_factory), + std::move(decoder_factory), audio_device_, std::move(audio_processing)); } test::RunLoop run_loop_; diff --git a/third_party/libwebrtc/audio/voip/voip_core.cc b/third_party/libwebrtc/audio/voip/voip_core.cc index 8df1c594aabf..c1e1a0686cd2 100644 --- a/third_party/libwebrtc/audio/voip/voip_core.cc +++ b/third_party/libwebrtc/audio/voip/voip_core.cc @@ -37,16 +37,16 @@ static constexpr int kMaxChannelId = 100000; } // namespace -VoipCore::VoipCore(rtc::scoped_refptr encoder_factory, +VoipCore::VoipCore(const Environment& env, + rtc::scoped_refptr encoder_factory, rtc::scoped_refptr decoder_factory, - std::unique_ptr task_queue_factory, rtc::scoped_refptr audio_device_module, - rtc::scoped_refptr audio_processing) { - encoder_factory_ = std::move(encoder_factory); - decoder_factory_ = std::move(decoder_factory); - task_queue_factory_ = std::move(task_queue_factory); - audio_device_module_ = std::move(audio_device_module); - audio_processing_ = std::move(audio_processing); + rtc::scoped_refptr audio_processing) + : env_(env), + encoder_factory_(std::move(encoder_factory)), + decoder_factory_(std::move(decoder_factory)), + audio_processing_(std::move(audio_processing)), + audio_device_module_(std::move(audio_device_module)) { audio_mixer_ = AudioMixerImpl::Create(); // AudioTransportImpl depends on audio mixer and audio processing instances. @@ -133,7 +133,7 @@ ChannelId VoipCore::CreateChannel(Transport* transport, rtc::scoped_refptr channel = rtc::make_ref_counted(transport, local_ssrc.value(), - task_queue_factory_.get(), + &env_.task_queue_factory(), audio_mixer_.get(), decoder_factory_); { @@ -380,8 +380,8 @@ VoipResult VoipCore::SetSendCodec(ChannelId channel_id, return VoipResult::kInvalidArgument; } - auto encoder = encoder_factory_->MakeAudioEncoder( - payload_type, encoder_format, absl::nullopt); + auto encoder = encoder_factory_->Create(env_, encoder_format, + {.payload_type = payload_type}); channel->SetEncoder(payload_type, encoder_format, std::move(encoder)); return VoipResult::kOk; diff --git a/third_party/libwebrtc/audio/voip/voip_core.h b/third_party/libwebrtc/audio/voip/voip_core.h index 4a6031dfe29a..3ee1e8cec9a6 100644 --- a/third_party/libwebrtc/audio/voip/voip_core.h +++ b/third_party/libwebrtc/audio/voip/voip_core.h @@ -21,8 +21,8 @@ #include "api/audio/audio_processing.h" #include "api/audio_codecs/audio_decoder_factory.h" #include "api/audio_codecs/audio_encoder_factory.h" +#include "api/environment/environment.h" #include "api/scoped_refptr.h" -#include "api/task_queue/task_queue_factory.h" #include "api/voip/voip_base.h" #include "api/voip/voip_codec.h" #include "api/voip/voip_dtmf.h" @@ -52,10 +52,9 @@ class VoipCore : public VoipEngine, public VoipStatistics, public VoipVolumeControl { public: - // Construct VoipCore with provided arguments. - VoipCore(rtc::scoped_refptr encoder_factory, + VoipCore(const Environment& env, + rtc::scoped_refptr encoder_factory, rtc::scoped_refptr decoder_factory, - std::unique_ptr task_queue_factory, rtc::scoped_refptr audio_device_module, rtc::scoped_refptr audio_processing); ~VoipCore() override = default; @@ -136,9 +135,9 @@ class VoipCore : public VoipEngine, bool UpdateAudioTransportWithSenders(); // Synchronization for these are handled internally. + const Environment env_; rtc::scoped_refptr encoder_factory_; rtc::scoped_refptr decoder_factory_; - std::unique_ptr task_queue_factory_; // Synchronization is handled internally by AudioProcessing. // Must be placed before `audio_device_module_` for proper destruction. diff --git a/third_party/libwebrtc/call/audio_receive_stream.h b/third_party/libwebrtc/call/audio_receive_stream.h index 5a5a160c78f3..35d80151f312 100644 --- a/third_party/libwebrtc/call/audio_receive_stream.h +++ b/third_party/libwebrtc/call/audio_receive_stream.h @@ -63,6 +63,7 @@ class AudioReceiveStreamInterface : public MediaReceiveStreamInterface { double jitter_buffer_minimum_delay_seconds = 0.0; uint64_t inserted_samples_for_deceleration = 0; uint64_t removed_samples_for_acceleration = 0; + double total_processing_delay_seconds = 0.0; // Stats below DO NOT correspond directly to anything in the WebRTC stats float expand_rate = 0.0f; float speech_expand_rate = 0.0f; diff --git a/third_party/libwebrtc/call/rtp_transport_controller_send.cc b/third_party/libwebrtc/call/rtp_transport_controller_send.cc index f66741723942..d22749692cf0 100644 --- a/third_party/libwebrtc/call/rtp_transport_controller_send.cc +++ b/third_party/libwebrtc/call/rtp_transport_controller_send.cc @@ -624,8 +624,6 @@ void RtpTransportControllerSend::OnTransportFeedback( void RtpTransportControllerSend::OnRemoteNetworkEstimate( NetworkStateEstimate estimate) { RTC_DCHECK_RUN_ON(&sequence_checker_); - env_.event_log().Log(std::make_unique( - estimate.link_capacity_lower, estimate.link_capacity_upper)); estimate.update_time = Timestamp::Millis(env_.clock().TimeInMilliseconds()); if (controller_) PostUpdates(controller_->OnNetworkStateEstimate(estimate)); diff --git a/third_party/libwebrtc/call/rtp_transport_controller_send.h b/third_party/libwebrtc/call/rtp_transport_controller_send.h index eada78ee0ed8..9b91d9acbfd7 100644 --- a/third_party/libwebrtc/call/rtp_transport_controller_send.h +++ b/third_party/libwebrtc/call/rtp_transport_controller_send.h @@ -120,6 +120,11 @@ class RtpTransportControllerSend final // Implements NetworkStateEstimateObserver interface void OnRemoteNetworkEstimate(NetworkStateEstimate estimate) override; + NetworkControllerInterface* GetNetworkController() override { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return controller_.get(); + } + private: void MaybeCreateControllers() RTC_RUN_ON(sequence_checker_); void UpdateNetworkAvailability() RTC_RUN_ON(sequence_checker_); diff --git a/third_party/libwebrtc/call/rtp_transport_controller_send_interface.h b/third_party/libwebrtc/call/rtp_transport_controller_send_interface.h index 3fa6df55bbb4..bb995f9cb438 100644 --- a/third_party/libwebrtc/call/rtp_transport_controller_send_interface.h +++ b/third_party/libwebrtc/call/rtp_transport_controller_send_interface.h @@ -25,6 +25,7 @@ #include "api/frame_transformer_interface.h" #include "api/transport/bandwidth_estimation_settings.h" #include "api/transport/bitrate_settings.h" +#include "api/transport/network_control.h" #include "api/units/timestamp.h" #include "call/rtp_config.h" #include "common_video/frame_counts.h" @@ -160,6 +161,7 @@ class RtpTransportControllerSendInterface { virtual void IncludeOverheadInPacedSender() = 0; virtual void EnsureStarted() = 0; + virtual NetworkControllerInterface* GetNetworkController() = 0; }; } // namespace webrtc diff --git a/third_party/libwebrtc/call/test/mock_rtp_transport_controller_send.h b/third_party/libwebrtc/call/test/mock_rtp_transport_controller_send.h index 70b851f07bfa..d1e1d64affc0 100644 --- a/third_party/libwebrtc/call/test/mock_rtp_transport_controller_send.h +++ b/third_party/libwebrtc/call/test/mock_rtp_transport_controller_send.h @@ -105,6 +105,10 @@ class MockRtpTransportControllerSend MOCK_METHOD(void, IncludeOverheadInPacedSender, (), (override)); MOCK_METHOD(void, OnReceivedPacket, (const ReceivedPacket&), (override)); MOCK_METHOD(void, EnsureStarted, (), (override)); + MOCK_METHOD(NetworkControllerInterface*, + GetNetworkController, + (), + (override)); }; } // namespace webrtc #endif // CALL_TEST_MOCK_RTP_TRANSPORT_CONTROLLER_SEND_H_ diff --git a/third_party/libwebrtc/call/version.cc b/third_party/libwebrtc/call/version.cc index 561dae47a2a9..939d3f59ebb9 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 2024-06-09T04:04:04"; +const char* const kSourceTimestamp = "WebRTC source stamp 2024-07-16T04:07:40"; void LoadWebRTCVersionInRegister() { // Using volatile to instruct the compiler to not optimize `p` away even diff --git a/third_party/libwebrtc/common_audio/include/audio_util.h b/third_party/libwebrtc/common_audio/include/audio_util.h index 12e65259a405..672ebd1aa006 100644 --- a/third_party/libwebrtc/common_audio/include/audio_util.h +++ b/third_party/libwebrtc/common_audio/include/audio_util.h @@ -25,6 +25,17 @@ namespace webrtc { typedef std::numeric_limits limits_int16; +// TODO(tommi, peah): Move these constants to their own header, e.g. +// `audio_constants.h`. Also consider if they should be in api/. + +// Absolute highest acceptable sample rate supported for audio processing, +// capture and codecs. Note that for some components some cases a lower limit +// applies which typically is 48000 but in some cases is lower. +constexpr int kMaxSampleRateHz = 384000; + +// Number of samples per channel for 10ms of audio at the highest sample rate. +constexpr size_t kMaxSamplesPerChannel10ms = kMaxSampleRateHz / 100u; + // The conversion functions use the following naming convention: // S16: int16_t [-32768, 32767] // Float: float [-1.0, 1.0] diff --git a/third_party/libwebrtc/common_audio/resampler/include/push_resampler.h b/third_party/libwebrtc/common_audio/resampler/include/push_resampler.h index 4a23d4624eb7..394e96b133b2 100644 --- a/third_party/libwebrtc/common_audio/resampler/include/push_resampler.h +++ b/third_party/libwebrtc/common_audio/resampler/include/push_resampler.h @@ -23,16 +23,13 @@ class PushSincResampler; // Wraps PushSincResampler to provide stereo support. // Note: This implementation assumes 10ms buffer sizes throughout. template -class PushResampler { +class PushResampler final { public: PushResampler(); - virtual ~PushResampler(); - - // Must be called whenever the parameters change. Free to be called at any - // time as it is a no-op if parameters have not changed since the last call. - int InitializeIfNeeded(int src_sample_rate_hz, - int dst_sample_rate_hz, - size_t num_channels); + PushResampler(size_t src_samples_per_channel, + size_t dst_samples_per_channel, + size_t num_channels); + ~PushResampler(); // Returns the total number of samples provided in destination (e.g. 32 kHz, // 2 channel audio gives 640 samples). @@ -42,6 +39,12 @@ class PushResampler { int Resample(MonoView src, MonoView dst); private: + // Ensures that source and destination buffers for deinterleaving are + // correctly configured prior to resampling that requires deinterleaving. + void EnsureInitialized(size_t src_samples_per_channel, + size_t dst_samples_per_channel, + size_t num_channels); + // Buffers used for when a deinterleaving step is necessary. std::unique_ptr source_; std::unique_ptr destination_; diff --git a/third_party/libwebrtc/common_audio/resampler/push_resampler.cc b/third_party/libwebrtc/common_audio/resampler/push_resampler.cc index 1cbf421b6d19..2e75679c82cd 100644 --- a/third_party/libwebrtc/common_audio/resampler/push_resampler.cc +++ b/third_party/libwebrtc/common_audio/resampler/push_resampler.cc @@ -22,55 +22,66 @@ namespace webrtc { +namespace { +// Maximum concurrent number of channels for `PushResampler<>`. +// Note that this may be different from what the maximum is for audio codecs. +constexpr int kMaxNumberOfChannels = 8; +} // namespace + template PushResampler::PushResampler() = default; +template +PushResampler::PushResampler(size_t src_samples_per_channel, + size_t dst_samples_per_channel, + size_t num_channels) { + EnsureInitialized(src_samples_per_channel, dst_samples_per_channel, + num_channels); +} + template PushResampler::~PushResampler() = default; template -int PushResampler::InitializeIfNeeded(int src_sample_rate_hz, - int dst_sample_rate_hz, +void PushResampler::EnsureInitialized(size_t src_samples_per_channel, + size_t dst_samples_per_channel, size_t num_channels) { - // These checks used to be factored out of this template function due to - // Windows debug build issues with clang. http://crbug.com/615050 - RTC_CHECK_GT(src_sample_rate_hz, 0); - RTC_CHECK_GT(dst_sample_rate_hz, 0); - RTC_CHECK_GT(num_channels, 0); + RTC_DCHECK_GT(src_samples_per_channel, 0); + RTC_DCHECK_GT(dst_samples_per_channel, 0); + RTC_DCHECK_GT(num_channels, 0); + RTC_DCHECK_LE(src_samples_per_channel, kMaxSamplesPerChannel10ms); + RTC_DCHECK_LE(dst_samples_per_channel, kMaxSamplesPerChannel10ms); + RTC_DCHECK_LE(num_channels, kMaxNumberOfChannels); - const size_t src_size_10ms_mono = - static_cast(src_sample_rate_hz / 100); - const size_t dst_size_10ms_mono = - static_cast(dst_sample_rate_hz / 100); - - if (src_size_10ms_mono == SamplesPerChannel(source_view_) && - dst_size_10ms_mono == SamplesPerChannel(destination_view_) && + if (src_samples_per_channel == SamplesPerChannel(source_view_) && + dst_samples_per_channel == SamplesPerChannel(destination_view_) && num_channels == NumChannels(source_view_)) { // No-op if settings haven't changed. - return 0; + return; } // Allocate two buffers for all source and destination channels. // Then organize source and destination views together with an array of // resamplers for each channel in the deinterlaved buffers. - source_.reset(new T[src_size_10ms_mono * num_channels]); - destination_.reset(new T[dst_size_10ms_mono * num_channels]); - source_view_ = - DeinterleavedView(source_.get(), src_size_10ms_mono, num_channels); - destination_view_ = DeinterleavedView(destination_.get(), - dst_size_10ms_mono, num_channels); + source_.reset(new T[src_samples_per_channel * num_channels]); + destination_.reset(new T[dst_samples_per_channel * num_channels]); + source_view_ = DeinterleavedView(source_.get(), src_samples_per_channel, + num_channels); + destination_view_ = DeinterleavedView( + destination_.get(), dst_samples_per_channel, num_channels); resamplers_.resize(num_channels); for (size_t i = 0; i < num_channels; ++i) { - resamplers_[i] = std::make_unique(src_size_10ms_mono, - dst_size_10ms_mono); + resamplers_[i] = std::make_unique( + src_samples_per_channel, dst_samples_per_channel); } - - return 0; } template int PushResampler::Resample(InterleavedView src, InterleavedView dst) { + EnsureInitialized(SamplesPerChannel(src), SamplesPerChannel(dst), + NumChannels(src)); + RTC_DCHECK_EQ(NumChannels(src), NumChannels(source_view_)); RTC_DCHECK_EQ(NumChannels(dst), NumChannels(destination_view_)); RTC_DCHECK_EQ(SamplesPerChannel(src), SamplesPerChannel(source_view_)); diff --git a/third_party/libwebrtc/common_audio/resampler/push_resampler_unittest.cc b/third_party/libwebrtc/common_audio/resampler/push_resampler_unittest.cc index 91f2233aad3d..4fba2f412efa 100644 --- a/third_party/libwebrtc/common_audio/resampler/push_resampler_unittest.cc +++ b/third_party/libwebrtc/common_audio/resampler/push_resampler_unittest.cc @@ -19,29 +19,24 @@ namespace webrtc { TEST(PushResamplerTest, VerifiesInputParameters) { - PushResampler resampler; - EXPECT_EQ(0, resampler.InitializeIfNeeded(16000, 16000, 1)); - EXPECT_EQ(0, resampler.InitializeIfNeeded(16000, 16000, 2)); - EXPECT_EQ(0, resampler.InitializeIfNeeded(16000, 16000, 8)); + PushResampler resampler1(160, 160, 1); + PushResampler resampler2(160, 160, 2); + PushResampler resampler3(160, 160, 8); } #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) TEST(PushResamplerDeathTest, VerifiesBadInputParameters1) { - PushResampler resampler; - RTC_EXPECT_DEATH(resampler.InitializeIfNeeded(-1, 16000, 1), - "src_sample_rate_hz"); + RTC_EXPECT_DEATH(PushResampler(-1, 160, 1), + "src_samples_per_channel"); } TEST(PushResamplerDeathTest, VerifiesBadInputParameters2) { - PushResampler resampler; - RTC_EXPECT_DEATH(resampler.InitializeIfNeeded(16000, -1, 1), - "dst_sample_rate_hz"); + RTC_EXPECT_DEATH(PushResampler(160, -1, 1), + "dst_samples_per_channel"); } TEST(PushResamplerDeathTest, VerifiesBadInputParameters3) { - PushResampler resampler; - RTC_EXPECT_DEATH(resampler.InitializeIfNeeded(16000, 16000, 0), - "num_channels"); + RTC_EXPECT_DEATH(PushResampler(160, 16000, 0), "num_channels"); } #endif diff --git a/third_party/libwebrtc/common_video/OWNERS b/third_party/libwebrtc/common_video/OWNERS index 455e247d9090..1c080874f091 100644 --- a/third_party/libwebrtc/common_video/OWNERS +++ b/third_party/libwebrtc/common_video/OWNERS @@ -1,4 +1,5 @@ magjed@webrtc.org marpan@webrtc.org sprang@webrtc.org +ssilkin@webrtc.org stefan@webrtc.org diff --git a/third_party/libwebrtc/common_video/h264/h264_bitstream_parser.cc b/third_party/libwebrtc/common_video/h264/h264_bitstream_parser.cc index 2311d0d2ee2c..5c0ebb83d8f9 100644 --- a/third_party/libwebrtc/common_video/h264/h264_bitstream_parser.cc +++ b/third_party/libwebrtc/common_video/h264/h264_bitstream_parser.cc @@ -31,15 +31,13 @@ H264BitstreamParser::H264BitstreamParser() = default; H264BitstreamParser::~H264BitstreamParser() = default; H264BitstreamParser::Result H264BitstreamParser::ParseNonParameterSetNalu( - const uint8_t* source, - size_t source_length, + rtc::ArrayView source, uint8_t nalu_type) { if (!sps_ || !pps_) return kInvalidStream; last_slice_qp_delta_ = absl::nullopt; - const std::vector slice_rbsp = - H264::ParseRbsp(source, source_length); + const std::vector slice_rbsp = H264::ParseRbsp(source); if (slice_rbsp.size() < H264::kNaluTypeSize) return kInvalidStream; @@ -51,6 +49,11 @@ H264BitstreamParser::Result H264BitstreamParser::ParseNonParameterSetNalu( bool is_idr = (source[0] & 0x0F) == H264::NaluType::kIdr; uint8_t nal_ref_idc = (source[0] & 0x60) >> 5; + uint32_t num_ref_idx_l0_active_minus1 = + pps_->num_ref_idx_l0_default_active_minus1; + uint32_t num_ref_idx_l1_active_minus1 = + pps_->num_ref_idx_l1_default_active_minus1; + // first_mb_in_slice: ue(v) slice_reader.ReadExponentialGolomb(); // slice_type: ue(v) @@ -114,10 +117,16 @@ H264BitstreamParser::Result H264BitstreamParser::ParseNonParameterSetNalu( // num_ref_idx_active_override_flag: u(1) if (slice_reader.Read()) { // num_ref_idx_l0_active_minus1: ue(v) - slice_reader.ReadExponentialGolomb(); + num_ref_idx_l0_active_minus1 = slice_reader.ReadExponentialGolomb(); + if (num_ref_idx_l0_active_minus1 > H264::kMaxReferenceIndex) { + return kInvalidStream; + } if (slice_type == H264::SliceType::kB) { // num_ref_idx_l1_active_minus1: ue(v) - slice_reader.ReadExponentialGolomb(); + num_ref_idx_l1_active_minus1 = slice_reader.ReadExponentialGolomb(); + if (num_ref_idx_l1_active_minus1 > H264::kMaxReferenceIndex) { + return kInvalidStream; + } } } break; @@ -180,17 +189,67 @@ H264BitstreamParser::Result H264BitstreamParser::ParseNonParameterSetNalu( if (!slice_reader.Ok()) { return kInvalidStream; } - // TODO(pbos): Do we need support for pred_weight_table()? if ((pps_->weighted_pred_flag && (slice_type == H264::SliceType::kP || slice_type == H264::SliceType::kSp)) || (pps_->weighted_bipred_idc == 1 && slice_type == H264::SliceType::kB)) { - RTC_LOG(LS_ERROR) << "Streams with pred_weight_table unsupported."; - return kUnsupportedStream; + // pred_weight_table() + // luma_log2_weight_denom: ue(v) + slice_reader.ReadExponentialGolomb(); + + // If separate_colour_plane_flag is equal to 0, ChromaArrayType is set equal + // to chroma_format_idc. Otherwise(separate_colour_plane_flag is equal to + // 1), ChromaArrayType is set equal to 0. + uint8_t chroma_array_type = + sps_->separate_colour_plane_flag == 0 ? sps_->chroma_format_idc : 0; + + if (chroma_array_type != 0) { + // chroma_log2_weight_denom: ue(v) + slice_reader.ReadExponentialGolomb(); + } + + for (uint32_t i = 0; i <= num_ref_idx_l0_active_minus1; i++) { + // luma_weight_l0_flag 2 u(1) + if (slice_reader.Read()) { + // luma_weight_l0[i] 2 se(v) + slice_reader.ReadExponentialGolomb(); + // luma_offset_l0[i] 2 se(v) + slice_reader.ReadExponentialGolomb(); + } + if (chroma_array_type != 0) { + // chroma_weight_l0_flag: u(1) + if (slice_reader.Read()) { + for (uint8_t j = 0; j < 2; j++) { + // chroma_weight_l0[i][j] 2 se(v) + slice_reader.ReadExponentialGolomb(); + // chroma_offset_l0[i][j] 2 se(v) + slice_reader.ReadExponentialGolomb(); + } + } + } + } + if (slice_type % 5 == 1) { + for (uint32_t i = 0; i <= num_ref_idx_l1_active_minus1; i++) { + // luma_weight_l1_flag: u(1) + if (slice_reader.Read()) { + // luma_weight_l1[i] 2 se(v) + slice_reader.ReadExponentialGolomb(); + // luma_offset_l1[i] 2 se(v) + slice_reader.ReadExponentialGolomb(); + } + if (chroma_array_type != 0) { + // chroma_weight_l1_flag: u(1) + if (slice_reader.Read()) { + for (uint8_t j = 0; j < 2; j++) { + // chroma_weight_l1[i][j] 2 se(v) + slice_reader.ReadExponentialGolomb(); + // chroma_offset_l1[i][j] 2 se(v) + slice_reader.ReadExponentialGolomb(); + } + } + } + } + } } - // if ((weighted_pred_flag && (slice_type == P || slice_type == SP)) || - // (weighted_bipred_idc == 1 && slice_type == B)) { - // pred_weight_table() - // } if (nal_ref_idc != 0) { // dec_ref_pic_marking(): if (is_idr) { @@ -247,19 +306,20 @@ H264BitstreamParser::Result H264BitstreamParser::ParseNonParameterSetNalu( return kOk; } -void H264BitstreamParser::ParseSlice(const uint8_t* slice, size_t length) { +void H264BitstreamParser::ParseSlice(rtc::ArrayView slice) { + if (slice.empty()) { + return; + } H264::NaluType nalu_type = H264::ParseNaluType(slice[0]); switch (nalu_type) { case H264::NaluType::kSps: { - sps_ = SpsParser::ParseSps(slice + H264::kNaluTypeSize, - length - H264::kNaluTypeSize); + sps_ = SpsParser::ParseSps(slice.subview(H264::kNaluTypeSize)); if (!sps_) RTC_DLOG(LS_WARNING) << "Unable to parse SPS from H264 bitstream."; break; } case H264::NaluType::kPps: { - pps_ = PpsParser::ParsePps(slice + H264::kNaluTypeSize, - length - H264::kNaluTypeSize); + pps_ = PpsParser::ParsePps(slice.subview(H264::kNaluTypeSize)); if (!pps_) RTC_DLOG(LS_WARNING) << "Unable to parse PPS from H264 bitstream."; break; @@ -269,7 +329,7 @@ void H264BitstreamParser::ParseSlice(const uint8_t* slice, size_t length) { case H264::NaluType::kPrefix: break; // Ignore these nalus, as we don't care about their contents. default: - Result res = ParseNonParameterSetNalu(slice, length, nalu_type); + Result res = ParseNonParameterSetNalu(slice, nalu_type); if (res != kOk) RTC_DLOG(LS_INFO) << "Failed to parse bitstream. Error: " << res; break; @@ -278,11 +338,10 @@ void H264BitstreamParser::ParseSlice(const uint8_t* slice, size_t length) { void H264BitstreamParser::ParseBitstream( rtc::ArrayView bitstream) { - std::vector nalu_indices = - H264::FindNaluIndices(bitstream.data(), bitstream.size()); + std::vector nalu_indices = H264::FindNaluIndices(bitstream); for (const H264::NaluIndex& index : nalu_indices) - ParseSlice(bitstream.data() + index.payload_start_offset, - index.payload_size); + ParseSlice( + bitstream.subview(index.payload_start_offset, index.payload_size)); } absl::optional H264BitstreamParser::GetLastSliceQp() const { diff --git a/third_party/libwebrtc/common_video/h264/h264_bitstream_parser.h b/third_party/libwebrtc/common_video/h264/h264_bitstream_parser.h index 05427825acf9..1fad9895f9b9 100644 --- a/third_party/libwebrtc/common_video/h264/h264_bitstream_parser.h +++ b/third_party/libwebrtc/common_video/h264/h264_bitstream_parser.h @@ -40,9 +40,8 @@ class H264BitstreamParser : public BitstreamParser { kInvalidStream, kUnsupportedStream, }; - void ParseSlice(const uint8_t* slice, size_t length); - Result ParseNonParameterSetNalu(const uint8_t* source, - size_t source_length, + void ParseSlice(rtc::ArrayView slice); + Result ParseNonParameterSetNalu(rtc::ArrayView source, uint8_t nalu_type); // SPS/PPS state, updated when parsing new SPS/PPS, used to parse slices. diff --git a/third_party/libwebrtc/common_video/h264/h264_bitstream_parser_unittest.cc b/third_party/libwebrtc/common_video/h264/h264_bitstream_parser_unittest.cc index 3f4f202af275..e03da7a12fca 100644 --- a/third_party/libwebrtc/common_video/h264/h264_bitstream_parser_unittest.cc +++ b/third_party/libwebrtc/common_video/h264/h264_bitstream_parser_unittest.cc @@ -44,6 +44,70 @@ uint8_t kH264BitstreamNextImageSliceChunkCabac[] = { 0x70, 0xbf, 0xc1, 0x4a, 0x16, 0x8f, 0x51, 0xf4, 0xca, 0xfb, 0xa3, 0x65, }; +uint8_t kH264BitstreamWeightedPred[] = { + 0x00, 0x00, 0x00, 0x01, 0x67, 0x64, 0x00, 0x28, 0xac, 0xb4, 0x03, 0xc0, + 0x11, 0x3f, 0x2e, 0x02, 0xd4, 0x04, 0x04, 0x05, 0x00, 0x00, 0x03, 0x00, + 0x01, 0x00, 0x00, 0x03, 0x00, 0x30, 0x8f, 0x18, 0x32, 0xa0, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x68, 0xef, 0x3c, 0xb0, 0x00, 0x00, + 0x00, 0xc0, 0x00, 0x00, 0x00, 0x01, 0x41, 0x9a, 0x26, 0x21, 0xf7, 0xff, + 0xfe, 0x9e, 0x10, 0x00, 0x00, 0x08, 0x78, 0x00, 0x00, 0x00, 0x12}; + +// First 4 P frames of CVWP1_TOSHIBA_E test file. +uint8_t H264BitstreamCVWP1SPS[] = {0x00, 0x00, 0x00, 0x01, 0x27, 0x4d, 0x40, + 0x14, 0xd9, 0x81, 0x60, 0x94, 0x40}; + +uint8_t H264BitstreamCVWP1PFrame1[] = { + 0x00, 0x00, 0x00, 0x01, 0x28, 0xcf, 0x1b, 0x88, 0x00, 0x00, 0x00, + 0x01, 0x21, 0x9a, 0x21, 0x8f, 0x02, 0xd8, 0x1b, 0xe0, 0x2c, 0xc3, + 0x80, 0x20, 0x00, 0xe4, 0xcd, 0x72, 0xfe, 0x1c, 0xfc, 0x2a, 0x00, + 0x02, 0x00, 0x26, 0x09, 0x04, 0xc1, 0x38, 0xe2, 0x9b, 0xcc, 0x60, + 0x54, 0xee, 0x62, 0x6b, 0x00, 0x28, 0x86, 0xce, 0x81, 0x0f, 0xd2, + 0x17, 0x26, 0x0d, 0x2f, 0x1c, 0x1d, 0xe3, 0x80, 0x01}; + +uint8_t H264BitstreamCVWP1PFrame2[] = { + 0x00, 0x00, 0x00, 0x01, 0x28, 0xca, 0xc6, 0xe2, 0x00, 0x00, 0x00, + 0x01, 0x21, 0x9a, 0x41, 0xcb, 0x01, 0x8e, 0x02, 0x76, 0x28, 0x68, + 0x20, 0x01, 0x9a, 0x33, 0x60, 0x58, 0xc3, 0x0d, 0x7c, 0x32, 0x00, + 0x02, 0x00, 0x7c, 0x5d, 0xf7, 0x22, 0x6c, 0x3d, 0xa3, 0xcc, 0x60, + 0x5a, 0x3d, 0x98, 0x3b, 0xf0, 0x14, 0x48, 0x1b, 0xa0, 0xdf, 0x69, + 0xfc, 0xf2, 0x66, 0x21, 0x4d, 0x72, 0x99, 0xc2, 0x1c}; + +uint8_t H264BitstreamCVWP1PFrame3[] = { + 0x00, 0x00, 0x00, 0x01, 0x28, 0xcb, 0xc6, 0xe2, 0x00, 0x00, 0x00, + 0x01, 0x21, 0x9a, 0x61, 0xcf, 0x04, 0xc0, 0x24, 0x20, 0x33, 0xc0, + 0x5d, 0x80, 0x80, 0x05, 0x08, 0x0a, 0xb0, 0x30, 0x81, 0xf8, 0x0d, + 0x70, 0x13, 0xa0, 0x31, 0x8e, 0x86, 0x94, 0x6c, 0x43, 0xbb, 0x58, + 0x44, 0xc2, 0x41, 0x7c, 0x92, 0x04, 0x7e, 0x9f, 0xbf, 0x01, 0xe9, + 0xab, 0x53, 0xfe, 0x8f, 0x1c, 0x00, 0x04, 0x1f, 0x23}; + +uint8_t H264BitstreamCVWP1PFrame4[] = { + 0x00, 0x00, 0x00, 0x01, 0x28, 0xc9, 0x31, 0xb8, 0x80, 0x00, 0x00, + 0x00, 0x01, 0x21, 0x9a, 0x81, 0xe1, 0x04, 0xe0, 0x4f, 0x0f, 0x12, + 0xc6, 0x58, 0x74, 0x34, 0x06, 0x73, 0x9f, 0x43, 0xa7, 0xd0, 0x3c, + 0x9c, 0x9c, 0x92, 0x4f, 0x84, 0x4f, 0xd6, 0x36, 0x63, 0xff, 0xa0, + 0x5b, 0x1c, 0x6f, 0x01, 0x0b, 0xc2, 0x5e, 0x7b, 0xb0, 0xd7, 0x8f, + 0x19, 0x70, 0x81, 0xfa, 0x93, 0x4d, 0x48, 0x4f, 0xd2}; + +// First 2 B frames of CVWP2_TOSHIBA_E test file. +uint8_t H264BitstreamCVWP2SPS[] = {0x00, 0x00, 0x00, 0x01, 0x27, 0x4d, 0x40, + 0x14, 0xec, 0xc0, 0xb0, 0x4a, 0x20}; + +uint8_t H264BitstreamCVWP2BFrame1[] = { + 0x00, 0x00, 0x00, 0x01, 0x28, 0xce, 0x1b, 0x88, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x9a, 0x3e, 0x19, 0x69, 0xa1, 0xc4, 0x1e, 0x5d, 0xea, + 0x84, 0x1c, 0x10, 0x65, 0x87, 0xc0, 0x25, 0x1b, 0x6d, 0x1e, 0xcf, + 0xf9, 0x8d, 0xf1, 0x2f, 0xec, 0xf8, 0xc2, 0x07, 0xfe, 0x02, 0x27, + 0xec, 0xcb, 0x74, 0x75, 0x59, 0xd5, 0x6e, 0xc0, 0x01, 0x4b, 0xb2, + 0xe7, 0x68, 0xfe, 0xef, 0xaf, 0xb6, 0x76, 0xc6, 0xc5}; + +uint8_t H264BitstreamCVWP2BFrame2[] = { + 0x00, 0x00, 0x00, 0x01, 0x28, 0xce, 0x1b, 0x88, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x9a, 0x3e, 0x19, 0x69, 0xa1, 0xc4, 0x1e, 0x5d, 0xea, + 0x84, 0x1c, 0x10, 0x65, 0x87, 0xc0, 0x25, 0x1b, 0x6d, 0x1e, 0xcf, + 0xf9, 0x8d, 0xf1, 0x2f, 0xec, 0xf8, 0xc2, 0x07, 0xfe, 0x02, 0x27, + 0xec, 0xcb, 0x74, 0x75, 0x59, 0xd5, 0x6e, 0xc0, 0x01, 0x4b, 0xb2, + 0xe7, 0x68, 0xfe, 0xef, 0xaf, 0xb6, 0x76, 0xc6, 0xc5}; + TEST(H264BitstreamParserTest, ReportsNoQpWithoutParsedSlices) { H264BitstreamParser h264_parser; EXPECT_FALSE(h264_parser.GetLastSliceQp().has_value()); @@ -81,4 +145,55 @@ TEST(H264BitstreamParserTest, ReportsLastSliceQpForCABACImageSlices) { EXPECT_EQ(24, *qp); } +TEST(H264BitstreamParserTest, ReportsLastSliceQpForWeightedPredSlices) { + H264BitstreamParser h264_parser; + h264_parser.ParseBitstream(kH264BitstreamWeightedPred); + + absl::optional qp = h264_parser.GetLastSliceQp(); + ASSERT_TRUE(qp.has_value()); + EXPECT_EQ(11, *qp); +} + +TEST(H264BitstreamParserTest, ReportsLastSliceQpForWeightedPredSlicesL0Active) { + H264BitstreamParser h264_parser; + absl::optional qp; + h264_parser.ParseBitstream(H264BitstreamCVWP1SPS); + + h264_parser.ParseBitstream(H264BitstreamCVWP1PFrame1); + qp = h264_parser.GetLastSliceQp(); + ASSERT_TRUE(qp.has_value()); + EXPECT_EQ(25, *qp); + + h264_parser.ParseBitstream(H264BitstreamCVWP1PFrame2); + qp = h264_parser.GetLastSliceQp(); + ASSERT_TRUE(qp.has_value()); + EXPECT_EQ(25, *qp); + + h264_parser.ParseBitstream(H264BitstreamCVWP1PFrame3); + qp = h264_parser.GetLastSliceQp(); + ASSERT_TRUE(qp.has_value()); + EXPECT_EQ(25, *qp); + + h264_parser.ParseBitstream(H264BitstreamCVWP1PFrame4); + qp = h264_parser.GetLastSliceQp(); + ASSERT_TRUE(qp.has_value()); + EXPECT_EQ(25, *qp); +} + +TEST(H264BitstreamParserTest, ReportsLastSliceQpForWeightedPredSlicesL1Active) { + H264BitstreamParser h264_parser; + absl::optional qp; + h264_parser.ParseBitstream(H264BitstreamCVWP2SPS); + + h264_parser.ParseBitstream(H264BitstreamCVWP2BFrame1); + qp = h264_parser.GetLastSliceQp(); + ASSERT_TRUE(qp.has_value()); + EXPECT_EQ(25, *qp); + + h264_parser.ParseBitstream(H264BitstreamCVWP2BFrame1); + qp = h264_parser.GetLastSliceQp(); + ASSERT_TRUE(qp.has_value()); + EXPECT_EQ(25, *qp); +} + } // namespace webrtc diff --git a/third_party/libwebrtc/common_video/h264/h264_common.cc b/third_party/libwebrtc/common_video/h264/h264_common.cc index 06d94e03054e..b9316421a570 100644 --- a/third_party/libwebrtc/common_video/h264/h264_common.cc +++ b/third_party/libwebrtc/common_video/h264/h264_common.cc @@ -17,19 +17,18 @@ namespace H264 { const uint8_t kNaluTypeMask = 0x1F; -std::vector FindNaluIndices(const uint8_t* buffer, - size_t buffer_size) { +std::vector FindNaluIndices(rtc::ArrayView buffer) { // This is sorta like Boyer-Moore, but with only the first optimization step: // given a 3-byte sequence we're looking at, if the 3rd byte isn't 1 or 0, // skip ahead to the next 3-byte sequence. 0s and 1s are relatively rare, so // this will skip the majority of reads/checks. std::vector sequences; - if (buffer_size < kNaluShortStartSequenceSize) + if (buffer.size() < kNaluShortStartSequenceSize) return sequences; static_assert(kNaluShortStartSequenceSize >= 2, "kNaluShortStartSequenceSize must be larger or equals to 2"); - const size_t end = buffer_size - kNaluShortStartSequenceSize; + const size_t end = buffer.size() - kNaluShortStartSequenceSize; for (size_t i = 0; i < end;) { if (buffer[i + 2] > 1) { i += 3; @@ -57,7 +56,7 @@ std::vector FindNaluIndices(const uint8_t* buffer, // Update length of last entry, if any. auto it = sequences.rbegin(); if (it != sequences.rend()) - it->payload_size = buffer_size - it->payload_start_offset; + it->payload_size = buffer.size() - it->payload_start_offset; return sequences; } @@ -66,16 +65,16 @@ NaluType ParseNaluType(uint8_t data) { return static_cast(data & kNaluTypeMask); } -std::vector ParseRbsp(const uint8_t* data, size_t length) { +std::vector ParseRbsp(rtc::ArrayView data) { std::vector out; - out.reserve(length); + out.reserve(data.size()); - for (size_t i = 0; i < length;) { + for (size_t i = 0; i < data.size();) { // Be careful about over/underflow here. byte_length_ - 3 can underflow, and // i + 3 can overflow, but byte_length_ - i can't, because i < byte_length_ // above, and that expression will produce the number of bytes left in // the stream including the byte at i. - if (length - i >= 3 && !data[i] && !data[i + 1] && data[i + 2] == 3) { + if (data.size() - i >= 3 && !data[i] && !data[i + 1] && data[i + 2] == 3) { // Two rbsp bytes. out.push_back(data[i++]); out.push_back(data[i++]); @@ -89,14 +88,13 @@ std::vector ParseRbsp(const uint8_t* data, size_t length) { return out; } -void WriteRbsp(const uint8_t* bytes, size_t length, rtc::Buffer* destination) { +void WriteRbsp(rtc::ArrayView bytes, rtc::Buffer* destination) { static const uint8_t kZerosInStartSequence = 2; static const uint8_t kEmulationByte = 0x03u; size_t num_consecutive_zeros = 0; - destination->EnsureCapacity(destination->size() + length); + destination->EnsureCapacity(destination->size() + bytes.size()); - for (size_t i = 0; i < length; ++i) { - uint8_t byte = bytes[i]; + for (uint8_t byte : bytes) { if (byte <= kEmulationByte && num_consecutive_zeros >= kZerosInStartSequence) { // Need to escape. diff --git a/third_party/libwebrtc/common_video/h264/h264_common.h b/third_party/libwebrtc/common_video/h264/h264_common.h index 1bc9867d3fbe..e7df02ae0d06 100644 --- a/third_party/libwebrtc/common_video/h264/h264_common.h +++ b/third_party/libwebrtc/common_video/h264/h264_common.h @@ -33,6 +33,9 @@ const size_t kNaluShortStartSequenceSize = 3; // The size of the NALU type byte (1). const size_t kNaluTypeSize = 1; +// Maximum reference index for reference pictures. +constexpr int kMaxReferenceIndex = 31; + enum NaluType : uint8_t { kSlice = 1, kIdr = 5, @@ -60,8 +63,14 @@ struct NaluIndex { }; // Returns a vector of the NALU indices in the given buffer. -RTC_EXPORT std::vector FindNaluIndices(const uint8_t* buffer, - size_t buffer_size); +RTC_EXPORT std::vector FindNaluIndices( + rtc::ArrayView buffer); + +// TODO: bugs.webrtc.org/42225170 - Deprecate. +inline std::vector FindNaluIndices(const uint8_t* buffer, + size_t buffer_size) { + return FindNaluIndices(rtc::MakeArrayView(buffer, buffer_size)); +} // Get the NAL type from the header byte immediately following start sequence. RTC_EXPORT NaluType ParseNaluType(uint8_t data); @@ -80,12 +89,24 @@ RTC_EXPORT NaluType ParseNaluType(uint8_t data); // the 03 emulation byte. // Parse the given data and remove any emulation byte escaping. -std::vector ParseRbsp(const uint8_t* data, size_t length); +std::vector ParseRbsp(rtc::ArrayView data); + +// TODO: bugs.webrtc.org/42225170 - Deprecate. +inline std::vector ParseRbsp(const uint8_t* data, size_t length) { + return ParseRbsp(rtc::MakeArrayView(data, length)); +} // Write the given data to the destination buffer, inserting and emulation // bytes in order to escape any data the could be interpreted as a start // sequence. -void WriteRbsp(const uint8_t* bytes, size_t length, rtc::Buffer* destination); +void WriteRbsp(rtc::ArrayView bytes, rtc::Buffer* destination); + +// TODO: bugs.webrtc.org/42225170 - Deprecate. +inline void WriteRbsp(const uint8_t* bytes, + size_t length, + rtc::Buffer* destination) { + WriteRbsp(rtc::MakeArrayView(bytes, length), destination); +} } // namespace H264 } // namespace webrtc diff --git a/third_party/libwebrtc/common_video/h264/pps_parser.cc b/third_party/libwebrtc/common_video/h264/pps_parser.cc index 2fc9749e8caf..f12ab9b0afab 100644 --- a/third_party/libwebrtc/common_video/h264/pps_parser.cc +++ b/third_party/libwebrtc/common_video/h264/pps_parser.cc @@ -29,16 +29,15 @@ constexpr int kMinPicInitQpDeltaValue = -26; // You can find it on this page: // http://www.itu.int/rec/T-REC-H.264 -absl::optional PpsParser::ParsePps(const uint8_t* data, - size_t length) { +absl::optional PpsParser::ParsePps( + rtc::ArrayView data) { // First, parse out rbsp, which is basically the source buffer minus emulation // bytes (the last byte of a 0x00 0x00 0x03 sequence). RBSP is defined in // section 7.3.1 of the H.264 standard. - return ParseInternal(H264::ParseRbsp(data, length)); + return ParseInternal(H264::ParseRbsp(data)); } -bool PpsParser::ParsePpsIds(const uint8_t* data, - size_t length, +bool PpsParser::ParsePpsIds(rtc::ArrayView data, uint32_t* pps_id, uint32_t* sps_id) { RTC_DCHECK(pps_id); @@ -46,28 +45,32 @@ bool PpsParser::ParsePpsIds(const uint8_t* data, // First, parse out rbsp, which is basically the source buffer minus emulation // bytes (the last byte of a 0x00 0x00 0x03 sequence). RBSP is defined in // section 7.3.1 of the H.264 standard. - std::vector unpacked_buffer = H264::ParseRbsp(data, length); + std::vector unpacked_buffer = H264::ParseRbsp(data); BitstreamReader reader(unpacked_buffer); *pps_id = reader.ReadExponentialGolomb(); *sps_id = reader.ReadExponentialGolomb(); return reader.Ok(); } -absl::optional PpsParser::ParsePpsIdFromSlice(const uint8_t* data, - size_t length) { - std::vector unpacked_buffer = H264::ParseRbsp(data, length); +absl::optional PpsParser::ParseSliceHeader( + rtc::ArrayView data) { + std::vector unpacked_buffer = H264::ParseRbsp(data); BitstreamReader slice_reader(unpacked_buffer); + PpsParser::SliceHeader slice_header; // first_mb_in_slice: ue(v) - slice_reader.ReadExponentialGolomb(); + slice_header.first_mb_in_slice = slice_reader.ReadExponentialGolomb(); // slice_type: ue(v) slice_reader.ReadExponentialGolomb(); // pic_parameter_set_id: ue(v) - uint32_t slice_pps_id = slice_reader.ReadExponentialGolomb(); + slice_header.pic_parameter_set_id = slice_reader.ReadExponentialGolomb(); + + // The rest of the slice header requires information from the SPS to parse. + if (!slice_reader.Ok()) { return absl::nullopt; } - return slice_pps_id; + return slice_header; } absl::optional PpsParser::ParseInternal( @@ -126,9 +129,13 @@ absl::optional PpsParser::ParseInternal( } } // num_ref_idx_l0_default_active_minus1: ue(v) - reader.ReadExponentialGolomb(); + pps.num_ref_idx_l0_default_active_minus1 = reader.ReadExponentialGolomb(); // num_ref_idx_l1_default_active_minus1: ue(v) - reader.ReadExponentialGolomb(); + pps.num_ref_idx_l1_default_active_minus1 = reader.ReadExponentialGolomb(); + if (pps.num_ref_idx_l0_default_active_minus1 > H264::kMaxReferenceIndex || + pps.num_ref_idx_l1_default_active_minus1 > H264::kMaxReferenceIndex) { + return absl::nullopt; + } // weighted_pred_flag: u(1) pps.weighted_pred_flag = reader.Read(); // weighted_bipred_idc: u(2) diff --git a/third_party/libwebrtc/common_video/h264/pps_parser.h b/third_party/libwebrtc/common_video/h264/pps_parser.h index 52717dcc2660..d2d0609ab994 100644 --- a/third_party/libwebrtc/common_video/h264/pps_parser.h +++ b/third_party/libwebrtc/common_video/h264/pps_parser.h @@ -30,6 +30,8 @@ class PpsParser { bool bottom_field_pic_order_in_frame_present_flag = false; bool weighted_pred_flag = false; bool entropy_coding_mode_flag = false; + uint32_t num_ref_idx_l0_default_active_minus1 = 0; + uint32_t num_ref_idx_l1_default_active_minus1 = 0; uint32_t weighted_bipred_idc = false; uint32_t redundant_pic_cnt_present_flag = 0; int pic_init_qp_minus26 = 0; @@ -37,16 +39,27 @@ class PpsParser { uint32_t sps_id = 0; }; - // Unpack RBSP and parse PPS state from the supplied buffer. - static absl::optional ParsePps(const uint8_t* data, size_t length); + struct SliceHeader { + SliceHeader() = default; - static bool ParsePpsIds(const uint8_t* data, - size_t length, + uint32_t first_mb_in_slice = 0; + uint32_t pic_parameter_set_id = 0; + }; + + // Unpack RBSP and parse PPS state from the supplied buffer. + static absl::optional ParsePps(rtc::ArrayView data); + // TODO: bugs.webrtc.org/42225170 - Deprecate. + static inline absl::optional ParsePps(const uint8_t* data, + size_t length) { + return ParsePps(rtc::MakeArrayView(data, length)); + } + + static bool ParsePpsIds(rtc::ArrayView data, uint32_t* pps_id, uint32_t* sps_id); - static absl::optional ParsePpsIdFromSlice(const uint8_t* data, - size_t length); + static absl::optional ParseSliceHeader( + rtc::ArrayView data); protected: // Parse the PPS state, for a buffer where RBSP decoding has already been diff --git a/third_party/libwebrtc/common_video/h264/pps_parser_unittest.cc b/third_party/libwebrtc/common_video/h264/pps_parser_unittest.cc index 4fe742d2e62f..a325e8ccf412 100644 --- a/third_party/libwebrtc/common_video/h264/pps_parser_unittest.cc +++ b/third_party/libwebrtc/common_video/h264/pps_parser_unittest.cc @@ -106,9 +106,9 @@ void WritePps(const PpsParser::PpsState& pps, } // num_ref_idx_l0_default_active_minus1: ue(v) - bit_buffer.WriteExponentialGolomb(kIgnored); + bit_buffer.WriteExponentialGolomb(pps.num_ref_idx_l0_default_active_minus1); // num_ref_idx_l1_default_active_minus1: ue(v) - bit_buffer.WriteExponentialGolomb(kIgnored); + bit_buffer.WriteExponentialGolomb(pps.num_ref_idx_l1_default_active_minus1); // weighted_pred_flag: u(1) bit_buffer.WriteBits(pps.weighted_pred_flag ? 1 : 0, 1); // weighted_bipred_idc: u(2) @@ -134,7 +134,7 @@ void WritePps(const PpsParser::PpsState& pps, bit_buffer.GetCurrentOffset(&byte_offset, &bit_offset); } - H264::WriteRbsp(data, byte_offset, out_buffer); + H264::WriteRbsp(rtc::MakeArrayView(data, byte_offset), out_buffer); } class PpsParserTest : public ::testing::Test { @@ -175,10 +175,14 @@ class PpsParserTest : public ::testing::Test { buffer_.Clear(); WritePps(pps, slice_group_map_type, num_slice_groups, pic_size_in_map_units, &buffer_); - parsed_pps_ = PpsParser::ParsePps(buffer_.data(), buffer_.size()); + parsed_pps_ = PpsParser::ParsePps(buffer_); ASSERT_TRUE(parsed_pps_); EXPECT_EQ(pps.bottom_field_pic_order_in_frame_present_flag, parsed_pps_->bottom_field_pic_order_in_frame_present_flag); + EXPECT_EQ(pps.num_ref_idx_l0_default_active_minus1, + parsed_pps_->num_ref_idx_l0_default_active_minus1); + EXPECT_EQ(pps.num_ref_idx_l1_default_active_minus1, + parsed_pps_->num_ref_idx_l1_default_active_minus1); EXPECT_EQ(pps.weighted_pred_flag, parsed_pps_->weighted_pred_flag); EXPECT_EQ(pps.weighted_bipred_idc, parsed_pps_->weighted_bipred_idc); EXPECT_EQ(pps.entropy_coding_mode_flag, @@ -214,17 +218,21 @@ TEST_F(PpsParserTest, MaxPps) { RunTest(); } -TEST_F(PpsParserTest, PpsIdFromSlice) { - std::vector nalu_indices = - H264::FindNaluIndices(kH264BitstreamChunk, sizeof(kH264BitstreamChunk)); +TEST_F(PpsParserTest, ParseSliceHeader) { + rtc::ArrayView chunk(kH264BitstreamChunk); + std::vector nalu_indices = H264::FindNaluIndices(chunk); EXPECT_EQ(nalu_indices.size(), 3ull); for (const auto& index : nalu_indices) { H264::NaluType nalu_type = - H264::ParseNaluType(kH264BitstreamChunk[index.payload_start_offset]); + H264::ParseNaluType(chunk[index.payload_start_offset]); if (nalu_type == H264::NaluType::kIdr) { - absl::optional pps_id = PpsParser::ParsePpsIdFromSlice( - kH264BitstreamChunk + index.payload_start_offset, index.payload_size); - EXPECT_EQ(pps_id, 0u); + // Skip NAL type header and parse slice header. + absl::optional slice_header = + PpsParser::ParseSliceHeader(chunk.subview( + index.payload_start_offset + 1, index.payload_size - 1)); + ASSERT_TRUE(slice_header.has_value()); + EXPECT_EQ(slice_header->first_mb_in_slice, 0u); + EXPECT_EQ(slice_header->pic_parameter_set_id, 0u); break; } } diff --git a/third_party/libwebrtc/common_video/h264/sps_parser.cc b/third_party/libwebrtc/common_video/h264/sps_parser.cc index e14334249c8f..000e028a3200 100644 --- a/third_party/libwebrtc/common_video/h264/sps_parser.cc +++ b/third_party/libwebrtc/common_video/h264/sps_parser.cc @@ -32,9 +32,9 @@ SpsParser::SpsState::~SpsState() = default; // http://www.itu.int/rec/T-REC-H.264 // Unpack RBSP and parse SPS state from the supplied buffer. -absl::optional SpsParser::ParseSps(const uint8_t* data, - size_t length) { - std::vector unpacked_buffer = H264::ParseRbsp(data, length); +absl::optional SpsParser::ParseSps( + rtc::ArrayView data) { + std::vector unpacked_buffer = H264::ParseRbsp(data); BitstreamReader reader(unpacked_buffer); return ParseSpsUpToVui(reader); } @@ -56,7 +56,7 @@ absl::optional SpsParser::ParseSpsUpToVui( // chroma_format_idc will be ChromaArrayType if separate_colour_plane_flag is // 0. It defaults to 1, when not specified. - uint32_t chroma_format_idc = 1; + sps.chroma_format_idc = 1; // profile_idc: u(8). We need it to determine if we need to read/skip chroma // formats. @@ -73,8 +73,8 @@ absl::optional SpsParser::ParseSpsUpToVui( profile_idc == 86 || profile_idc == 118 || profile_idc == 128 || profile_idc == 138 || profile_idc == 139 || profile_idc == 134) { // chroma_format_idc: ue(v) - chroma_format_idc = reader.ReadExponentialGolomb(); - if (chroma_format_idc == 3) { + sps.chroma_format_idc = reader.ReadExponentialGolomb(); + if (sps.chroma_format_idc == 3) { // separate_colour_plane_flag: u(1) sps.separate_colour_plane_flag = reader.ReadBit(); } @@ -89,7 +89,7 @@ absl::optional SpsParser::ParseSpsUpToVui( // Process the scaling lists just enough to be able to properly // skip over them, so we can still read the resolution on streams // where this is included. - int scaling_list_count = (chroma_format_idc == 3 ? 12 : 8); + int scaling_list_count = (sps.chroma_format_idc == 3 ? 12 : 8); for (int i = 0; i < scaling_list_count; ++i) { // seq_scaling_list_present_flag[i] : u(1) if (reader.Read()) { @@ -202,17 +202,17 @@ absl::optional SpsParser::ParseSpsUpToVui( // Figure out the crop units in pixels. That's based on the chroma format's // sampling, which is indicated by chroma_format_idc. - if (sps.separate_colour_plane_flag || chroma_format_idc == 0) { + if (sps.separate_colour_plane_flag || sps.chroma_format_idc == 0) { frame_crop_bottom_offset *= (2 - sps.frame_mbs_only_flag); frame_crop_top_offset *= (2 - sps.frame_mbs_only_flag); - } else if (!sps.separate_colour_plane_flag && chroma_format_idc > 0) { + } else if (!sps.separate_colour_plane_flag && sps.chroma_format_idc > 0) { // Width multipliers for formats 1 (4:2:0) and 2 (4:2:2). - if (chroma_format_idc == 1 || chroma_format_idc == 2) { + if (sps.chroma_format_idc == 1 || sps.chroma_format_idc == 2) { frame_crop_left_offset *= 2; frame_crop_right_offset *= 2; } // Height multipliers for format 1 (4:2:0). - if (chroma_format_idc == 1) { + if (sps.chroma_format_idc == 1) { frame_crop_top_offset *= 2; frame_crop_bottom_offset *= 2; } diff --git a/third_party/libwebrtc/common_video/h264/sps_parser.h b/third_party/libwebrtc/common_video/h264/sps_parser.h index a69bd19690ed..ae8df6079265 100644 --- a/third_party/libwebrtc/common_video/h264/sps_parser.h +++ b/third_party/libwebrtc/common_video/h264/sps_parser.h @@ -30,6 +30,7 @@ class RTC_EXPORT SpsParser { uint32_t width = 0; uint32_t height = 0; uint32_t delta_pic_order_always_zero_flag = 0; + uint32_t chroma_format_idc = 1; uint32_t separate_colour_plane_flag = 0; uint32_t frame_mbs_only_flag = 0; uint32_t log2_max_frame_num = 4; // Smallest valid value. @@ -41,7 +42,12 @@ class RTC_EXPORT SpsParser { }; // Unpack RBSP and parse SPS state from the supplied buffer. - static absl::optional ParseSps(const uint8_t* data, size_t length); + static absl::optional ParseSps(rtc::ArrayView data); + // TODO: bugs.webrtc.org/42225170 - Deprecate. + static inline absl::optional ParseSps(const uint8_t* data, + size_t length) { + return ParseSps(rtc::MakeArrayView(data, length)); + } protected: // Parse the SPS state, up till the VUI part, for a buffer where RBSP diff --git a/third_party/libwebrtc/common_video/h264/sps_parser_unittest.cc b/third_party/libwebrtc/common_video/h264/sps_parser_unittest.cc index c9326e4b28d9..721aa3f05139 100644 --- a/third_party/libwebrtc/common_video/h264/sps_parser_unittest.cc +++ b/third_party/libwebrtc/common_video/h264/sps_parser_unittest.cc @@ -107,7 +107,7 @@ void GenerateFakeSps(uint16_t width, } out_buffer->Clear(); - H264::WriteRbsp(rbsp, byte_count, out_buffer); + H264::WriteRbsp(rtc::MakeArrayView(rbsp, byte_count), out_buffer); } TEST(H264SpsParserTest, TestSampleSPSHdLandscape) { @@ -116,8 +116,7 @@ TEST(H264SpsParserTest, TestSampleSPSHdLandscape) { const uint8_t buffer[] = {0x7A, 0x00, 0x1F, 0xBC, 0xD9, 0x40, 0x50, 0x05, 0xBA, 0x10, 0x00, 0x00, 0x03, 0x00, 0xC0, 0x00, 0x00, 0x2A, 0xE0, 0xF1, 0x83, 0x19, 0x60}; - absl::optional sps = - SpsParser::ParseSps(buffer, arraysize(buffer)); + absl::optional sps = SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(1280u, sps->width); EXPECT_EQ(720u, sps->height); @@ -129,8 +128,7 @@ TEST(H264SpsParserTest, TestSampleSPSVgaLandscape) { const uint8_t buffer[] = {0x7A, 0x00, 0x1E, 0xBC, 0xD9, 0x40, 0xA0, 0x2F, 0xF8, 0x98, 0x40, 0x00, 0x00, 0x03, 0x01, 0x80, 0x00, 0x00, 0x56, 0x83, 0xC5, 0x8B, 0x65, 0x80}; - absl::optional sps = - SpsParser::ParseSps(buffer, arraysize(buffer)); + absl::optional sps = SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(640u, sps->width); EXPECT_EQ(360u, sps->height); @@ -142,8 +140,7 @@ TEST(H264SpsParserTest, TestSampleSPSWeirdResolution) { const uint8_t buffer[] = {0x7A, 0x00, 0x0D, 0xBC, 0xD9, 0x43, 0x43, 0x3E, 0x5E, 0x10, 0x00, 0x00, 0x03, 0x00, 0x60, 0x00, 0x00, 0x15, 0xA0, 0xF1, 0x42, 0x99, 0x60}; - absl::optional sps = - SpsParser::ParseSps(buffer, arraysize(buffer)); + absl::optional sps = SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(200u, sps->width); EXPECT_EQ(400u, sps->height); @@ -152,8 +149,7 @@ TEST(H264SpsParserTest, TestSampleSPSWeirdResolution) { TEST(H264SpsParserTest, TestSyntheticSPSQvgaLandscape) { rtc::Buffer buffer; GenerateFakeSps(320u, 180u, 1, 0, 0, &buffer); - absl::optional sps = - SpsParser::ParseSps(buffer.data(), buffer.size()); + absl::optional sps = SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(320u, sps->width); EXPECT_EQ(180u, sps->height); @@ -163,8 +159,7 @@ TEST(H264SpsParserTest, TestSyntheticSPSQvgaLandscape) { TEST(H264SpsParserTest, TestSyntheticSPSWeirdResolution) { rtc::Buffer buffer; GenerateFakeSps(156u, 122u, 2, 0, 0, &buffer); - absl::optional sps = - SpsParser::ParseSps(buffer.data(), buffer.size()); + absl::optional sps = SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(156u, sps->width); EXPECT_EQ(122u, sps->height); @@ -178,8 +173,7 @@ TEST(H264SpsParserTest, TestSampleSPSWithScalingLists) { 0x10, 0xc2, 0x00, 0x84, 0x3b, 0x50, 0x3c, 0x01, 0x13, 0xf2, 0xcd, 0xc0, 0x40, 0x40, 0x50, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x01, 0xe8, 0x40}; - absl::optional sps = - SpsParser::ParseSps(buffer, arraysize(buffer)); + absl::optional sps = SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(1920u, sps->width); EXPECT_EQ(1080u, sps->height); @@ -188,8 +182,7 @@ TEST(H264SpsParserTest, TestSampleSPSWithScalingLists) { TEST(H264SpsParserTest, TestLog2MaxFrameNumMinus4) { rtc::Buffer buffer; GenerateFakeSps(320u, 180u, 1, 0, 0, &buffer); - absl::optional sps = - SpsParser::ParseSps(buffer.data(), buffer.size()); + absl::optional sps = SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(320u, sps->width); EXPECT_EQ(180u, sps->height); @@ -197,7 +190,7 @@ TEST(H264SpsParserTest, TestLog2MaxFrameNumMinus4) { EXPECT_EQ(4u, sps->log2_max_frame_num); GenerateFakeSps(320u, 180u, 1, 12, 0, &buffer); - sps = SpsParser::ParseSps(buffer.data(), buffer.size()); + sps = SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(320u, sps->width); EXPECT_EQ(180u, sps->height); @@ -205,14 +198,13 @@ TEST(H264SpsParserTest, TestLog2MaxFrameNumMinus4) { EXPECT_EQ(16u, sps->log2_max_frame_num); GenerateFakeSps(320u, 180u, 1, 13, 0, &buffer); - EXPECT_FALSE(SpsParser::ParseSps(buffer.data(), buffer.size())); + EXPECT_FALSE(SpsParser::ParseSps(buffer)); } TEST(H264SpsParserTest, TestLog2MaxPicOrderCntMinus4) { rtc::Buffer buffer; GenerateFakeSps(320u, 180u, 1, 0, 0, &buffer); - absl::optional sps = - SpsParser::ParseSps(buffer.data(), buffer.size()); + absl::optional sps = SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(320u, sps->width); EXPECT_EQ(180u, sps->height); @@ -220,15 +212,14 @@ TEST(H264SpsParserTest, TestLog2MaxPicOrderCntMinus4) { EXPECT_EQ(4u, sps->log2_max_pic_order_cnt_lsb); GenerateFakeSps(320u, 180u, 1, 0, 12, &buffer); - EXPECT_TRUE(static_cast( - sps = SpsParser::ParseSps(buffer.data(), buffer.size()))); + EXPECT_TRUE(static_cast(sps = SpsParser::ParseSps(buffer))); EXPECT_EQ(320u, sps->width); EXPECT_EQ(180u, sps->height); EXPECT_EQ(1u, sps->id); EXPECT_EQ(16u, sps->log2_max_pic_order_cnt_lsb); GenerateFakeSps(320u, 180u, 1, 0, 13, &buffer); - EXPECT_FALSE(SpsParser::ParseSps(buffer.data(), buffer.size())); + EXPECT_FALSE(SpsParser::ParseSps(buffer)); } } // namespace webrtc diff --git a/third_party/libwebrtc/common_video/h264/sps_vui_rewriter.cc b/third_party/libwebrtc/common_video/h264/sps_vui_rewriter.cc index 117e92a1e59d..fa824a597dd6 100644 --- a/third_party/libwebrtc/common_video/h264/sps_vui_rewriter.cc +++ b/third_party/libwebrtc/common_video/h264/sps_vui_rewriter.cc @@ -135,14 +135,13 @@ void SpsVuiRewriter::UpdateStats(ParseResult result, Direction direction) { } SpsVuiRewriter::ParseResult SpsVuiRewriter::ParseAndRewriteSps( - const uint8_t* buffer, - size_t length, + rtc::ArrayView buffer, absl::optional* sps, const webrtc::ColorSpace* color_space, rtc::Buffer* destination) { // Create temporary RBSP decoded buffer of the payload (exlcuding the // leading nalu type header byte (the SpsParser uses only the payload). - std::vector rbsp_buffer = H264::ParseRbsp(buffer, length); + std::vector rbsp_buffer = H264::ParseRbsp(buffer); BitstreamReader source_buffer(rbsp_buffer); absl::optional sps_state = SpsParser::ParseSpsUpToVui(source_buffer); @@ -153,7 +152,7 @@ SpsVuiRewriter::ParseResult SpsVuiRewriter::ParseAndRewriteSps( // We're going to completely muck up alignment, so we need a BitBufferWriter // to write with. - rtc::Buffer out_buffer(length + kMaxVuiSpsIncrease); + rtc::Buffer out_buffer(buffer.size() + kMaxVuiSpsIncrease); rtc::BitBufferWriter sps_writer(out_buffer.data(), out_buffer.size()); // Check how far the SpsParser has read, and copy that data in bulk. @@ -200,26 +199,25 @@ SpsVuiRewriter::ParseResult SpsVuiRewriter::ParseAndRewriteSps( bit_offset = 0; } - RTC_DCHECK(byte_offset <= length + kMaxVuiSpsIncrease); + RTC_DCHECK(byte_offset <= buffer.size() + kMaxVuiSpsIncrease); RTC_CHECK(destination != nullptr); out_buffer.SetSize(byte_offset); // Write updates SPS to destination with added RBSP - H264::WriteRbsp(out_buffer.data(), out_buffer.size(), destination); + H264::WriteRbsp(out_buffer, destination); return ParseResult::kVuiRewritten; } SpsVuiRewriter::ParseResult SpsVuiRewriter::ParseAndRewriteSps( - const uint8_t* buffer, - size_t length, + rtc::ArrayView buffer, absl::optional* sps, const webrtc::ColorSpace* color_space, rtc::Buffer* destination, Direction direction) { ParseResult result = - ParseAndRewriteSps(buffer, length, sps, color_space, destination); + ParseAndRewriteSps(buffer, sps, color_space, destination); UpdateStats(result, direction); return result; } @@ -227,22 +225,23 @@ SpsVuiRewriter::ParseResult SpsVuiRewriter::ParseAndRewriteSps( rtc::Buffer SpsVuiRewriter::ParseOutgoingBitstreamAndRewrite( rtc::ArrayView buffer, const webrtc::ColorSpace* color_space) { - std::vector nalus = - H264::FindNaluIndices(buffer.data(), buffer.size()); + std::vector nalus = H264::FindNaluIndices(buffer); // Allocate some extra space for potentially adding a missing VUI. rtc::Buffer output_buffer(/*size=*/0, /*capacity=*/buffer.size() + nalus.size() * kMaxVuiSpsIncrease); - for (const H264::NaluIndex& nalu : nalus) { + for (const H264::NaluIndex& nalu_index : nalus) { // Copy NAL unit start code. - const uint8_t* start_code_ptr = buffer.data() + nalu.start_offset; - const size_t start_code_length = - nalu.payload_start_offset - nalu.start_offset; - const uint8_t* nalu_ptr = buffer.data() + nalu.payload_start_offset; - const size_t nalu_length = nalu.payload_size; - - if (H264::ParseNaluType(nalu_ptr[0]) == H264::NaluType::kSps) { + rtc::ArrayView start_code = buffer.subview( + nalu_index.start_offset, + nalu_index.payload_start_offset - nalu_index.start_offset); + rtc::ArrayView nalu = buffer.subview( + nalu_index.payload_start_offset, nalu_index.payload_size); + if (nalu.empty()) { + continue; + } + if (H264::ParseNaluType(nalu[0]) == H264::NaluType::kSps) { // Check if stream uses picture order count type 0, and if so rewrite it // to enable faster decoding. Streams in that format incur additional // delay because it allows decode order to differ from render order. @@ -259,24 +258,24 @@ rtc::Buffer SpsVuiRewriter::ParseOutgoingBitstreamAndRewrite( // Add the type header to the output buffer first, so that the rewriter // can append modified payload on top of that. - output_nalu.AppendData(nalu_ptr[0]); + output_nalu.AppendData(nalu[0]); - ParseResult result = ParseAndRewriteSps( - nalu_ptr + H264::kNaluTypeSize, nalu_length - H264::kNaluTypeSize, - &sps, color_space, &output_nalu, Direction::kOutgoing); + ParseResult result = + ParseAndRewriteSps(nalu.subview(H264::kNaluTypeSize), &sps, + color_space, &output_nalu, Direction::kOutgoing); if (result == ParseResult::kVuiRewritten) { - output_buffer.AppendData(start_code_ptr, start_code_length); + output_buffer.AppendData(start_code); output_buffer.AppendData(output_nalu.data(), output_nalu.size()); continue; } - } else if (H264::ParseNaluType(nalu_ptr[0]) == H264::NaluType::kAud) { + } else if (H264::ParseNaluType(nalu[0]) == H264::NaluType::kAud) { // Skip the access unit delimiter copy. continue; } // vui wasn't rewritten and it is not aud, copy the nal unit as is. - output_buffer.AppendData(start_code_ptr, start_code_length); - output_buffer.AppendData(nalu_ptr, nalu_length); + output_buffer.AppendData(start_code); + output_buffer.AppendData(nalu); } return output_buffer; } diff --git a/third_party/libwebrtc/common_video/h264/sps_vui_rewriter.h b/third_party/libwebrtc/common_video/h264/sps_vui_rewriter.h index ef80d5b60e98..66f7e1a70280 100644 --- a/third_party/libwebrtc/common_video/h264/sps_vui_rewriter.h +++ b/third_party/libwebrtc/common_video/h264/sps_vui_rewriter.h @@ -43,8 +43,7 @@ class SpsVuiRewriter : private SpsParser { // (NALU start, type, Stap-A, etc) have already been parsed and that RBSP // decoding has been performed. static ParseResult ParseAndRewriteSps( - const uint8_t* buffer, - size_t length, + rtc::ArrayView buffer, absl::optional* sps, const ColorSpace* color_space, rtc::Buffer* destination, @@ -58,8 +57,7 @@ class SpsVuiRewriter : private SpsParser { private: static ParseResult ParseAndRewriteSps( - const uint8_t* buffer, - size_t length, + rtc::ArrayView buffer, absl::optional* sps, const ColorSpace* color_space, rtc::Buffer* destination); diff --git a/third_party/libwebrtc/common_video/h264/sps_vui_rewriter_unittest.cc b/third_party/libwebrtc/common_video/h264/sps_vui_rewriter_unittest.cc index 2907949e6c4a..d440905375d9 100644 --- a/third_party/libwebrtc/common_video/h264/sps_vui_rewriter_unittest.cc +++ b/third_party/libwebrtc/common_video/h264/sps_vui_rewriter_unittest.cc @@ -297,7 +297,7 @@ void GenerateFakeSps(const VuiHeader& vui, rtc::Buffer* out_buffer) { byte_count++; } - H264::WriteRbsp(rbsp, byte_count, out_buffer); + H264::WriteRbsp(rtc::MakeArrayView(rbsp, byte_count), out_buffer); } void TestSps(const VuiHeader& vui, @@ -310,8 +310,8 @@ void TestSps(const VuiHeader& vui, absl::optional sps; rtc::Buffer rewritten_sps; SpsVuiRewriter::ParseResult result = SpsVuiRewriter::ParseAndRewriteSps( - original_sps.data(), original_sps.size(), &sps, color_space, - &rewritten_sps, SpsVuiRewriter::Direction::kIncoming); + original_sps, &sps, color_space, &rewritten_sps, + SpsVuiRewriter::Direction::kIncoming); EXPECT_EQ(expected_parse_result, result); ASSERT_TRUE(sps); EXPECT_EQ(sps->width, kWidth); @@ -324,7 +324,7 @@ void TestSps(const VuiHeader& vui, // Ensure that added/rewritten SPS is parsable. rtc::Buffer tmp; result = SpsVuiRewriter::ParseAndRewriteSps( - rewritten_sps.data(), rewritten_sps.size(), &sps, nullptr, &tmp, + rewritten_sps, &sps, nullptr, &tmp, SpsVuiRewriter::Direction::kIncoming); EXPECT_EQ(SpsVuiRewriter::ParseResult::kVuiOk, result); ASSERT_TRUE(sps); 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 3287d78e6b08..c9d17b062836 100644 --- a/third_party/libwebrtc/common_video/h265/h265_bitstream_parser.cc +++ b/third_party/libwebrtc/common_video/h265/h265_bitstream_parser.cc @@ -80,13 +80,11 @@ H265BitstreamParser::~H265BitstreamParser() = default; // section 7.3.6.1. You can find it on this page: // http://www.itu.int/rec/T-REC-H.265 H265BitstreamParser::Result H265BitstreamParser::ParseNonParameterSetNalu( - const uint8_t* source, - size_t source_length, + rtc::ArrayView source, uint8_t nalu_type) { last_slice_qp_delta_ = absl::nullopt; last_slice_pps_id_ = absl::nullopt; - const std::vector slice_rbsp = - H265::ParseRbsp(source, source_length); + const std::vector slice_rbsp = H265::ParseRbsp(source); if (slice_rbsp.size() < H265::kNaluHeaderSize) return kInvalidStream; @@ -420,14 +418,14 @@ const H265SpsParser::SpsState* H265BitstreamParser::GetSPS(uint32_t id) const { return &it->second; } -void H265BitstreamParser::ParseSlice(const uint8_t* slice, size_t length) { +void H265BitstreamParser::ParseSlice(rtc::ArrayView slice) { H265::NaluType nalu_type = H265::ParseNaluType(slice[0]); switch (nalu_type) { case H265::NaluType::kVps: { absl::optional vps_state; - if (length >= H265::kNaluHeaderSize) { - vps_state = H265VpsParser::ParseVps(slice + H265::kNaluHeaderSize, - length - H265::kNaluHeaderSize); + if (slice.size() >= H265::kNaluHeaderSize) { + vps_state = + H265VpsParser::ParseVps(slice.subview(H265::kNaluHeaderSize)); } if (!vps_state) { @@ -439,9 +437,9 @@ void H265BitstreamParser::ParseSlice(const uint8_t* slice, size_t length) { } case H265::NaluType::kSps: { absl::optional sps_state; - if (length >= H265::kNaluHeaderSize) { - sps_state = H265SpsParser::ParseSps(slice + H265::kNaluHeaderSize, - length - H265::kNaluHeaderSize); + if (slice.size() >= H265::kNaluHeaderSize) { + sps_state = + H265SpsParser::ParseSps(slice.subview(H265::kNaluHeaderSize)); } if (!sps_state) { RTC_LOG(LS_WARNING) << "Unable to parse SPS from H265 bitstream."; @@ -452,9 +450,9 @@ void H265BitstreamParser::ParseSlice(const uint8_t* slice, size_t length) { } case H265::NaluType::kPps: { absl::optional pps_state; - if (length >= H265::kNaluHeaderSize) { - std::vector unpacked_buffer = H265::ParseRbsp( - slice + H265::kNaluHeaderSize, length - H265::kNaluHeaderSize); + if (slice.size() >= H265::kNaluHeaderSize) { + std::vector unpacked_buffer = + H265::ParseRbsp(slice.subview(H265::kNaluHeaderSize)); BitstreamReader slice_reader(unpacked_buffer); // pic_parameter_set_id: ue(v) uint32_t pps_id = slice_reader.ReadExponentialGolomb(); @@ -463,8 +461,8 @@ void H265BitstreamParser::ParseSlice(const uint8_t* slice, size_t length) { uint32_t sps_id = slice_reader.ReadExponentialGolomb(); IN_RANGE_OR_RETURN_VOID(sps_id, 0, 15); const H265SpsParser::SpsState* sps = GetSPS(sps_id); - pps_state = H265PpsParser::ParsePps( - slice + H265::kNaluHeaderSize, length - H265::kNaluHeaderSize, sps); + pps_state = + H265PpsParser::ParsePps(slice.subview(H265::kNaluHeaderSize), sps); } if (!pps_state) { RTC_LOG(LS_WARNING) << "Unable to parse PPS from H265 bitstream."; @@ -480,7 +478,7 @@ void H265BitstreamParser::ParseSlice(const uint8_t* slice, size_t length) { case H265::NaluType::kFu: break; default: - Result res = ParseNonParameterSetNalu(slice, length, nalu_type); + Result res = ParseNonParameterSetNalu(slice, nalu_type); if (res != kOk) { RTC_LOG(LS_INFO) << "Failed to parse bitstream. Error: " << res; } @@ -489,10 +487,10 @@ void H265BitstreamParser::ParseSlice(const uint8_t* slice, size_t length) { } absl::optional -H265BitstreamParser::ParsePpsIdFromSliceSegmentLayerRbsp(const uint8_t* data, - size_t length, - uint8_t nalu_type) { - std::vector unpacked_buffer = H265::ParseRbsp(data, length); +H265BitstreamParser::ParsePpsIdFromSliceSegmentLayerRbsp( + rtc::ArrayView data, + uint8_t nalu_type) { + std::vector unpacked_buffer = H265::ParseRbsp(data); BitstreamReader slice_reader(unpacked_buffer); // first_slice_segment_in_pic_flag: u(1) @@ -519,10 +517,10 @@ H265BitstreamParser::ParsePpsIdFromSliceSegmentLayerRbsp(const uint8_t* data, void H265BitstreamParser::ParseBitstream( rtc::ArrayView bitstream) { - std::vector nalu_indices = - H265::FindNaluIndices(bitstream.data(), bitstream.size()); + std::vector nalu_indices = H265::FindNaluIndices(bitstream); for (const H265::NaluIndex& index : nalu_indices) - ParseSlice(&bitstream[index.payload_start_offset], index.payload_size); + ParseSlice( + bitstream.subview(index.payload_start_offset, index.payload_size)); } absl::optional H265BitstreamParser::GetLastSliceQp() const { diff --git a/third_party/libwebrtc/common_video/h265/h265_bitstream_parser.h b/third_party/libwebrtc/common_video/h265/h265_bitstream_parser.h index 48f27ef2e7da..cf18ccb68671 100644 --- a/third_party/libwebrtc/common_video/h265/h265_bitstream_parser.h +++ b/third_party/libwebrtc/common_video/h265/h265_bitstream_parser.h @@ -40,8 +40,7 @@ class RTC_EXPORT H265BitstreamParser : public BitstreamParser { absl::optional GetLastSlicePpsId() const; static absl::optional ParsePpsIdFromSliceSegmentLayerRbsp( - const uint8_t* data, - size_t length, + rtc::ArrayView data, uint8_t nalu_type); protected: @@ -50,9 +49,8 @@ class RTC_EXPORT H265BitstreamParser : public BitstreamParser { kInvalidStream, kUnsupportedStream, }; - void ParseSlice(const uint8_t* slice, size_t length); - Result ParseNonParameterSetNalu(const uint8_t* source, - size_t source_length, + void ParseSlice(rtc::ArrayView slice); + Result ParseNonParameterSetNalu(rtc::ArrayView source, uint8_t nalu_type); const H265PpsParser::PpsState* GetPPS(uint32_t id) const; diff --git a/third_party/libwebrtc/common_video/h265/h265_bitstream_parser_unittest.cc b/third_party/libwebrtc/common_video/h265/h265_bitstream_parser_unittest.cc index 7ca979433ac5..5889c7058981 100644 --- a/third_party/libwebrtc/common_video/h265/h265_bitstream_parser_unittest.cc +++ b/third_party/libwebrtc/common_video/h265/h265_bitstream_parser_unittest.cc @@ -127,8 +127,8 @@ TEST(H265BitstreamParserTest, ReportsLastSliceQpFromShortTermReferenceSlices) { TEST(H265BitstreamParserTest, PpsIdFromSlice) { H265BitstreamParser h265_parser; absl::optional pps_id = - h265_parser.ParsePpsIdFromSliceSegmentLayerRbsp( - kH265SliceChunk, sizeof(kH265SliceChunk), H265::NaluType::kTrailR); + h265_parser.ParsePpsIdFromSliceSegmentLayerRbsp(kH265SliceChunk, + H265::NaluType::kTrailR); ASSERT_TRUE(pps_id); EXPECT_EQ(1u, *pps_id); } diff --git a/third_party/libwebrtc/common_video/h265/h265_common.cc b/third_party/libwebrtc/common_video/h265/h265_common.cc index 70864495bcf0..28b29e98a7d1 100644 --- a/third_party/libwebrtc/common_video/h265/h265_common.cc +++ b/third_party/libwebrtc/common_video/h265/h265_common.cc @@ -17,10 +17,8 @@ namespace H265 { constexpr uint8_t kNaluTypeMask = 0x7E; -std::vector FindNaluIndices(const uint8_t* buffer, - size_t buffer_size) { - std::vector indices = - H264::FindNaluIndices(buffer, buffer_size); +std::vector FindNaluIndices(rtc::ArrayView buffer) { + std::vector indices = H264::FindNaluIndices(buffer); std::vector results; for (auto& index : indices) { results.push_back( @@ -33,12 +31,12 @@ NaluType ParseNaluType(uint8_t data) { return static_cast((data & kNaluTypeMask) >> 1); } -std::vector ParseRbsp(const uint8_t* data, size_t length) { - return H264::ParseRbsp(data, length); +std::vector ParseRbsp(rtc::ArrayView data) { + return H264::ParseRbsp(data); } -void WriteRbsp(const uint8_t* bytes, size_t length, rtc::Buffer* destination) { - H264::WriteRbsp(bytes, length, destination); +void WriteRbsp(rtc::ArrayView bytes, rtc::Buffer* destination) { + H264::WriteRbsp(bytes, destination); } uint32_t Log2Ceiling(uint32_t value) { diff --git a/third_party/libwebrtc/common_video/h265/h265_common.h b/third_party/libwebrtc/common_video/h265/h265_common.h index b9186dcd2e1a..50ca1b307338 100644 --- a/third_party/libwebrtc/common_video/h265/h265_common.h +++ b/third_party/libwebrtc/common_video/h265/h265_common.h @@ -77,8 +77,14 @@ struct NaluIndex { }; // Returns a vector of the NALU indices in the given buffer. -RTC_EXPORT std::vector FindNaluIndices(const uint8_t* buffer, - size_t buffer_size); +RTC_EXPORT std::vector FindNaluIndices( + rtc::ArrayView buffer); + +// TODO: bugs.webrtc.org/42225170 - Deprecate. +inline std::vector FindNaluIndices(const uint8_t* buffer, + size_t buffer_size) { + return FindNaluIndices(rtc::MakeArrayView(buffer, buffer_size)); +} // Get the NAL type from the header byte immediately following start sequence. RTC_EXPORT NaluType ParseNaluType(uint8_t data); @@ -97,12 +103,24 @@ RTC_EXPORT NaluType ParseNaluType(uint8_t data); // the 03 emulation byte. // Parse the given data and remove any emulation byte escaping. -std::vector ParseRbsp(const uint8_t* data, size_t length); +std::vector ParseRbsp(rtc::ArrayView data); + +// TODO: bugs.webrtc.org/42225170 - Deprecate. +inline std::vector ParseRbsp(const uint8_t* data, size_t length) { + return ParseRbsp(rtc::MakeArrayView(data, length)); +} // Write the given data to the destination buffer, inserting and emulation // bytes in order to escape any data the could be interpreted as a start // sequence. -void WriteRbsp(const uint8_t* bytes, size_t length, rtc::Buffer* destination); +void WriteRbsp(rtc::ArrayView bytes, rtc::Buffer* destination); + +// TODO: bugs.webrtc.org/42225170 - Deprecate. +inline void WriteRbsp(const uint8_t* bytes, + size_t length, + rtc::Buffer* destination) { + WriteRbsp(rtc::MakeArrayView(bytes, length), destination); +} uint32_t Log2Ceiling(uint32_t value); diff --git a/third_party/libwebrtc/common_video/h265/h265_pps_parser.cc b/third_party/libwebrtc/common_video/h265/h265_pps_parser.cc index 1cc9abd79460..3339f652fd96 100644 --- a/third_party/libwebrtc/common_video/h265/h265_pps_parser.cc +++ b/third_party/libwebrtc/common_video/h265/h265_pps_parser.cc @@ -63,17 +63,15 @@ namespace webrtc { // http://www.itu.int/rec/T-REC-H.265 absl::optional H265PpsParser::ParsePps( - const uint8_t* data, - size_t length, + rtc::ArrayView data, const H265SpsParser::SpsState* sps) { // First, parse out rbsp, which is basically the source buffer minus emulation // bytes (the last byte of a 0x00 0x00 0x03 sequence). RBSP is defined in // section 7.3.1.1 of the H.265 standard. - return ParseInternal(H265::ParseRbsp(data, length), sps); + return ParseInternal(H265::ParseRbsp(data), sps); } -bool H265PpsParser::ParsePpsIds(const uint8_t* data, - size_t length, +bool H265PpsParser::ParsePpsIds(rtc::ArrayView data, uint32_t* pps_id, uint32_t* sps_id) { RTC_DCHECK(pps_id); @@ -81,7 +79,7 @@ bool H265PpsParser::ParsePpsIds(const uint8_t* data, // First, parse out rbsp, which is basically the source buffer minus emulation // bytes (the last byte of a 0x00 0x00 0x03 sequence). RBSP is defined in // section 7.3.1.1 of the H.265 standard. - std::vector unpacked_buffer = H265::ParseRbsp(data, length); + std::vector unpacked_buffer = H265::ParseRbsp(data); BitstreamReader reader(unpacked_buffer); *pps_id = reader.ReadExponentialGolomb(); IN_RANGE_OR_RETURN_FALSE(*pps_id, 0, 63); diff --git a/third_party/libwebrtc/common_video/h265/h265_pps_parser.h b/third_party/libwebrtc/common_video/h265/h265_pps_parser.h index 62bc18fe6b46..1bd0eadb7ec6 100644 --- a/third_party/libwebrtc/common_video/h265/h265_pps_parser.h +++ b/third_party/libwebrtc/common_video/h265/h265_pps_parser.h @@ -43,14 +43,26 @@ class RTC_EXPORT H265PpsParser { }; // Unpack RBSP and parse PPS state from the supplied buffer. - static absl::optional ParsePps(const uint8_t* data, - size_t length, + static absl::optional ParsePps(rtc::ArrayView data, const H265SpsParser::SpsState* sps); + // TODO: bugs.webrtc.org/42225170 - Deprecate. + static inline absl::optional ParsePps( + const uint8_t* data, + size_t length, + const H265SpsParser::SpsState* sps) { + return ParsePps(rtc::MakeArrayView(data, length), sps); + } - static bool ParsePpsIds(const uint8_t* data, - size_t length, + static bool ParsePpsIds(rtc::ArrayView data, uint32_t* pps_id, uint32_t* sps_id); + // TODO: bugs.webrtc.org/42225170 - Deprecate. + static inline bool ParsePpsIds(const uint8_t* data, + size_t length, + uint32_t* pps_id, + uint32_t* sps_id) { + return ParsePpsIds(rtc::MakeArrayView(data, length), pps_id, sps_id); + } protected: // Parse the PPS state, for a bit buffer where RBSP decoding has already been diff --git a/third_party/libwebrtc/common_video/h265/h265_pps_parser_unittest.cc b/third_party/libwebrtc/common_video/h265/h265_pps_parser_unittest.cc index d91fc1a55c5c..c683a4300891 100644 --- a/third_party/libwebrtc/common_video/h265/h265_pps_parser_unittest.cc +++ b/third_party/libwebrtc/common_video/h265/h265_pps_parser_unittest.cc @@ -161,7 +161,7 @@ void WritePps(const H265PpsParser::PpsState& pps, bit_buffer.GetCurrentOffset(&byte_offset, &bit_offset); } - H265::WriteRbsp(data, byte_offset, out_buffer); + H265::WriteRbsp(rtc::MakeArrayView(data, byte_offset), out_buffer); } class H265PpsParserTest : public ::testing::Test { @@ -196,9 +196,8 @@ class H265PpsParserTest : public ::testing::Test { 0x16, 0x59, 0x59, 0xa4, 0x93, 0x2b, 0x80, 0x40, 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x07, 0x82}; H265SpsParser::SpsState parsed_sps = - H265SpsParser::ParseSps(sps_buffer, arraysize(sps_buffer)).value(); - parsed_pps_ = - H265PpsParser::ParsePps(buffer_.data(), buffer_.size(), &parsed_sps); + H265SpsParser::ParseSps(sps_buffer).value(); + parsed_pps_ = H265PpsParser::ParsePps(buffer_, &parsed_sps); ASSERT_TRUE(parsed_pps_); EXPECT_EQ(pps.dependent_slice_segments_enabled_flag, parsed_pps_->dependent_slice_segments_enabled_flag); diff --git a/third_party/libwebrtc/common_video/h265/h265_sps_parser.cc b/third_party/libwebrtc/common_video/h265/h265_sps_parser.cc index a2da4b9b7ba5..cb59d6094f9c 100644 --- a/third_party/libwebrtc/common_video/h265/h265_sps_parser.cc +++ b/third_party/libwebrtc/common_video/h265/h265_sps_parser.cc @@ -104,10 +104,8 @@ size_t H265SpsParser::GetDpbMaxPicBuf(int general_profile_idc) { // Unpack RBSP and parse SPS state from the supplied buffer. absl::optional H265SpsParser::ParseSps( - const uint8_t* data, - size_t length) { - RTC_DCHECK(data); - return ParseSpsInternal(H265::ParseRbsp(data, length)); + rtc::ArrayView data) { + return ParseSpsInternal(H265::ParseRbsp(data)); } bool H265SpsParser::ParseScalingListData(BitstreamReader& reader) { diff --git a/third_party/libwebrtc/common_video/h265/h265_sps_parser.h b/third_party/libwebrtc/common_video/h265/h265_sps_parser.h index 7ec170671d2e..072e2e9791bc 100644 --- a/third_party/libwebrtc/common_video/h265/h265_sps_parser.h +++ b/third_party/libwebrtc/common_video/h265/h265_sps_parser.h @@ -104,7 +104,12 @@ class RTC_EXPORT H265SpsParser { }; // Unpack RBSP and parse SPS state from the supplied buffer. - static absl::optional ParseSps(const uint8_t* data, size_t length); + static absl::optional ParseSps(rtc::ArrayView data); + // TODO: bugs.webrtc.org/42225170 - Deprecate. + static inline absl::optional ParseSps(const uint8_t* data, + size_t length) { + return ParseSps(rtc::MakeArrayView(data, length)); + } static bool ParseScalingListData(BitstreamReader& reader); diff --git a/third_party/libwebrtc/common_video/h265/h265_sps_parser_unittest.cc b/third_party/libwebrtc/common_video/h265/h265_sps_parser_unittest.cc index 26af4b1170ba..46d8d2d53a81 100644 --- a/third_party/libwebrtc/common_video/h265/h265_sps_parser_unittest.cc +++ b/third_party/libwebrtc/common_video/h265/h265_sps_parser_unittest.cc @@ -365,7 +365,7 @@ void WriteSps(uint16_t width, } out_buffer->Clear(); - H265::WriteRbsp(rbsp, byte_count, out_buffer); + H265::WriteRbsp(rtc::MakeArrayView(rbsp, byte_count), out_buffer); } class H265SpsParserTest : public ::testing::Test { @@ -389,8 +389,7 @@ TEST_F(H265SpsParserTest, TestSampleSPSHdLandscape) { 0x02, 0x80, 0x80, 0x2d, 0x16, 0x59, 0x59, 0xa4, 0x93, 0x2b, 0x80, 0x40, 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x07, 0x82}; - absl::optional sps = - H265SpsParser::ParseSps(buffer, arraysize(buffer)); + absl::optional sps = H265SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(1280u, sps->width); EXPECT_EQ(720u, sps->height); @@ -418,8 +417,7 @@ TEST_F(H265SpsParserTest, TestSampleSPSVerticalCropLandscape) { 0x05, 0x02, 0x01, 0x09, 0xf2, 0xe5, 0x95, 0x9a, 0x49, 0x32, 0xb8, 0x04, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0x78, 0x20}; - absl::optional sps = - H265SpsParser::ParseSps(buffer, arraysize(buffer)); + absl::optional sps = H265SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(640u, sps->width); EXPECT_EQ(260u, sps->height); @@ -446,8 +444,7 @@ TEST_F(H265SpsParserTest, TestSampleSPSHorizontalAndVerticalCrop) { 0x08, 0x48, 0x04, 0x27, 0x72, 0xe5, 0x95, 0x9a, 0x49, 0x32, 0xb8, 0x04, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0x78, 0x20}; - absl::optional sps = - H265SpsParser::ParseSps(buffer, arraysize(buffer)); + absl::optional sps = H265SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(260u, sps->width); EXPECT_EQ(260u, sps->height); @@ -456,8 +453,7 @@ TEST_F(H265SpsParserTest, TestSampleSPSHorizontalAndVerticalCrop) { TEST_F(H265SpsParserTest, TestSyntheticSPSQvgaLandscape) { rtc::Buffer buffer; WriteSps(320u, 180u, 1, 0, 1, 0, &buffer); - absl::optional sps = - H265SpsParser::ParseSps(buffer.data(), buffer.size()); + absl::optional sps = H265SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(320u, sps->width); EXPECT_EQ(180u, sps->height); @@ -467,8 +463,7 @@ TEST_F(H265SpsParserTest, TestSyntheticSPSQvgaLandscape) { TEST_F(H265SpsParserTest, TestSyntheticSPSWeirdResolution) { rtc::Buffer buffer; WriteSps(156u, 122u, 2, 0, 1, 0, &buffer); - absl::optional sps = - H265SpsParser::ParseSps(buffer.data(), buffer.size()); + absl::optional sps = H265SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(156u, sps->width); EXPECT_EQ(122u, sps->height); @@ -478,8 +473,7 @@ TEST_F(H265SpsParserTest, TestSyntheticSPSWeirdResolution) { TEST_F(H265SpsParserTest, TestLog2MaxSubLayersMinus1) { rtc::Buffer buffer; WriteSps(320u, 180u, 1, 0, 1, 0, &buffer); - absl::optional sps = - H265SpsParser::ParseSps(buffer.data(), buffer.size()); + absl::optional sps = H265SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(320u, sps->width); EXPECT_EQ(180u, sps->height); @@ -488,7 +482,7 @@ TEST_F(H265SpsParserTest, TestLog2MaxSubLayersMinus1) { WriteSps(320u, 180u, 1, 6, 1, 0, &buffer); absl::optional sps1 = - H265SpsParser::ParseSps(buffer.data(), buffer.size()); + H265SpsParser::ParseSps(buffer); ASSERT_TRUE(sps1.has_value()); EXPECT_EQ(320u, sps1->width); EXPECT_EQ(180u, sps1->height); @@ -497,15 +491,14 @@ TEST_F(H265SpsParserTest, TestLog2MaxSubLayersMinus1) { WriteSps(320u, 180u, 1, 7, 1, 0, &buffer); absl::optional result = - H265SpsParser::ParseSps(buffer.data(), buffer.size()); + H265SpsParser::ParseSps(buffer); EXPECT_FALSE(result.has_value()); } TEST_F(H265SpsParserTest, TestSubLayerOrderingInfoPresentFlag) { rtc::Buffer buffer; WriteSps(320u, 180u, 1, 6, 1, 0, &buffer); - absl::optional sps = - H265SpsParser::ParseSps(buffer.data(), buffer.size()); + absl::optional sps = H265SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(320u, sps->width); EXPECT_EQ(180u, sps->height); @@ -514,7 +507,7 @@ TEST_F(H265SpsParserTest, TestSubLayerOrderingInfoPresentFlag) { WriteSps(320u, 180u, 1, 6, 1, 0, &buffer); absl::optional sps1 = - H265SpsParser::ParseSps(buffer.data(), buffer.size()); + H265SpsParser::ParseSps(buffer); ASSERT_TRUE(sps1.has_value()); EXPECT_EQ(320u, sps1->width); EXPECT_EQ(180u, sps1->height); @@ -525,8 +518,7 @@ TEST_F(H265SpsParserTest, TestSubLayerOrderingInfoPresentFlag) { TEST_F(H265SpsParserTest, TestLongTermRefPicsPresentFlag) { rtc::Buffer buffer; WriteSps(320u, 180u, 1, 0, 1, 0, &buffer); - absl::optional sps = - H265SpsParser::ParseSps(buffer.data(), buffer.size()); + absl::optional sps = H265SpsParser::ParseSps(buffer); ASSERT_TRUE(sps.has_value()); EXPECT_EQ(320u, sps->width); EXPECT_EQ(180u, sps->height); @@ -535,7 +527,7 @@ TEST_F(H265SpsParserTest, TestLongTermRefPicsPresentFlag) { WriteSps(320u, 180u, 1, 6, 1, 1, &buffer); absl::optional sps1 = - H265SpsParser::ParseSps(buffer.data(), buffer.size()); + H265SpsParser::ParseSps(buffer); ASSERT_TRUE(sps1.has_value()); EXPECT_EQ(320u, sps1->width); EXPECT_EQ(180u, sps1->height); diff --git a/third_party/libwebrtc/common_video/h265/h265_vps_parser.cc b/third_party/libwebrtc/common_video/h265/h265_vps_parser.cc index 16b967dad4f0..0a00370163e3 100644 --- a/third_party/libwebrtc/common_video/h265/h265_vps_parser.cc +++ b/third_party/libwebrtc/common_video/h265/h265_vps_parser.cc @@ -25,10 +25,8 @@ H265VpsParser::VpsState::VpsState() = default; // Unpack RBSP and parse VPS state from the supplied buffer. absl::optional H265VpsParser::ParseVps( - const uint8_t* data, - size_t length) { - RTC_DCHECK(data); - return ParseInternal(H265::ParseRbsp(data, length)); + rtc::ArrayView data) { + return ParseInternal(H265::ParseRbsp(data)); } absl::optional H265VpsParser::ParseInternal( diff --git a/third_party/libwebrtc/common_video/h265/h265_vps_parser.h b/third_party/libwebrtc/common_video/h265/h265_vps_parser.h index c793996bc1dd..e8ca1a07dec0 100644 --- a/third_party/libwebrtc/common_video/h265/h265_vps_parser.h +++ b/third_party/libwebrtc/common_video/h265/h265_vps_parser.h @@ -29,7 +29,12 @@ class RTC_EXPORT H265VpsParser { }; // Unpack RBSP and parse VPS state from the supplied buffer. - static absl::optional ParseVps(const uint8_t* data, size_t length); + static absl::optional ParseVps(rtc::ArrayView data); + // TODO: bugs.webrtc.org/42225170 - Deprecate. + static inline absl::optional ParseVps(const uint8_t* data, + size_t length) { + return ParseVps(rtc::MakeArrayView(data, length)); + } protected: // Parse the VPS state, for a bit buffer where RBSP decoding has already been diff --git a/third_party/libwebrtc/common_video/h265/h265_vps_parser_unittest.cc b/third_party/libwebrtc/common_video/h265/h265_vps_parser_unittest.cc index 24e8a77154fa..87e1f586341c 100644 --- a/third_party/libwebrtc/common_video/h265/h265_vps_parser_unittest.cc +++ b/third_party/libwebrtc/common_video/h265/h265_vps_parser_unittest.cc @@ -41,8 +41,7 @@ TEST_F(H265VpsParserTest, TestSampleVPSId) { 0x1c, 0x01, 0xff, 0xff, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x9d, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x78, 0x95, 0x98, 0x09, }; - EXPECT_TRUE(static_cast( - vps_ = H265VpsParser::ParseVps(buffer, arraysize(buffer)))); + EXPECT_TRUE(static_cast(vps_ = H265VpsParser::ParseVps(buffer))); EXPECT_EQ(1u, vps_->id); } diff --git a/third_party/libwebrtc/experiments/field_trials.py b/third_party/libwebrtc/experiments/field_trials.py index 6fd3d04b1161..9a9ca9114be5 100755 --- a/third_party/libwebrtc/experiments/field_trials.py +++ b/third_party/libwebrtc/experiments/field_trials.py @@ -59,6 +59,9 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([ FieldTrial('WebRTC-Av1-GetEncoderInfoOverride', 42225234, date(2024, 4, 1)), + FieldTrial('WebRTC-BitrateAdjusterUseNewfangledHeadroomAdjustment', + 349561566, + date(2025, 8, 26)), FieldTrial('WebRTC-Bwe-LimitPacingFactorByUpperLinkCapacityEstimate', 42220543, date(2025, 1, 1)), @@ -89,9 +92,9 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([ FieldTrial('WebRTC-JitterEstimatorConfig', 42224404, date(2024, 4, 1)), - FieldTrial('WebRTC-LibaomAv1Encoder-MaxConsecFrameDrop', - 42226184, - date(2024, 4, 1)), + FieldTrial('WebRTC-LibaomAv1Encoder-AdaptiveMaxConsecDrops', + 351644568, + date(2025, 7, 1)), FieldTrial('WebRTC-LibvpxVp9Encoder-SvcFrameDropConfig', 42226190, date(2024, 9, 1)), @@ -107,6 +110,15 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([ FieldTrial('WebRTC-PermuteTlsClientHello', 42225803, date(2024, 7, 1)), + FieldTrial('WebRTC-QCM-Dynamic-AV1', + 349860657, + date(2025, 7, 1)), + FieldTrial('WebRTC-QCM-Dynamic-VP8', + 349860657, + date(2025, 7, 1)), + FieldTrial('WebRTC-QCM-Dynamic-VP9', + 349860657, + date(2025, 7, 1)), FieldTrial('WebRTC-ReceiveBufferSize', 42225927, date(2024, 4, 1)), @@ -155,6 +167,9 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([ FieldTrial('WebRTC-Video-H26xPacketBuffer', 41480904, date(2024, 6, 1)), + FieldTrial('WebRTC-Video-Vp9FlexibleMode', + 329396373, + date(2025, 6, 26)), # keep-sorted end ]) # yapf: disable @@ -790,9 +805,6 @@ POLICY_EXEMPT_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([ FieldTrial('WebRTC-VP8-Postproc-Config-Arm', 42231704, INDEFINITE), - FieldTrial('WebRTC-VP8ConferenceTemporalLayers', - 42234443, - INDEFINITE), FieldTrial('WebRTC-VP8IosMaxNumberOfThread', 42220027, date(2024, 4, 1)), @@ -872,7 +884,7 @@ POLICY_EXEMPT_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([ ]) # yapf: disable POLICY_EXEMPT_FIELD_TRIALS_DIGEST: str = \ - 'd6beac9eb318c70cd1695598b3d3c069cd17b42f' + 'ad853beba9dddb16d9f45164a8d69b5d01e7d1c9' REGISTERED_FIELD_TRIALS: FrozenSet[FieldTrial] = ACTIVE_FIELD_TRIALS.union( POLICY_EXEMPT_FIELD_TRIALS) diff --git a/third_party/libwebrtc/infra/config/config.star b/third_party/libwebrtc/infra/config/config.star index c070fa4074c6..895568688721 100755 --- a/third_party/libwebrtc/infra/config/config.star +++ b/third_party/libwebrtc/infra/config/config.star @@ -217,6 +217,10 @@ luci.realm(name = "pools/try-tests", bindings = [ ), ]) luci.realm(name = "try", bindings = [ + luci.binding( + roles = "role/buildbucket.creator", + groups = "project-webrtc-led-users", + ), luci.binding( roles = "role/swarming.taskTriggerer", groups = "project-webrtc-led-users", @@ -236,6 +240,10 @@ luci.realm(name = "pools/perf", bindings = [ ), ]) luci.realm(name = "perf", bindings = [ + luci.binding( + roles = "role/buildbucket.creator", + groups = "project-webrtc-led-users", + ), luci.binding( roles = "role/swarming.taskTriggerer", groups = "project-webrtc-led-users", @@ -248,6 +256,10 @@ luci.realm(name = "@root", bindings = [ roles = "role/swarming.poolUser", groups = "project-webrtc-admins", ), + luci.binding( + roles = "role/buildbucket.creator", + groups = "project-webrtc-admins", + ), luci.binding( roles = "role/swarming.taskTriggerer", groups = "project-webrtc-admins", @@ -264,6 +276,10 @@ luci.bucket( "project-webrtc-tryjob-access", ]), ], + constraints = luci.bucket_constraints( + pools = ["luci.webrtc.try"], + service_accounts = ["webrtc-try-builder@chops-service-accounts.iam.gserviceaccount.com"], + ), ) luci.bucket( @@ -634,11 +650,7 @@ def perf_builder(name, perf_cat, **kwargs): add_milo(name, {"perf": perf_cat}) properties = make_reclient_properties("rbe-webrtc-trusted") properties["builder_group"] = "client.webrtc.perf" - dimensions = {"pool": "luci.webrtc.perf", "os": "Linux", "cores": "2"} - if "Android" in name or "Fuchsia" in name: - # Android perf testers require more performant bots to finish under 3 hours. - # Fuchsia perf testers encountered "no space left on device" error on multiple runs. - dimensions["cores"] = "8" + dimensions = {"pool": "luci.webrtc.perf", "os": "Linux"} return webrtc_builder( name = name, dimensions = dimensions, @@ -821,6 +833,8 @@ try_builder("win11_debug", cq = None) chromium_try_builder("win_chromium_compile") chromium_try_builder("win_chromium_compile_dbg") +try_builder("iwyu_verifier", cq = None) + try_builder( "presubmit", recipe = "run_presubmit", diff --git a/third_party/libwebrtc/infra/config/cr-buildbucket.cfg b/third_party/libwebrtc/infra/config/cr-buildbucket.cfg index 039c580a3482..456fe1fac00a 100644 --- a/third_party/libwebrtc/infra/config/cr-buildbucket.cfg +++ b/third_party/libwebrtc/infra/config/cr-buildbucket.cfg @@ -2281,7 +2281,6 @@ buckets { name: "Perf Android32 (R Pixel5)" swarming_host: "chromium-swarm.appspot.com" swarming_tags: "vpython:native-python-wrapper" - dimensions: "cores:8" dimensions: "os:Linux" dimensions: "pool:luci.webrtc.perf" exe { @@ -2327,7 +2326,6 @@ buckets { name: "Perf Android64 (R Pixel5)" swarming_host: "chromium-swarm.appspot.com" swarming_tags: "vpython:native-python-wrapper" - dimensions: "cores:8" dimensions: "os:Linux" dimensions: "pool:luci.webrtc.perf" exe { @@ -2373,7 +2371,6 @@ buckets { name: "Perf Fuchsia" swarming_host: "chromium-swarm.appspot.com" swarming_tags: "vpython:native-python-wrapper" - dimensions: "cores:8" dimensions: "os:Linux" dimensions: "pool:luci.webrtc.perf" exe { @@ -2419,7 +2416,6 @@ buckets { name: "Perf Linux Bionic" swarming_host: "chromium-swarm.appspot.com" swarming_tags: "vpython:native-python-wrapper" - dimensions: "cores:2" dimensions: "os:Linux" dimensions: "pool:luci.webrtc.perf" exe { @@ -2465,7 +2461,6 @@ buckets { name: "Perf Mac 11" swarming_host: "chromium-swarm.appspot.com" swarming_tags: "vpython:native-python-wrapper" - dimensions: "cores:2" dimensions: "os:Linux" dimensions: "pool:luci.webrtc.perf" exe { @@ -2511,7 +2506,6 @@ buckets { name: "Perf Mac M1 Arm64 12" swarming_host: "chromium-swarm.appspot.com" swarming_tags: "vpython:native-python-wrapper" - dimensions: "cores:2" dimensions: "os:Linux" dimensions: "pool:luci.webrtc.perf" exe { @@ -2557,7 +2551,6 @@ buckets { name: "Perf Win 10" swarming_host: "chromium-swarm.appspot.com" swarming_tags: "vpython:native-python-wrapper" - dimensions: "cores:2" dimensions: "os:Linux" dimensions: "pool:luci.webrtc.perf" exe { @@ -3575,6 +3568,53 @@ buckets { } } } + builders { + name: "iwyu_verifier" + swarming_host: "chromium-swarm.appspot.com" + swarming_tags: "vpython:native-python-wrapper" + dimensions: "builderless:1" + dimensions: "cpu:x86-64" + dimensions: "os:Linux" + 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: "linux_asan" swarming_host: "chromium-swarm.appspot.com" @@ -5646,4 +5686,8 @@ buckets { } } } + constraints { + pools: "luci.webrtc.try" + service_accounts: "webrtc-try-builder@chops-service-accounts.iam.gserviceaccount.com" + } } diff --git a/third_party/libwebrtc/infra/config/luci-milo.cfg b/third_party/libwebrtc/infra/config/luci-milo.cfg index 3f3a18832a37..35cc997e4231 100644 --- a/third_party/libwebrtc/infra/config/luci-milo.cfg +++ b/third_party/libwebrtc/infra/config/luci-milo.cfg @@ -605,6 +605,9 @@ consoles { builders { name: "buildbucket/luci.webrtc.try/win_chromium_compile_dbg" } + builders { + name: "buildbucket/luci.webrtc.try/iwyu_verifier" + } builders { name: "buildbucket/luci.webrtc.try/presubmit" } diff --git a/third_party/libwebrtc/infra/config/luci-notify.cfg b/third_party/libwebrtc/infra/config/luci-notify.cfg index 129d0e426871..53a6c5eb3b82 100644 --- a/third_party/libwebrtc/infra/config/luci-notify.cfg +++ b/third_party/libwebrtc/infra/config/luci-notify.cfg @@ -1635,6 +1635,19 @@ notifiers { name: "ios_dbg_simulator" } } +notifiers { + notifications { + on_new_status: INFRA_FAILURE + email { + recipients: "webrtc-troopers-robots@google.com" + } + template: "infra_failure" + } + builders { + bucket: "try" + name: "iwyu_verifier" + } +} notifiers { notifications { on_new_status: INFRA_FAILURE diff --git a/third_party/libwebrtc/infra/config/project.cfg b/third_party/libwebrtc/infra/config/project.cfg index fb19e5782cf5..7ee089eda04e 100644 --- a/third_party/libwebrtc/infra/config/project.cfg +++ b/third_party/libwebrtc/infra/config/project.cfg @@ -7,7 +7,7 @@ name: "webrtc" access: "group:all" lucicfg { - version: "1.43.5" + version: "1.43.8" package_dir: "." config_dir: "." entry_point: "config.star" diff --git a/third_party/libwebrtc/infra/config/realms.cfg b/third_party/libwebrtc/infra/config/realms.cfg index 171667555f8d..409a7506c5bd 100644 --- a/third_party/libwebrtc/infra/config/realms.cfg +++ b/third_party/libwebrtc/infra/config/realms.cfg @@ -18,6 +18,10 @@ realms { role: "role/analysis.reader" principals: "group:all" } + bindings { + role: "role/buildbucket.creator" + principals: "group:project-webrtc-admins" + } bindings { role: "role/buildbucket.reader" principals: "group:all" @@ -96,6 +100,10 @@ realms { role: "role/buildbucket.builderServiceAccount" principals: "user:webrtc-ci-builder@chops-service-accounts.iam.gserviceaccount.com" } + bindings { + role: "role/buildbucket.creator" + principals: "group:project-webrtc-led-users" + } bindings { role: "role/buildbucket.triggerer" principals: "group:service-account-chromeperf" @@ -174,6 +182,10 @@ realms { role: "role/buildbucket.builderServiceAccount" principals: "user:webrtc-try-builder@chops-service-accounts.iam.gserviceaccount.com" } + bindings { + role: "role/buildbucket.creator" + principals: "group:project-webrtc-led-users" + } bindings { role: "role/buildbucket.triggerer" principals: "group:project-webrtc-tryjob-access" diff --git a/third_party/libwebrtc/infra/specs/client.webrtc.json b/third_party/libwebrtc/infra/specs/client.webrtc.json index db0d5d4d7573..2a018caff580 100644 --- a/third_party/libwebrtc/infra/specs/client.webrtc.json +++ b/third_party/libwebrtc/infra/specs/client.webrtc.json @@ -7962,7 +7962,7 @@ "17.4", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -7970,7 +7970,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "apprtcmobile_tests iPhone 14 17.4", + "name": "apprtcmobile_tests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -7989,29 +7989,29 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "apprtcmobile_tests", "test_id_prefix": "ninja://examples:apprtcmobile_tests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8019,7 +8019,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "apprtcmobile_tests iPhone X 15.5", + "name": "apprtcmobile_tests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8038,19 +8038,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -8060,7 +8060,7 @@ "16.4", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8087,7 +8087,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -8108,7 +8108,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8116,7 +8116,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "audio_decoder_unittests iPhone 14 17.4", + "name": "audio_decoder_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8135,28 +8135,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8164,7 +8164,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "audio_decoder_unittests iPhone X 15.5", + "name": "audio_decoder_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8183,19 +8183,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -8204,7 +8204,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8231,7 +8231,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -8252,7 +8252,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8260,7 +8260,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "common_audio_unittests iPhone 14 17.4", + "name": "common_audio_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8279,28 +8279,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8308,7 +8308,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "common_audio_unittests iPhone X 15.5", + "name": "common_audio_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8327,19 +8327,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -8348,7 +8348,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8375,7 +8375,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -8396,7 +8396,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8404,7 +8404,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "common_video_unittests iPhone 14 17.4", + "name": "common_video_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8423,28 +8423,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8452,7 +8452,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "common_video_unittests iPhone X 15.5", + "name": "common_video_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8471,19 +8471,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -8492,7 +8492,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8519,7 +8519,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -8540,7 +8540,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8548,7 +8548,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "dcsctp_unittests iPhone 14 17.4", + "name": "dcsctp_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8567,28 +8567,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "dcsctp_unittests", "test_id_prefix": "ninja://net/dcsctp:dcsctp_unittests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8596,7 +8596,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "dcsctp_unittests iPhone X 15.5", + "name": "dcsctp_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8615,19 +8615,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -8636,7 +8636,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8663,7 +8663,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -8684,7 +8684,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8692,7 +8692,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "modules_tests iPhone 14 17.4", + "name": "modules_tests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8711,12 +8711,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -8724,16 +8724,16 @@ }, "test": "modules_tests", "test_id_prefix": "ninja://modules:modules_tests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8741,7 +8741,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "modules_tests iPhone X 15.5", + "name": "modules_tests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8760,12 +8760,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.0" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -8773,7 +8773,7 @@ }, "test": "modules_tests", "test_id_prefix": "ninja://modules:modules_tests/", - "variant_id": "iPhone X 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -8782,7 +8782,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8809,7 +8809,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -8831,7 +8831,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8839,7 +8839,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "modules_unittests iPhone 14 17.4", + "name": "modules_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8859,12 +8859,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -8872,16 +8872,16 @@ }, "test": "modules_unittests", "test_id_prefix": "ninja://modules:modules_unittests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8889,7 +8889,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "modules_unittests iPhone X 15.5", + "name": "modules_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -8909,12 +8909,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.0" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -8922,7 +8922,7 @@ }, "test": "modules_unittests", "test_id_prefix": "ninja://modules:modules_unittests/", - "variant_id": "iPhone X 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -8931,7 +8931,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8959,7 +8959,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -8981,7 +8981,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -8989,7 +8989,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_media_unittests iPhone 14 17.4", + "name": "rtc_media_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9008,28 +9008,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9037,7 +9037,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_media_unittests iPhone X 15.5", + "name": "rtc_media_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9056,19 +9056,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -9077,7 +9077,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9104,7 +9104,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -9125,7 +9125,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9133,7 +9133,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_pc_unittests iPhone 14 17.4", + "name": "rtc_pc_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9152,28 +9152,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9181,7 +9181,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_pc_unittests iPhone X 15.5", + "name": "rtc_pc_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9200,19 +9200,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -9221,7 +9221,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9248,7 +9248,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -9269,7 +9269,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9277,7 +9277,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_stats_unittests iPhone 14 17.4", + "name": "rtc_stats_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9296,28 +9296,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9325,7 +9325,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_stats_unittests iPhone X 15.5", + "name": "rtc_stats_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9344,19 +9344,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -9365,7 +9365,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9392,7 +9392,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -9413,7 +9413,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9421,7 +9421,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_unittests iPhone 14 17.4", + "name": "rtc_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9440,12 +9440,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -9453,16 +9453,16 @@ }, "test": "rtc_unittests", "test_id_prefix": "ninja://:rtc_unittests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9470,7 +9470,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_unittests iPhone X 15.5", + "name": "rtc_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9489,12 +9489,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.0" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -9502,7 +9502,7 @@ }, "test": "rtc_unittests", "test_id_prefix": "ninja://:rtc_unittests/", - "variant_id": "iPhone X 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -9511,7 +9511,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9538,7 +9538,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -9561,7 +9561,7 @@ "17.4", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9569,7 +9569,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "sdk_framework_unittests iPhone 14 17.4", + "name": "sdk_framework_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9588,29 +9588,29 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9618,7 +9618,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "sdk_framework_unittests iPhone X 15.5", + "name": "sdk_framework_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9637,19 +9637,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -9659,7 +9659,7 @@ "16.4", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9686,7 +9686,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -9708,7 +9708,7 @@ "17.4", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9716,7 +9716,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "sdk_unittests iPhone 14 17.4", + "name": "sdk_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9735,29 +9735,29 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "sdk_unittests", "test_id_prefix": "ninja://sdk:sdk_unittests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9765,7 +9765,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "sdk_unittests iPhone X 15.5", + "name": "sdk_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9784,19 +9784,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -9806,7 +9806,7 @@ "16.4", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9833,7 +9833,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -9854,7 +9854,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9862,7 +9862,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "svc_tests iPhone 14 17.4", + "name": "svc_tests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9882,12 +9882,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -9895,16 +9895,16 @@ }, "test": "svc_tests", "test_id_prefix": "ninja://pc:svc_tests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9912,7 +9912,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "svc_tests iPhone X 15.5", + "name": "svc_tests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -9932,12 +9932,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.0" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -9945,7 +9945,7 @@ }, "test": "svc_tests", "test_id_prefix": "ninja://pc:svc_tests/", - "variant_id": "iPhone X 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -9954,7 +9954,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -9982,7 +9982,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -10004,7 +10004,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10012,7 +10012,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "system_wrappers_unittests iPhone 14 17.4", + "name": "system_wrappers_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -10031,28 +10031,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10060,7 +10060,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "system_wrappers_unittests iPhone X 15.5", + "name": "system_wrappers_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -10079,19 +10079,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -10100,7 +10100,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10127,7 +10127,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -10148,7 +10148,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10156,7 +10156,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "test_support_unittests iPhone 14 17.4", + "name": "test_support_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -10175,28 +10175,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10204,7 +10204,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "test_support_unittests iPhone X 15.5", + "name": "test_support_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -10223,19 +10223,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -10244,7 +10244,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10271,7 +10271,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -10292,7 +10292,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10300,7 +10300,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "tools_unittests iPhone 14 17.4", + "name": "tools_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -10319,28 +10319,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "tools_unittests", "test_id_prefix": "ninja://rtc_tools:tools_unittests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10348,7 +10348,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "tools_unittests iPhone X 15.5", + "name": "tools_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -10367,19 +10367,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -10388,7 +10388,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10415,7 +10415,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -10436,7 +10436,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10444,7 +10444,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "video_engine_tests iPhone 14 17.4", + "name": "video_engine_tests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -10463,12 +10463,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -10476,16 +10476,16 @@ }, "test": "video_engine_tests", "test_id_prefix": "ninja://:video_engine_tests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10493,7 +10493,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "video_engine_tests iPhone X 15.5", + "name": "video_engine_tests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -10512,12 +10512,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.0" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -10525,7 +10525,7 @@ }, "test": "video_engine_tests", "test_id_prefix": "ninja://:video_engine_tests/", - "variant_id": "iPhone X 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -10534,7 +10534,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10561,7 +10561,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -10583,7 +10583,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10591,7 +10591,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "voip_unittests iPhone 14 17.4", + "name": "voip_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -10610,28 +10610,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "voip_unittests", "test_id_prefix": "ninja://:voip_unittests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10639,7 +10639,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "voip_unittests iPhone X 15.5", + "name": "voip_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -10658,19 +10658,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.0" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "voip_unittests", "test_id_prefix": "ninja://:voip_unittests/", - "variant_id": "iPhone X 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -10679,7 +10679,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10706,7 +10706,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -10727,7 +10727,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10735,7 +10735,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "webrtc_nonparallel_tests iPhone 14 17.4", + "name": "webrtc_nonparallel_tests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -10754,28 +10754,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "webrtc_nonparallel_tests", "test_id_prefix": "ninja://:webrtc_nonparallel_tests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10783,7 +10783,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "webrtc_nonparallel_tests iPhone X 15.5", + "name": "webrtc_nonparallel_tests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -10802,19 +10802,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -10823,7 +10823,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -10850,7 +10850,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { diff --git a/third_party/libwebrtc/infra/specs/internal.client.webrtc.json b/third_party/libwebrtc/infra/specs/internal.client.webrtc.json index a3d758295b42..ec9c84c0b05d 100644 --- a/third_party/libwebrtc/infra/specs/internal.client.webrtc.json +++ b/third_party/libwebrtc/infra/specs/internal.client.webrtc.json @@ -7,7 +7,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -28,13 +28,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -47,7 +46,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -68,13 +67,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -88,7 +86,7 @@ "--readline-timeout=1200", "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -109,15 +107,14 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "hard_timeout": 7200, "io_timeout": 7200, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -131,7 +128,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -152,13 +149,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -172,7 +168,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -193,13 +189,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -212,7 +207,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -233,13 +228,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -252,7 +246,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -273,13 +267,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -292,7 +285,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -313,13 +306,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -332,7 +324,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -353,13 +345,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -372,7 +363,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -393,13 +384,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -419,7 +409,7 @@ "--write_perf_output_on_ios", "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -444,9 +434,8 @@ } ], "dimensions": { - "device_status": "available", - "id": "mac-438-e504", - "os": "iOS-17.4.1", + "cpu": "arm64", + "os": "iOS-17.5.1", "pool": "WebRTC" }, "hard_timeout": 10800, @@ -454,7 +443,7 @@ "io_timeout": 10800, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -471,7 +460,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -492,13 +481,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -511,7 +499,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -532,13 +520,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -552,7 +539,7 @@ "--readline-timeout=1200", "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -573,15 +560,14 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "hard_timeout": 7200, "io_timeout": 7200, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -595,7 +581,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -616,13 +602,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -636,7 +621,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -657,13 +642,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -676,7 +660,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -697,13 +681,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -716,7 +699,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -737,13 +720,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -756,7 +738,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -777,13 +759,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -796,7 +777,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -817,13 +798,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], @@ -836,7 +816,7 @@ "args": [ "--xctest", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}" ], @@ -857,13 +837,12 @@ } ], "dimensions": { - "device_status": "available", - "os": "iOS-16.7.1", + "os": "iOS-16.7.5", "pool": "chrome.tests" }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" } ], diff --git a/third_party/libwebrtc/infra/specs/mixins.pyl b/third_party/libwebrtc/infra/specs/mixins.pyl index 5e9f9badf12f..8fa9277a1795 100644 --- a/third_party/libwebrtc/infra/specs/mixins.pyl +++ b/third_party/libwebrtc/infra/specs/mixins.pyl @@ -18,6 +18,13 @@ } } }, + 'arm64': { + 'swarming': { + 'dimensions': { + 'cpu': 'arm64' + } + } + }, 'chrome-tester-service-account': { 'swarming': { 'service_account': @@ -61,9 +68,8 @@ 'ios-device-16.7': { 'swarming': { 'dimensions': { - 'os': 'iOS-16.7.1', - 'pool': 'chrome.tests', - 'device_status': 'available' + 'os': 'iOS-16.7.5', + 'pool': 'chrome.tests' } } }, @@ -71,21 +77,11 @@ 'swarming': { 'idempotent': False, 'dimensions': { - 'os': 'iOS-17.4.1', - 'pool': 'WebRTC', - 'id': 'mac-438-e504', - 'device_status': 'available' + 'os': 'iOS-17.5.1', + 'pool': 'WebRTC' } } }, - 'ios_runtime_cache_15_5': { - 'swarming': { - 'named_caches': [{ - 'name': 'runtime_ios_15_5', - 'path': 'Runtime-ios-15.5' - }] - } - }, 'ios_runtime_cache_16_4': { 'swarming': { 'named_caches': [{ @@ -94,11 +90,19 @@ }] } }, - 'ios_runtime_cache_17_4': { + 'ios_runtime_cache_17_5': { 'swarming': { 'named_caches': [{ - 'name': 'runtime_ios_17_4', - 'path': 'Runtime-ios-17.4' + 'name': 'runtime_ios_17_5', + 'path': 'Runtime-ios-17.5' + }] + } + }, + 'ios_runtime_cache_18_0': { + 'swarming': { + 'named_caches': [{ + 'name': 'runtime_ios_18_0', + 'path': 'Runtime-ios-18.0' }] } }, @@ -315,10 +319,10 @@ } }, 'xcode_15_main': { - 'args': ['--xcode-build-version', '15c500b'], + 'args': ['--xcode-build-version', '15f31d'], 'swarming': { 'named_caches': [{ - 'name': 'xcode_ios_15c500b', + 'name': 'xcode_ios_15f31d', 'path': 'Xcode.app' }] } diff --git a/third_party/libwebrtc/infra/specs/mixins_webrtc.pyl b/third_party/libwebrtc/infra/specs/mixins_webrtc.pyl index fa64b60f21d6..52fcdf1f38ea 100644 --- a/third_party/libwebrtc/infra/specs/mixins_webrtc.pyl +++ b/third_party/libwebrtc/infra/specs/mixins_webrtc.pyl @@ -41,9 +41,9 @@ 'ios-device-16.7': { 'swarming': { 'dimensions': { - 'os': 'iOS-16.7.1', + 'os': 'iOS-16.7.5', 'pool': 'chrome.tests', - 'device_status': 'available' + #'device_status': 'available' } } }, @@ -51,21 +51,12 @@ 'swarming': { 'idempotent': False, 'dimensions': { - 'os': 'iOS-17.4.1', + 'os': 'iOS-17.5.1', 'pool': 'WebRTC', - 'id': 'mac-438-e504', - 'device_status': 'available' + #'device_status': 'available' }, }, }, - 'ios_runtime_cache_15_5': { - 'swarming': { - 'named_caches': [{ - 'name': 'runtime_ios_15_5', - 'path': 'Runtime-ios-15.5' - }] - } - }, '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. diff --git a/third_party/libwebrtc/infra/specs/test_suites.pyl b/third_party/libwebrtc/infra/specs/test_suites.pyl index ee2dbc8f96bb..478e521b2d89 100644 --- a/third_party/libwebrtc/infra/specs/test_suites.pyl +++ b/third_party/libwebrtc/infra/specs/test_suites.pyl @@ -264,9 +264,9 @@ 'ios_simulator_tests_matrix': { 'ios_simulator_tests': { 'variants': [ - 'SIM_IPHONE_X_15_5', 'SIM_IPHONE_X_16_4', 'SIM_IPHONE_14_17_4', + 'SIM_IPHONE_15_18_0', ], }, }, diff --git a/third_party/libwebrtc/infra/specs/tryserver.webrtc.json b/third_party/libwebrtc/infra/specs/tryserver.webrtc.json index 642fb38005ab..7f4ba6416023 100644 --- a/third_party/libwebrtc/infra/specs/tryserver.webrtc.json +++ b/third_party/libwebrtc/infra/specs/tryserver.webrtc.json @@ -2324,7 +2324,7 @@ "17.4", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2332,7 +2332,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "apprtcmobile_tests iPhone 14 17.4", + "name": "apprtcmobile_tests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -2351,29 +2351,29 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "apprtcmobile_tests", "test_id_prefix": "ninja://examples:apprtcmobile_tests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2381,7 +2381,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "apprtcmobile_tests iPhone X 15.5", + "name": "apprtcmobile_tests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -2400,19 +2400,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -2422,7 +2422,7 @@ "16.4", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2449,7 +2449,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -2470,7 +2470,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2478,7 +2478,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "audio_decoder_unittests iPhone 14 17.4", + "name": "audio_decoder_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -2497,28 +2497,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2526,7 +2526,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "audio_decoder_unittests iPhone X 15.5", + "name": "audio_decoder_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -2545,19 +2545,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -2566,7 +2566,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2593,7 +2593,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -2614,7 +2614,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2622,7 +2622,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "common_audio_unittests iPhone 14 17.4", + "name": "common_audio_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -2641,28 +2641,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2670,7 +2670,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "common_audio_unittests iPhone X 15.5", + "name": "common_audio_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -2689,19 +2689,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -2710,7 +2710,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2737,7 +2737,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -2758,7 +2758,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2766,7 +2766,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "common_video_unittests iPhone 14 17.4", + "name": "common_video_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -2785,28 +2785,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2814,7 +2814,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "common_video_unittests iPhone X 15.5", + "name": "common_video_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -2833,19 +2833,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -2854,7 +2854,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2881,7 +2881,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -2902,7 +2902,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2910,7 +2910,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "dcsctp_unittests iPhone 14 17.4", + "name": "dcsctp_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -2929,28 +2929,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "dcsctp_unittests", "test_id_prefix": "ninja://net/dcsctp:dcsctp_unittests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -2958,7 +2958,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "dcsctp_unittests iPhone X 15.5", + "name": "dcsctp_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -2977,19 +2977,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -2998,7 +2998,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3025,7 +3025,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -3046,7 +3046,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3054,7 +3054,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "modules_tests iPhone 14 17.4", + "name": "modules_tests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3073,12 +3073,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -3086,16 +3086,16 @@ }, "test": "modules_tests", "test_id_prefix": "ninja://modules:modules_tests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3103,7 +3103,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "modules_tests iPhone X 15.5", + "name": "modules_tests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3122,12 +3122,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.0" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -3135,7 +3135,7 @@ }, "test": "modules_tests", "test_id_prefix": "ninja://modules:modules_tests/", - "variant_id": "iPhone X 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -3144,7 +3144,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3171,7 +3171,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -3193,7 +3193,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3201,7 +3201,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "modules_unittests iPhone 14 17.4", + "name": "modules_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3221,12 +3221,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -3234,16 +3234,16 @@ }, "test": "modules_unittests", "test_id_prefix": "ninja://modules:modules_unittests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3251,7 +3251,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "modules_unittests iPhone X 15.5", + "name": "modules_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3271,12 +3271,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.0" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -3284,7 +3284,7 @@ }, "test": "modules_unittests", "test_id_prefix": "ninja://modules:modules_unittests/", - "variant_id": "iPhone X 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -3293,7 +3293,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3321,7 +3321,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -3343,7 +3343,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3351,7 +3351,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_media_unittests iPhone 14 17.4", + "name": "rtc_media_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3370,28 +3370,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3399,7 +3399,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_media_unittests iPhone X 15.5", + "name": "rtc_media_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3418,19 +3418,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -3439,7 +3439,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3466,7 +3466,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -3487,7 +3487,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3495,7 +3495,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_pc_unittests iPhone 14 17.4", + "name": "rtc_pc_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3514,28 +3514,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3543,7 +3543,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_pc_unittests iPhone X 15.5", + "name": "rtc_pc_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3562,19 +3562,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -3583,7 +3583,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3610,7 +3610,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -3631,7 +3631,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3639,7 +3639,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_stats_unittests iPhone 14 17.4", + "name": "rtc_stats_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3658,28 +3658,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3687,7 +3687,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_stats_unittests iPhone X 15.5", + "name": "rtc_stats_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3706,19 +3706,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -3727,7 +3727,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3754,7 +3754,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -3775,7 +3775,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3783,7 +3783,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_unittests iPhone 14 17.4", + "name": "rtc_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3802,12 +3802,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -3815,16 +3815,16 @@ }, "test": "rtc_unittests", "test_id_prefix": "ninja://:rtc_unittests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3832,7 +3832,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "rtc_unittests iPhone X 15.5", + "name": "rtc_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3851,12 +3851,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.0" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -3864,7 +3864,7 @@ }, "test": "rtc_unittests", "test_id_prefix": "ninja://:rtc_unittests/", - "variant_id": "iPhone X 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -3873,7 +3873,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3900,7 +3900,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -3923,7 +3923,7 @@ "17.4", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3931,7 +3931,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "sdk_framework_unittests iPhone 14 17.4", + "name": "sdk_framework_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3950,29 +3950,29 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -3980,7 +3980,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "sdk_framework_unittests iPhone X 15.5", + "name": "sdk_framework_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -3999,19 +3999,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -4021,7 +4021,7 @@ "16.4", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4048,7 +4048,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -4070,7 +4070,7 @@ "17.4", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4078,7 +4078,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "sdk_unittests iPhone 14 17.4", + "name": "sdk_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4097,29 +4097,29 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "sdk_unittests", "test_id_prefix": "ninja://sdk:sdk_unittests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4127,7 +4127,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "sdk_unittests iPhone X 15.5", + "name": "sdk_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4146,19 +4146,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -4168,7 +4168,7 @@ "16.4", "--xcodebuild-sim-runner", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4195,7 +4195,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -4216,7 +4216,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4224,7 +4224,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "svc_tests iPhone 14 17.4", + "name": "svc_tests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4244,12 +4244,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -4257,16 +4257,16 @@ }, "test": "svc_tests", "test_id_prefix": "ninja://pc:svc_tests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4274,7 +4274,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "svc_tests iPhone X 15.5", + "name": "svc_tests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4294,12 +4294,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.0" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -4307,7 +4307,7 @@ }, "test": "svc_tests", "test_id_prefix": "ninja://pc:svc_tests/", - "variant_id": "iPhone X 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -4316,7 +4316,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4344,7 +4344,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -4366,7 +4366,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4374,7 +4374,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "system_wrappers_unittests iPhone 14 17.4", + "name": "system_wrappers_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4393,28 +4393,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4422,7 +4422,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "system_wrappers_unittests iPhone X 15.5", + "name": "system_wrappers_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4441,19 +4441,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -4462,7 +4462,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4489,7 +4489,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -4510,7 +4510,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4518,7 +4518,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "test_support_unittests iPhone 14 17.4", + "name": "test_support_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4537,28 +4537,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "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 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4566,7 +4566,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "test_support_unittests iPhone X 15.5", + "name": "test_support_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4585,19 +4585,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -4606,7 +4606,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4633,7 +4633,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -4654,7 +4654,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4662,7 +4662,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "tools_unittests iPhone 14 17.4", + "name": "tools_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4681,28 +4681,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "tools_unittests", "test_id_prefix": "ninja://rtc_tools:tools_unittests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4710,7 +4710,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "tools_unittests iPhone X 15.5", + "name": "tools_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4729,19 +4729,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -4750,7 +4750,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4777,7 +4777,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -4798,7 +4798,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4806,7 +4806,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "video_engine_tests iPhone 14 17.4", + "name": "video_engine_tests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4825,12 +4825,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -4838,16 +4838,16 @@ }, "test": "video_engine_tests", "test_id_prefix": "ninja://:video_engine_tests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4855,7 +4855,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "video_engine_tests iPhone X 15.5", + "name": "video_engine_tests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4874,12 +4874,12 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.0" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com", @@ -4887,7 +4887,7 @@ }, "test": "video_engine_tests", "test_id_prefix": "ninja://:video_engine_tests/", - "variant_id": "iPhone X 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -4896,7 +4896,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4923,7 +4923,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -4945,7 +4945,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -4953,7 +4953,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "voip_unittests iPhone 14 17.4", + "name": "voip_unittests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -4972,28 +4972,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "voip_unittests", "test_id_prefix": "ninja://:voip_unittests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -5001,7 +5001,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "voip_unittests iPhone X 15.5", + "name": "voip_unittests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -5020,19 +5020,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.0" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "voip_unittests", "test_id_prefix": "ninja://:voip_unittests/", - "variant_id": "iPhone X 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -5041,7 +5041,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -5068,7 +5068,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -5089,7 +5089,7 @@ "--version", "17.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -5097,7 +5097,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "webrtc_nonparallel_tests iPhone 14 17.4", + "name": "webrtc_nonparallel_tests iPhone 14 17.5", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -5116,28 +5116,28 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_17_4", - "path": "Runtime-ios-17.4" + "name": "runtime_ios_17_5", + "path": "Runtime-ios-17.5" } ], "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com" }, "test": "webrtc_nonparallel_tests", "test_id_prefix": "ninja://:webrtc_nonparallel_tests/", - "variant_id": "iPhone 14 17.4" + "variant_id": "iPhone 14 17.5" }, { "args": [ "--platform", - "iPhone X", + "iPhone 15", "--version", - "15.5", + "18.0", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -5145,7 +5145,7 @@ "merge": { "script": "//testing/merge_scripts/standard_isolated_script_merge.py" }, - "name": "webrtc_nonparallel_tests iPhone X 15.5", + "name": "webrtc_nonparallel_tests iPhone 15 18.0", "resultdb": { "enable": true, "has_native_resultdb_integration": true @@ -5164,19 +5164,19 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { - "name": "runtime_ios_15_5", - "path": "Runtime-ios-15.5" + "name": "runtime_ios_18_0", + "path": "Runtime-ios-18.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 15.5" + "variant_id": "iPhone 15 18.0" }, { "args": [ @@ -5185,7 +5185,7 @@ "--version", "16.4", "--xcode-build-version", - "15c500b", + "15f31d", "--out-dir", "${ISOLATED_OUTDIR}", "--xctest" @@ -5212,7 +5212,7 @@ }, "named_caches": [ { - "name": "xcode_ios_15c500b", + "name": "xcode_ios_15f31d", "path": "Xcode.app" }, { @@ -5228,6 +5228,7 @@ } ] }, + "iwyu_verifier": {}, "linux_asan": { "isolated_scripts": [ { diff --git a/third_party/libwebrtc/infra/specs/variants.pyl b/third_party/libwebrtc/infra/specs/variants.pyl index 690829c2b1b4..444fbbb502d7 100644 --- a/third_party/libwebrtc/infra/specs/variants.pyl +++ b/third_party/libwebrtc/infra/specs/variants.pyl @@ -7,16 +7,6 @@ # be found in the AUTHORS file in the root of the source tree. { - 'SIM_IPHONE_X_15_5': { - 'args': [ - '--platform', - 'iPhone X', - '--version', - '15.5', - ], - 'identifier': 'iPhone X 15.5', - 'mixins': ['xcode_15_main', 'ios_runtime_cache_15_5'], - }, 'SIM_IPHONE_X_16_4': { 'args': [ '--platform', @@ -34,7 +24,17 @@ '--version', '17.4', ], - 'identifier': 'iPhone 14 17.4', - 'mixins': ['xcode_15_main', 'ios_runtime_cache_17_4'], + 'identifier': 'iPhone 14 17.5', + 'mixins': ['xcode_15_main', 'ios_runtime_cache_17_5'], + }, + 'SIM_IPHONE_15_18_0': { + 'args': [ + '--platform', + 'iPhone 15', + '--version', + '18.0', + ], + 'identifier': 'iPhone 15 18.0', + 'mixins': ['xcode_15_main', 'ios_runtime_cache_18_0'], }, } diff --git a/third_party/libwebrtc/infra/specs/waterfalls.pyl b/third_party/libwebrtc/infra/specs/waterfalls.pyl index 0248ac0afa37..cdb925f07613 100644 --- a/third_party/libwebrtc/infra/specs/waterfalls.pyl +++ b/third_party/libwebrtc/infra/specs/waterfalls.pyl @@ -332,7 +332,7 @@ }, 'iOS64 Perf': { 'mixins': [ - 'ios-device-perf', 'webrtc-xctest', 'timeout-3h', + 'arm64', 'ios-device-perf', 'webrtc-xctest', 'timeout-3h', 'chrome-tester-service-account', 'xcode_15_main', 'mac_toolchain', 'has_native_resultdb_integration', 'out_dir_arg' ], @@ -438,6 +438,7 @@ 'isolated_scripts': 'ios_simulator_tests_matrix', }, }, + 'iwyu_verifier': {}, 'linux_asan': { 'os_type': 'linux', 'mixins': ['linux-focal', 'x86-64', 'resultdb-json-format'], diff --git a/third_party/libwebrtc/logging/BUILD.gn b/third_party/libwebrtc/logging/BUILD.gn index e9237b748a91..1f700950b3a0 100644 --- a/third_party/libwebrtc/logging/BUILD.gn +++ b/third_party/libwebrtc/logging/BUILD.gn @@ -663,6 +663,7 @@ if (rtc_enable_protobuf) { "../modules/rtp_rtcp:rtp_rtcp_format", "../rtc_base:checks", "../rtc_base:protobuf_utils", + "../rtc_base:stringutils", "../test:rtp_test_utils", "//third_party/abseil-cpp/absl/flags:flag", "//third_party/abseil-cpp/absl/flags:parse", diff --git a/third_party/libwebrtc/logging/rtc_event_log/rtc_event_log2rtp_dump.cc b/third_party/libwebrtc/logging/rtc_event_log/rtc_event_log2rtp_dump.cc index 6a4dd5836648..df30fe37e6fb 100644 --- a/third_party/libwebrtc/logging/rtc_event_log/rtc_event_log2rtp_dump.cc +++ b/third_party/libwebrtc/logging/rtc_event_log/rtc_event_log2rtp_dump.cc @@ -13,7 +13,6 @@ #include #include -#include // no-presubmit-check TODO(webrtc:8982): #include #include @@ -31,6 +30,7 @@ #include "modules/rtp_rtcp/source/rtp_header_extensions.h" #include "modules/rtp_rtcp/source/rtp_packet.h" #include "rtc_base/checks.h" +#include "rtc_base/string_to_number.h" #include "test/rtp_file_reader.h" #include "test/rtp_file_writer.h" @@ -76,19 +76,8 @@ using MediaType = webrtc::ParsedRtcEventLog::MediaType; // of the command-line flag. In this case, no value is written to the output // variable. absl::optional ParseSsrc(absl::string_view str) { - // If the input string starts with 0x or 0X it indicates a hexadecimal number. - uint32_t ssrc; - auto read_mode = std::dec; - if (str.size() > 2 && - (str.substr(0, 2) == "0x" || str.substr(0, 2) == "0X")) { - read_mode = std::hex; - str = str.substr(2); - } - std::stringstream ss(std::string{str}); - ss >> read_mode >> ssrc; - if (str.empty() || (!ss.fail() && ss.eof())) - return ssrc; - return absl::nullopt; + // Set `base` to 0 to allow detection of the "0x" prefix in case hex is used. + return rtc::StringToNumber(str, 0); } bool ShouldSkipStream(MediaType media_type, diff --git a/third_party/libwebrtc/media/BUILD.gn b/third_party/libwebrtc/media/BUILD.gn index 6d1c90fc7056..cde30ba1fefb 100644 --- a/third_party/libwebrtc/media/BUILD.gn +++ b/third_party/libwebrtc/media/BUILD.gn @@ -473,15 +473,23 @@ rtc_library("rtc_simulcast_encoder_adapter") { deps = [ ":rtc_sdp_video_format_utils", ":video_common", + "../api:array_view", "../api:fec_controller_api", "../api:field_trials_view", "../api:scoped_refptr", "../api:sequence_checker", "../api/environment", + "../api/units:data_rate", + "../api/units:timestamp", + "../api/video:encoded_image", + "../api/video:video_bitrate_allocation", + "../api/video:video_bitrate_allocator", "../api/video:video_codec_constants", "../api/video:video_frame", + "../api/video:video_frame_type", "../api/video:video_rtp_headers", "../api/video_codecs:rtc_software_fallback_wrappers", + "../api/video_codecs:scalability_mode", "../api/video_codecs:video_codecs_api", "../call:video_stream_api", "../common_video", diff --git a/third_party/libwebrtc/media/base/media_channel.h b/third_party/libwebrtc/media/base/media_channel.h index fad10131e9c3..6e1f2b103f7b 100644 --- a/third_party/libwebrtc/media/base/media_channel.h +++ b/third_party/libwebrtc/media/base/media_channel.h @@ -95,7 +95,7 @@ static std::string ToStringIfSet(const char* key, template static std::string VectorToString(const std::vector& vals) { - rtc::StringBuilder ost; // no-presubmit-check TODO(webrtc:8982) + rtc::StringBuilder ost; ost << "["; for (size_t i = 0; i < vals.size(); ++i) { if (i > 0) { @@ -472,6 +472,8 @@ struct MediaReceiverInfo { absl::optional fec_packets_discarded; // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-fecbytesreceived absl::optional fec_bytes_received; + // https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats-totalprocessingdelay + double total_processing_delay_seconds = 0.0; // Remote outbound stats derived by the received RTCP sender reports. // https://w3c.github.io/webrtc-stats/#remoteoutboundrtpstats-dict* diff --git a/third_party/libwebrtc/media/engine/fake_webrtc_call.cc b/third_party/libwebrtc/media/engine/fake_webrtc_call.cc index f5a1f693e4eb..894f69fedcb5 100644 --- a/third_party/libwebrtc/media/engine/fake_webrtc_call.cc +++ b/third_party/libwebrtc/media/engine/fake_webrtc_call.cc @@ -259,11 +259,8 @@ void FakeVideoSendStream::OnFrame(const webrtc::VideoFrame& frame) { encoder_config_); } else { webrtc::VideoEncoder::EncoderInfo encoder_info; - auto factory = rtc::make_ref_counted( - encoder_config_.video_format.name, encoder_config_.max_qp, - encoder_config_.content_type == - webrtc::VideoEncoderConfig::ContentType::kScreen, - encoder_config_.legacy_conference_mode, encoder_info); + auto factory = + rtc::make_ref_counted(encoder_info); video_streams_ = factory->CreateEncoderStreams( env_.field_trials(), frame.width(), frame.height(), encoder_config_); @@ -302,10 +299,8 @@ void FakeVideoSendStream::ReconfigureVideoEncoder( env_.field_trials(), width, height, config); } else { webrtc::VideoEncoder::EncoderInfo encoder_info; - auto factory = rtc::make_ref_counted( - config.video_format.name, config.max_qp, - config.content_type == webrtc::VideoEncoderConfig::ContentType::kScreen, - config.legacy_conference_mode, encoder_info); + auto factory = + rtc::make_ref_counted(encoder_info); video_streams_ = factory->CreateEncoderStreams(env_.field_trials(), width, height, config); diff --git a/third_party/libwebrtc/media/engine/simulcast_encoder_adapter.cc b/third_party/libwebrtc/media/engine/simulcast_encoder_adapter.cc index 08720321a599..9bdb4d508fb0 100644 --- a/third_party/libwebrtc/media/engine/simulcast_encoder_adapter.cc +++ b/third_party/libwebrtc/media/engine/simulcast_encoder_adapter.cc @@ -15,21 +15,41 @@ #include #include +#include +#include #include +#include #include +#include #include "absl/algorithm/container.h" +#include "absl/base/nullability.h" #include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/environment/environment.h" +#include "api/fec_controller_override.h" #include "api/field_trials_view.h" #include "api/scoped_refptr.h" -#include "api/video/i420_buffer.h" +#include "api/sequence_checker.h" +#include "api/units/data_rate.h" +#include "api/units/timestamp.h" +#include "api/video/encoded_image.h" +#include "api/video/video_bitrate_allocation.h" +#include "api/video/video_bitrate_allocator.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_buffer.h" +#include "api/video/video_frame_type.h" #include "api/video/video_rotation.h" +#include "api/video_codecs/scalability_mode.h" +#include "api/video_codecs/sdp_video_format.h" +#include "api/video_codecs/simulcast_stream.h" +#include "api/video_codecs/video_codec.h" #include "api/video_codecs/video_encoder.h" #include "api/video_codecs/video_encoder_factory.h" #include "api/video_codecs/video_encoder_software_fallback_wrapper.h" -#include "media/base/media_constants.h" +#include "common_video/framerate_controller.h" #include "media/base/sdp_video_format_utils.h" #include "media/base/video_common.h" #include "modules/video_coding/include/video_error_codes.h" @@ -257,6 +277,7 @@ SimulcastEncoderAdapter::SimulcastEncoderAdapter( } SimulcastEncoderAdapter::~SimulcastEncoderAdapter() { + RTC_DCHECK_RUN_ON(&encoder_queue_); RTC_DCHECK(!Initialized()); DestroyStoredEncoders(); } @@ -365,6 +386,8 @@ int SimulcastEncoderAdapter::InitEncode( } encoder_context->Release(); + encoder_context->encoder().RegisterEncodeCompleteCallback( + encoded_complete_callback_); if (total_streams_count_ == 1) { RTC_LOG(LS_ERROR) << "[SEA] InitEncode: failed with error code: " << WebRtcVideoCodecErrorToString(ret); @@ -692,6 +715,7 @@ bool SimulcastEncoderAdapter::Initialized() const { } void SimulcastEncoderAdapter::DestroyStoredEncoders() { + RTC_DCHECK_RUN_ON(&encoder_queue_); while (!cached_encoder_contexts_.empty()) { cached_encoder_contexts_.pop_back(); } @@ -700,6 +724,7 @@ void SimulcastEncoderAdapter::DestroyStoredEncoders() { std::unique_ptr SimulcastEncoderAdapter::FetchOrCreateEncoderContext( bool is_lowest_quality_stream) const { + RTC_DCHECK_RUN_ON(&encoder_queue_); bool prefer_temporal_support = fallback_encoder_factory_ != nullptr && is_lowest_quality_stream && prefer_temporal_support_on_base_layer_; @@ -789,18 +814,16 @@ webrtc::VideoCodec SimulcastEncoderAdapter::MakeStreamCodec( // To support the full set of scalability modes in the event that this is the // only active encoding, prefer VideoCodec::GetScalabilityMode() if all other // encodings are inactive. - if (codec.GetScalabilityMode().has_value()) { - bool only_active_stream = true; - for (int i = 0; i < codec.numberOfSimulcastStreams; ++i) { - if (i != stream_idx && codec.simulcastStream[i].active) { - only_active_stream = false; - break; - } - } - if (only_active_stream) { - scalability_mode = codec.GetScalabilityMode(); + bool only_active_stream = true; + for (int i = 0; i < codec.numberOfSimulcastStreams; ++i) { + if (i != stream_idx && codec.simulcastStream[i].active) { + only_active_stream = false; + break; } } + if (codec.GetScalabilityMode().has_value() && only_active_stream) { + scalability_mode = codec.GetScalabilityMode(); + } if (scalability_mode.has_value()) { codec_params.SetScalabilityMode(*scalability_mode); } @@ -829,6 +852,15 @@ webrtc::VideoCodec SimulcastEncoderAdapter::MakeStreamCodec( } else if (codec.codecType == webrtc::kVideoCodecH264) { codec_params.H264()->numberOfTemporalLayers = stream_params.numberOfTemporalLayers; + } else if (codec.codecType == webrtc::kVideoCodecVP9 && + scalability_mode.has_value() && !only_active_stream) { + // If VP9 simulcast then explicitly set a single spatial layer for each + // simulcast stream. + codec_params.VP9()->numberOfSpatialLayers = 1; + codec_params.VP9()->numberOfTemporalLayers = + stream_params.GetNumberOfTemporalLayers(); + codec_params.VP9()->interLayerPred = InterLayerPredMode::kOff; + codec_params.spatialLayers[0] = stream_params; } // Cap start bitrate to the min bitrate in order to avoid strange codec 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 049c78ef03f4..5d386333d7d3 100644 --- a/third_party/libwebrtc/media/engine/simulcast_encoder_adapter_unittest.cc +++ b/third_party/libwebrtc/media/engine/simulcast_encoder_adapter_unittest.cc @@ -191,12 +191,16 @@ class MockVideoEncoderFactory : public VideoEncoderFactory { std::vector limits) { resolution_bitrate_limits_ = limits; } + void set_fallback_from_simulcast(bool value) { + fallback_from_simulcast_ = value; + } void DestroyVideoEncoder(VideoEncoder* encoder); private: bool create_video_encoder_return_nullptr_ = false; int32_t init_encode_return_value_ = 0; + bool fallback_from_simulcast_ = false; std::vector encoders_; std::vector encoder_names_; // Keep number of entries in sync with `kMaxSimulcastStreams`. @@ -221,6 +225,9 @@ class MockVideoEncoder : public VideoEncoder { int32_t InitEncode(const VideoCodec* codecSettings, const VideoEncoder::Settings& settings) override { codec_ = *codecSettings; + if (codec_.numberOfSimulcastStreams > 1 && fallback_from_simulcast_) { + return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE; + } return init_encode_return_value_; } @@ -263,6 +270,8 @@ class MockVideoEncoder : public VideoEncoder { const VideoCodec& codec() const { return codec_; } + EncodedImageCallback* callback() const { return callback_; } + void SendEncodedImage(int width, int height) { // Sends a fake image of the given width/height. EncodedImage image; @@ -285,6 +294,10 @@ class MockVideoEncoder : public VideoEncoder { init_encode_return_value_ = value; } + void set_fallback_from_simulcast(bool value) { + fallback_from_simulcast_ = value; + } + void set_scaling_settings(const VideoEncoder::ScalingSettings& settings) { scaling_settings_ = settings; } @@ -343,6 +356,7 @@ class MockVideoEncoder : public VideoEncoder { bool has_trusted_rate_controller_ = false; bool is_hardware_accelerated_ = false; int32_t init_encode_return_value_ = 0; + bool fallback_from_simulcast_ = false; VideoEncoder::RateControlParameters last_set_rates_; FramerateFractions fps_allocation_; bool supports_simulcast_ = false; @@ -368,6 +382,7 @@ std::unique_ptr MockVideoEncoderFactory::Create( auto encoder = std::make_unique<::testing::NiceMock>(this); encoder->set_init_encode_return_value(init_encode_return_value_); + encoder->set_fallback_from_simulcast(fallback_from_simulcast_); const char* encoder_name = encoder_names_.empty() ? "codec_implementation_name" : encoder_names_[encoders_.size()]; @@ -508,6 +523,28 @@ class TestSimulcastEncoderAdapterFake : public ::testing::Test, adapter_->RegisterEncodeCompleteCallback(this); } + void SetupCodecWithEarlyEncodeCompleteCallback( + std::vector active_streams) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast(kTestTemporalLayerProfile), + kVideoCodecVP8); + ASSERT_LE(active_streams.size(), codec_.numberOfSimulcastStreams); + codec_.numberOfSimulcastStreams = active_streams.size(); + for (size_t stream_idx = 0; stream_idx < kMaxSimulcastStreams; + ++stream_idx) { + if (stream_idx >= codec_.numberOfSimulcastStreams) { + // Reset parameters of unspecified stream. + codec_.simulcastStream[stream_idx] = {0}; + } else { + codec_.simulcastStream[stream_idx].active = active_streams[stream_idx]; + } + } + rate_allocator_ = std::make_unique(env_, codec_); + // Register the callback before the InitEncode(). + adapter_->RegisterEncodeCompleteCallback(this); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + } + void VerifyCodec(const VideoCodec& ref, int stream_index) { const VideoCodec& target = helper_->factory()->encoders()[stream_index]->codec(); @@ -597,6 +634,17 @@ TEST_F(TestSimulcastEncoderAdapterFake, InitEncode) { VerifyCodecSettings(); } +TEST_F(TestSimulcastEncoderAdapterFake, EarlyCallbackSetupNotLost) { + helper_->factory()->set_supports_simulcast(true); + helper_->factory()->set_fallback_from_simulcast(true); + SetupCodecWithEarlyEncodeCompleteCallback( + /*active_streams=*/{true, true, true}); + for (size_t idx = 0; idx < 3; ++idx) { + auto callback = helper_->factory()->encoders()[idx]->callback(); + EXPECT_NE(callback, nullptr); + } +} + TEST_F(TestSimulcastEncoderAdapterFake, ReleaseWithoutInitEncode) { EXPECT_EQ(0, adapter_->Release()); } diff --git a/third_party/libwebrtc/media/engine/webrtc_video_engine.cc b/third_party/libwebrtc/media/engine/webrtc_video_engine.cc index 70176e88afb8..ac7d0f884aec 100644 --- a/third_party/libwebrtc/media/engine/webrtc_video_engine.cc +++ b/third_party/libwebrtc/media/engine/webrtc_video_engine.cc @@ -978,10 +978,8 @@ WebRtcVideoSendChannel::WebRtcVideoSendStream::ConfigureVideoEncoderSettings( {{"off", webrtc::InterLayerPredMode::kOff}, {"on", webrtc::InterLayerPredMode::kOn}, {"onkeypic", webrtc::InterLayerPredMode::kOnKeyPic}}); - webrtc::FieldTrialFlag force_flexible_mode("FlexibleMode"); webrtc::ParseFieldTrial( - {&interlayer_pred_experiment_enabled, &inter_layer_pred_mode, - &force_flexible_mode}, + {&interlayer_pred_experiment_enabled, &inter_layer_pred_mode}, call_->trials().Lookup("WebRTC-Vp9InterLayerPred")); if (interlayer_pred_experiment_enabled) { vp9_settings.interLayerPred = inter_layer_pred_mode; @@ -989,7 +987,10 @@ WebRtcVideoSendChannel::WebRtcVideoSendStream::ConfigureVideoEncoderSettings( // Limit inter-layer prediction to key pictures by default. vp9_settings.interLayerPred = webrtc::InterLayerPredMode::kOnKeyPic; } - vp9_settings.flexibleMode = force_flexible_mode.Get(); + + // TODO(webrtc:329396373): Remove after flexible mode is fully deployed. + vp9_settings.flexibleMode = + !IsDisabled(call_->trials(), "WebRTC-Video-Vp9FlexibleMode"); } else { // Multiple spatial layers vp9 screenshare needs flexible mode. vp9_settings.flexibleMode = vp9_settings.numberOfSpatialLayers > 1; @@ -1503,9 +1504,9 @@ bool WebRtcVideoSendChannel::AddSendStream(const StreamParams& sp) { RTC_DCHECK(ssrc != 0); send_streams_[ssrc] = stream; - if (ssrc_list_changed_callback_) { - ssrc_list_changed_callback_(send_ssrcs_); - } + if (ssrc_list_changed_callback_) { + ssrc_list_changed_callback_(send_ssrcs_); + } if (sending_) { stream->SetSend(true); @@ -2239,21 +2240,10 @@ WebRtcVideoSendChannel::WebRtcVideoSendStream::CreateVideoEncoderConfig( // Ensure frame dropping is always enabled. encoder_config.frame_drop_enabled = true; - int max_qp; - switch (encoder_config.codec_type) { - case webrtc::kVideoCodecH264: - case webrtc::kVideoCodecH265: - max_qp = kDefaultVideoMaxQpH26x; - break; - case webrtc::kVideoCodecVP8: - case webrtc::kVideoCodecVP9: - case webrtc::kVideoCodecAV1: - case webrtc::kVideoCodecGeneric: - max_qp = kDefaultVideoMaxQpVpx; - break; + int max_qp = -1; + if (codec.GetParam(kCodecParamMaxQuantization, &max_qp) && max_qp > 0) { + encoder_config.max_qp = max_qp; } - codec.GetParam(kCodecParamMaxQuantization, &max_qp); - encoder_config.max_qp = max_qp; return encoder_config; } @@ -3146,22 +3136,22 @@ bool WebRtcVideoReceiveChannel::MaybeCreateDefaultReceiveStream( // BWE has already been notified of this received packet. return false; } - // Ignore unknown ssrcs if we recently created an unsignalled receive - // stream since this shouldn't happen frequently. Getting into a state - // of creating decoders on every packet eats up processing time (e.g. - // https://crbug.com/1069603) and this cooldown prevents that. - if (last_unsignalled_ssrc_creation_time_ms_.has_value()) { - int64_t now_ms = rtc::TimeMillis(); - if (now_ms - last_unsignalled_ssrc_creation_time_ms_.value() < - kUnsignaledSsrcCooldownMs) { - // We've already created an unsignalled ssrc stream within the last - // 0.5 s, ignore with a warning. - RTC_LOG(LS_WARNING) - << "Another unsignalled ssrc packet arrived shortly after the " - << "creation of an unsignalled ssrc stream. Dropping packet."; - return false; - } + // Ignore unknown ssrcs if we recently created an unsignalled receive + // stream since this shouldn't happen frequently. Getting into a state + // of creating decoders on every packet eats up processing time (e.g. + // https://crbug.com/1069603) and this cooldown prevents that. + if (last_unsignalled_ssrc_creation_time_ms_.has_value()) { + int64_t now_ms = rtc::TimeMillis(); + if (now_ms - last_unsignalled_ssrc_creation_time_ms_.value() < + kUnsignaledSsrcCooldownMs) { + // We've already created an unsignalled ssrc stream within the last + // 0.5 s, ignore with a warning. + RTC_LOG(LS_WARNING) + << "Another unsignalled ssrc packet arrived shortly after the " + << "creation of an unsignalled ssrc stream. Dropping packet."; + return false; } + } // RTX SSRC not yet known. ReCreateDefaultReceiveStream(packet.Ssrc(), absl::nullopt); 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 3ab079c7a325..0259b02c8d0f 100644 --- a/third_party/libwebrtc/media/engine/webrtc_video_engine_unittest.cc +++ b/third_party/libwebrtc/media/engine/webrtc_video_engine_unittest.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -80,9 +81,11 @@ #include "test/rtcp_packet_parser.h" #include "test/scoped_key_value_config.h" #include "test/time_controller/simulated_time_controller.h" +#include "video/config/encoder_stream_factory.h" #include "video/config/simulcast.h" using ::testing::_; +using ::testing::Combine; using ::testing::Contains; using ::testing::Each; using ::testing::ElementsAre; @@ -7998,128 +8001,28 @@ TEST_F(WebRtcVideoChannelTest, SetRtpSendParametersPrioritySimulcastStreams) { EXPECT_TRUE(send_channel_->SetVideoSend(primary_ssrc, nullptr, nullptr)); } -TEST_F(WebRtcVideoChannelTest, - GetAndSetRtpSendParametersScaleResolutionDownByVP8) { +struct ScaleResolutionDownByTestParameters { + std::string field_trials; + webrtc::Resolution resolution; + std::vector> scale_resolution_down_by; + std::vector expected_resolutions; +}; + +class WebRtcVideoChannelScaleResolutionDownByTest + : public WebRtcVideoChannelTest, + public ::testing::WithParamInterface< + std::tuple> {}; + +TEST_P(WebRtcVideoChannelScaleResolutionDownByTest, ScaleResolutionDownBy) { + ScaleResolutionDownByTestParameters test_params = std::get<0>(GetParam()); + std::string codec_name = std::get<1>(GetParam()); + webrtc::test::ScopedKeyValueConfig field_trial(field_trials_, + test_params.field_trials); + // Set up WebRtcVideoChannel for 3-layer simulcast. + encoder_factory_->AddSupportedVideoCodecType(codec_name); VideoSenderParameters parameters; - parameters.codecs.push_back(cricket::CreateVideoCodec(kVp8CodecName)); - ASSERT_TRUE(send_channel_->SetSenderParameters(parameters)); - FakeVideoSendStream* stream = SetUpSimulcast(true, /*with_rtx=*/false); - - webrtc::test::FrameForwarder frame_forwarder; - FakeFrameSource frame_source(1280, 720, rtc::kNumMicrosecsPerSec / 30); - - VideoOptions options; - EXPECT_TRUE( - send_channel_->SetVideoSend(last_ssrc_, &options, &frame_forwarder)); - send_channel_->SetSend(true); - - // Try layers in natural order (smallest to largest). - { - auto rtp_parameters = send_channel_->GetRtpSendParameters(last_ssrc_); - ASSERT_EQ(3u, rtp_parameters.encodings.size()); - rtp_parameters.encodings[0].scale_resolution_down_by = 4.0; - rtp_parameters.encodings[1].scale_resolution_down_by = 2.0; - rtp_parameters.encodings[2].scale_resolution_down_by = 1.0; - auto result = - send_channel_->SetRtpSendParameters(last_ssrc_, rtp_parameters); - ASSERT_TRUE(result.ok()); - - frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame()); - - std::vector video_streams = stream->GetVideoStreams(); - ASSERT_EQ(3u, video_streams.size()); - EXPECT_EQ(320u, video_streams[0].width); - EXPECT_EQ(180u, video_streams[0].height); - EXPECT_EQ(640u, video_streams[1].width); - EXPECT_EQ(360u, video_streams[1].height); - EXPECT_EQ(1280u, video_streams[2].width); - EXPECT_EQ(720u, video_streams[2].height); - } - - // Try layers in reverse natural order (largest to smallest). - { - auto rtp_parameters = send_channel_->GetRtpSendParameters(last_ssrc_); - ASSERT_EQ(3u, rtp_parameters.encodings.size()); - rtp_parameters.encodings[0].scale_resolution_down_by = 1.0; - rtp_parameters.encodings[1].scale_resolution_down_by = 2.0; - rtp_parameters.encodings[2].scale_resolution_down_by = 4.0; - auto result = - send_channel_->SetRtpSendParameters(last_ssrc_, rtp_parameters); - ASSERT_TRUE(result.ok()); - - frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame()); - - std::vector video_streams = stream->GetVideoStreams(); - ASSERT_EQ(3u, video_streams.size()); - EXPECT_EQ(1280u, video_streams[0].width); - EXPECT_EQ(720u, video_streams[0].height); - EXPECT_EQ(640u, video_streams[1].width); - EXPECT_EQ(360u, video_streams[1].height); - EXPECT_EQ(320u, video_streams[2].width); - EXPECT_EQ(180u, video_streams[2].height); - } - - // Try layers in mixed order. - { - auto rtp_parameters = send_channel_->GetRtpSendParameters(last_ssrc_); - ASSERT_EQ(3u, rtp_parameters.encodings.size()); - rtp_parameters.encodings[0].scale_resolution_down_by = 10.0; - rtp_parameters.encodings[1].scale_resolution_down_by = 2.0; - rtp_parameters.encodings[2].scale_resolution_down_by = 4.0; - auto result = - send_channel_->SetRtpSendParameters(last_ssrc_, rtp_parameters); - ASSERT_TRUE(result.ok()); - - frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame()); - - std::vector video_streams = stream->GetVideoStreams(); - ASSERT_EQ(3u, video_streams.size()); - EXPECT_EQ(128u, video_streams[0].width); - EXPECT_EQ(72u, video_streams[0].height); - EXPECT_EQ(640u, video_streams[1].width); - EXPECT_EQ(360u, video_streams[1].height); - EXPECT_EQ(320u, video_streams[2].width); - EXPECT_EQ(180u, video_streams[2].height); - } - - // Try with a missing scale setting, defaults to 1.0 if any other is set. - { - auto rtp_parameters = send_channel_->GetRtpSendParameters(last_ssrc_); - ASSERT_EQ(3u, rtp_parameters.encodings.size()); - rtp_parameters.encodings[0].scale_resolution_down_by = 1.0; - rtp_parameters.encodings[1].scale_resolution_down_by.reset(); - rtp_parameters.encodings[2].scale_resolution_down_by = 4.0; - auto result = - send_channel_->SetRtpSendParameters(last_ssrc_, rtp_parameters); - ASSERT_TRUE(result.ok()); - - frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame()); - - std::vector video_streams = stream->GetVideoStreams(); - ASSERT_EQ(3u, video_streams.size()); - EXPECT_EQ(1280u, video_streams[0].width); - EXPECT_EQ(720u, video_streams[0].height); - EXPECT_EQ(1280u, video_streams[1].width); - EXPECT_EQ(720u, video_streams[1].height); - EXPECT_EQ(320u, video_streams[2].width); - EXPECT_EQ(180u, video_streams[2].height); - } - - EXPECT_TRUE(send_channel_->SetVideoSend(last_ssrc_, nullptr, nullptr)); -} - -TEST_F(WebRtcVideoChannelTest, - GetAndSetRtpSendParametersScaleResolutionDownByVP8WithOddResolution) { - // Ensure that the top layer has width and height divisible by 2^3, - // so that the bottom layer has width and height divisible by 2. - // TODO(bugs.webrtc.org/8785): Remove this field trial when we fully trust - // the number of simulcast layers set by the app. - webrtc::test::ScopedKeyValueConfig field_trial( - field_trials_, "WebRTC-NormalizeSimulcastResolution/Enabled-3/"); - - // Set up WebRtcVideoChannel for 3-layer VP8 simulcast. - VideoSenderParameters parameters; - parameters.codecs.push_back(cricket::CreateVideoCodec(kVp8CodecName)); + parameters.codecs.push_back(cricket::CreateVideoCodec(codec_name)); ASSERT_TRUE(send_channel_->SetSenderParameters(parameters)); FakeVideoSendStream* stream = SetUpSimulcast(true, /*with_rtx=*/false); webrtc::test::FrameForwarder frame_forwarder; @@ -8130,194 +8033,87 @@ TEST_F(WebRtcVideoChannelTest, // Set `scale_resolution_down_by`'s. auto rtp_parameters = send_channel_->GetRtpSendParameters(last_ssrc_); ASSERT_EQ(rtp_parameters.encodings.size(), 3u); - rtp_parameters.encodings[0].scale_resolution_down_by = 1.0; - rtp_parameters.encodings[1].scale_resolution_down_by = 2.0; - rtp_parameters.encodings[2].scale_resolution_down_by = 4.0; + rtp_parameters.encodings[0].scale_resolution_down_by = + test_params.scale_resolution_down_by[0]; + rtp_parameters.encodings[1].scale_resolution_down_by = + test_params.scale_resolution_down_by[1]; + rtp_parameters.encodings[2].scale_resolution_down_by = + test_params.scale_resolution_down_by[2]; const auto result = send_channel_->SetRtpSendParameters(last_ssrc_, rtp_parameters); ASSERT_TRUE(result.ok()); // Use a capture resolution whose width and height are not divisible by 2^3. // (See field trial set at the top of the test.) - FakeFrameSource frame_source(2007, 1207, rtc::kNumMicrosecsPerSec / 30); + FakeFrameSource frame_source(test_params.resolution.width, + test_params.resolution.height, + rtc::kNumMicrosecsPerSec / 30); frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame()); // Ensure the scaling is correct. - const auto video_streams = stream->GetVideoStreams(); - ASSERT_EQ(video_streams.size(), 3u); - // Ensure that we round the capture resolution down for the top layer... - EXPECT_EQ(video_streams[0].width, 2000u); - EXPECT_EQ(video_streams[0].height, 1200u); - EXPECT_EQ(video_streams[1].width, 1000u); - EXPECT_EQ(video_streams[1].height, 600u); - // ...and that the bottom layer has a width/height divisible by 2. - EXPECT_EQ(video_streams[2].width, 500u); - EXPECT_EQ(video_streams[2].height, 300u); + const auto streams = stream->GetVideoStreams(); + ASSERT_EQ(streams.size(), 3u); + EXPECT_EQ(static_cast(streams[0].width), + test_params.expected_resolutions[0].width); + EXPECT_EQ(static_cast(streams[0].height), + test_params.expected_resolutions[0].height); + EXPECT_EQ(static_cast(streams[1].width), + test_params.expected_resolutions[1].width); + EXPECT_EQ(static_cast(streams[1].height), + test_params.expected_resolutions[1].height); + EXPECT_EQ(static_cast(streams[2].width), + test_params.expected_resolutions[2].width); + EXPECT_EQ(static_cast(streams[2].height), + test_params.expected_resolutions[2].height); // Tear down. EXPECT_TRUE(send_channel_->SetVideoSend(last_ssrc_, nullptr, nullptr)); } -TEST_F(WebRtcVideoChannelTest, - GetAndSetRtpSendParametersScaleResolutionDownByH264) { - encoder_factory_->AddSupportedVideoCodecType(kH264CodecName); - VideoSenderParameters parameters; - parameters.codecs.push_back(cricket::CreateVideoCodec(kH264CodecName)); - ASSERT_TRUE(send_channel_->SetSenderParameters(parameters)); - FakeVideoSendStream* stream = SetUpSimulcast(true, /*with_rtx=*/false); - - webrtc::test::FrameForwarder frame_forwarder; - FakeFrameSource frame_source(1280, 720, rtc::kNumMicrosecsPerSec / 30); - - VideoOptions options; - EXPECT_TRUE( - send_channel_->SetVideoSend(last_ssrc_, &options, &frame_forwarder)); - send_channel_->SetSend(true); - - // Try layers in natural order (smallest to largest). - { - auto rtp_parameters = send_channel_->GetRtpSendParameters(last_ssrc_); - ASSERT_EQ(3u, rtp_parameters.encodings.size()); - rtp_parameters.encodings[0].scale_resolution_down_by = 4.0; - rtp_parameters.encodings[1].scale_resolution_down_by = 2.0; - rtp_parameters.encodings[2].scale_resolution_down_by = 1.0; - auto result = - send_channel_->SetRtpSendParameters(last_ssrc_, rtp_parameters); - ASSERT_TRUE(result.ok()); - - frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame()); - - std::vector video_streams = stream->GetVideoStreams(); - ASSERT_EQ(3u, video_streams.size()); - EXPECT_EQ(320u, video_streams[0].width); - EXPECT_EQ(180u, video_streams[0].height); - EXPECT_EQ(640u, video_streams[1].width); - EXPECT_EQ(360u, video_streams[1].height); - EXPECT_EQ(1280u, video_streams[2].width); - EXPECT_EQ(720u, video_streams[2].height); - } - - // Try layers in reverse natural order (largest to smallest). - { - auto rtp_parameters = send_channel_->GetRtpSendParameters(last_ssrc_); - ASSERT_EQ(3u, rtp_parameters.encodings.size()); - rtp_parameters.encodings[0].scale_resolution_down_by = 1.0; - rtp_parameters.encodings[1].scale_resolution_down_by = 2.0; - rtp_parameters.encodings[2].scale_resolution_down_by = 4.0; - auto result = - send_channel_->SetRtpSendParameters(last_ssrc_, rtp_parameters); - ASSERT_TRUE(result.ok()); - - frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame()); - - std::vector video_streams = stream->GetVideoStreams(); - ASSERT_EQ(3u, video_streams.size()); - EXPECT_EQ(1280u, video_streams[0].width); - EXPECT_EQ(720u, video_streams[0].height); - EXPECT_EQ(640u, video_streams[1].width); - EXPECT_EQ(360u, video_streams[1].height); - EXPECT_EQ(320u, video_streams[2].width); - EXPECT_EQ(180u, video_streams[2].height); - } - - // Try layers in mixed order. - { - auto rtp_parameters = send_channel_->GetRtpSendParameters(last_ssrc_); - ASSERT_EQ(3u, rtp_parameters.encodings.size()); - rtp_parameters.encodings[0].scale_resolution_down_by = 10.0; - rtp_parameters.encodings[1].scale_resolution_down_by = 2.0; - rtp_parameters.encodings[2].scale_resolution_down_by = 4.0; - auto result = - send_channel_->SetRtpSendParameters(last_ssrc_, rtp_parameters); - ASSERT_TRUE(result.ok()); - - frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame()); - - std::vector video_streams = stream->GetVideoStreams(); - ASSERT_EQ(3u, video_streams.size()); - EXPECT_EQ(128u, video_streams[0].width); - EXPECT_EQ(72u, video_streams[0].height); - EXPECT_EQ(640u, video_streams[1].width); - EXPECT_EQ(360u, video_streams[1].height); - EXPECT_EQ(320u, video_streams[2].width); - EXPECT_EQ(180u, video_streams[2].height); - } - - // Try with a missing scale setting, defaults to 1.0 if any other is set. - { - auto rtp_parameters = send_channel_->GetRtpSendParameters(last_ssrc_); - ASSERT_EQ(3u, rtp_parameters.encodings.size()); - rtp_parameters.encodings[0].scale_resolution_down_by = 1.0; - rtp_parameters.encodings[1].scale_resolution_down_by.reset(); - rtp_parameters.encodings[2].scale_resolution_down_by = 4.0; - auto result = - send_channel_->SetRtpSendParameters(last_ssrc_, rtp_parameters); - ASSERT_TRUE(result.ok()); - - frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame()); - - std::vector video_streams = stream->GetVideoStreams(); - ASSERT_EQ(3u, video_streams.size()); - EXPECT_EQ(1280u, video_streams[0].width); - EXPECT_EQ(720u, video_streams[0].height); - EXPECT_EQ(1280u, video_streams[1].width); - EXPECT_EQ(720u, video_streams[1].height); - EXPECT_EQ(320u, video_streams[2].width); - EXPECT_EQ(180u, video_streams[2].height); - } - EXPECT_TRUE(send_channel_->SetVideoSend(last_ssrc_, nullptr, nullptr)); -} - -TEST_F(WebRtcVideoChannelTest, - GetAndSetRtpSendParametersScaleResolutionDownByH264WithOddResolution) { - // Ensure that the top layer has width and height divisible by 2^3, - // so that the bottom layer has width and height divisible by 2. - // TODO(bugs.webrtc.org/8785): Remove this field trial when we fully trust - // the number of simulcast layers set by the app. - webrtc::test::ScopedKeyValueConfig field_trial( - field_trials_, "WebRTC-NormalizeSimulcastResolution/Enabled-3/"); - - // Set up WebRtcVideoChannel for 3-layer H264 simulcast. - encoder_factory_->AddSupportedVideoCodecType(kH264CodecName); - VideoSenderParameters parameters; - parameters.codecs.push_back(cricket::CreateVideoCodec(kH264CodecName)); - ASSERT_TRUE(send_channel_->SetSenderParameters(parameters)); - FakeVideoSendStream* stream = SetUpSimulcast(true, /*with_rtx=*/false); - webrtc::test::FrameForwarder frame_forwarder; - EXPECT_TRUE(send_channel_->SetVideoSend(last_ssrc_, /*options=*/nullptr, - &frame_forwarder)); - send_channel_->SetSend(true); - - // Set `scale_resolution_down_by`'s. - auto rtp_parameters = send_channel_->GetRtpSendParameters(last_ssrc_); - ASSERT_EQ(rtp_parameters.encodings.size(), 3u); - rtp_parameters.encodings[0].scale_resolution_down_by = 1.0; - rtp_parameters.encodings[1].scale_resolution_down_by = 2.0; - rtp_parameters.encodings[2].scale_resolution_down_by = 4.0; - const auto result = - send_channel_->SetRtpSendParameters(last_ssrc_, rtp_parameters); - ASSERT_TRUE(result.ok()); - - // Use a capture resolution whose width and height are not divisible by 2^3. - // (See field trial set at the top of the test.) - FakeFrameSource frame_source(2007, 1207, rtc::kNumMicrosecsPerSec / 30); - frame_forwarder.IncomingCapturedFrame(frame_source.GetFrame()); - - // Ensure the scaling is correct. - const auto video_streams = stream->GetVideoStreams(); - ASSERT_EQ(video_streams.size(), 3u); - // Ensure that we round the capture resolution down for the top layer... - EXPECT_EQ(video_streams[0].width, 2000u); - EXPECT_EQ(video_streams[0].height, 1200u); - EXPECT_EQ(video_streams[1].width, 1000u); - EXPECT_EQ(video_streams[1].height, 600u); - // ...and that the bottom layer has a width/height divisible by 2. - EXPECT_EQ(video_streams[2].width, 500u); - EXPECT_EQ(video_streams[2].height, 300u); - - // Tear down. - EXPECT_TRUE(send_channel_->SetVideoSend(last_ssrc_, nullptr, nullptr)); -} +INSTANTIATE_TEST_SUITE_P( + All, + WebRtcVideoChannelScaleResolutionDownByTest, + Combine(Values( + // Try layers in natural order (smallest to largest). + ScaleResolutionDownByTestParameters{ + .resolution = {.width = 1280, .height = 720}, + .scale_resolution_down_by = {4, 2, 1}, + .expected_resolutions = {{.width = 320, .height = 180}, + {.width = 640, .height = 360}, + {.width = 1280, .height = 720}}}, + // Try layers in reverse natural order (largest to smallest). + ScaleResolutionDownByTestParameters{ + .resolution = {.width = 1280, .height = 720}, + .scale_resolution_down_by = {1, 2, 4}, + .expected_resolutions = {{.width = 1280, .height = 720}, + {.width = 640, .height = 360}, + {.width = 320, .height = 180}}}, + // Try layers in mixed order. + ScaleResolutionDownByTestParameters{ + .resolution = {.width = 1280, .height = 720}, + .scale_resolution_down_by = {10, 2, 4}, + .expected_resolutions = {{.width = 128, .height = 72}, + {.width = 640, .height = 360}, + {.width = 320, .height = 180}}}, + // Try with a missing scale setting, defaults to 1.0 if any + // other is set. + ScaleResolutionDownByTestParameters{ + .resolution = {.width = 1280, .height = 720}, + .scale_resolution_down_by = {1, std::nullopt, 4}, + .expected_resolutions = {{.width = 1280, .height = 720}, + {.width = 1280, .height = 720}, + {.width = 320, .height = 180}}}, + // Odd resolution. Request alignment by 8 to get the resolution + // of the smallest layer multiple by 2. + ScaleResolutionDownByTestParameters{ + .field_trials = + "WebRTC-NormalizeSimulcastResolution/Enabled-3/", + .resolution = {.width = 2007, .height = 1207}, + .scale_resolution_down_by = {1, 2, 4}, + .expected_resolutions = {{.width = 2000, .height = 1200}, + {.width = 1000, .height = 600}, + {.width = 500, .height = 300}}}), + Values(kVp8CodecName, kH264CodecName))); TEST_F(WebRtcVideoChannelTest, GetAndSetRtpSendParametersMaxFramerate) { SetUpSimulcast(true, /*with_rtx=*/false); @@ -9589,11 +9385,12 @@ class WebRtcVideoChannelSimulcastTest : public ::testing::Test { std::vector expected_streams; if (num_configured_streams > 1 || conference_mode) { - expected_streams = GetSimulcastConfig( - /*min_layers=*/1, num_configured_streams, capture_width, - capture_height, webrtc::kDefaultBitratePriority, - kDefaultVideoMaxQpVpx, screenshare && conference_mode, true, - field_trials_, webrtc::kVideoCodecVP8); + const webrtc::VideoEncoderConfig& encoder_config = + stream->GetEncoderConfig(); + webrtc::VideoEncoder::EncoderInfo encoder_info; + auto factory = rtc::make_ref_counted(encoder_info); + expected_streams = factory->CreateEncoderStreams( + field_trials_, capture_width, capture_height, encoder_config); if (screenshare && conference_mode) { for (const webrtc::VideoStream& stream : expected_streams) { // Never scale screen content. @@ -9637,7 +9434,7 @@ class WebRtcVideoChannelSimulcastTest : public ::testing::Test { video_streams[i].max_bitrate_bps); EXPECT_GT(video_streams[i].max_qp, 0); - EXPECT_EQ(expected_streams[i].max_qp, video_streams[i].max_qp); + EXPECT_EQ(video_streams[i].max_qp, kDefaultVideoMaxQpVpx); EXPECT_EQ(num_configured_streams > 1 || conference_mode, expected_streams[i].num_temporal_layers.has_value()); diff --git a/third_party/libwebrtc/media/engine/webrtc_voice_engine.cc b/third_party/libwebrtc/media/engine/webrtc_voice_engine.cc index e76cebc8ff4b..95a8f36a03b8 100644 --- a/third_party/libwebrtc/media/engine/webrtc_voice_engine.cc +++ b/third_party/libwebrtc/media/engine/webrtc_voice_engine.cc @@ -2652,7 +2652,7 @@ bool WebRtcVoiceReceiveChannel::GetStats(VoiceMediaReceiveInfo* info, rinfo.round_trip_time = stats.round_trip_time; rinfo.round_trip_time_measurements = stats.round_trip_time_measurements; rinfo.total_round_trip_time = stats.total_round_trip_time; - + rinfo.total_processing_delay_seconds = stats.total_processing_delay_seconds; if (recv_nack_enabled_) { rinfo.nacks_sent = stats.nacks_sent; } 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 c4d50066321a..42e46a2e7778 100644 --- a/third_party/libwebrtc/media/engine/webrtc_voice_engine_unittest.cc +++ b/third_party/libwebrtc/media/engine/webrtc_voice_engine_unittest.cc @@ -717,6 +717,7 @@ class WebRtcVoiceEngineTestFake : public ::testing::TestWithParam { stats.concealment_events = 12; stats.jitter_buffer_delay_seconds = 34; stats.jitter_buffer_emitted_count = 77; + stats.total_processing_delay_seconds = 0.123; stats.expand_rate = 5.67f; stats.speech_expand_rate = 8.90f; stats.secondary_decoded_rate = 1.23f; @@ -765,6 +766,8 @@ class WebRtcVoiceEngineTestFake : public ::testing::TestWithParam { stats.jitter_buffer_delay_seconds); EXPECT_EQ(info.jitter_buffer_emitted_count, stats.jitter_buffer_emitted_count); + EXPECT_EQ(info.total_processing_delay_seconds, + stats.total_processing_delay_seconds); EXPECT_EQ(info.expand_rate, stats.expand_rate); EXPECT_EQ(info.speech_expand_rate, stats.speech_expand_rate); EXPECT_EQ(info.secondary_decoded_rate, stats.secondary_decoded_rate); diff --git a/third_party/libwebrtc/media/sctp/dcsctp_transport.cc b/third_party/libwebrtc/media/sctp/dcsctp_transport.cc index 99ecc94a6872..3d54b7d742bc 100644 --- a/third_party/libwebrtc/media/sctp/dcsctp_transport.cc +++ b/third_party/libwebrtc/media/sctp/dcsctp_transport.cc @@ -197,6 +197,8 @@ bool DcSctpTransport::Start(int local_sctp_port, DataChannelInterface::MaxSendQueueSize(); // This is just set to avoid denial-of-service. Practically unlimited. options.max_send_buffer_size = std::numeric_limits::max(); + options.enable_message_interleaving = + env_.field_trials().IsEnabled("WebRTC-DataChannelMessageInterleaving"); std::unique_ptr packet_observer; if (RTC_LOG_CHECK_LEVEL(LS_VERBOSE)) { diff --git a/third_party/libwebrtc/modules/BUILD.gn b/third_party/libwebrtc/modules/BUILD.gn index 3c43876ea602..540e85673e7b 100644 --- a/third_party/libwebrtc/modules/BUILD.gn +++ b/third_party/libwebrtc/modules/BUILD.gn @@ -143,7 +143,6 @@ if (rtc_include_tests && !build_with_chromium) { "../resources/audio_processing/transient/wpd5.dat", "../resources/audio_processing/transient/wpd6.dat", "../resources/audio_processing/transient/wpd7.dat", - "../resources/deflicker_before_cif_short.yuv", "../resources/far16_stereo.pcm", "../resources/far176_stereo.pcm", "../resources/far192_stereo.pcm", @@ -154,7 +153,6 @@ if (rtc_include_tests && !build_with_chromium) { "../resources/far88_stereo.pcm", "../resources/far8_stereo.pcm", "../resources/far96_stereo.pcm", - "../resources/foremanColorEnhanced_cif_short.yuv", "../resources/foreman_cif.yuv", "../resources/foreman_cif_short.yuv", "../resources/near16_stereo.pcm", diff --git a/third_party/libwebrtc/modules/audio_coding/BUILD.gn b/third_party/libwebrtc/modules/audio_coding/BUILD.gn index 2ba849ff6891..021eb8e8f0df 100644 --- a/third_party/libwebrtc/modules/audio_coding/BUILD.gn +++ b/third_party/libwebrtc/modules/audio_coding/BUILD.gn @@ -469,8 +469,11 @@ rtc_library("webrtc_opus") { ":audio_coding_opus_common", ":audio_network_adaptor", "../../api:array_view", + "../../api:field_trials_view", "../../api/audio_codecs:audio_codecs_api", "../../api/audio_codecs/opus:audio_encoder_opus_config", + "../../api/environment", + "../../api/transport:field_trial_based_config", "../../common_audio", "../../rtc_base:buffer", "../../rtc_base:checks", @@ -483,6 +486,7 @@ rtc_library("webrtc_opus") { "../../rtc_base:stringutils", "../../rtc_base:timeutils", "../../system_wrappers:field_trial", + "//third_party/abseil-cpp/absl/memory", "//third_party/abseil-cpp/absl/strings", "//third_party/abseil-cpp/absl/strings:string_view", "//third_party/abseil-cpp/absl/types:optional", @@ -505,6 +509,7 @@ rtc_library("webrtc_multiopus") { deps = [ ":audio_coding_opus_common", + "../../api:array_view", "../../api/audio_codecs:audio_codecs_api", "../../api/audio_codecs/opus:audio_decoder_opus_config", "../../api/audio_codecs/opus:audio_encoder_opus_config", @@ -513,6 +518,7 @@ rtc_library("webrtc_multiopus") { "../../rtc_base:checks", "../../rtc_base:logging", "../../rtc_base:macromagic", + "../../rtc_base:safe_conversions", "../../rtc_base:safe_minmax", "../../rtc_base:stringutils", "//third_party/abseil-cpp/absl/memory", @@ -940,6 +946,8 @@ rtc_library("audio_coding_modules_tests_shared") { "../../api/audio:audio_frame_api", "../../api/audio_codecs:builtin_audio_decoder_factory", "../../api/audio_codecs:builtin_audio_encoder_factory", + "../../api/environment", + "../../api/environment:environment_factory", "../../api/neteq:neteq_api", "../../api/units:timestamp", "../../rtc_base:checks", @@ -1071,6 +1079,8 @@ if (rtc_include_tests) { "../../api/audio_codecs/ilbc:audio_encoder_ilbc", "../../api/audio_codecs/opus:audio_decoder_opus", "../../api/audio_codecs/opus:audio_encoder_opus", + "../../api/environment", + "../../api/environment:environment_factory", "../../api/units:timestamp", "../../common_audio", "../../rtc_base:checks", @@ -1153,6 +1163,8 @@ if (rtc_include_tests) { "../../api/audio_codecs:audio_codecs_api", "../../api/audio_codecs:builtin_audio_decoder_factory", "../../api/audio_codecs:builtin_audio_encoder_factory", + "../../api/environment", + "../../api/environment:environment_factory", "../../rtc_base:checks", "../../rtc_base:stringutils", "../../test:test_support", @@ -1684,6 +1696,8 @@ if (rtc_include_tests) { "../../api/audio_codecs/opus:audio_decoder_opus", "../../api/audio_codecs/opus:audio_encoder_multiopus", "../../api/audio_codecs/opus:audio_encoder_opus", + "../../api/environment", + "../../api/environment:environment_factory", "../../api/neteq:default_neteq_controller_factory", "../../api/neteq:neteq_api", "../../api/neteq:neteq_controller_api", @@ -1713,6 +1727,7 @@ if (rtc_include_tests) { "../../system_wrappers", "../../test:audio_codec_mocks", "../../test:audio_test_common", + "../../test:explicit_key_value_config", "../../test:field_trial", "../../test:fileutils", "../../test:rtc_expect_death", diff --git a/third_party/libwebrtc/modules/audio_coding/acm2/acm_receiver.cc b/third_party/libwebrtc/modules/audio_coding/acm2/acm_receiver.cc index 964c9dc2ef84..99c827723072 100644 --- a/third_party/libwebrtc/modules/audio_coding/acm2/acm_receiver.cc +++ b/third_party/libwebrtc/modules/audio_coding/acm2/acm_receiver.cc @@ -303,6 +303,8 @@ void AcmReceiver::GetNetworkStatistics( neteq_lifetime_stat.removed_samples_for_acceleration; acm_stat->fecPacketsReceived = neteq_lifetime_stat.fec_packets_received; acm_stat->fecPacketsDiscarded = neteq_lifetime_stat.fec_packets_discarded; + acm_stat->totalProcessingDelayUs = + neteq_lifetime_stat.total_processing_delay_us; acm_stat->packetsDiscarded = neteq_lifetime_stat.packets_discarded; NetEqOperationsAndState neteq_operations_and_state = diff --git a/third_party/libwebrtc/modules/audio_coding/acm2/acm_receiver_unittest.cc b/third_party/libwebrtc/modules/audio_coding/acm2/acm_receiver_unittest.cc index e86c7189612b..8b6342492a92 100644 --- a/third_party/libwebrtc/modules/audio_coding/acm2/acm_receiver_unittest.cc +++ b/third_party/libwebrtc/modules/audio_coding/acm2/acm_receiver_unittest.cc @@ -16,6 +16,8 @@ #include "absl/types/optional.h" #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/environment/environment.h" +#include "api/environment/environment_factory.h" #include "api/units/timestamp.h" #include "modules/audio_coding/codecs/cng/audio_encoder_cng.h" #include "modules/audio_coding/include/audio_coding_module.h" @@ -69,7 +71,7 @@ class AcmReceiverTestOldApi : public AudioPacketizationCallback, encoder_factory_->QueryAudioEncoder(format); RTC_CHECK(info.has_value()); std::unique_ptr enc = - encoder_factory_->MakeAudioEncoder(payload_type, format, absl::nullopt); + encoder_factory_->Create(env_, format, {.payload_type = payload_type}); // If we have a compatible CN specification, stack a CNG on top. auto it = cng_payload_types.find(info->sample_rate_hz); @@ -132,6 +134,7 @@ class AcmReceiverTestOldApi : public AudioPacketizationCallback, return 0; } + const Environment env_ = CreateEnvironment(); const rtc::scoped_refptr encoder_factory_ = CreateBuiltinAudioEncoderFactory(); const rtc::scoped_refptr decoder_factory_ = diff --git a/third_party/libwebrtc/modules/audio_coding/acm2/acm_remixing.h b/third_party/libwebrtc/modules/audio_coding/acm2/acm_remixing.h index 661569b03341..827f7bd36cad 100644 --- a/third_party/libwebrtc/modules/audio_coding/acm2/acm_remixing.h +++ b/third_party/libwebrtc/modules/audio_coding/acm2/acm_remixing.h @@ -13,6 +13,7 @@ #include +#include "api/array_view.h" #include "api/audio/audio_frame.h" namespace webrtc { diff --git a/third_party/libwebrtc/modules/audio_coding/acm2/acm_resampler.cc b/third_party/libwebrtc/modules/audio_coding/acm2/acm_resampler.cc index 6e2a3bcac4b2..0f1be66eb5e1 100644 --- a/third_party/libwebrtc/modules/audio_coding/acm2/acm_resampler.cc +++ b/third_party/libwebrtc/modules/audio_coding/acm2/acm_resampler.cc @@ -44,14 +44,6 @@ int ACMResampler::Resample10Msec(const int16_t* in_audio, return static_cast(dst.samples_per_channel()); } - if (resampler_.InitializeIfNeeded(in_freq_hz, out_freq_hz, - num_audio_channels) != 0) { - RTC_LOG(LS_ERROR) << "InitializeIfNeeded(" << in_freq_hz << ", " - << out_freq_hz << ", " << num_audio_channels - << ") failed."; - return -1; - } - int out_length = resampler_.Resample(src, dst); if (out_length == -1) { RTC_LOG(LS_ERROR) << "Resample(" << in_audio << ", " << src.data().size() diff --git a/third_party/libwebrtc/modules/audio_coding/acm2/acm_send_test.cc b/third_party/libwebrtc/modules/audio_coding/acm2/acm_send_test.cc index fddaa87701c2..c08fd4d6610a 100644 --- a/third_party/libwebrtc/modules/audio_coding/acm2/acm_send_test.cc +++ b/third_party/libwebrtc/modules/audio_coding/acm2/acm_send_test.cc @@ -18,6 +18,7 @@ #include "api/audio_codecs/audio_encoder.h" #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/environment/environment_factory.h" #include "modules/audio_coding/include/audio_coding_module.h" #include "modules/audio_coding/neteq/tools/input_audio_file.h" #include "modules/audio_coding/neteq/tools/packet.h" @@ -32,6 +33,7 @@ AcmSendTestOldApi::AcmSendTestOldApi(InputAudioFile* audio_source, int source_rate_hz, int test_duration_ms) : clock_(0), + env_(CreateEnvironment(&clock_)), acm_(webrtc::AudioCodingModule::Create()), audio_source_(audio_source), source_rate_hz_(source_rate_hz), @@ -73,7 +75,7 @@ bool AcmSendTestOldApi::RegisterCodec(absl::string_view payload_name, frame_size_samples, rtc::CheckedDivExact(clockrate_hz, 1000))); auto factory = CreateBuiltinAudioEncoderFactory(); acm_->SetEncoder( - factory->MakeAudioEncoder(payload_type, format, absl::nullopt)); + factory->Create(env_, format, {.payload_type = payload_type})); codec_registered_ = true; input_frame_.num_channels_ = num_channels; RTC_DCHECK_LE(input_block_size_samples_ * input_frame_.num_channels_, diff --git a/third_party/libwebrtc/modules/audio_coding/acm2/acm_send_test.h b/third_party/libwebrtc/modules/audio_coding/acm2/acm_send_test.h index 0bd24705fd30..1bba67618b18 100644 --- a/third_party/libwebrtc/modules/audio_coding/acm2/acm_send_test.h +++ b/third_party/libwebrtc/modules/audio_coding/acm2/acm_send_test.h @@ -16,6 +16,7 @@ #include "absl/strings/string_view.h" #include "api/audio/audio_frame.h" +#include "api/environment/environment.h" #include "modules/audio_coding/include/audio_coding_module.h" #include "modules/audio_coding/neteq/tools/packet_source.h" #include "system_wrappers/include/clock.h" @@ -70,6 +71,7 @@ class AcmSendTestOldApi : public AudioPacketizationCallback, std::unique_ptr CreatePacket(); SimulatedClock clock_; + const Environment env_; std::unique_ptr acm_; InputAudioFile* audio_source_; int source_rate_hz_; diff --git a/third_party/libwebrtc/modules/audio_coding/acm2/audio_coding_module_unittest.cc b/third_party/libwebrtc/modules/audio_coding/acm2/audio_coding_module_unittest.cc index 44e7bceb32f6..8eaa2bf0c1b5 100644 --- a/third_party/libwebrtc/modules/audio_coding/acm2/audio_coding_module_unittest.cc +++ b/third_party/libwebrtc/modules/audio_coding/acm2/audio_coding_module_unittest.cc @@ -25,6 +25,8 @@ #include "api/audio_codecs/opus/audio_decoder_opus.h" #include "api/audio_codecs/opus/audio_encoder_multi_channel_opus.h" #include "api/audio_codecs/opus/audio_encoder_opus.h" +#include "api/environment/environment.h" +#include "api/environment/environment_factory.h" #include "api/units/timestamp.h" #include "modules/audio_coding/acm2/acm_receive_test.h" #include "modules/audio_coding/acm2/acm_send_test.h" @@ -163,8 +165,8 @@ class PacketizationCallbackStubOldApi : public AudioPacketizationCallback { class AudioCodingModuleTestOldApi : public ::testing::Test { protected: AudioCodingModuleTestOldApi() - : rtp_utility_(new RtpData(kFrameSizeSamples, kPayloadType)), - clock_(Clock::GetRealTimeClock()) {} + : env_(CreateEnvironment()), + rtp_utility_(new RtpData(kFrameSizeSamples, kPayloadType)) {} ~AudioCodingModuleTestOldApi() {} @@ -173,7 +175,7 @@ class AudioCodingModuleTestOldApi : public ::testing::Test { void SetUp() { acm_ = AudioCodingModule::Create(); acm2::AcmReceiver::Config config; - config.clock = *clock_; + config.clock = env_.clock(); config.decoder_factory = CreateBuiltinAudioDecoderFactory(); acm_receiver_ = std::make_unique(config); @@ -199,8 +201,8 @@ class AudioCodingModuleTestOldApi : public ::testing::Test { virtual void RegisterCodec() { acm_receiver_->SetCodecs({{kPayloadType, *audio_format_}}); - acm_->SetEncoder(CreateBuiltinAudioEncoderFactory()->MakeAudioEncoder( - kPayloadType, *audio_format_, absl::nullopt)); + acm_->SetEncoder(CreateBuiltinAudioEncoderFactory()->Create( + env_, *audio_format_, {.payload_type = kPayloadType})); } virtual void InsertPacketAndPullAudio() { @@ -240,6 +242,7 @@ class AudioCodingModuleTestOldApi : public ::testing::Test { VerifyEncoding(); } + Environment env_; std::unique_ptr rtp_utility_; std::unique_ptr acm_; std::unique_ptr acm_receiver_; @@ -249,8 +252,6 @@ class AudioCodingModuleTestOldApi : public ::testing::Test { absl::optional audio_format_; int pac_size_ = -1; - - Clock* clock_; }; class AudioCodingModuleTestOldApiDeathTest @@ -381,7 +382,9 @@ class AudioCodingModuleMtTestOldApi : public AudioCodingModuleTestOldApi { pull_audio_count_(0), next_insert_packet_time_ms_(0), fake_clock_(new SimulatedClock(0)) { - clock_ = fake_clock_.get(); + EnvironmentFactory override_clock(env_); + override_clock.Set(fake_clock_.get()); + env_ = override_clock.Create(); } void SetUp() { @@ -458,7 +461,7 @@ class AudioCodingModuleMtTestOldApi : public AudioCodingModuleTestOldApi { SleepMs(1); { MutexLock lock(&mutex_); - if (clock_->TimeInMilliseconds() < next_insert_packet_time_ms_) { + if (env_.clock().TimeInMilliseconds() < next_insert_packet_time_ms_) { return; } next_insert_packet_time_ms_ += 10; @@ -473,7 +476,7 @@ class AudioCodingModuleMtTestOldApi : public AudioCodingModuleTestOldApi { { MutexLock lock(&mutex_); // Don't let the insert thread fall behind. - if (next_insert_packet_time_ms_ < clock_->TimeInMilliseconds()) { + if (next_insert_packet_time_ms_ < env_.clock().TimeInMilliseconds()) { return; } ++pull_audio_count_; @@ -534,9 +537,10 @@ class AcmAbsoluteCaptureTimestamp : public ::testing::Test { rtc::scoped_refptr codec_factory = CreateBuiltinAudioEncoderFactory(); acm_ = AudioCodingModule::Create(); - std::unique_ptr encoder = codec_factory->MakeAudioEncoder( - 111, SdpAudioFormat("OPUS", kSampleRateHz, kNumChannels), - absl::nullopt); + std::unique_ptr encoder = codec_factory->Create( + CreateEnvironment(), + SdpAudioFormat("OPUS", kSampleRateHz, kNumChannels), + {.payload_type = 111}); encoder->SetDtx(true); encoder->SetReceiverFrameLengthRange(kPTimeMs, kPTimeMs); acm_->SetEncoder(std::move(encoder)); diff --git a/third_party/libwebrtc/modules/audio_coding/codecs/builtin_audio_encoder_factory_unittest.cc b/third_party/libwebrtc/modules/audio_coding/codecs/builtin_audio_encoder_factory_unittest.cc index 95b67de610b8..6ca08b517a45 100644 --- a/third_party/libwebrtc/modules/audio_coding/codecs/builtin_audio_encoder_factory_unittest.cc +++ b/third_party/libwebrtc/modules/audio_coding/codecs/builtin_audio_encoder_factory_unittest.cc @@ -14,6 +14,8 @@ #include #include +#include "api/environment/environment.h" +#include "api/environment/environment_factory.h" #include "rtc_base/numerics/safe_conversions.h" #include "test/gmock.h" #include "test/gtest.h" @@ -40,11 +42,12 @@ TEST_P(AudioEncoderFactoryTest, CanQueryAllSupportedFormats) { } TEST_P(AudioEncoderFactoryTest, CanConstructAllSupportedEncoders) { + const Environment env = CreateEnvironment(); auto factory = GetParam(); auto supported_encoders = factory->GetSupportedEncoders(); for (const auto& spec : supported_encoders) { auto info = factory->QueryAudioEncoder(spec.format); - auto encoder = factory->MakeAudioEncoder(127, spec.format, absl::nullopt); + auto encoder = factory->Create(env, spec.format, {.payload_type = 127}); EXPECT_TRUE(encoder); EXPECT_EQ(encoder->SampleRateHz(), info->sample_rate_hz); EXPECT_EQ(encoder->NumChannels(), info->num_channels); @@ -54,6 +57,7 @@ TEST_P(AudioEncoderFactoryTest, CanConstructAllSupportedEncoders) { TEST_P(AudioEncoderFactoryTest, CanRunAllSupportedEncoders) { constexpr int kTestPayloadType = 127; + const Environment env = CreateEnvironment(); auto factory = GetParam(); auto supported_encoders = factory->GetSupportedEncoders(); for (const auto& spec : supported_encoders) { @@ -64,7 +68,7 @@ TEST_P(AudioEncoderFactoryTest, CanRunAllSupportedEncoders) { } #endif auto encoder = - factory->MakeAudioEncoder(kTestPayloadType, spec.format, absl::nullopt); + factory->Create(env, spec.format, {.payload_type = kTestPayloadType}); EXPECT_TRUE(encoder); encoder->Reset(); const int num_samples = rtc::checked_cast( @@ -153,6 +157,7 @@ TEST(BuiltinAudioEncoderFactoryTest, SupportsTheExpectedFormats) { // Tests that using more channels than the maximum does not work. TEST(BuiltinAudioEncoderFactoryTest, MaxNrOfChannels) { + const Environment env = CreateEnvironment(); rtc::scoped_refptr aef = CreateBuiltinAudioEncoderFactory(); std::vector codecs = { @@ -173,11 +178,10 @@ TEST(BuiltinAudioEncoderFactoryTest, MaxNrOfChannels) { }; for (auto codec : codecs) { - EXPECT_FALSE(aef->MakeAudioEncoder( - /*payload_type=*/111, - /*format=*/ + EXPECT_FALSE(aef->Create( + env, /*format=*/ SdpAudioFormat(codec, 32000, AudioEncoder::kMaxNumberOfChannels + 1), - /*codec_pair_id=*/absl::nullopt)); + {.payload_type = 111})); } } diff --git a/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_multi_channel_opus_impl.cc b/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_multi_channel_opus_impl.cc index 38a11c123d88..9d140f74dc55 100644 --- a/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_multi_channel_opus_impl.cc +++ b/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_multi_channel_opus_impl.cc @@ -20,15 +20,17 @@ #include "modules/audio_coding/codecs/opus/audio_encoder_multi_channel_opus_impl.h" #include +#include #include #include #include #include "absl/strings/match.h" +#include "api/audio_codecs/opus/audio_encoder_opus_config.h" #include "modules/audio_coding/codecs/opus/audio_coder_opus_common.h" -#include "rtc_base/arraysize.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" +#include "rtc_base/numerics/safe_conversions.h" #include "rtc_base/string_to_number.h" namespace webrtc { diff --git a/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_multi_channel_opus_impl.h b/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_multi_channel_opus_impl.h index 8a7210515c80..6d6e63b885f6 100644 --- a/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_multi_channel_opus_impl.h +++ b/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_multi_channel_opus_impl.h @@ -11,16 +11,21 @@ #ifndef MODULES_AUDIO_CODING_CODECS_OPUS_AUDIO_ENCODER_MULTI_CHANNEL_OPUS_IMPL_H_ #define MODULES_AUDIO_CODING_CODECS_OPUS_AUDIO_ENCODER_MULTI_CHANNEL_OPUS_IMPL_H_ +#include +#include + #include #include #include #include "absl/types/optional.h" +#include "api/array_view.h" #include "api/audio_codecs/audio_encoder.h" #include "api/audio_codecs/audio_format.h" #include "api/audio_codecs/opus/audio_encoder_multi_channel_opus_config.h" #include "api/units/time_delta.h" #include "modules/audio_coding/codecs/opus/opus_interface.h" +#include "rtc_base/buffer.h" namespace webrtc { diff --git a/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc b/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc index 17e0e33b1d8d..8e23b7b88f8a 100644 --- a/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc +++ b/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.cc @@ -16,8 +16,11 @@ #include #include +#include "absl/memory/memory.h" #include "absl/strings/match.h" #include "absl/strings/string_view.h" +#include "api/field_trials_view.h" +#include "api/transport/field_trial_based_config.h" #include "modules/audio_coding/audio_network_adaptor/audio_network_adaptor_impl.h" #include "modules/audio_coding/audio_network_adaptor/controller_manager.h" #include "modules/audio_coding/codecs/opus/audio_coder_opus_common.h" @@ -31,7 +34,6 @@ #include "rtc_base/string_encode.h" #include "rtc_base/string_to_number.h" #include "rtc_base/time_utils.h" -#include "system_wrappers/include/field_trial.h" namespace webrtc { @@ -163,14 +165,14 @@ int GetBitrateBps(const AudioEncoderOpusConfig& config) { return *config.bitrate_bps; } -std::vector GetBitrateMultipliers() { +std::vector GetBitrateMultipliers(const FieldTrialsView& field_trials) { constexpr char kBitrateMultipliersName[] = "WebRTC-Audio-OpusBitrateMultipliers"; const bool use_bitrate_multipliers = - webrtc::field_trial::IsEnabled(kBitrateMultipliersName); + field_trials.IsEnabled(kBitrateMultipliersName); if (use_bitrate_multipliers) { const std::string field_trial_string = - webrtc::field_trial::FindFullName(kBitrateMultipliersName); + field_trials.Lookup(kBitrateMultipliersName); std::vector pieces; rtc::tokenize(field_trial_string, '-', &pieces); if (pieces.size() < 2 || pieces[0] != "Enabled") { @@ -227,16 +229,6 @@ AudioCodecInfo AudioEncoderOpusImpl::QueryAudioEncoder( return info; } -std::unique_ptr AudioEncoderOpusImpl::MakeAudioEncoder( - const AudioEncoderOpusConfig& config, - int payload_type) { - if (!config.IsOk()) { - RTC_DCHECK_NOTREACHED(); - return nullptr; - } - return std::make_unique(config, payload_type); -} - absl::optional AudioEncoderOpusImpl::SdpToConfig( const SdpAudioFormat& format) { if (!absl::EqualsIgnoreCase(format.name, "opus") || @@ -345,9 +337,35 @@ class AudioEncoderOpusImpl::PacketLossFractionSmoother { rtc::ExpFilter smoother_; }; +std::unique_ptr AudioEncoderOpusImpl::CreateForTesting( + const Environment& env, + const AudioEncoderOpusConfig& config, + int payload_type, + const AudioNetworkAdaptorCreator& audio_network_adaptor_creator, + std::unique_ptr bitrate_smoother) { + // Using `new` to access a non-public constructor. + return absl::WrapUnique(new AudioEncoderOpusImpl( + env.field_trials(), config, payload_type, audio_network_adaptor_creator, + std::move(bitrate_smoother))); +} + +AudioEncoderOpusImpl::AudioEncoderOpusImpl(const Environment& env, + const AudioEncoderOpusConfig& config, + int payload_type) + : AudioEncoderOpusImpl( + env.field_trials(), + config, + payload_type, + [this](absl::string_view config_string, RtcEventLog* event_log) { + return DefaultAudioNetworkAdaptorCreator(config_string, event_log); + }, + // We choose 5sec as initial time constant due to empirical data. + std::make_unique(5'000)) {} + AudioEncoderOpusImpl::AudioEncoderOpusImpl(const AudioEncoderOpusConfig& config, int payload_type) : AudioEncoderOpusImpl( + FieldTrialBasedConfig(), config, payload_type, [this](absl::string_view config_string, RtcEventLog* event_log) { @@ -357,17 +375,17 @@ AudioEncoderOpusImpl::AudioEncoderOpusImpl(const AudioEncoderOpusConfig& config, std::make_unique(5000)) {} AudioEncoderOpusImpl::AudioEncoderOpusImpl( + const FieldTrialsView& field_trials, const AudioEncoderOpusConfig& config, int payload_type, const AudioNetworkAdaptorCreator& audio_network_adaptor_creator, std::unique_ptr bitrate_smoother) : payload_type_(payload_type), - use_stable_target_for_adaptation_(!webrtc::field_trial::IsDisabled( - "WebRTC-Audio-StableTargetAdaptation")), - adjust_bandwidth_( - webrtc::field_trial::IsEnabled("WebRTC-AdjustOpusBandwidth")), + use_stable_target_for_adaptation_( + !field_trials.IsDisabled("WebRTC-Audio-StableTargetAdaptation")), + adjust_bandwidth_(field_trials.IsEnabled("WebRTC-AdjustOpusBandwidth")), bitrate_changed_(true), - bitrate_multipliers_(GetBitrateMultipliers()), + bitrate_multipliers_(GetBitrateMultipliers(field_trials)), packet_loss_rate_(0.0), inst_(nullptr), packet_loss_fraction_smoother_(new PacketLossFractionSmoother()), @@ -384,10 +402,6 @@ AudioEncoderOpusImpl::AudioEncoderOpusImpl( SetProjectedPacketLossRate(packet_loss_rate_); } -AudioEncoderOpusImpl::AudioEncoderOpusImpl(int payload_type, - const SdpAudioFormat& format) - : AudioEncoderOpusImpl(*SdpToConfig(format), payload_type) {} - AudioEncoderOpusImpl::~AudioEncoderOpusImpl() { RTC_CHECK_EQ(0, WebRtcOpus_EncoderFree(inst_)); } diff --git a/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.h b/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.h index 8c5c2350162c..7568631b4ee7 100644 --- a/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.h +++ b/third_party/libwebrtc/modules/audio_coding/codecs/opus/audio_encoder_opus.h @@ -21,6 +21,7 @@ #include "api/audio_codecs/audio_encoder.h" #include "api/audio_codecs/audio_format.h" #include "api/audio_codecs/opus/audio_encoder_opus_config.h" +#include "api/environment/environment.h" #include "common_audio/smoothing_filter.h" #include "modules/audio_coding/audio_network_adaptor/include/audio_network_adaptor.h" #include "modules/audio_coding/codecs/opus/opus_interface.h" @@ -49,16 +50,21 @@ class AudioEncoderOpusImpl final : public AudioEncoder { std::function(absl::string_view, RtcEventLog*)>; - AudioEncoderOpusImpl(const AudioEncoderOpusConfig& config, int payload_type); - - // Dependency injection for testing. - AudioEncoderOpusImpl( + static std::unique_ptr CreateForTesting( + const Environment& env, const AudioEncoderOpusConfig& config, int payload_type, const AudioNetworkAdaptorCreator& audio_network_adaptor_creator, std::unique_ptr bitrate_smoother); - AudioEncoderOpusImpl(int payload_type, const SdpAudioFormat& format); + AudioEncoderOpusImpl(const Environment& env, + const AudioEncoderOpusConfig& config, + int payload_type); + + [[deprecated("bugs.webrtc.org/343086059")]] AudioEncoderOpusImpl( + const AudioEncoderOpusConfig& config, + int payload_type); + ~AudioEncoderOpusImpl() override; AudioEncoderOpusImpl(const AudioEncoderOpusImpl&) = delete; @@ -120,13 +126,19 @@ class AudioEncoderOpusImpl final : public AudioEncoder { private: class PacketLossFractionSmoother; + // TODO: bugs.webrtc.org/343086059 - Replace field_trials with Environment + // when public constructors that do not provide the Environment are removed. + AudioEncoderOpusImpl( + const FieldTrialsView& field_trials, + const AudioEncoderOpusConfig& config, + int payload_type, + const AudioNetworkAdaptorCreator& audio_network_adaptor_creator, + std::unique_ptr bitrate_smoother); + static absl::optional SdpToConfig( const SdpAudioFormat& format); static void AppendSupportedEncoders(std::vector* specs); static AudioCodecInfo QueryAudioEncoder(const AudioEncoderOpusConfig& config); - static std::unique_ptr MakeAudioEncoder( - const AudioEncoderOpusConfig&, - int payload_type); size_t Num10msFramesPerPacket() const; size_t SamplesPer10msFrame() const; 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 f82ef965db73..996bf53f03f6 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 @@ -15,6 +15,7 @@ #include #include "absl/strings/string_view.h" +#include "api/environment/environment_factory.h" #include "common_audio/mocks/mock_smoothing_filter.h" #include "modules/audio_coding/audio_network_adaptor/mock/mock_audio_network_adaptor.h" #include "modules/audio_coding/codecs/opus/audio_encoder_opus.h" @@ -22,17 +23,18 @@ #include "modules/audio_coding/neteq/tools/audio_loop.h" #include "rtc_base/checks.h" #include "rtc_base/fake_clock.h" -#include "test/field_trial.h" +#include "test/explicit_key_value_config.h" #include "test/gmock.h" #include "test/gtest.h" +#include "test/scoped_key_value_config.h" #include "test/testsupport/file_utils.h" namespace webrtc { +namespace { +using test::ExplicitKeyValueConfig; using ::testing::NiceMock; using ::testing::Return; -namespace { - constexpr int kDefaultOpusPayloadType = 105; constexpr int kDefaultOpusRate = 32000; constexpr int kDefaultOpusPacSize = 960; @@ -52,8 +54,10 @@ struct AudioEncoderOpusStates { AudioEncoderOpusConfig config; }; -std::unique_ptr CreateCodec(int sample_rate_hz, - size_t num_channels) { +std::unique_ptr CreateCodec( + int sample_rate_hz, + size_t num_channels, + const FieldTrialsView* field_trials = nullptr) { std::unique_ptr states = std::make_unique(); states->mock_audio_network_adaptor = nullptr; @@ -85,9 +89,9 @@ std::unique_ptr CreateCodec(int sample_rate_hz, new MockSmoothingFilter()); states->mock_bitrate_smoother = bitrate_smoother.get(); - states->encoder.reset( - new AudioEncoderOpusImpl(states->config, kDefaultOpusPayloadType, creator, - std::move(bitrate_smoother))); + states->encoder = AudioEncoderOpusImpl::CreateForTesting( + CreateEnvironment(field_trials), states->config, kDefaultOpusPayloadType, + creator, std::move(bitrate_smoother)); return states; } @@ -264,9 +268,9 @@ TEST_P(AudioEncoderOpusTest, TEST_P(AudioEncoderOpusTest, InvokeAudioNetworkAdaptorOnReceivedUplinkBandwidth) { - test::ScopedFieldTrials override_field_trials( + ExplicitKeyValueConfig field_trials( "WebRTC-Audio-StableTargetAdaptation/Disabled/"); - auto states = CreateCodec(sample_rate_hz_, 2); + auto states = CreateCodec(sample_rate_hz_, 2, &field_trials); states->encoder->EnableAudioNetworkAdaptor("", nullptr); auto config = CreateEncoderRuntimeConfig(); @@ -485,9 +489,9 @@ TEST_P(AudioEncoderOpusTest, EmptyConfigDoesNotAffectEncoderSettings) { } TEST_P(AudioEncoderOpusTest, UpdateUplinkBandwidthInAudioNetworkAdaptor) { - test::ScopedFieldTrials override_field_trials( + ExplicitKeyValueConfig field_trials( "WebRTC-Audio-StableTargetAdaptation/Disabled/"); - auto states = CreateCodec(sample_rate_hz_, 2); + auto states = CreateCodec(sample_rate_hz_, 2, &field_trials); states->encoder->EnableAudioNetworkAdaptor("", nullptr); const size_t opus_rate_khz = rtc::CheckedDivExact(sample_rate_hz_, 1000); const std::vector audio(opus_rate_khz * 10 * 2, 0); @@ -819,7 +823,16 @@ TEST_P(AudioEncoderOpusTest, OpusFlagDtxAsNonSpeech) { } TEST(AudioEncoderOpusTest, OpusDtxFilteringHighEnergyRefreshPackets) { - test::ScopedFieldTrials override_field_trials( + // TODO: bugs.webrtc.org/343086059 - Use `ExplicitKeyValueConfig` type to + // ensure global field trial string is not used when this field trial is + // queried from the passed Environment. + // There are currently two complications for that: + // - field trial is queried by WebRtcOpus_EncoderCreate that follows c-style + // interface, and thus is not ready to accept c++ interface FieldTrialsView + // - field trial is queried during `RecreateEncoderInstance`, i.e., opus + // encoder needs to save field trials passed at construction. That will be + // simpler once all public constructors accept webrtc::Environment. + test::ScopedKeyValueConfig field_trials( "WebRTC-Audio-OpusAvoidNoisePumpingDuringDtx/Enabled/"); const std::string kInputFileName = webrtc::test::ResourcePath("audio_coding/testfile16kHz", "pcm"); @@ -828,7 +841,8 @@ TEST(AudioEncoderOpusTest, OpusDtxFilteringHighEnergyRefreshPackets) { config.dtx_enabled = true; config.sample_rate_hz = kSampleRateHz; constexpr int payload_type = 17; - const auto encoder = AudioEncoderOpus::MakeAudioEncoder(config, payload_type); + AudioEncoderOpusImpl encoder(CreateEnvironment(&field_trials), config, + payload_type); test::AudioLoop audio_loop; constexpr size_t kMaxLoopLengthSaples = kSampleRateHz * 11.6f; constexpr size_t kInputBlockSizeSamples = kSampleRateHz / 100; @@ -849,7 +863,7 @@ TEST(AudioEncoderOpusTest, OpusDtxFilteringHighEnergyRefreshPackets) { // Every second call to the encoder will generate an Opus packet. for (int j = 0; j < 2; j++) { auto next_frame = audio_loop.GetNextBlock(); - info = encoder->Encode(rtp_timestamp, next_frame, &encoded); + info = encoder.Encode(rtp_timestamp, next_frame, &encoded); if (opus_entered_dtx) { size_t silence_frame_start = rtp_timestamp - timestamp_start_silence; silence_filled = silence_frame_start >= kSilenceDurationSamples; @@ -890,7 +904,7 @@ TEST(AudioEncoderOpusTest, OpusDtxFilteringHighEnergyRefreshPackets) { silence.begin() + silence_frame_start, silence.begin() + silence_frame_start + kInputBlockSizeSamples, silence_frame.begin(), [gain](float s) { return gain * s; }); - info = encoder->Encode(rtp_timestamp, silence_frame, &encoded); + info = encoder.Encode(rtp_timestamp, silence_frame, &encoded); rtp_timestamp += kInputBlockSizeSamples; } EXPECT_TRUE(info.encoded_bytes > 0 || last_packet_dtx_frame); diff --git a/third_party/libwebrtc/modules/audio_coding/include/audio_coding_module_typedefs.h b/third_party/libwebrtc/modules/audio_coding/include/audio_coding_module_typedefs.h index 4b880fb6338f..f37510819529 100644 --- a/third_party/libwebrtc/modules/audio_coding/include/audio_coding_module_typedefs.h +++ b/third_party/libwebrtc/modules/audio_coding/include/audio_coding_module_typedefs.h @@ -94,6 +94,7 @@ struct NetworkStatistics { uint64_t removedSamplesForAcceleration; uint64_t fecPacketsReceived; uint64_t fecPacketsDiscarded; + uint64_t totalProcessingDelayUs; // Stats below correspond to similarly-named fields in the WebRTC stats spec. // https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats uint64_t packetsDiscarded; diff --git a/third_party/libwebrtc/modules/audio_coding/neteq/neteq_impl.cc b/third_party/libwebrtc/modules/audio_coding/neteq/neteq_impl.cc index dcdac980b20e..fa3d47aace10 100644 --- a/third_party/libwebrtc/modules/audio_coding/neteq/neteq_impl.cc +++ b/third_party/libwebrtc/modules/audio_coding/neteq/neteq_impl.cc @@ -1976,9 +1976,17 @@ int NetEqImpl::ExtractPackets(size_t required_samples, extracted_samples = packet->timestamp - first_timestamp + packet_duration; RTC_DCHECK(controller_); - stats_->JitterBufferDelay(packet_duration, waiting_time_ms, - controller_->TargetLevelMs(), - controller_->UnlimitedTargetLevelMs()); + TimeDelta processing_time = TimeDelta::Zero(); + + if (packet->packet_info.has_value() && + !packet->packet_info->receive_time().IsMinusInfinity()) { + processing_time = + clock_->CurrentTime() - packet->packet_info->receive_time(); + } + + stats_->JitterBufferDelay( + packet_duration, waiting_time_ms, controller_->TargetLevelMs(), + controller_->UnlimitedTargetLevelMs(), processing_time.us()); // Check what packet is available next. next_packet = packet_buffer_->PeekNextPacket(); diff --git a/third_party/libwebrtc/modules/audio_coding/neteq/neteq_unittest.cc b/third_party/libwebrtc/modules/audio_coding/neteq/neteq_unittest.cc index 8ebed6fa62d2..2ddd9e376f76 100644 --- a/third_party/libwebrtc/modules/audio_coding/neteq/neteq_unittest.cc +++ b/third_party/libwebrtc/modules/audio_coding/neteq/neteq_unittest.cc @@ -909,6 +909,7 @@ void NetEqDecodingTestFaxMode::TestJitterBufferDelay(bool apply_packet_loss) { // Get packet. if (packets_sent > kDelayInNumPackets) { + clock_.AdvanceTime(TimeDelta::Millis(kPacketLenMs)); neteq_->GetAudio(&out_frame_, &muted); packets_received++; @@ -928,6 +929,7 @@ void NetEqDecodingTestFaxMode::TestJitterBufferDelay(bool apply_packet_loss) { } if (apply_packet_loss) { + clock_.AdvanceTime(TimeDelta::Millis(kPacketLenMs)); // Extra call to GetAudio to cause concealment. neteq_->GetAudio(&out_frame_, &muted); } @@ -939,6 +941,11 @@ void NetEqDecodingTestFaxMode::TestJitterBufferDelay(bool apply_packet_loss) { EXPECT_EQ(expected_emitted_count, stats.jitter_buffer_emitted_count); EXPECT_EQ(expected_target_delay, rtc::checked_cast(stats.jitter_buffer_target_delay_ms)); + // In this test, since the packets are inserted with a receive time equal to + // the current clock time, the jitter buffer delay should match the total + // processing delay. + EXPECT_EQ(stats.jitter_buffer_delay_ms * 1000, + stats.total_processing_delay_us); } TEST_F(NetEqDecodingTestFaxMode, TestJitterBufferDelayWithoutLoss) { diff --git a/third_party/libwebrtc/modules/audio_coding/neteq/statistics_calculator.cc b/third_party/libwebrtc/modules/audio_coding/neteq/statistics_calculator.cc index 70cfc2b3a8c0..b4fd5e8ead5b 100644 --- a/third_party/libwebrtc/modules/audio_coding/neteq/statistics_calculator.cc +++ b/third_party/libwebrtc/modules/audio_coding/neteq/statistics_calculator.cc @@ -262,17 +262,19 @@ void StatisticsCalculator::IncreaseCounter(size_t num_samples, int fs_hz) { lifetime_stats_.total_samples_received += num_samples; } -void StatisticsCalculator::JitterBufferDelay( - size_t num_samples, - uint64_t waiting_time_ms, - uint64_t target_delay_ms, - uint64_t unlimited_target_delay_ms) { +void StatisticsCalculator::JitterBufferDelay(size_t num_samples, + uint64_t waiting_time_ms, + uint64_t target_delay_ms, + uint64_t unlimited_target_delay_ms, + uint64_t processing_delay_us) { lifetime_stats_.jitter_buffer_delay_ms += waiting_time_ms * num_samples; lifetime_stats_.jitter_buffer_target_delay_ms += target_delay_ms * num_samples; lifetime_stats_.jitter_buffer_minimum_delay_ms += unlimited_target_delay_ms * num_samples; lifetime_stats_.jitter_buffer_emitted_count += num_samples; + lifetime_stats_.total_processing_delay_us += + num_samples * processing_delay_us; } void StatisticsCalculator::SecondaryDecodedSamples(int num_samples) { diff --git a/third_party/libwebrtc/modules/audio_coding/neteq/statistics_calculator.h b/third_party/libwebrtc/modules/audio_coding/neteq/statistics_calculator.h index 33a22d02ddbd..ced3a690932c 100644 --- a/third_party/libwebrtc/modules/audio_coding/neteq/statistics_calculator.h +++ b/third_party/libwebrtc/modules/audio_coding/neteq/statistics_calculator.h @@ -86,7 +86,8 @@ class StatisticsCalculator { void JitterBufferDelay(size_t num_samples, uint64_t waiting_time_ms, uint64_t target_delay_ms, - uint64_t unlimited_target_delay_ms); + uint64_t unlimited_target_delay_ms, + uint64_t processing_delay_us); // Stores new packet waiting time in waiting time statistics. void StoreWaitingTime(int waiting_time_ms); diff --git a/third_party/libwebrtc/modules/audio_coding/neteq/statistics_calculator_unittest.cc b/third_party/libwebrtc/modules/audio_coding/neteq/statistics_calculator_unittest.cc index 491cd83dc49f..99ccba996bb4 100644 --- a/third_party/libwebrtc/modules/audio_coding/neteq/statistics_calculator_unittest.cc +++ b/third_party/libwebrtc/modules/audio_coding/neteq/statistics_calculator_unittest.cc @@ -203,4 +203,33 @@ TEST(StatisticsCalculator, DiscardedPackets) { statistics_calculator.GetLifetimeStatistics().packets_discarded); } +TEST(StatisticsCalculator, JitterBufferDelay) { + StatisticsCalculator stats; + NetEqLifetimeStatistics lts; + lts = stats.GetLifetimeStatistics(); + EXPECT_EQ(lts.total_processing_delay_us, 0ul); + stats.JitterBufferDelay(/*num_samples=*/480, + /*waiting_time_ms=*/90ul, + /*target_delay_ms=*/80ul, + /*unlimited_target_delay_ms=*/70, + /*processing_delay_us=*/100 * 1000ul); + lts = stats.GetLifetimeStatistics(); + EXPECT_EQ(lts.jitter_buffer_delay_ms / 480, 90ul); + EXPECT_EQ(lts.jitter_buffer_target_delay_ms / 480, 80ul); + EXPECT_EQ(lts.jitter_buffer_minimum_delay_ms / 480, 70ul); + EXPECT_EQ(lts.total_processing_delay_us / 480, 100 * 1000ul); + EXPECT_EQ(lts.jitter_buffer_emitted_count, 480ul); + stats.JitterBufferDelay(/*num_samples=*/480, + /*waiting_time_ms=*/90ul, + /*target_delay_ms=*/80ul, + /*unlimited_target_delay_ms=*/70, + /*processing_delay_us=*/100 * 1000ul); + lts = stats.GetLifetimeStatistics(); + EXPECT_EQ(lts.jitter_buffer_delay_ms / 960, 90ul); + EXPECT_EQ(lts.jitter_buffer_target_delay_ms / 960, 80ul); + EXPECT_EQ(lts.jitter_buffer_minimum_delay_ms / 960, 70ul); + EXPECT_EQ(lts.total_processing_delay_us / 960, 100 * 1000ul); + EXPECT_EQ(lts.jitter_buffer_emitted_count, 960ul); +} + } // namespace webrtc diff --git a/third_party/libwebrtc/modules/audio_coding/test/EncodeDecodeTest.cc b/third_party/libwebrtc/modules/audio_coding/test/EncodeDecodeTest.cc index c51ad370d8ea..a480773cb631 100644 --- a/third_party/libwebrtc/modules/audio_coding/test/EncodeDecodeTest.cc +++ b/third_party/libwebrtc/modules/audio_coding/test/EncodeDecodeTest.cc @@ -18,6 +18,8 @@ #include "absl/strings/string_view.h" #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/environment/environment.h" +#include "api/environment/environment_factory.h" #include "modules/audio_coding/include/audio_coding_module.h" #include "rtc_base/strings/string_builder.h" #include "test/gtest.h" @@ -67,8 +69,8 @@ void Sender::Setup(AudioCodingModule* acm, // Fast-forward 1 second (100 blocks) since the file starts with silence. _pcmFile.FastForward(100); - acm->SetEncoder(CreateBuiltinAudioEncoderFactory()->MakeAudioEncoder( - payload_type, format, absl::nullopt)); + acm->SetEncoder(CreateBuiltinAudioEncoderFactory()->Create( + CreateEnvironment(), format, {.payload_type = payload_type})); _packetization = new TestPacketization(rtpStream, format.clockrate_hz); EXPECT_EQ(0, acm->RegisterTransportCallback(_packetization)); diff --git a/third_party/libwebrtc/modules/audio_coding/test/TestAllCodecs.cc b/third_party/libwebrtc/modules/audio_coding/test/TestAllCodecs.cc index 9d9c317b7c8a..821b88117db3 100644 --- a/third_party/libwebrtc/modules/audio_coding/test/TestAllCodecs.cc +++ b/third_party/libwebrtc/modules/audio_coding/test/TestAllCodecs.cc @@ -17,6 +17,7 @@ #include "absl/strings/match.h" #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/environment/environment_factory.h" #include "modules/audio_coding/include/audio_coding_module_typedefs.h" #include "modules/include/module_common_types.h" #include "rtc_base/logging.h" @@ -107,7 +108,8 @@ void TestPack::reset_payload_size() { } TestAllCodecs::TestAllCodecs() - : acm_a_(AudioCodingModule::Create()), + : env_(CreateEnvironment()), + acm_a_(AudioCodingModule::Create()), acm_b_(std::make_unique( acm2::AcmReceiver::Config(CreateBuiltinAudioDecoderFactory()))), channel_a_to_b_(NULL), @@ -319,12 +321,10 @@ void TestAllCodecs::RegisterSendCodec(char* codec_name, } auto factory = CreateBuiltinAudioEncoderFactory(); - constexpr int payload_type = 17; SdpAudioFormat format = {codec_name, clockrate_hz, num_channels}; format.parameters["ptime"] = rtc::ToString(rtc::CheckedDivExact( packet_size, rtc::CheckedDivExact(sampling_freq_hz, 1000))); - acm_a_->SetEncoder( - factory->MakeAudioEncoder(payload_type, format, absl::nullopt)); + acm_a_->SetEncoder(factory->Create(env_, format, {.payload_type = 17})); } void TestAllCodecs::Run(TestPack* channel) { diff --git a/third_party/libwebrtc/modules/audio_coding/test/TestAllCodecs.h b/third_party/libwebrtc/modules/audio_coding/test/TestAllCodecs.h index a17038ad8455..e078f49f42c0 100644 --- a/third_party/libwebrtc/modules/audio_coding/test/TestAllCodecs.h +++ b/third_party/libwebrtc/modules/audio_coding/test/TestAllCodecs.h @@ -13,6 +13,7 @@ #include +#include "api/environment/environment.h" #include "modules/audio_coding/acm2/acm_receiver.h" #include "modules/audio_coding/include/audio_coding_module.h" #include "modules/audio_coding/test/PCMFile.h" @@ -68,6 +69,7 @@ class TestAllCodecs { void Run(TestPack* channel); void OpenOutFile(int test_number); + const Environment env_; std::unique_ptr acm_a_; std::unique_ptr acm_b_; TestPack* channel_a_to_b_; diff --git a/third_party/libwebrtc/modules/audio_coding/test/TestRedFec.cc b/third_party/libwebrtc/modules/audio_coding/test/TestRedFec.cc index 83e5d61f36c9..6f765e9407bc 100644 --- a/third_party/libwebrtc/modules/audio_coding/test/TestRedFec.cc +++ b/third_party/libwebrtc/modules/audio_coding/test/TestRedFec.cc @@ -24,6 +24,7 @@ #include "api/audio_codecs/g722/audio_encoder_g722.h" #include "api/audio_codecs/opus/audio_decoder_opus.h" #include "api/audio_codecs/opus/audio_encoder_opus.h" +#include "api/environment/environment_factory.h" #include "modules/audio_coding/codecs/cng/audio_encoder_cng.h" #include "modules/audio_coding/codecs/red/audio_encoder_copy_red.h" #include "modules/audio_coding/include/audio_coding_module_typedefs.h" @@ -34,7 +35,8 @@ namespace webrtc { TestRedFec::TestRedFec() - : encoder_factory_(CreateAudioEncoderFactory()), @@ -136,8 +138,8 @@ void TestRedFec::RegisterSendCodec( bool use_red) { constexpr int payload_type = 17, cn_payload_type = 27, red_payload_type = 37; - auto encoder = encoder_factory_->MakeAudioEncoder(payload_type, codec_format, - absl::nullopt); + auto encoder = encoder_factory_->Create(env_, codec_format, + {.payload_type = payload_type}); EXPECT_NE(encoder, nullptr); std::map receive_codecs = {{payload_type, codec_format}}; if (!absl::EqualsIgnoreCase(codec_format.name, "opus")) { diff --git a/third_party/libwebrtc/modules/audio_coding/test/TestRedFec.h b/third_party/libwebrtc/modules/audio_coding/test/TestRedFec.h index 173b03f4fc84..8c43c1d58267 100644 --- a/third_party/libwebrtc/modules/audio_coding/test/TestRedFec.h +++ b/third_party/libwebrtc/modules/audio_coding/test/TestRedFec.h @@ -16,6 +16,7 @@ #include "api/audio_codecs/audio_decoder_factory.h" #include "api/audio_codecs/audio_encoder_factory.h" +#include "api/environment/environment.h" #include "common_audio/vad/include/vad.h" #include "modules/audio_coding/acm2/acm_receiver.h" #include "modules/audio_coding/test/Channel.h" @@ -40,6 +41,7 @@ class TestRedFec final { void OpenOutFile(int16_t testNumber); test::ScopedKeyValueConfig field_trials_; + const Environment env_; const rtc::scoped_refptr encoder_factory_; const rtc::scoped_refptr decoder_factory_; std::unique_ptr _acmA; diff --git a/third_party/libwebrtc/modules/audio_coding/test/TestStereo.cc b/third_party/libwebrtc/modules/audio_coding/test/TestStereo.cc index cf1f91365fc5..535aadb49510 100644 --- a/third_party/libwebrtc/modules/audio_coding/test/TestStereo.cc +++ b/third_party/libwebrtc/modules/audio_coding/test/TestStereo.cc @@ -15,6 +15,7 @@ #include "absl/strings/match.h" #include "api/audio_codecs/builtin_audio_decoder_factory.h" #include "api/audio_codecs/builtin_audio_encoder_factory.h" +#include "api/environment/environment_factory.h" #include "modules/audio_coding/include/audio_coding_module_typedefs.h" #include "modules/include/module_common_types.h" #include "rtc_base/strings/string_builder.h" @@ -98,7 +99,8 @@ void TestPackStereo::set_lost_packet(bool lost) { } TestStereo::TestStereo() - : acm_a_(AudioCodingModule::Create()), + : env_(CreateEnvironment()), + acm_a_(AudioCodingModule::Create()), acm_b_(std::make_unique( acm2::AcmReceiver::Config(CreateBuiltinAudioDecoderFactory()))), channel_a2b_(NULL), @@ -488,10 +490,9 @@ void TestStereo::RegisterSendCodec(char side, channels = 2; params["maxaveragebitrate"] = rtc::ToString(rate); } - constexpr int payload_type = 17; - auto encoder = encoder_factory->MakeAudioEncoder( - payload_type, SdpAudioFormat(codec_name, clockrate_hz, channels, params), - absl::nullopt); + auto encoder = encoder_factory->Create( + env_, SdpAudioFormat(codec_name, clockrate_hz, channels, params), + {.payload_type = 17}); EXPECT_NE(nullptr, encoder); my_acm->SetEncoder(std::move(encoder)); diff --git a/third_party/libwebrtc/modules/audio_coding/test/TestStereo.h b/third_party/libwebrtc/modules/audio_coding/test/TestStereo.h index a215c90ec1a0..b53187b9d5ae 100644 --- a/third_party/libwebrtc/modules/audio_coding/test/TestStereo.h +++ b/third_party/libwebrtc/modules/audio_coding/test/TestStereo.h @@ -15,6 +15,7 @@ #include +#include "api/environment/environment.h" #include "modules/audio_coding/acm2/acm_receiver.h" #include "modules/audio_coding/include/audio_coding_module.h" #include "modules/audio_coding/test/PCMFile.h" @@ -81,6 +82,7 @@ class TestStereo { int percent_loss = 0); void OpenOutFile(int16_t test_number); + const Environment env_; std::unique_ptr acm_a_; std::unique_ptr acm_b_; diff --git a/third_party/libwebrtc/modules/audio_coding/test/TestVADDTX.cc b/third_party/libwebrtc/modules/audio_coding/test/TestVADDTX.cc index d6595c047cf7..17eba2656716 100644 --- a/third_party/libwebrtc/modules/audio_coding/test/TestVADDTX.cc +++ b/third_party/libwebrtc/modules/audio_coding/test/TestVADDTX.cc @@ -20,6 +20,7 @@ #include "api/audio_codecs/ilbc/audio_encoder_ilbc.h" #include "api/audio_codecs/opus/audio_decoder_opus.h" #include "api/audio_codecs/opus/audio_encoder_opus.h" +#include "api/environment/environment_factory.h" #include "modules/audio_coding/codecs/cng/audio_encoder_cng.h" #include "modules/audio_coding/test/PCMFile.h" #include "rtc_base/strings/string_builder.h" @@ -66,7 +67,8 @@ void MonitoringAudioPacketizationCallback::GetStatistics(uint32_t* counter) { } TestVadDtx::TestVadDtx() - : encoder_factory_( + : env_(CreateEnvironment()), + encoder_factory_( CreateAudioEncoderFactory()), decoder_factory_( CreateAudioDecoderFactory()), @@ -87,8 +89,8 @@ bool TestVadDtx::RegisterCodec(const SdpAudioFormat& codec_format, constexpr int payload_type = 17, cn_payload_type = 117; bool added_comfort_noise = false; - auto encoder = encoder_factory_->MakeAudioEncoder(payload_type, codec_format, - absl::nullopt); + auto encoder = encoder_factory_->Create(env_, codec_format, + {.payload_type = payload_type}); if (vad_mode.has_value() && !absl::EqualsIgnoreCase(codec_format.name, "opus")) { AudioEncoderCngConfig config; diff --git a/third_party/libwebrtc/modules/audio_coding/test/TestVADDTX.h b/third_party/libwebrtc/modules/audio_coding/test/TestVADDTX.h index 17b3f4185d78..427c94c106c9 100644 --- a/third_party/libwebrtc/modules/audio_coding/test/TestVADDTX.h +++ b/third_party/libwebrtc/modules/audio_coding/test/TestVADDTX.h @@ -16,6 +16,7 @@ #include "absl/strings/string_view.h" #include "api/audio_codecs/audio_decoder_factory.h" #include "api/audio_codecs/audio_encoder_factory.h" +#include "api/environment/environment.h" #include "common_audio/vad/include/vad.h" #include "modules/audio_coding/acm2/acm_receiver.h" #include "modules/audio_coding/include/audio_coding_module.h" @@ -82,6 +83,7 @@ class TestVadDtx { bool append, const int* expects); + const Environment env_; const rtc::scoped_refptr encoder_factory_; const rtc::scoped_refptr decoder_factory_; std::unique_ptr acm_send_; diff --git a/third_party/libwebrtc/modules/audio_mixer/frame_combiner.cc b/third_party/libwebrtc/modules/audio_mixer/frame_combiner.cc index 96d1d86f673a..dfe9511f5a7c 100644 --- a/third_party/libwebrtc/modules/audio_mixer/frame_combiner.cc +++ b/third_party/libwebrtc/modules/audio_mixer/frame_combiner.cc @@ -36,17 +36,13 @@ namespace webrtc { namespace { -using MixingBuffer = - std::array, - FrameCombiner::kMaximumNumberOfChannels>; - void SetAudioFrameFields(rtc::ArrayView mix_list, size_t number_of_channels, int sample_rate, size_t number_of_streams, AudioFrame* audio_frame_for_mixing) { - const size_t samples_per_channel = static_cast( - (sample_rate * webrtc::AudioMixerImpl::kFrameDurationInMs) / 1000); + const size_t samples_per_channel = + SampleRateToDefaultChannelSize(sample_rate); // TODO(minyue): Issue bugs.webrtc.org/3390. // Audio frame timestamp. The 'timestamp_' field is set to dummy @@ -85,56 +81,47 @@ void MixFewFramesWithNoLimiter(rtc::ArrayView mix_list, return; } RTC_DCHECK_LE(mix_list.size(), 1); - std::copy(mix_list[0]->data(), - mix_list[0]->data() + - mix_list[0]->num_channels_ * mix_list[0]->samples_per_channel_, - audio_frame_for_mixing->mutable_data()); + InterleavedView dst = audio_frame_for_mixing->mutable_data( + mix_list[0]->samples_per_channel_, mix_list[0]->num_channels_); + CopySamples(dst, mix_list[0]->data_view()); } void MixToFloatFrame(rtc::ArrayView mix_list, - size_t samples_per_channel, - size_t number_of_channels, - MixingBuffer* mixing_buffer) { - RTC_DCHECK_LE(samples_per_channel, FrameCombiner::kMaximumChannelSize); - RTC_DCHECK_LE(number_of_channels, FrameCombiner::kMaximumNumberOfChannels); + DeinterleavedView& mixing_buffer) { + const size_t number_of_channels = NumChannels(mixing_buffer); // Clear the mixing buffer. - *mixing_buffer = {}; + rtc::ArrayView raw_data = mixing_buffer.data(); + ClearSamples(raw_data); // Convert to FloatS16 and mix. for (size_t i = 0; i < mix_list.size(); ++i) { - const AudioFrame* const frame = mix_list[i]; - const int16_t* const frame_data = frame->data(); - for (size_t j = 0; j < std::min(number_of_channels, - FrameCombiner::kMaximumNumberOfChannels); - ++j) { - for (size_t k = 0; k < std::min(samples_per_channel, - FrameCombiner::kMaximumChannelSize); - ++k) { - (*mixing_buffer)[j][k] += frame_data[number_of_channels * k + j]; + InterleavedView frame_data = mix_list[i]->data_view(); + RTC_CHECK(!frame_data.empty()); + for (size_t j = 0; j < number_of_channels; ++j) { + MonoView channel = mixing_buffer[j]; + for (size_t k = 0; k < SamplesPerChannel(channel); ++k) { + channel[k] += frame_data[number_of_channels * k + j]; } } } } -void RunLimiter(AudioFrameView mixing_buffer_view, Limiter* limiter) { - const size_t sample_rate = mixing_buffer_view.samples_per_channel() * 1000 / - AudioMixerImpl::kFrameDurationInMs; - // TODO(alessiob): Avoid calling SetSampleRate every time. - limiter->SetSampleRate(sample_rate); - limiter->Process(mixing_buffer_view); +void RunLimiter(DeinterleavedView deinterleaved, Limiter* limiter) { + limiter->SetSamplesPerChannel(deinterleaved.samples_per_channel()); + limiter->Process(deinterleaved); } // Both interleaves and rounds. -void InterleaveToAudioFrame(AudioFrameView mixing_buffer_view, +void InterleaveToAudioFrame(DeinterleavedView deinterleaved, AudioFrame* audio_frame_for_mixing) { - const size_t number_of_channels = mixing_buffer_view.num_channels(); - const size_t samples_per_channel = mixing_buffer_view.samples_per_channel(); - int16_t* const mixing_data = audio_frame_for_mixing->mutable_data(); + InterleavedView mixing_data = audio_frame_for_mixing->mutable_data( + deinterleaved.samples_per_channel(), deinterleaved.num_channels()); // Put data in the result frame. - for (size_t i = 0; i < number_of_channels; ++i) { - for (size_t j = 0; j < samples_per_channel; ++j) { - mixing_data[number_of_channels * j + i] = - FloatS16ToS16(mixing_buffer_view.channel(i)[j]); + for (size_t i = 0; i < mixing_data.num_channels(); ++i) { + auto channel = deinterleaved[i]; + for (size_t j = 0; j < mixing_data.samples_per_channel(); ++j) { + mixing_data[mixing_data.num_channels() * j + i] = + FloatS16ToS16(channel[j]); } } } @@ -145,10 +132,7 @@ constexpr size_t FrameCombiner::kMaximumChannelSize; FrameCombiner::FrameCombiner(bool use_limiter) : data_dumper_(new ApmDataDumper(0)), - mixing_buffer_( - std::make_unique, - kMaximumNumberOfChannels>>()), - limiter_(static_cast(48000), data_dumper_.get(), "AudioMixer"), + limiter_(data_dumper_.get(), kMaximumChannelSize, "AudioMixer"), use_limiter_(use_limiter) { static_assert(kMaximumChannelSize * kMaximumNumberOfChannels <= AudioFrame::kMaxDataSizeSamples, @@ -163,17 +147,26 @@ void FrameCombiner::Combine(rtc::ArrayView mix_list, size_t number_of_streams, AudioFrame* audio_frame_for_mixing) { RTC_DCHECK(audio_frame_for_mixing); + RTC_DCHECK_GT(sample_rate, 0); + + // Note: `mix_list` is allowed to be empty. + // See FrameCombiner.CombiningZeroFramesShouldProduceSilence. + + // Make sure to cap `number_of_channels` to the kMaximumNumberOfChannels + // limits since processing from hereon out will be bound by them. + number_of_channels = std::min(number_of_channels, kMaximumNumberOfChannels); SetAudioFrameFields(mix_list, number_of_channels, sample_rate, number_of_streams, audio_frame_for_mixing); - const size_t samples_per_channel = static_cast( - (sample_rate * webrtc::AudioMixerImpl::kFrameDurationInMs) / 1000); + size_t samples_per_channel = SampleRateToDefaultChannelSize(sample_rate); +#if RTC_DCHECK_IS_ON for (const auto* frame : mix_list) { RTC_DCHECK_EQ(samples_per_channel, frame->samples_per_channel_); RTC_DCHECK_EQ(sample_rate, frame->sample_rate_hz_); } +#endif // The 'num_channels_' field of frames in 'mix_list' could be // different from 'number_of_channels'. @@ -186,28 +179,23 @@ void FrameCombiner::Combine(rtc::ArrayView mix_list, return; } - MixToFloatFrame(mix_list, samples_per_channel, number_of_channels, - mixing_buffer_.get()); - - const size_t output_number_of_channels = - std::min(number_of_channels, kMaximumNumberOfChannels); - const size_t output_samples_per_channel = - std::min(samples_per_channel, kMaximumChannelSize); - - // Put float data in an AudioFrameView. - std::array channel_pointers{}; - for (size_t i = 0; i < output_number_of_channels; ++i) { - channel_pointers[i] = &(*mixing_buffer_.get())[i][0]; - } - AudioFrameView mixing_buffer_view(&channel_pointers[0], - output_number_of_channels, - output_samples_per_channel); + // Make sure that the size of the view based on the desired + // `samples_per_channel` and `number_of_channels` doesn't exceed the size of + // the `mixing_buffer_` buffer. + RTC_DCHECK_LE(samples_per_channel, kMaximumChannelSize); + // Since the above check is a DCHECK only, clamp down on `samples_per_channel` + // to make sure we don't exceed the buffer size in non-dcheck builds. + // See also FrameCombinerDeathTest.DebugBuildCrashesWithHighRate. + samples_per_channel = std::min(samples_per_channel, kMaximumChannelSize); + DeinterleavedView deinterleaved( + mixing_buffer_.data(), samples_per_channel, number_of_channels); + MixToFloatFrame(mix_list, deinterleaved); if (use_limiter_) { - RunLimiter(mixing_buffer_view, &limiter_); + RunLimiter(deinterleaved, &limiter_); } - InterleaveToAudioFrame(mixing_buffer_view, audio_frame_for_mixing); + InterleaveToAudioFrame(deinterleaved, audio_frame_for_mixing); } } // namespace webrtc diff --git a/third_party/libwebrtc/modules/audio_mixer/frame_combiner.h b/third_party/libwebrtc/modules/audio_mixer/frame_combiner.h index 6185b29f8ab1..6b8ff33bcbe3 100644 --- a/third_party/libwebrtc/modules/audio_mixer/frame_combiner.h +++ b/third_party/libwebrtc/modules/audio_mixer/frame_combiner.h @@ -42,14 +42,12 @@ class FrameCombiner { static constexpr size_t kMaximumNumberOfChannels = 8; static constexpr size_t kMaximumChannelSize = 48 * 10; - using MixingBuffer = std::array, - kMaximumNumberOfChannels>; - private: std::unique_ptr data_dumper_; - std::unique_ptr mixing_buffer_; Limiter limiter_; const bool use_limiter_; + std::array + mixing_buffer_ = {}; }; } // namespace webrtc diff --git a/third_party/libwebrtc/modules/audio_mixer/frame_combiner_unittest.cc b/third_party/libwebrtc/modules/audio_mixer/frame_combiner_unittest.cc index 486f551f7845..80c2b995759d 100644 --- a/third_party/libwebrtc/modules/audio_mixer/frame_combiner_unittest.cc +++ b/third_party/libwebrtc/modules/audio_mixer/frame_combiner_unittest.cc @@ -186,14 +186,13 @@ TEST(FrameCombinerDeathTest, DebugBuildCrashesWithHighRate) { const std::vector frames_to_combine( all_frames.begin(), all_frames.begin() + number_of_frames); AudioFrame audio_frame_for_mixing; -#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +#if GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) EXPECT_DEATH( combiner.Combine(frames_to_combine, number_of_channels, rate, frames_to_combine.size(), &audio_frame_for_mixing), - ""); -#elif !RTC_DCHECK_IS_ON - combiner.Combine(frames_to_combine, number_of_channels, rate, - frames_to_combine.size(), &audio_frame_for_mixing); + "") + << "number_of_channels=" << number_of_channels << ", rate=" << rate + << ", frames to combine=" << frames_to_combine.size(); #endif } } diff --git a/third_party/libwebrtc/modules/audio_processing/BUILD.gn b/third_party/libwebrtc/modules/audio_processing/BUILD.gn index 2f32a4a6d819..8cabb1d71a0f 100644 --- a/third_party/libwebrtc/modules/audio_processing/BUILD.gn +++ b/third_party/libwebrtc/modules/audio_processing/BUILD.gn @@ -56,6 +56,7 @@ rtc_library("audio_buffer") { deps = [ "../../api:array_view", + "../../api/audio:audio_frame_api", "../../api/audio:audio_processing", "../../common_audio", "../../common_audio:common_audio_c", @@ -108,6 +109,7 @@ rtc_library("gain_controller2") { ":apm_logging", ":audio_buffer", ":audio_frame_view", + "../../api/audio:audio_frame_api", "../../api/audio:audio_processing", "../../common_audio", "../../rtc_base:checks", @@ -363,6 +365,7 @@ if (rtc_include_tests) { "../../api:scoped_refptr", "../../api/audio:aec3_config", "../../api/audio:aec3_factory", + "../../api/audio:audio_frame_api", "../../api/audio:audio_processing", "../../api/audio:echo_detector_creator", "../../common_audio", diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/BUILD.gn b/third_party/libwebrtc/modules/audio_processing/agc2/BUILD.gn index 0635120c37d8..73b2beb56884 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/BUILD.gn +++ b/third_party/libwebrtc/modules/audio_processing/agc2/BUILD.gn @@ -49,7 +49,7 @@ rtc_library("adaptive_digital_gain_controller") { ":common", ":gain_applier", "..:apm_logging", - "..:audio_frame_view", + "../../../api/audio:audio_frame_api", "../../../api/audio:audio_processing", "../../../common_audio", "../../../rtc_base:checks", @@ -148,6 +148,7 @@ rtc_library("fixed_digital") { "..:apm_logging", "..:audio_frame_view", "../../../api:array_view", + "../../../api/audio:audio_frame_api", "../../../common_audio", "../../../rtc_base:checks", "../../../rtc_base:gtest_prod", @@ -173,7 +174,7 @@ rtc_library("gain_applier") { deps = [ ":common", "..:audio_frame_view", - "../../../api:array_view", + "../../../api/audio:audio_frame_api", "../../../rtc_base:safe_minmax", ] } @@ -231,8 +232,7 @@ rtc_library("noise_level_estimator") { deps = [ ":biquad_filter", "..:apm_logging", - "..:audio_frame_view", - "../../../api:array_view", + "../../../api/audio:audio_frame_api", "../../../rtc_base:checks", "../../../system_wrappers", ] @@ -265,8 +265,7 @@ rtc_library("vad_wrapper") { deps = [ ":common", ":cpu_features", - "..:audio_frame_view", - "../../../api:array_view", + "../../../api/audio:audio_frame_api", "../../../common_audio", "../../../rtc_base:checks", "rnn_vad", @@ -334,7 +333,7 @@ rtc_library("gain_applier_unittest") { deps = [ ":gain_applier", ":test_utils", - "..:audio_frame_view", + "../../../api/audio:audio_frame_api", "../../../rtc_base:gunit_helpers", "../../../test:test_support", ] @@ -388,6 +387,7 @@ rtc_library("fixed_digital_unittests") { "..:apm_logging", "..:audio_frame_view", "../../../api:array_view", + "../../../api/audio:audio_frame_api", "../../../common_audio", "../../../rtc_base:checks", "../../../rtc_base:gunit_helpers", @@ -433,9 +433,8 @@ rtc_library("noise_estimator_unittests") { ":noise_level_estimator", ":test_utils", "..:apm_logging", - "..:audio_frame_view", - "../../../api:array_view", "../../../api:function_view", + "../../../api/audio:audio_frame_api", "../../../rtc_base:checks", "../../../rtc_base:gunit_helpers", ] @@ -447,7 +446,7 @@ rtc_library("vad_wrapper_unittests") { deps = [ ":common", ":vad_wrapper", - "..:audio_frame_view", + "../../../api/audio:audio_frame_api", "../../../rtc_base:checks", "../../../rtc_base:gunit_helpers", "../../../rtc_base:safe_compare", @@ -469,6 +468,7 @@ rtc_library("test_utils") { ] deps = [ "..:audio_frame_view", + "../../../api/audio:audio_frame_api", "../../../rtc_base:checks", "../../../rtc_base:random", ] diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/adaptive_digital_gain_controller.cc b/third_party/libwebrtc/modules/audio_processing/agc2/adaptive_digital_gain_controller.cc index e8edab602cc7..5f924cbbcffb 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/adaptive_digital_gain_controller.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/adaptive_digital_gain_controller.cc @@ -124,7 +124,7 @@ AdaptiveDigitalGainController::AdaptiveDigitalGainController( } void AdaptiveDigitalGainController::Process(const FrameInfo& info, - AudioFrameView frame) { + DeinterleavedView frame) { RTC_DCHECK_GE(info.speech_level_dbfs, -150.0f); RTC_DCHECK_GE(frame.num_channels(), 1); RTC_DCHECK( diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/adaptive_digital_gain_controller.h b/third_party/libwebrtc/modules/audio_processing/agc2/adaptive_digital_gain_controller.h index 9ae74a2dc8a4..d464dc6b2c10 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/adaptive_digital_gain_controller.h +++ b/third_party/libwebrtc/modules/audio_processing/agc2/adaptive_digital_gain_controller.h @@ -14,8 +14,8 @@ #include #include "api/audio/audio_processing.h" +#include "api/audio/audio_view.h" #include "modules/audio_processing/agc2/gain_applier.h" -#include "modules/audio_processing/include/audio_frame_view.h" namespace webrtc { @@ -46,7 +46,7 @@ class AdaptiveDigitalGainController { // Analyzes `info`, updates the digital gain and applies it to a 10 ms // `frame`. Supports any sample rate supported by APM. - void Process(const FrameInfo& info, AudioFrameView frame); + void Process(const FrameInfo& info, DeinterleavedView frame); private: ApmDataDumper* const apm_data_dumper_; diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/adaptive_digital_gain_controller_unittest.cc b/third_party/libwebrtc/modules/audio_processing/agc2/adaptive_digital_gain_controller_unittest.cc index 88fb792f2dde..39e175d4037a 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/adaptive_digital_gain_controller_unittest.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/adaptive_digital_gain_controller_unittest.cc @@ -83,7 +83,7 @@ TEST(GainController2AdaptiveDigitalGainControllerTest, // Make one call with reasonable audio level values and settings. VectorFloatFrame fake_audio(kStereo, kFrameLen10ms48kHz, 10000.0f); helper.gain_applier->Process(GetFrameInfoToNotAdapt(kDefaultConfig), - fake_audio.float_frame_view()); + fake_audio.view()); } // Checks that the maximum allowed gain is applied. @@ -103,7 +103,7 @@ TEST(GainController2AdaptiveDigitalGainControllerTest, MaxGainApplied) { float applied_gain; for (int i = 0; i < kNumFramesToAdapt; ++i) { VectorFloatFrame fake_audio(kMono, kFrameLen10ms8kHz, 1.0f); - helper.gain_applier->Process(info, fake_audio.float_frame_view()); + helper.gain_applier->Process(info, fake_audio.view()); applied_gain = fake_audio.float_frame_view().channel(0)[0]; } const float applied_gain_db = 20.0f * std::log10f(applied_gain); @@ -129,8 +129,8 @@ TEST(GainController2AdaptiveDigitalGainControllerTest, GainDoesNotChangeFast) { AdaptiveDigitalGainController::FrameInfo info = GetFrameInfoToNotAdapt(kDefaultConfig); info.speech_level_dbfs = initial_level_dbfs; - helper.gain_applier->Process(info, fake_audio.float_frame_view()); - float current_gain_linear = fake_audio.float_frame_view().channel(0)[0]; + helper.gain_applier->Process(info, fake_audio.view()); + float current_gain_linear = fake_audio.view()[0][0]; EXPECT_LE(std::abs(current_gain_linear - last_gain_linear), max_change_per_frame_linear); last_gain_linear = current_gain_linear; @@ -143,8 +143,8 @@ TEST(GainController2AdaptiveDigitalGainControllerTest, GainDoesNotChangeFast) { AdaptiveDigitalGainController::FrameInfo info = GetFrameInfoToNotAdapt(kDefaultConfig); info.speech_level_dbfs = 0.f; - helper.gain_applier->Process(info, fake_audio.float_frame_view()); - float current_gain_linear = fake_audio.float_frame_view().channel(0)[0]; + helper.gain_applier->Process(info, fake_audio.view()); + float current_gain_linear = fake_audio.view()[0][0]; EXPECT_LE(std::abs(current_gain_linear - last_gain_linear), max_change_per_frame_linear); last_gain_linear = current_gain_linear; @@ -160,10 +160,10 @@ TEST(GainController2AdaptiveDigitalGainControllerTest, GainIsRampedInAFrame) { AdaptiveDigitalGainController::FrameInfo info = GetFrameInfoToNotAdapt(kDefaultConfig); info.speech_level_dbfs = initial_level_dbfs; - helper.gain_applier->Process(info, fake_audio.float_frame_view()); + helper.gain_applier->Process(info, fake_audio.view()); float maximal_difference = 0.0f; float current_value = 1.0f * DbToRatio(kDefaultConfig.initial_gain_db); - for (const auto& x : fake_audio.float_frame_view().channel(0)) { + for (const auto& x : fake_audio.view()[0]) { const float difference = std::abs(x - current_value); maximal_difference = std::max(maximal_difference, difference); current_value = x; @@ -195,13 +195,13 @@ TEST(GainController2AdaptiveDigitalGainControllerTest, NoiseLimitsGain) { GetFrameInfoToNotAdapt(kDefaultConfig); info.speech_level_dbfs = initial_level_dbfs; info.noise_rms_dbfs = kWithNoiseDbfs; - helper.gain_applier->Process(info, fake_audio.float_frame_view()); + auto fake_view = fake_audio.view(); + helper.gain_applier->Process(info, fake_view); // Wait so that the adaptive gain applier has time to lower the gain. if (i > num_initial_frames) { const float maximal_ratio = - *std::max_element(fake_audio.float_frame_view().channel(0).begin(), - fake_audio.float_frame_view().channel(0).end()); + *std::max_element(fake_view[0].begin(), fake_view[0].end()); EXPECT_NEAR(maximal_ratio, 1.0f, 0.001f); } @@ -217,7 +217,7 @@ TEST(GainController2AdaptiveDigitalGainControllerTest, AdaptiveDigitalGainController::FrameInfo info = GetFrameInfoToNotAdapt(kDefaultConfig); info.speech_level_dbfs = 5.0f; - helper.gain_applier->Process(info, fake_audio.float_frame_view()); + helper.gain_applier->Process(info, fake_audio.view()); } TEST(GainController2AdaptiveDigitalGainControllerTest, AudioLevelLimitsGain) { @@ -239,13 +239,13 @@ TEST(GainController2AdaptiveDigitalGainControllerTest, AudioLevelLimitsGain) { info.speech_level_dbfs = initial_level_dbfs; info.limiter_envelope_dbfs = 1.0f; info.speech_level_reliable = false; - helper.gain_applier->Process(info, fake_audio.float_frame_view()); + auto fake_view = fake_audio.view(); + helper.gain_applier->Process(info, fake_view); // Wait so that the adaptive gain applier has time to lower the gain. if (i > num_initial_frames) { const float maximal_ratio = - *std::max_element(fake_audio.float_frame_view().channel(0).begin(), - fake_audio.float_frame_view().channel(0).end()); + *std::max_element(fake_view[0].begin(), fake_view[0].end()); EXPECT_NEAR(maximal_ratio, 1.0f, 0.001f); } @@ -271,8 +271,8 @@ TEST_P(AdaptiveDigitalGainControllerParametrizedTest, for (int i = 0; i < adjacent_speech_frames_threshold(); ++i) { SCOPED_TRACE(i); VectorFloatFrame audio(kMono, kFrameLen10ms48kHz, 1.0f); - helper.gain_applier->Process(info, audio.float_frame_view()); - const float gain = audio.float_frame_view().channel(0)[0]; + helper.gain_applier->Process(info, audio.view()); + const float gain = audio.view()[0][0]; if (i > 0) { EXPECT_EQ(prev_gain, gain); // No gain increase applied. } @@ -293,16 +293,16 @@ TEST_P(AdaptiveDigitalGainControllerParametrizedTest, for (int i = 0; i < adjacent_speech_frames_threshold(); ++i) { SCOPED_TRACE(i); VectorFloatFrame audio(kMono, kFrameLen10ms48kHz, 1.0f); - helper.gain_applier->Process(info, audio.float_frame_view()); - prev_gain = audio.float_frame_view().channel(0)[0]; + helper.gain_applier->Process(info, audio.view()); + prev_gain = audio.view()[0][0]; } // Process one more speech frame. VectorFloatFrame audio(kMono, kFrameLen10ms48kHz, 1.0f); - helper.gain_applier->Process(info, audio.float_frame_view()); + helper.gain_applier->Process(info, audio.view()); // An increased gain has been applied. - EXPECT_GT(audio.float_frame_view().channel(0)[0], prev_gain); + EXPECT_GT(audio.view()[0][0], prev_gain); } INSTANTIATE_TEST_SUITE_P( diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/fixed_digital_level_estimator.cc b/third_party/libwebrtc/modules/audio_processing/agc2/fixed_digital_level_estimator.cc index 1995b24913c8..73edcf052346 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/fixed_digital_level_estimator.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/fixed_digital_level_estimator.cc @@ -14,6 +14,7 @@ #include #include "api/array_view.h" +#include "api/audio/audio_frame.h" #include "modules/audio_processing/logging/apm_data_dumper.h" #include "rtc_base/checks.h" @@ -34,14 +35,17 @@ constexpr float kDecayFilterConstant = 0.9971259f; } // namespace FixedDigitalLevelEstimator::FixedDigitalLevelEstimator( - int sample_rate_hz, + size_t samples_per_channel, ApmDataDumper* apm_data_dumper) : apm_data_dumper_(apm_data_dumper), filter_state_level_(kInitialFilterStateLevel) { - SetSampleRate(sample_rate_hz); + SetSamplesPerChannel(samples_per_channel); CheckParameterCombination(); RTC_DCHECK(apm_data_dumper_); - apm_data_dumper_->DumpRaw("agc2_level_estimator_samplerate", sample_rate_hz); + // Convert `samples_per_channel` to sample rate for + // `agc2_level_estimator_samplerate`. + apm_data_dumper_->DumpRaw("agc2_level_estimator_samplerate", + samples_per_channel * kDefaultAudioBuffersPerSec); } void FixedDigitalLevelEstimator::CheckParameterCombination() { @@ -52,15 +56,15 @@ void FixedDigitalLevelEstimator::CheckParameterCombination() { } std::array FixedDigitalLevelEstimator::ComputeLevel( - const AudioFrameView& float_frame) { + DeinterleavedView float_frame) { RTC_DCHECK_GT(float_frame.num_channels(), 0); RTC_DCHECK_EQ(float_frame.samples_per_channel(), samples_in_frame_); // Compute max envelope without smoothing. std::array envelope{}; - for (int channel_idx = 0; channel_idx < float_frame.num_channels(); + for (size_t channel_idx = 0; channel_idx < float_frame.num_channels(); ++channel_idx) { - const auto channel = float_frame.channel(channel_idx); + const auto channel = float_frame[channel_idx]; for (int sub_frame = 0; sub_frame < kSubFramesInFrame; ++sub_frame) { for (int sample_in_sub_frame = 0; sample_in_sub_frame < samples_in_sub_frame_; ++sample_in_sub_frame) { @@ -95,7 +99,7 @@ std::array FixedDigitalLevelEstimator::ComputeLevel( // Dump data for debug. RTC_DCHECK(apm_data_dumper_); - const auto channel = float_frame.channel(0); + const auto channel = float_frame[0]; apm_data_dumper_->DumpRaw("agc2_level_estimator_samples", samples_in_sub_frame_, &channel[sub_frame * samples_in_sub_frame_]); @@ -106,9 +110,9 @@ std::array FixedDigitalLevelEstimator::ComputeLevel( return envelope; } -void FixedDigitalLevelEstimator::SetSampleRate(int sample_rate_hz) { - samples_in_frame_ = - rtc::CheckedDivExact(sample_rate_hz * kFrameDurationMs, 1000); +void FixedDigitalLevelEstimator::SetSamplesPerChannel( + size_t samples_per_channel) { + samples_in_frame_ = static_cast(samples_per_channel); samples_in_sub_frame_ = rtc::CheckedDivExact(samples_in_frame_, kSubFramesInFrame); CheckParameterCombination(); diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/fixed_digital_level_estimator.h b/third_party/libwebrtc/modules/audio_processing/agc2/fixed_digital_level_estimator.h index d26b55950c1b..1669acdc7124 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/fixed_digital_level_estimator.h +++ b/third_party/libwebrtc/modules/audio_processing/agc2/fixed_digital_level_estimator.h @@ -25,12 +25,16 @@ class ApmDataDumper; // filtering. class FixedDigitalLevelEstimator { public: - // Sample rates are allowed if the number of samples in a frame - // (sample_rate_hz * kFrameDurationMs / 1000) is divisible by + // `samples_per_channel` is expected to be derived from this formula: + // sample_rate_hz * kFrameDurationMs / 1000 + // or, for a 10ms duration: + // sample_rate_hz / 100 + // I.e. the number of samples for 10ms of the given sample rate. The + // expectation is that samples per channel is divisible by // kSubFramesInSample. For kFrameDurationMs=10 and - // kSubFramesInSample=20, this means that sample_rate_hz has to be - // divisible by 2000. - FixedDigitalLevelEstimator(int sample_rate_hz, + // kSubFramesInSample=20, this means that the original sample rate has to be + // divisible by 2000 and therefore `samples_per_channel` by 20. + FixedDigitalLevelEstimator(size_t samples_per_channel, ApmDataDumper* apm_data_dumper); FixedDigitalLevelEstimator(const FixedDigitalLevelEstimator&) = delete; @@ -42,11 +46,11 @@ class FixedDigitalLevelEstimator { // ms of audio produces a level estimates in the same scale. The // level estimate contains kSubFramesInFrame values. std::array ComputeLevel( - const AudioFrameView& float_frame); + DeinterleavedView float_frame); // Rate may be changed at any time (but not concurrently) from the // value passed to the constructor. The class is not thread safe. - void SetSampleRate(int sample_rate_hz); + void SetSamplesPerChannel(size_t samples_per_channel); // Resets the level estimator internal state. void Reset(); diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/fixed_digital_level_estimator_unittest.cc b/third_party/libwebrtc/modules/audio_processing/agc2/fixed_digital_level_estimator_unittest.cc index 97b421d04ca3..c76db85a5cbc 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/fixed_digital_level_estimator_unittest.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/fixed_digital_level_estimator_unittest.cc @@ -12,6 +12,7 @@ #include +#include "api/audio/audio_frame.h" #include "common_audio/include/audio_util.h" #include "modules/audio_processing/agc2/agc2_common.h" #include "modules/audio_processing/agc2/agc2_testing_common.h" @@ -26,21 +27,21 @@ constexpr float kInputLevel = 10000.f; // Run audio at specified settings through the level estimator, and // verify that the output level falls within the bounds. -void TestLevelEstimator(int sample_rate_hz, +void TestLevelEstimator(size_t samples_per_channel, int num_channels, float input_level_linear_scale, float expected_min, float expected_max) { ApmDataDumper apm_data_dumper(0); - FixedDigitalLevelEstimator level_estimator(sample_rate_hz, &apm_data_dumper); + FixedDigitalLevelEstimator level_estimator(samples_per_channel, + &apm_data_dumper); const VectorFloatFrame vectors_with_float_frame( - num_channels, rtc::CheckedDivExact(sample_rate_hz, 100), - input_level_linear_scale); + num_channels, samples_per_channel, input_level_linear_scale); for (int i = 0; i < 500; ++i) { - const auto level = level_estimator.ComputeLevel( - vectors_with_float_frame.float_frame_view()); + const auto level = + level_estimator.ComputeLevel(vectors_with_float_frame.view()); // Give the estimator some time to ramp up. if (i < 50) { @@ -56,7 +57,7 @@ void TestLevelEstimator(int sample_rate_hz, // Returns time it takes for the level estimator to decrease its level // estimate by 'level_reduction_db'. -float TimeMsToDecreaseLevel(int sample_rate_hz, +float TimeMsToDecreaseLevel(size_t samples_per_channel, int num_channels, float input_level_db, float level_reduction_db) { @@ -64,29 +65,30 @@ float TimeMsToDecreaseLevel(int sample_rate_hz, RTC_DCHECK_GT(level_reduction_db, 0); const VectorFloatFrame vectors_with_float_frame( - num_channels, rtc::CheckedDivExact(sample_rate_hz, 100), input_level); + num_channels, samples_per_channel, input_level); ApmDataDumper apm_data_dumper(0); - FixedDigitalLevelEstimator level_estimator(sample_rate_hz, &apm_data_dumper); + FixedDigitalLevelEstimator level_estimator(samples_per_channel, + &apm_data_dumper); // Give the LevelEstimator plenty of time to ramp up and stabilize float last_level = 0.f; for (int i = 0; i < 500; ++i) { - const auto level_envelope = level_estimator.ComputeLevel( - vectors_with_float_frame.float_frame_view()); + const auto level_envelope = + level_estimator.ComputeLevel(vectors_with_float_frame.view()); last_level = *level_envelope.rbegin(); } // Set input to 0. - VectorFloatFrame vectors_with_zero_float_frame( - num_channels, rtc::CheckedDivExact(sample_rate_hz, 100), 0); + VectorFloatFrame vectors_with_zero_float_frame(num_channels, + samples_per_channel, 0); const float reduced_level_linear = DbfsToFloatS16(input_level_db - level_reduction_db); int sub_frames_until_level_reduction = 0; while (last_level > reduced_level_linear) { - const auto level_envelope = level_estimator.ComputeLevel( - vectors_with_zero_float_frame.float_frame_view()); + const auto level_envelope = + level_estimator.ComputeLevel(vectors_with_zero_float_frame.view()); for (const auto& v : level_envelope) { EXPECT_LT(v, last_level); sub_frames_until_level_reduction++; @@ -102,21 +104,22 @@ float TimeMsToDecreaseLevel(int sample_rate_hz, } // namespace TEST(GainController2FixedDigitalLevelEstimator, EstimatorShouldNotCrash) { - TestLevelEstimator(8000, 1, 0, std::numeric_limits::lowest(), + TestLevelEstimator(SampleRateToDefaultChannelSize(8000u), 1, 0, + std::numeric_limits::lowest(), std::numeric_limits::max()); } TEST(GainController2FixedDigitalLevelEstimator, EstimatorShouldEstimateConstantLevel) { - TestLevelEstimator(10000, 1, kInputLevel, kInputLevel * 0.99, - kInputLevel * 1.01); + TestLevelEstimator(SampleRateToDefaultChannelSize(10000u), 1, kInputLevel, + kInputLevel * 0.99, kInputLevel * 1.01); } TEST(GainController2FixedDigitalLevelEstimator, EstimatorShouldEstimateConstantLevelForManyChannels) { constexpr size_t num_channels = 10; - TestLevelEstimator(20000, num_channels, kInputLevel, kInputLevel * 0.99, - kInputLevel * 1.01); + TestLevelEstimator(SampleRateToDefaultChannelSize(20000u), num_channels, + kInputLevel, kInputLevel * 0.99, kInputLevel * 1.01); } TEST(GainController2FixedDigitalLevelEstimator, TimeToDecreaseForLowLevel) { @@ -125,7 +128,8 @@ TEST(GainController2FixedDigitalLevelEstimator, TimeToDecreaseForLowLevel) { constexpr float kExpectedTime = kLevelReductionDb * test::kDecayMs; const float time_to_decrease = - TimeMsToDecreaseLevel(22000, 1, kInitialLowLevel, kLevelReductionDb); + TimeMsToDecreaseLevel(SampleRateToDefaultChannelSize(22000u), 1, + kInitialLowLevel, kLevelReductionDb); EXPECT_LE(kExpectedTime * 0.9, time_to_decrease); EXPECT_LE(time_to_decrease, kExpectedTime * 1.1); @@ -136,8 +140,8 @@ TEST(GainController2FixedDigitalLevelEstimator, constexpr float kLevelReductionDb = 25; constexpr float kExpectedTime = kLevelReductionDb * test::kDecayMs; - const float time_to_decrease = - TimeMsToDecreaseLevel(26000, 1, 0, kLevelReductionDb); + const float time_to_decrease = TimeMsToDecreaseLevel( + SampleRateToDefaultChannelSize(26000u), 1, 0, kLevelReductionDb); EXPECT_LE(kExpectedTime * 0.9, time_to_decrease); EXPECT_LE(time_to_decrease, kExpectedTime * 1.1); @@ -150,7 +154,8 @@ TEST(GainController2FixedDigitalLevelEstimator, constexpr size_t kNumChannels = 10; const float time_to_decrease = - TimeMsToDecreaseLevel(28000, kNumChannels, 0, kLevelReductionDb); + TimeMsToDecreaseLevel(SampleRateToDefaultChannelSize(28000u), + kNumChannels, 0, kLevelReductionDb); EXPECT_LE(kExpectedTime * 0.9, time_to_decrease); EXPECT_LE(time_to_decrease, kExpectedTime * 1.1); diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/gain_applier.cc b/third_party/libwebrtc/modules/audio_processing/agc2/gain_applier.cc index f9e276d3a85d..f833ad1fbe5a 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/gain_applier.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/gain_applier.cc @@ -10,7 +10,7 @@ #include "modules/audio_processing/agc2/gain_applier.h" -#include "api/array_view.h" +#include "api/audio/audio_view.h" #include "modules/audio_processing/agc2/agc2_common.h" #include "rtc_base/numerics/safe_minmax.h" @@ -24,9 +24,9 @@ bool GainCloseToOne(float gain_factor) { gain_factor <= 1.f + 1.f / kMaxFloatS16Value; } -void ClipSignal(AudioFrameView signal) { - for (int k = 0; k < signal.num_channels(); ++k) { - rtc::ArrayView channel_view = signal.channel(k); +void ClipSignal(DeinterleavedView signal) { + for (size_t k = 0; k < signal.num_channels(); ++k) { + MonoView channel_view = signal[k]; for (auto& sample : channel_view) { sample = rtc::SafeClamp(sample, kMinFloatS16Value, kMaxFloatS16Value); } @@ -36,7 +36,7 @@ void ClipSignal(AudioFrameView signal) { void ApplyGainWithRamping(float last_gain_linear, float gain_at_end_of_frame_linear, float inverse_samples_per_channel, - AudioFrameView float_frame) { + DeinterleavedView float_frame) { // Do not modify the signal. if (last_gain_linear == gain_at_end_of_frame_linear && GainCloseToOne(gain_at_end_of_frame_linear)) { @@ -45,8 +45,8 @@ void ApplyGainWithRamping(float last_gain_linear, // Gain is constant and different from 1. if (last_gain_linear == gain_at_end_of_frame_linear) { - for (int k = 0; k < float_frame.num_channels(); ++k) { - rtc::ArrayView channel_view = float_frame.channel(k); + for (size_t k = 0; k < float_frame.num_channels(); ++k) { + MonoView channel_view = float_frame[k]; for (auto& sample : channel_view) { sample *= gain_at_end_of_frame_linear; } @@ -57,12 +57,12 @@ void ApplyGainWithRamping(float last_gain_linear, // The gain changes. We have to change slowly to avoid discontinuities. const float increment = (gain_at_end_of_frame_linear - last_gain_linear) * inverse_samples_per_channel; - float gain = last_gain_linear; - for (int i = 0; i < float_frame.samples_per_channel(); ++i) { - for (int ch = 0; ch < float_frame.num_channels(); ++ch) { - float_frame.channel(ch)[i] *= gain; + for (size_t ch = 0; ch < float_frame.num_channels(); ++ch) { + float gain = last_gain_linear; + for (float& sample : float_frame[ch]) { + sample *= gain; + gain += increment; } - gain += increment; } } @@ -73,7 +73,7 @@ GainApplier::GainApplier(bool hard_clip_samples, float initial_gain_factor) last_gain_factor_(initial_gain_factor), current_gain_factor_(initial_gain_factor) {} -void GainApplier::ApplyGain(AudioFrameView signal) { +void GainApplier::ApplyGain(DeinterleavedView signal) { if (static_cast(signal.samples_per_channel()) != samples_per_channel_) { Initialize(signal.samples_per_channel()); } diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/gain_applier.h b/third_party/libwebrtc/modules/audio_processing/agc2/gain_applier.h index ba8a4a4cd27b..82ae82eeef1d 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/gain_applier.h +++ b/third_party/libwebrtc/modules/audio_processing/agc2/gain_applier.h @@ -13,6 +13,7 @@ #include +#include "api/audio/audio_view.h" #include "modules/audio_processing/include/audio_frame_view.h" namespace webrtc { @@ -20,10 +21,15 @@ class GainApplier { public: GainApplier(bool hard_clip_samples, float initial_gain_factor); - void ApplyGain(AudioFrameView signal); + void ApplyGain(DeinterleavedView signal); void SetGainFactor(float gain_factor); float GetGainFactor() const { return current_gain_factor_; } + [[deprecated("Use DeinterleavedView<> version")]] void ApplyGain( + AudioFrameView signal) { + ApplyGain(signal.view()); + } + private: void Initialize(int samples_per_channel); diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/gain_applier_unittest.cc b/third_party/libwebrtc/modules/audio_processing/agc2/gain_applier_unittest.cc index 3296345e62ef..7548faa61b55 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/gain_applier_unittest.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/gain_applier_unittest.cc @@ -15,6 +15,7 @@ #include #include +#include "api/audio/audio_view.h" #include "modules/audio_processing/agc2/vector_float_frame.h" #include "rtc_base/gunit.h" @@ -25,9 +26,9 @@ TEST(AutomaticGainController2GainApplier, InitialGainIsRespected) { VectorFloatFrame fake_audio(1, 1, initial_signal_level); GainApplier gain_applier(true, gain_factor); - gain_applier.ApplyGain(fake_audio.float_frame_view()); - EXPECT_NEAR(fake_audio.float_frame_view().channel(0)[0], - initial_signal_level * gain_factor, 0.1f); + auto fake_view = fake_audio.view(); + gain_applier.ApplyGain(fake_audio.view()); + EXPECT_NEAR(fake_view[0][0], initial_signal_level * gain_factor, 0.1f); } TEST(AutomaticGainController2GainApplier, ClippingIsDone) { @@ -36,9 +37,9 @@ TEST(AutomaticGainController2GainApplier, ClippingIsDone) { VectorFloatFrame fake_audio(1, 1, initial_signal_level); GainApplier gain_applier(true, gain_factor); - gain_applier.ApplyGain(fake_audio.float_frame_view()); - EXPECT_NEAR(fake_audio.float_frame_view().channel(0)[0], - std::numeric_limits::max(), 0.1f); + gain_applier.ApplyGain(fake_audio.view()); + EXPECT_NEAR(fake_audio.view()[0][0], std::numeric_limits::max(), + 0.1f); } TEST(AutomaticGainController2GainApplier, ClippingIsNotDone) { @@ -47,10 +48,10 @@ TEST(AutomaticGainController2GainApplier, ClippingIsNotDone) { VectorFloatFrame fake_audio(1, 1, initial_signal_level); GainApplier gain_applier(false, gain_factor); - gain_applier.ApplyGain(fake_audio.float_frame_view()); + gain_applier.ApplyGain(fake_audio.view()); - EXPECT_NEAR(fake_audio.float_frame_view().channel(0)[0], - initial_signal_level * gain_factor, 0.1f); + EXPECT_NEAR(fake_audio.view()[0][0], initial_signal_level * gain_factor, + 0.1f); } TEST(AutomaticGainController2GainApplier, RampingIsDone) { @@ -64,13 +65,13 @@ TEST(AutomaticGainController2GainApplier, RampingIsDone) { GainApplier gain_applier(false, initial_gain_factor); gain_applier.SetGainFactor(target_gain_factor); - gain_applier.ApplyGain(fake_audio.float_frame_view()); + gain_applier.ApplyGain(fake_audio.view()); // The maximal gain change should be close to that in linear interpolation. for (size_t channel = 0; channel < num_channels; ++channel) { float max_signal_change = 0.f; float last_signal_level = initial_signal_level; - for (const auto sample : fake_audio.float_frame_view().channel(channel)) { + for (const auto sample : fake_audio.view()[channel]) { const float current_change = fabs(last_signal_level - sample); max_signal_change = std::max(max_signal_change, current_change); last_signal_level = sample; @@ -84,10 +85,10 @@ TEST(AutomaticGainController2GainApplier, RampingIsDone) { // Next frame should have the desired level. VectorFloatFrame next_fake_audio_frame(num_channels, samples_per_channel, initial_signal_level); - gain_applier.ApplyGain(next_fake_audio_frame.float_frame_view()); + gain_applier.ApplyGain(next_fake_audio_frame.view()); // The last sample should have the new gain. - EXPECT_NEAR(next_fake_audio_frame.float_frame_view().channel(0)[0], + EXPECT_NEAR(next_fake_audio_frame.view()[0][0], initial_signal_level * target_gain_factor, 0.1f); } } // namespace webrtc diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/limiter.cc b/third_party/libwebrtc/modules/audio_processing/agc2/limiter.cc index 7a1e2202be7f..7a99b94dcebd 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/limiter.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/limiter.cc @@ -46,22 +46,20 @@ void InterpolateFirstSubframe(float last_factor, void ComputePerSampleSubframeFactors( const std::array& scaling_factors, - int samples_per_channel, - rtc::ArrayView per_sample_scaling_factors) { - const int num_subframes = scaling_factors.size() - 1; - const int subframe_size = - rtc::CheckedDivExact(samples_per_channel, num_subframes); + MonoView per_sample_scaling_factors) { + const size_t num_subframes = scaling_factors.size() - 1; + const int subframe_size = rtc::CheckedDivExact( + SamplesPerChannel(per_sample_scaling_factors), num_subframes); // Handle first sub-frame differently in case of attack. const bool is_attack = scaling_factors[0] > scaling_factors[1]; if (is_attack) { InterpolateFirstSubframe( scaling_factors[0], scaling_factors[1], - rtc::ArrayView( - per_sample_scaling_factors.subview(0, subframe_size))); + per_sample_scaling_factors.subview(0, subframe_size)); } - for (int i = is_attack ? 1 : 0; i < num_subframes; ++i) { + for (size_t i = is_attack ? 1 : 0; i < num_subframes; ++i) { const int subframe_start = i * subframe_size; const float scaling_start = scaling_factors[i]; const float scaling_end = scaling_factors[i + 1]; @@ -73,39 +71,36 @@ void ComputePerSampleSubframeFactors( } } -void ScaleSamples(rtc::ArrayView per_sample_scaling_factors, - AudioFrameView signal) { +void ScaleSamples(MonoView per_sample_scaling_factors, + DeinterleavedView signal) { const int samples_per_channel = signal.samples_per_channel(); - RTC_DCHECK_EQ(samples_per_channel, per_sample_scaling_factors.size()); - for (int i = 0; i < signal.num_channels(); ++i) { - rtc::ArrayView channel = signal.channel(i); + RTC_DCHECK_EQ(samples_per_channel, + SamplesPerChannel(per_sample_scaling_factors)); + for (size_t i = 0; i < signal.num_channels(); ++i) { + MonoView channel = signal[i]; for (int j = 0; j < samples_per_channel; ++j) { channel[j] = rtc::SafeClamp(channel[j] * per_sample_scaling_factors[j], kMinFloatS16Value, kMaxFloatS16Value); } } } - -void CheckLimiterSampleRate(int sample_rate_hz) { - // Check that per_sample_scaling_factors_ is large enough. - RTC_DCHECK_LE(sample_rate_hz, - kMaximalNumberOfSamplesPerChannel * 1000 / kFrameDurationMs); -} - } // namespace -Limiter::Limiter(int sample_rate_hz, - ApmDataDumper* apm_data_dumper, +Limiter::Limiter(ApmDataDumper* apm_data_dumper, + size_t samples_per_channel, absl::string_view histogram_name) : interp_gain_curve_(apm_data_dumper, histogram_name), - level_estimator_(sample_rate_hz, apm_data_dumper), + level_estimator_(samples_per_channel, apm_data_dumper), apm_data_dumper_(apm_data_dumper) { - CheckLimiterSampleRate(sample_rate_hz); + RTC_DCHECK_LE(samples_per_channel, kMaximalNumberOfSamplesPerChannel); } Limiter::~Limiter() = default; -void Limiter::Process(AudioFrameView signal) { +void Limiter::Process(DeinterleavedView signal) { + RTC_DCHECK_LE(signal.samples_per_channel(), + kMaximalNumberOfSamplesPerChannel); + const std::array level_estimate = level_estimator_.ComputeLevel(signal); @@ -116,13 +111,9 @@ void Limiter::Process(AudioFrameView signal) { return interp_gain_curve_.LookUpGainToApply(x); }); - const int samples_per_channel = signal.samples_per_channel(); - RTC_DCHECK_LE(samples_per_channel, kMaximalNumberOfSamplesPerChannel); - - auto per_sample_scaling_factors = rtc::ArrayView( - &per_sample_scaling_factors_[0], samples_per_channel); - ComputePerSampleSubframeFactors(scaling_factors_, samples_per_channel, - per_sample_scaling_factors); + MonoView per_sample_scaling_factors(&per_sample_scaling_factors_[0], + signal.samples_per_channel()); + ComputePerSampleSubframeFactors(scaling_factors_, per_sample_scaling_factors); ScaleSamples(per_sample_scaling_factors, signal); last_scaling_factor_ = scaling_factors_.back(); @@ -139,9 +130,9 @@ InterpolatedGainCurve::Stats Limiter::GetGainCurveStats() const { return interp_gain_curve_.get_stats(); } -void Limiter::SetSampleRate(int sample_rate_hz) { - CheckLimiterSampleRate(sample_rate_hz); - level_estimator_.SetSampleRate(sample_rate_hz); +void Limiter::SetSamplesPerChannel(size_t samples_per_channel) { + RTC_DCHECK_LE(samples_per_channel, kMaximalNumberOfSamplesPerChannel); + level_estimator_.SetSamplesPerChannel(samples_per_channel); } void Limiter::Reset() { diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/limiter.h b/third_party/libwebrtc/modules/audio_processing/agc2/limiter.h index d4d556349c1b..55cb1a5b15c6 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/limiter.h +++ b/third_party/libwebrtc/modules/audio_processing/agc2/limiter.h @@ -14,6 +14,7 @@ #include #include "absl/strings/string_view.h" +#include "api/audio/audio_frame.h" #include "modules/audio_processing/agc2/fixed_digital_level_estimator.h" #include "modules/audio_processing/agc2/interpolated_gain_curve.h" #include "modules/audio_processing/include/audio_frame_view.h" @@ -23,23 +24,25 @@ class ApmDataDumper; class Limiter { public: - Limiter(int sample_rate_hz, - ApmDataDumper* apm_data_dumper, + // See `SetSamplesPerChannel()` for valid values for `samples_per_channel`. + Limiter(ApmDataDumper* apm_data_dumper, + size_t samples_per_channel, absl::string_view histogram_name_prefix); + Limiter(const Limiter& limiter) = delete; Limiter& operator=(const Limiter& limiter) = delete; ~Limiter(); // Applies limiter and hard-clipping to `signal`. - void Process(AudioFrameView signal); + void Process(DeinterleavedView signal); + InterpolatedGainCurve::Stats GetGainCurveStats() const; - // Supported rates must be - // * supported by FixedDigitalLevelEstimator - // * below kMaximalNumberOfSamplesPerChannel*1000/kFrameDurationMs - // so that samples_per_channel fit in the - // per_sample_scaling_factors_ array. - void SetSampleRate(int sample_rate_hz); + // Supported values must be + // * Supported by FixedDigitalLevelEstimator + // * Below or equal to kMaximalNumberOfSamplesPerChannel so that samples + // fit in the per_sample_scaling_factors_ array. + void SetSamplesPerChannel(size_t samples_per_channel); // Resets the internal state. void Reset(); diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/limiter_unittest.cc b/third_party/libwebrtc/modules/audio_processing/agc2/limiter_unittest.cc index e662a7fc8904..6c72e729ee59 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/limiter_unittest.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/limiter_unittest.cc @@ -10,6 +10,8 @@ #include "modules/audio_processing/agc2/limiter.h" +#include + #include "common_audio/include/audio_util.h" #include "modules/audio_processing/agc2/agc2_common.h" #include "modules/audio_processing/agc2/agc2_testing_common.h" @@ -20,40 +22,40 @@ namespace webrtc { TEST(Limiter, LimiterShouldConstructAndRun) { - const int sample_rate_hz = 48000; + constexpr size_t kSamplesPerChannel = 480; ApmDataDumper apm_data_dumper(0); - Limiter limiter(sample_rate_hz, &apm_data_dumper, ""); + Limiter limiter(&apm_data_dumper, kSamplesPerChannel, ""); - VectorFloatFrame vectors_with_float_frame(1, sample_rate_hz / 100, - kMaxAbsFloatS16Value); - limiter.Process(vectors_with_float_frame.float_frame_view()); + std::array buffer; + buffer.fill(kMaxAbsFloatS16Value); + limiter.Process( + DeinterleavedView(buffer.data(), kSamplesPerChannel, 1)); } TEST(Limiter, OutputVolumeAboveThreshold) { - const int sample_rate_hz = 48000; + constexpr size_t kSamplesPerChannel = 480; const float input_level = (kMaxAbsFloatS16Value + DbfsToFloatS16(test::kLimiterMaxInputLevelDbFs)) / 2.f; ApmDataDumper apm_data_dumper(0); - Limiter limiter(sample_rate_hz, &apm_data_dumper, ""); + Limiter limiter(&apm_data_dumper, kSamplesPerChannel, ""); + + std::array buffer; // Give the level estimator time to adapt. for (int i = 0; i < 5; ++i) { - VectorFloatFrame vectors_with_float_frame(1, sample_rate_hz / 100, - input_level); - limiter.Process(vectors_with_float_frame.float_frame_view()); + std::fill(buffer.begin(), buffer.end(), input_level); + limiter.Process( + DeinterleavedView(buffer.data(), kSamplesPerChannel, 1)); } - VectorFloatFrame vectors_with_float_frame(1, sample_rate_hz / 100, - input_level); - limiter.Process(vectors_with_float_frame.float_frame_view()); - rtc::ArrayView channel = - vectors_with_float_frame.float_frame_view().channel(0); - - for (const auto& sample : channel) { - EXPECT_LT(0.9f * kMaxAbsFloatS16Value, sample); + std::fill(buffer.begin(), buffer.end(), input_level); + limiter.Process( + DeinterleavedView(buffer.data(), kSamplesPerChannel, 1)); + for (const auto& sample : buffer) { + ASSERT_LT(0.9f * kMaxAbsFloatS16Value, sample); } } diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/noise_level_estimator.cc b/third_party/libwebrtc/modules/audio_processing/agc2/noise_level_estimator.cc index 691513b5094a..c43738aad363 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/noise_level_estimator.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/noise_level_estimator.cc @@ -16,7 +16,7 @@ #include #include -#include "api/array_view.h" +#include "api/audio/audio_view.h" #include "modules/audio_processing/logging/apm_data_dumper.h" #include "rtc_base/checks.h" @@ -25,11 +25,12 @@ namespace { constexpr int kFramesPerSecond = 100; -float FrameEnergy(const AudioFrameView& audio) { +float FrameEnergy(DeinterleavedView audio) { float energy = 0.0f; - for (int k = 0; k < audio.num_channels(); ++k) { + for (size_t k = 0; k < audio.num_channels(); ++k) { + MonoView ch = audio[k]; float channel_energy = - std::accumulate(audio.channel(k).begin(), audio.channel(k).end(), 0.0f, + std::accumulate(ch.begin(), ch.end(), 0.0f, [](float a, float b) -> float { return a + b * b; }); energy = std::max(channel_energy, energy); } @@ -81,7 +82,7 @@ class NoiseFloorEstimator : public NoiseLevelEstimator { NoiseFloorEstimator& operator=(const NoiseFloorEstimator&) = delete; ~NoiseFloorEstimator() = default; - float Analyze(const AudioFrameView& frame) override { + float Analyze(DeinterleavedView frame) override { // Detect sample rate changes. const int sample_rate_hz = static_cast(frame.samples_per_channel() * kFramesPerSecond); diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/noise_level_estimator.h b/third_party/libwebrtc/modules/audio_processing/agc2/noise_level_estimator.h index 9f3b957486f5..8df4cbc93dd8 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/noise_level_estimator.h +++ b/third_party/libwebrtc/modules/audio_processing/agc2/noise_level_estimator.h @@ -13,7 +13,7 @@ #include -#include "modules/audio_processing/include/audio_frame_view.h" +#include "api/audio/audio_view.h" namespace webrtc { class ApmDataDumper; @@ -24,7 +24,7 @@ class NoiseLevelEstimator { virtual ~NoiseLevelEstimator() = default; // Analyzes a 10 ms `frame`, updates the noise level estimation and returns // the value for the latter in dBFS. - virtual float Analyze(const AudioFrameView& frame) = 0; + virtual float Analyze(DeinterleavedView frame) = 0; }; // Creates a noise level estimator based on noise floor detection. diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/noise_level_estimator_unittest.cc b/third_party/libwebrtc/modules/audio_processing/agc2/noise_level_estimator_unittest.cc index 8168c5a22942..9d42bfc0fba5 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/noise_level_estimator_unittest.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/noise_level_estimator_unittest.cc @@ -15,6 +15,7 @@ #include #include +#include "api/audio/audio_view.h" #include "api/function_view.h" #include "modules/audio_processing/agc2/agc2_testing_common.h" #include "modules/audio_processing/agc2/vector_float_frame.h" @@ -36,13 +37,13 @@ float RunEstimator(rtc::FunctionView sample_generator, rtc::CheckedDivExact(sample_rate_hz, kFramesPerSecond); VectorFloatFrame signal(1, samples_per_channel, 0.0f); for (int i = 0; i < kNumIterations; ++i) { - AudioFrameView frame_view = signal.float_frame_view(); + DeinterleavedView frame_view = signal.view(); for (int j = 0; j < samples_per_channel; ++j) { - frame_view.channel(0)[j] = sample_generator(); + frame_view[0][j] = sample_generator(); } estimator.Analyze(frame_view); } - return estimator.Analyze(signal.float_frame_view()); + return estimator.Analyze(signal.view()); } class NoiseEstimatorParametrization : public ::testing::TestWithParam { diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/vad_wrapper.cc b/third_party/libwebrtc/modules/audio_processing/agc2/vad_wrapper.cc index d4fa32ada953..8de8abd315a4 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/vad_wrapper.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/vad_wrapper.cc @@ -13,7 +13,6 @@ #include #include -#include "api/array_view.h" #include "common_audio/resampler/include/push_resampler.h" #include "modules/audio_processing/agc2/agc2_common.h" #include "modules/audio_processing/agc2/rnn_vad/common.h" @@ -36,7 +35,7 @@ class MonoVadImpl : public VoiceActivityDetectorWrapper::MonoVad { int SampleRateHz() const override { return rnn_vad::kSampleRate24kHz; } void Reset() override { rnn_vad_.Reset(); } - float Analyze(rtc::ArrayView frame) override { + float Analyze(MonoView frame) override { RTC_DCHECK_EQ(frame.size(), rnn_vad::kFrameSize10ms24kHz); std::array feature_vector; const bool is_silence = features_extractor_.CheckSilenceComputeFeatures( @@ -73,29 +72,22 @@ VoiceActivityDetectorWrapper::VoiceActivityDetectorWrapper( int sample_rate_hz) : vad_reset_period_frames_( rtc::CheckedDivExact(vad_reset_period_ms, kFrameDurationMs)), + frame_size_(rtc::CheckedDivExact(sample_rate_hz, kNumFramesPerSecond)), time_to_vad_reset_(vad_reset_period_frames_), - vad_(std::move(vad)) { - RTC_DCHECK(vad_); + vad_(std::move(vad)), + resampled_buffer_( + rtc::CheckedDivExact(vad_->SampleRateHz(), kNumFramesPerSecond)), + resampler_(frame_size_, + resampled_buffer_.size(), + /*num_channels=*/1) { RTC_DCHECK_GT(vad_reset_period_frames_, 1); - resampled_buffer_.resize( - rtc::CheckedDivExact(vad_->SampleRateHz(), kNumFramesPerSecond)); - Initialize(sample_rate_hz); + vad_->Reset(); } VoiceActivityDetectorWrapper::~VoiceActivityDetectorWrapper() = default; -void VoiceActivityDetectorWrapper::Initialize(int sample_rate_hz) { - RTC_DCHECK_GT(sample_rate_hz, 0); - frame_size_ = rtc::CheckedDivExact(sample_rate_hz, kNumFramesPerSecond); - int status = - resampler_.InitializeIfNeeded(sample_rate_hz, vad_->SampleRateHz(), - /*num_channels=*/1); - constexpr int kStatusOk = 0; - RTC_DCHECK_EQ(status, kStatusOk); - vad_->Reset(); -} - -float VoiceActivityDetectorWrapper::Analyze(AudioFrameView frame) { +float VoiceActivityDetectorWrapper::Analyze( + DeinterleavedView frame) { // Periodically reset the VAD. time_to_vad_reset_--; if (time_to_vad_reset_ <= 0) { @@ -106,7 +98,7 @@ float VoiceActivityDetectorWrapper::Analyze(AudioFrameView frame) { // Resample the first channel of `frame`. RTC_DCHECK_EQ(frame.samples_per_channel(), frame_size_); MonoView dst(resampled_buffer_.data(), resampled_buffer_.size()); - resampler_.Resample(frame.channel(0), dst); + resampler_.Resample(frame[0], dst); return vad_->Analyze(resampled_buffer_); } diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/vad_wrapper.h b/third_party/libwebrtc/modules/audio_processing/agc2/vad_wrapper.h index 459c47163032..025a48ef224b 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/vad_wrapper.h +++ b/third_party/libwebrtc/modules/audio_processing/agc2/vad_wrapper.h @@ -14,10 +14,9 @@ #include #include -#include "api/array_view.h" +#include "api/audio/audio_view.h" #include "common_audio/resampler/include/push_resampler.h" #include "modules/audio_processing/agc2/cpu_features.h" -#include "modules/audio_processing/include/audio_frame_view.h" namespace webrtc { @@ -37,7 +36,7 @@ class VoiceActivityDetectorWrapper { // Resets the internal state. virtual void Reset() = 0; // Analyzes an audio frame and returns the speech probability. - virtual float Analyze(rtc::ArrayView frame) = 0; + virtual float Analyze(MonoView frame) = 0; }; // Ctor. Uses `cpu_features` to instantiate the default VAD. @@ -60,21 +59,18 @@ class VoiceActivityDetectorWrapper { delete; ~VoiceActivityDetectorWrapper(); - // Initializes the VAD wrapper. - void Initialize(int sample_rate_hz); - // Analyzes the first channel of `frame` and returns the speech probability. // `frame` must be a 10 ms frame with the sample rate specified in the last // `Initialize()` call. - float Analyze(AudioFrameView frame); + float Analyze(DeinterleavedView frame); private: const int vad_reset_period_frames_; - int frame_size_; + const int frame_size_; int time_to_vad_reset_; - PushResampler resampler_; std::unique_ptr vad_; std::vector resampled_buffer_; + PushResampler resampler_; }; } // namespace webrtc diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/vad_wrapper_unittest.cc b/third_party/libwebrtc/modules/audio_processing/agc2/vad_wrapper_unittest.cc index 91efdb566e0b..9d8761d23e0d 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/vad_wrapper_unittest.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/vad_wrapper_unittest.cc @@ -16,8 +16,8 @@ #include #include +#include "api/audio/audio_view.h" #include "modules/audio_processing/agc2/agc2_common.h" -#include "modules/audio_processing/include/audio_frame_view.h" #include "rtc_base/checks.h" #include "rtc_base/gunit.h" #include "rtc_base/numerics/safe_compare.h" @@ -50,7 +50,7 @@ class MockVad : public VoiceActivityDetectorWrapper::MonoVad { TEST(GainController2VoiceActivityDetectorWrapper, CtorAndInitReadSampleRate) { auto vad = std::make_unique(); EXPECT_CALL(*vad, SampleRateHz) - .Times(2) + .Times(1) .WillRepeatedly(Return(kSampleRate8kHz)); EXPECT_CALL(*vad, Reset).Times(AnyNumber()); auto vad_wrapper = std::make_unique( @@ -85,11 +85,9 @@ struct FrameWithView { explicit FrameWithView(int sample_rate_hz) : samples(rtc::CheckedDivExact(sample_rate_hz, kNumFramesPerSecond), 0.0f), - channel0(samples.data()), - view(&channel0, /*num_channels=*/1, samples.size()) {} + view(samples.data(), samples.size(), /*num_channels=*/1) {} std::vector samples; - const float* const channel0; - const AudioFrameView view; + const DeinterleavedView view; }; // Checks that the expected speech probabilities are returned. diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/vector_float_frame.cc b/third_party/libwebrtc/modules/audio_processing/agc2/vector_float_frame.cc index a70d81519609..85dd7feb2159 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/vector_float_frame.cc +++ b/third_party/libwebrtc/modules/audio_processing/agc2/vector_float_frame.cc @@ -12,28 +12,20 @@ namespace webrtc { -namespace { - -std::vector ConstructChannelPointers( - std::vector>* x) { - std::vector channel_ptrs; - for (auto& v : *x) { - channel_ptrs.push_back(v.data()); - } - return channel_ptrs; -} -} // namespace - VectorFloatFrame::VectorFloatFrame(int num_channels, int samples_per_channel, float start_value) - : channels_(num_channels, - std::vector(samples_per_channel, start_value)), - channel_ptrs_(ConstructChannelPointers(&channels_)), - float_frame_view_(channel_ptrs_.data(), - channels_.size(), - samples_per_channel) {} + : channels_(num_channels * samples_per_channel, start_value), + view_(channels_.data(), samples_per_channel, num_channels) {} VectorFloatFrame::~VectorFloatFrame() = default; +AudioFrameView VectorFloatFrame::float_frame_view() { + return AudioFrameView(view_); +} + +AudioFrameView VectorFloatFrame::float_frame_view() const { + return AudioFrameView(view_); +} + } // namespace webrtc diff --git a/third_party/libwebrtc/modules/audio_processing/agc2/vector_float_frame.h b/third_party/libwebrtc/modules/audio_processing/agc2/vector_float_frame.h index b521f346f959..e2a32113139d 100644 --- a/third_party/libwebrtc/modules/audio_processing/agc2/vector_float_frame.h +++ b/third_party/libwebrtc/modules/audio_processing/agc2/vector_float_frame.h @@ -13,6 +13,7 @@ #include +#include "api/audio/audio_view.h" #include "modules/audio_processing/include/audio_frame_view.h" namespace webrtc { @@ -24,17 +25,17 @@ class VectorFloatFrame { VectorFloatFrame(int num_channels, int samples_per_channel, float start_value); - const AudioFrameView& float_frame_view() { return float_frame_view_; } - AudioFrameView float_frame_view() const { - return float_frame_view_; - } - ~VectorFloatFrame(); + AudioFrameView float_frame_view(); + AudioFrameView float_frame_view() const; + + DeinterleavedView view() { return view_; } + DeinterleavedView view() const { return view_; } + private: - std::vector> channels_; - std::vector channel_ptrs_; - AudioFrameView float_frame_view_; + std::vector channels_; + DeinterleavedView view_; }; } // namespace webrtc diff --git a/third_party/libwebrtc/modules/audio_processing/audio_buffer.cc b/third_party/libwebrtc/modules/audio_processing/audio_buffer.cc index 3dbe1fe072ca..c48d444664fc 100644 --- a/third_party/libwebrtc/modules/audio_processing/audio_buffer.cc +++ b/third_party/libwebrtc/modules/audio_processing/audio_buffer.cc @@ -15,7 +15,6 @@ #include #include "common_audio/channel_buffer.h" -#include "common_audio/include/audio_util.h" #include "common_audio/resampler/push_sinc_resampler.h" #include "modules/audio_processing/splitting_filter.h" #include "rtc_base/checks.h" @@ -25,7 +24,6 @@ namespace { constexpr size_t kSamplesPer32kHzChannel = 320; constexpr size_t kSamplesPer48kHzChannel = 480; -constexpr size_t kMaxSamplesPerChannel = AudioBuffer::kMaxSampleRate / 100; size_t NumBandsFromFramesPerChannel(size_t num_frames) { if (num_frames == kSamplesPer32kHzChannel) { @@ -110,9 +108,9 @@ void AudioBuffer::CopyFrom(const float* const* stacked_data, const bool resampling_needed = input_num_frames_ != buffer_num_frames_; if (downmix_needed) { - RTC_DCHECK_GE(kMaxSamplesPerChannel, input_num_frames_); + RTC_DCHECK_GE(kMaxSamplesPerChannel10ms, input_num_frames_); - std::array downmix; + std::array downmix; if (downmix_by_averaging_) { const float kOneByNumChannels = 1.f / input_num_channels_; for (size_t i = 0; i < input_num_frames_; ++i) { @@ -230,7 +228,7 @@ void AudioBuffer::CopyFrom(const int16_t* const interleaved_data, if (num_channels_ == 1) { if (input_num_channels_ == 1) { if (resampling_required) { - std::array float_buffer; + std::array float_buffer; S16ToFloatS16(interleaved, input_num_frames_, float_buffer.data()); input_resamplers_[0]->Resample(float_buffer.data(), input_num_frames_, data_->channels()[0], @@ -239,7 +237,7 @@ void AudioBuffer::CopyFrom(const int16_t* const interleaved_data, S16ToFloatS16(interleaved, input_num_frames_, data_->channels()[0]); } } else { - std::array float_buffer; + std::array float_buffer; float* downmixed_data = resampling_required ? float_buffer.data() : data_->channels()[0]; if (downmix_by_averaging_) { @@ -274,7 +272,7 @@ void AudioBuffer::CopyFrom(const int16_t* const interleaved_data, }; if (resampling_required) { - std::array float_buffer; + std::array float_buffer; for (size_t i = 0; i < num_channels_; ++i) { deinterleave_channel(i, num_channels_, input_num_frames_, interleaved, float_buffer.data()); @@ -302,7 +300,7 @@ void AudioBuffer::CopyTo(const StreamConfig& stream_config, int16_t* interleaved = interleaved_data; if (num_channels_ == 1) { - std::array float_buffer; + std::array float_buffer; if (resampling_required) { output_resamplers_[0]->Resample(data_->channels()[0], buffer_num_frames_, @@ -335,7 +333,7 @@ void AudioBuffer::CopyTo(const StreamConfig& stream_config, if (resampling_required) { for (size_t i = 0; i < num_channels_; ++i) { - std::array float_buffer; + std::array float_buffer; output_resamplers_[i]->Resample(data_->channels()[i], buffer_num_frames_, float_buffer.data(), output_num_frames_); diff --git a/third_party/libwebrtc/modules/audio_processing/audio_buffer.h b/third_party/libwebrtc/modules/audio_processing/audio_buffer.h index fd69f74ed122..9369572af8f2 100644 --- a/third_party/libwebrtc/modules/audio_processing/audio_buffer.h +++ b/third_party/libwebrtc/modules/audio_processing/audio_buffer.h @@ -18,7 +18,9 @@ #include #include "api/audio/audio_processing.h" +#include "api/audio/audio_view.h" #include "common_audio/channel_buffer.h" +#include "common_audio/include/audio_util.h" namespace webrtc { @@ -32,7 +34,8 @@ enum Band { kBand0To8kHz = 0, kBand8To16kHz = 1, kBand16To24kHz = 2 }; class AudioBuffer { public: static const int kSplitBandSize = 160; - static const int kMaxSampleRate = 384000; + // TODO(tommi): Remove this (`AudioBuffer::kMaxSampleRate`) constant. + static const int kMaxSampleRate = webrtc::kMaxSampleRateHz; AudioBuffer(size_t input_rate, size_t input_num_channels, size_t buffer_rate, @@ -56,6 +59,13 @@ class AudioBuffer { // reset at each call to CopyFrom or InterleaveFrom. void set_num_channels(size_t num_channels); + // Returns a DeinterleavedView<> over the channel data. + DeinterleavedView view() { + return DeinterleavedView( + num_channels_ && buffer_num_frames_ ? channels()[0] : nullptr, + buffer_num_frames_, num_channels_); + } + size_t num_channels() const { return num_channels_; } size_t num_frames() const { return buffer_num_frames_; } size_t num_frames_per_band() const { return num_split_frames_; } diff --git a/third_party/libwebrtc/modules/audio_processing/audio_buffer_unittest.cc b/third_party/libwebrtc/modules/audio_processing/audio_buffer_unittest.cc index f3b2ddc68950..ef3479e4f5d3 100644 --- a/third_party/libwebrtc/modules/audio_processing/audio_buffer_unittest.cc +++ b/third_party/libwebrtc/modules/audio_processing/audio_buffer_unittest.cc @@ -12,6 +12,7 @@ #include +#include "api/audio/audio_view.h" #include "test/gtest.h" #include "test/testsupport/rtc_expect_death.h" @@ -90,4 +91,28 @@ TEST(AudioBufferTest, CopyWithResampling) { // Verify that energies match. EXPECT_NEAR(energy_ab1, energy_ab2 * 32000.f / 48000.f, .01f * energy_ab1); } + +TEST(AudioBufferTest, DeinterleavedView) { + AudioBuffer ab(48000, 2, 48000, 2, 48000, 2); + // Fill the buffer with data. + const float pi = std::acos(-1.f); + float* const* channels = ab.channels(); + for (size_t ch = 0; ch < ab.num_channels(); ++ch) { + for (size_t i = 0; i < ab.num_frames(); ++i) { + channels[ch][i] = std::sin(2 * pi * 100.f / 32000.f * i); + } + } + + // Verify that the DeinterleavedView correctly maps to channels. + DeinterleavedView view = ab.view(); + ASSERT_EQ(view.num_channels(), ab.num_channels()); + for (size_t c = 0; c < view.num_channels(); ++c) { + MonoView channel = view[c]; + EXPECT_EQ(SamplesPerChannel(channel), ab.num_frames()); + for (size_t s = 0; s < SamplesPerChannel(channel); ++s) { + ASSERT_EQ(channel[s], channels[c][s]); + } + } +} + } // namespace webrtc diff --git a/third_party/libwebrtc/modules/audio_processing/audio_frame_view_unittest.cc b/third_party/libwebrtc/modules/audio_processing/audio_frame_view_unittest.cc index fd25bc3b0b1a..30f1d8e0c3f4 100644 --- a/third_party/libwebrtc/modules/audio_processing/audio_frame_view_unittest.cc +++ b/third_party/libwebrtc/modules/audio_processing/audio_frame_view_unittest.cc @@ -10,7 +10,11 @@ #include "modules/audio_processing/include/audio_frame_view.h" +#include + +#include "common_audio/channel_buffer.h" #include "modules/audio_processing/audio_buffer.h" +#include "rtc_base/arraysize.h" #include "test/gtest.h" namespace webrtc { @@ -19,8 +23,8 @@ TEST(AudioFrameTest, ConstructFromAudioBuffer) { constexpr int kNumChannels = 2; constexpr float kFloatConstant = 1272.f; constexpr float kIntConstant = 17252; - const webrtc::StreamConfig stream_config(kSampleRateHz, kNumChannels); - webrtc::AudioBuffer buffer( + const StreamConfig stream_config(kSampleRateHz, kNumChannels); + AudioBuffer buffer( stream_config.sample_rate_hz(), stream_config.num_channels(), stream_config.sample_rate_hz(), stream_config.num_channels(), stream_config.sample_rate_hz(), stream_config.num_channels()); @@ -48,4 +52,40 @@ TEST(AudioFrameTest, ConstructFromAudioBuffer) { non_const_float_view.channel(0)[0] = kIntConstant; EXPECT_EQ(buffer.channels()[0][0], kIntConstant); } + +TEST(AudioFrameTest, ConstructFromChannelBuffer) { + ChannelBuffer buffer(480, 2); + AudioFrameView view(buffer.channels(), buffer.num_channels(), + buffer.num_frames()); + EXPECT_EQ(view.num_channels(), 2); + EXPECT_EQ(view.samples_per_channel(), 480); +} + +TEST(AudioFrameTest, ToDeinterleavedView) { + ChannelBuffer buffer(480, 2); + AudioFrameView view(buffer.channels(), buffer.num_channels(), + buffer.num_frames()); + + DeinterleavedView non_const_view = view.view(); + DeinterleavedView const_view = + static_cast&>(view).view(); + + ASSERT_EQ(non_const_view.num_channels(), 2u); + ASSERT_EQ(const_view.num_channels(), 2u); + for (size_t i = 0; i < non_const_view.num_channels(); ++i) { + EXPECT_EQ(non_const_view[i].data(), const_view[i].data()); + EXPECT_EQ(non_const_view[i].data(), view.channel(i).data()); + } +} + +TEST(AudioFrameTest, FromDeinterleavedView) { + std::array buffer; + DeinterleavedView view(buffer.data(), buffer.size() / 2u, 2u); + AudioFrameView frame_view(view); + EXPECT_EQ(static_cast(frame_view.num_channels()), + view.num_channels()); + EXPECT_EQ(frame_view[0], view[0]); + EXPECT_EQ(frame_view[1], view[1]); +} + } // 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 5f6dd59d0269..2fb021c7c3e8 100644 --- a/third_party/libwebrtc/modules/audio_processing/audio_processing_impl.cc +++ b/third_party/libwebrtc/modules/audio_processing/audio_processing_impl.cc @@ -324,230 +324,10 @@ constexpr int kUnspecifiedDataDumpInputVolume = -100; // Throughout webrtc, it's assumed that success is represented by zero. static_assert(AudioProcessing::kNoError == 0, "kNoError must be zero"); -absl::optional -AudioProcessingImpl::GetGainController2ExperimentParams() { - constexpr char kFieldTrialName[] = "WebRTC-Audio-GainController2"; - - if (!field_trial::IsEnabled(kFieldTrialName)) { - return absl::nullopt; - } - - FieldTrialFlag enabled("Enabled", false); - - // Whether the gain control should switch to AGC2. Enabled by default. - FieldTrialParameter switch_to_agc2("switch_to_agc2", true); - - // AGC2 input volume controller configuration. - constexpr InputVolumeController::Config kDefaultInputVolumeControllerConfig; - FieldTrialConstrained min_input_volume( - "min_input_volume", kDefaultInputVolumeControllerConfig.min_input_volume, - 0, 255); - FieldTrialConstrained clipped_level_min( - "clipped_level_min", - kDefaultInputVolumeControllerConfig.clipped_level_min, 0, 255); - FieldTrialConstrained clipped_level_step( - "clipped_level_step", - kDefaultInputVolumeControllerConfig.clipped_level_step, 0, 255); - FieldTrialConstrained clipped_ratio_threshold( - "clipped_ratio_threshold", - kDefaultInputVolumeControllerConfig.clipped_ratio_threshold, 0, 1); - FieldTrialConstrained clipped_wait_frames( - "clipped_wait_frames", - kDefaultInputVolumeControllerConfig.clipped_wait_frames, 0, - absl::nullopt); - FieldTrialParameter enable_clipping_predictor( - "enable_clipping_predictor", - kDefaultInputVolumeControllerConfig.enable_clipping_predictor); - FieldTrialConstrained target_range_max_dbfs( - "target_range_max_dbfs", - kDefaultInputVolumeControllerConfig.target_range_max_dbfs, -90, 30); - FieldTrialConstrained target_range_min_dbfs( - "target_range_min_dbfs", - kDefaultInputVolumeControllerConfig.target_range_min_dbfs, -90, 30); - FieldTrialConstrained update_input_volume_wait_frames( - "update_input_volume_wait_frames", - kDefaultInputVolumeControllerConfig.update_input_volume_wait_frames, 0, - absl::nullopt); - FieldTrialConstrained speech_probability_threshold( - "speech_probability_threshold", - kDefaultInputVolumeControllerConfig.speech_probability_threshold, 0, 1); - FieldTrialConstrained speech_ratio_threshold( - "speech_ratio_threshold", - kDefaultInputVolumeControllerConfig.speech_ratio_threshold, 0, 1); - - // AGC2 adaptive digital controller configuration. - constexpr AudioProcessing::Config::GainController2::AdaptiveDigital - kDefaultAdaptiveDigitalConfig; - FieldTrialConstrained headroom_db( - "headroom_db", kDefaultAdaptiveDigitalConfig.headroom_db, 0, - absl::nullopt); - FieldTrialConstrained max_gain_db( - "max_gain_db", kDefaultAdaptiveDigitalConfig.max_gain_db, 0, - absl::nullopt); - FieldTrialConstrained initial_gain_db( - "initial_gain_db", kDefaultAdaptiveDigitalConfig.initial_gain_db, 0, - absl::nullopt); - FieldTrialConstrained max_gain_change_db_per_second( - "max_gain_change_db_per_second", - kDefaultAdaptiveDigitalConfig.max_gain_change_db_per_second, 0, - absl::nullopt); - FieldTrialConstrained max_output_noise_level_dbfs( - "max_output_noise_level_dbfs", - kDefaultAdaptiveDigitalConfig.max_output_noise_level_dbfs, absl::nullopt, - 0); - - // Transient suppressor. - FieldTrialParameter disallow_transient_suppressor_usage( - "disallow_transient_suppressor_usage", false); - - // Field-trial based override for the input volume controller and adaptive - // digital configs. - ParseFieldTrial( - {&enabled, &switch_to_agc2, &min_input_volume, &clipped_level_min, - &clipped_level_step, &clipped_ratio_threshold, &clipped_wait_frames, - &enable_clipping_predictor, &target_range_max_dbfs, - &target_range_min_dbfs, &update_input_volume_wait_frames, - &speech_probability_threshold, &speech_ratio_threshold, &headroom_db, - &max_gain_db, &initial_gain_db, &max_gain_change_db_per_second, - &max_output_noise_level_dbfs, &disallow_transient_suppressor_usage}, - field_trial::FindFullName(kFieldTrialName)); - // Checked already by `IsEnabled()` before parsing, therefore always true. - RTC_DCHECK(enabled); - - const bool do_not_change_agc_config = !switch_to_agc2.Get(); - if (do_not_change_agc_config && !disallow_transient_suppressor_usage.Get()) { - // Return an unspecifed value since, in this case, both the AGC2 and TS - // configurations won't be adjusted. - return absl::nullopt; - } - using Params = AudioProcessingImpl::GainController2ExperimentParams; - if (do_not_change_agc_config) { - // Return a value that leaves the AGC2 config unchanged and that always - // disables TS. - return Params{.agc2_config = absl::nullopt, - .disallow_transient_suppressor_usage = true}; - } - // Return a value that switches all the gain control to AGC2. - return Params{ - .agc2_config = - Params::Agc2Config{ - .input_volume_controller = - { - .min_input_volume = min_input_volume.Get(), - .clipped_level_min = clipped_level_min.Get(), - .clipped_level_step = clipped_level_step.Get(), - .clipped_ratio_threshold = - static_cast(clipped_ratio_threshold.Get()), - .clipped_wait_frames = clipped_wait_frames.Get(), - .enable_clipping_predictor = - enable_clipping_predictor.Get(), - .target_range_max_dbfs = target_range_max_dbfs.Get(), - .target_range_min_dbfs = target_range_min_dbfs.Get(), - .update_input_volume_wait_frames = - update_input_volume_wait_frames.Get(), - .speech_probability_threshold = static_cast( - speech_probability_threshold.Get()), - .speech_ratio_threshold = - static_cast(speech_ratio_threshold.Get()), - }, - .adaptive_digital_controller = - { - .enabled = false, - .headroom_db = static_cast(headroom_db.Get()), - .max_gain_db = static_cast(max_gain_db.Get()), - .initial_gain_db = - static_cast(initial_gain_db.Get()), - .max_gain_change_db_per_second = static_cast( - max_gain_change_db_per_second.Get()), - .max_output_noise_level_dbfs = - static_cast(max_output_noise_level_dbfs.Get()), - }}, - .disallow_transient_suppressor_usage = - disallow_transient_suppressor_usage.Get()}; -} - -AudioProcessing::Config AudioProcessingImpl::AdjustConfig( - const AudioProcessing::Config& config, - const absl::optional& - experiment_params) { - if (!experiment_params.has_value() || - (!experiment_params->agc2_config.has_value() && - !experiment_params->disallow_transient_suppressor_usage)) { - // When the experiment parameters are unspecified or when the AGC and TS - // configuration are not overridden, return the unmodified configuration. - return config; - } - - AudioProcessing::Config adjusted_config = config; - - // Override the transient suppressor configuration. - if (experiment_params->disallow_transient_suppressor_usage) { - adjusted_config.transient_suppression.enabled = false; - } - - // Override the auto gain control configuration if the AGC1 analog gain - // controller is active and `experiment_params->agc2_config` is specified. - const bool agc1_analog_enabled = - config.gain_controller1.enabled && - (config.gain_controller1.mode == - AudioProcessing::Config::GainController1::kAdaptiveAnalog || - config.gain_controller1.analog_gain_controller.enabled); - if (agc1_analog_enabled && experiment_params->agc2_config.has_value()) { - // Check that the unadjusted AGC config meets the preconditions. - const bool hybrid_agc_config_detected = - config.gain_controller1.enabled && - config.gain_controller1.analog_gain_controller.enabled && - !config.gain_controller1.analog_gain_controller - .enable_digital_adaptive && - config.gain_controller2.enabled && - config.gain_controller2.adaptive_digital.enabled; - const bool full_agc1_config_detected = - config.gain_controller1.enabled && - config.gain_controller1.analog_gain_controller.enabled && - config.gain_controller1.analog_gain_controller - .enable_digital_adaptive && - !config.gain_controller2.enabled; - const bool one_and_only_one_input_volume_controller = - hybrid_agc_config_detected != full_agc1_config_detected; - const bool agc2_input_volume_controller_enabled = - config.gain_controller2.enabled && - config.gain_controller2.input_volume_controller.enabled; - if (!one_and_only_one_input_volume_controller || - agc2_input_volume_controller_enabled) { - RTC_LOG(LS_ERROR) << "Cannot adjust AGC config (precondition failed)"; - if (!one_and_only_one_input_volume_controller) - RTC_LOG(LS_ERROR) - << "One and only one input volume controller must be enabled."; - if (agc2_input_volume_controller_enabled) - RTC_LOG(LS_ERROR) - << "The AGC2 input volume controller must be disabled."; - } else { - adjusted_config.gain_controller1.enabled = false; - adjusted_config.gain_controller1.analog_gain_controller.enabled = false; - - adjusted_config.gain_controller2.enabled = true; - adjusted_config.gain_controller2.input_volume_controller.enabled = true; - adjusted_config.gain_controller2.adaptive_digital = - experiment_params->agc2_config->adaptive_digital_controller; - adjusted_config.gain_controller2.adaptive_digital.enabled = true; - } - } - - return adjusted_config; -} - bool AudioProcessingImpl::UseApmVadSubModule( - const AudioProcessing::Config& config, - const absl::optional& experiment_params) { - // The VAD as an APM sub-module is needed only in one case, that is when TS - // and AGC2 are both enabled and when the AGC2 experiment is running and its - // parameters require to fully switch the gain control to AGC2. - return config.transient_suppression.enabled && - config.gain_controller2.enabled && - (config.gain_controller2.input_volume_controller.enabled || - config.gain_controller2.adaptive_digital.enabled) && - experiment_params.has_value() && - experiment_params->agc2_config.has_value(); + const AudioProcessing::Config& config) { + // Without "WebRTC-Audio-GainController2" always return false. + return false; } AudioProcessingImpl::SubmoduleStates::SubmoduleStates( @@ -667,14 +447,13 @@ AudioProcessingImpl::AudioProcessingImpl( : data_dumper_(new ApmDataDumper(instance_count_.fetch_add(1) + 1)), use_setup_specific_default_aec3_config_( UseSetupSpecificDefaultAec3Congfig()), - gain_controller2_experiment_params_(GetGainController2ExperimentParams()), transient_suppressor_vad_mode_(TransientSuppressor::VadMode::kDefault), capture_runtime_settings_(RuntimeSettingQueueSize()), render_runtime_settings_(RuntimeSettingQueueSize()), capture_runtime_settings_enqueuer_(&capture_runtime_settings_), render_runtime_settings_enqueuer_(&render_runtime_settings_), echo_control_factory_(std::move(echo_control_factory)), - config_(AdjustConfig(config, gain_controller2_experiment_params_)), + config_(config), submodule_states_(!!capture_post_processor, !!render_pre_processor, !!capture_analyzer), @@ -909,52 +688,44 @@ void AudioProcessingImpl::ApplyConfig(const AudioProcessing::Config& config) { MutexLock lock_render(&mutex_render_); MutexLock lock_capture(&mutex_capture_); - const auto adjusted_config = - AdjustConfig(config, gain_controller2_experiment_params_); - RTC_LOG(LS_INFO) << "AudioProcessing::ApplyConfig: " - << adjusted_config.ToString(); + RTC_LOG(LS_INFO) << "AudioProcessing::ApplyConfig: " << config.ToString(); const bool pipeline_config_changed = config_.pipeline.multi_channel_render != - adjusted_config.pipeline.multi_channel_render || + config.pipeline.multi_channel_render || config_.pipeline.multi_channel_capture != - adjusted_config.pipeline.multi_channel_capture || + config.pipeline.multi_channel_capture || config_.pipeline.maximum_internal_processing_rate != - adjusted_config.pipeline.maximum_internal_processing_rate || + config.pipeline.maximum_internal_processing_rate || config_.pipeline.capture_downmix_method != - adjusted_config.pipeline.capture_downmix_method; + config.pipeline.capture_downmix_method; const bool aec_config_changed = - config_.echo_canceller.enabled != - adjusted_config.echo_canceller.enabled || - config_.echo_canceller.mobile_mode != - adjusted_config.echo_canceller.mobile_mode; + config_.echo_canceller.enabled != config.echo_canceller.enabled || + config_.echo_canceller.mobile_mode != config.echo_canceller.mobile_mode; const bool agc1_config_changed = - config_.gain_controller1 != adjusted_config.gain_controller1; + config_.gain_controller1 != config.gain_controller1; const bool agc2_config_changed = - config_.gain_controller2 != adjusted_config.gain_controller2; + config_.gain_controller2 != config.gain_controller2; const bool ns_config_changed = - config_.noise_suppression.enabled != - adjusted_config.noise_suppression.enabled || - config_.noise_suppression.level != - adjusted_config.noise_suppression.level; + config_.noise_suppression.enabled != config.noise_suppression.enabled || + config_.noise_suppression.level != config.noise_suppression.level; const bool ts_config_changed = config_.transient_suppression.enabled != - adjusted_config.transient_suppression.enabled; + config.transient_suppression.enabled; const bool pre_amplifier_config_changed = - config_.pre_amplifier.enabled != adjusted_config.pre_amplifier.enabled || + config_.pre_amplifier.enabled != config.pre_amplifier.enabled || config_.pre_amplifier.fixed_gain_factor != - adjusted_config.pre_amplifier.fixed_gain_factor; + config.pre_amplifier.fixed_gain_factor; const bool gain_adjustment_config_changed = - config_.capture_level_adjustment != - adjusted_config.capture_level_adjustment; + config_.capture_level_adjustment != config.capture_level_adjustment; - config_ = adjusted_config; + config_ = config; if (aec_config_changed) { InitializeEchoController(); @@ -1702,10 +1473,8 @@ int AudioProcessingImpl::ProcessCaptureStreamLocked() { absl::optional voice_probability; if (!!submodules_.voice_activity_detector) { - voice_probability = submodules_.voice_activity_detector->Analyze( - AudioFrameView(capture_buffer->channels(), - capture_buffer->num_channels(), - capture_buffer->num_frames())); + voice_probability = + submodules_.voice_activity_detector->Analyze(capture_buffer->view()); } if (submodules_.transient_suppressor) { @@ -2158,7 +1927,7 @@ void AudioProcessingImpl::InitializeTransientSuppressor() { const TransientSuppressor::VadMode previous_vad_mode = transient_suppressor_vad_mode_; transient_suppressor_vad_mode_ = TransientSuppressor::VadMode::kDefault; - if (UseApmVadSubModule(config_, gain_controller2_experiment_params_)) { + if (UseApmVadSubModule(config_)) { transient_suppressor_vad_mode_ = TransientSuppressor::VadMode::kRnnVad; } const bool vad_mode_changed = @@ -2366,20 +2135,13 @@ void AudioProcessingImpl::InitializeGainController2() { submodules_.gain_controller2.reset(); return; } - // Override the input volume controller configuration if the AGC2 experiment - // is running and its parameters require to fully switch the gain control to + // Input volume controller configuration if the AGC2 is running + // and its parameters require to fully switch the gain control to // AGC2. - const bool input_volume_controller_config_overridden = - gain_controller2_experiment_params_.has_value() && - gain_controller2_experiment_params_->agc2_config.has_value(); const InputVolumeController::Config input_volume_controller_config = - input_volume_controller_config_overridden - ? gain_controller2_experiment_params_->agc2_config - ->input_volume_controller - : InputVolumeController::Config{}; + InputVolumeController::Config{}; // If the APM VAD sub-module is not used, let AGC2 use its internal VAD. - const bool use_internal_vad = - !UseApmVadSubModule(config_, gain_controller2_experiment_params_); + const bool use_internal_vad = !UseApmVadSubModule(config_); submodules_.gain_controller2 = std::make_unique( config_.gain_controller2, input_volume_controller_config, proc_fullband_sample_rate_hz(), num_output_channels(), use_internal_vad); @@ -2388,22 +2150,16 @@ void AudioProcessingImpl::InitializeGainController2() { } void AudioProcessingImpl::InitializeVoiceActivityDetector() { - if (!UseApmVadSubModule(config_, gain_controller2_experiment_params_)) { + if (!UseApmVadSubModule(config_)) { submodules_.voice_activity_detector.reset(); return; } - if (!submodules_.voice_activity_detector) { - RTC_DCHECK(!!submodules_.gain_controller2); - // TODO(bugs.webrtc.org/13663): Cache CPU features in APM and use here. - submodules_.voice_activity_detector = - std::make_unique( - submodules_.gain_controller2->GetCpuFeatures(), - proc_fullband_sample_rate_hz()); - } else { - submodules_.voice_activity_detector->Initialize( - proc_fullband_sample_rate_hz()); - } + // TODO(bugs.webrtc.org/13663): Cache CPU features in APM and use here. + submodules_.voice_activity_detector = + std::make_unique( + submodules_.gain_controller2->GetCpuFeatures(), + proc_fullband_sample_rate_hz()); } void AudioProcessingImpl::InitializeNoiseSuppressor() { 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 51b9ffdaea85..f03e27f5f0e1 100644 --- a/third_party/libwebrtc/modules/audio_processing/audio_processing_impl.h +++ b/third_party/libwebrtc/modules/audio_processing/audio_processing_impl.h @@ -163,9 +163,6 @@ class AudioProcessingImpl : public AudioProcessing { ReinitializeTransientSuppressor); FRIEND_TEST_ALL_PREFIXES(ApmWithSubmodulesExcludedTest, BitexactWithDisabledModules); - FRIEND_TEST_ALL_PREFIXES( - AudioProcessingImplGainController2FieldTrialParametrizedTest, - ConfigAdjustedWhenExperimentEnabled); void set_stream_analog_level_locked(int level) RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_capture_); @@ -194,46 +191,9 @@ class AudioProcessingImpl : public AudioProcessing { static std::atomic instance_count_; const bool use_setup_specific_default_aec3_config_; - // Parameters for the "GainController2" experiment which determines whether - // the following APM sub-modules are created and, if so, their configurations: - // AGC2 (`gain_controller2`), AGC1 (`gain_control`, `agc_manager`) and TS - // (`transient_suppressor`). - // TODO(bugs.webrtc.org/7494): Remove when the "WebRTC-Audio-GainController2" - // field trial is removed. - struct GainController2ExperimentParams { - struct Agc2Config { - InputVolumeController::Config input_volume_controller; - AudioProcessing::Config::GainController2::AdaptiveDigital - adaptive_digital_controller; - }; - // When `agc2_config` is specified, all gain control switches to AGC2 and - // the configuration is overridden. - absl::optional agc2_config; - // When true, the transient suppressor submodule is never created regardless - // of the APM configuration. - bool disallow_transient_suppressor_usage; - }; - // Specified when the "WebRTC-Audio-GainController2" field trial is specified. - // TODO(bugs.webrtc.org/7494): Remove when the "WebRTC-Audio-GainController2" - // field trial is removed. - const absl::optional - gain_controller2_experiment_params_; - - // Parses the "WebRTC-Audio-GainController2" field trial. If disabled, returns - // an unspecified value. - static absl::optional - GetGainController2ExperimentParams(); - - // When `experiment_params` is specified, returns an APM configuration - // modified according to the experiment parameters. Otherwise returns - // `config`. - static AudioProcessing::Config AdjustConfig( - const AudioProcessing::Config& config, - const absl::optional& experiment_params); - // Returns true if the APM VAD sub-module should be used. - static bool UseApmVadSubModule( - const AudioProcessing::Config& config, - const absl::optional& experiment_params); + // Deprecated. + // TODO(bugs.webrtc.org/7494): Remove. + static bool UseApmVadSubModule(const AudioProcessing::Config& config); TransientSuppressor::VadMode transient_suppressor_vad_mode_; diff --git a/third_party/libwebrtc/modules/audio_processing/audio_processing_impl_unittest.cc b/third_party/libwebrtc/modules/audio_processing/audio_processing_impl_unittest.cc index 8bcc94c9c2e7..b6676b668734 100644 --- a/third_party/libwebrtc/modules/audio_processing/audio_processing_impl_unittest.cc +++ b/third_party/libwebrtc/modules/audio_processing/audio_processing_impl_unittest.cc @@ -520,64 +520,6 @@ TEST(AudioProcessingImplTest, apm->ProcessStream(frame.data(), stream_config, stream_config, frame.data()); } -TEST(AudioProcessingImplTest, - ProcessWithAgc2AndTransientSuppressorVadModeDefault) { - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Disabled/"); - auto apm = AudioProcessingBuilder() - .SetConfig({.gain_controller1{.enabled = false}}) - .Create(); - ASSERT_EQ(apm->Initialize(), AudioProcessing::kNoError); - webrtc::AudioProcessing::Config apm_config; - apm_config.gain_controller1.enabled = false; - apm_config.gain_controller2.enabled = true; - apm_config.gain_controller2.adaptive_digital.enabled = true; - apm_config.transient_suppression.enabled = true; - apm->ApplyConfig(apm_config); - constexpr int kSampleRateHz = 48000; - constexpr int kNumChannels = 1; - std::array buffer; - float* channel_pointers[] = {buffer.data()}; - StreamConfig stream_config(/*sample_rate_hz=*/kSampleRateHz, - /*num_channels=*/kNumChannels); - Random random_generator(2341U); - constexpr int kFramesToProcess = 10; - for (int i = 0; i < kFramesToProcess; ++i) { - RandomizeSampleVector(&random_generator, buffer); - ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, - channel_pointers), - kNoErr); - } -} - -TEST(AudioProcessingImplTest, - ProcessWithAgc2AndTransientSuppressorVadModeRnnVad) { - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:true/"); - rtc::scoped_refptr apm = AudioProcessingBuilder().Create(); - ASSERT_EQ(apm->Initialize(), AudioProcessing::kNoError); - webrtc::AudioProcessing::Config apm_config; - apm_config.gain_controller1.enabled = false; - apm_config.gain_controller2.enabled = true; - apm_config.gain_controller2.adaptive_digital.enabled = true; - apm_config.transient_suppression.enabled = true; - apm->ApplyConfig(apm_config); - constexpr int kSampleRateHz = 48000; - constexpr int kNumChannels = 1; - std::array buffer; - float* channel_pointers[] = {buffer.data()}; - StreamConfig stream_config(/*sample_rate_hz=*/kSampleRateHz, - /*num_channels=*/kNumChannels); - Random random_generator(2341U); - constexpr int kFramesToProcess = 10; - for (int i = 0; i < kFramesToProcess; ++i) { - RandomizeSampleVector(&random_generator, buffer); - ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, - channel_pointers), - kNoErr); - } -} - TEST(AudioProcessingImplTest, EchoControllerObservesPlayoutVolumeChange) { // Tests that the echo controller observes an echo path gain change when a // playout volume change is reported. @@ -1060,320 +1002,11 @@ TEST(AudioProcessingImplTest, EXPECT_EQ(ProcessInputVolume(*apm, kOneFrame, /*initial_volume=*/135), 135); } -TEST(AudioProcessingImplTest, - Agc2FieldTrialDoNotSwitchToFullAgc2WhenNoAgcIsActive) { - constexpr AudioProcessing::Config kOriginal{ - .gain_controller1{.enabled = false}, - .gain_controller2{.enabled = false}, - }; - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:true/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); - EXPECT_EQ(adjusted.gain_controller1, kOriginal.gain_controller1); - EXPECT_EQ(adjusted.gain_controller2, kOriginal.gain_controller2); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(kOriginal); - adjusted = apm->GetConfig(); - EXPECT_EQ(adjusted.gain_controller1, kOriginal.gain_controller1); - EXPECT_EQ(adjusted.gain_controller2, kOriginal.gain_controller2); -} - -TEST(AudioProcessingImplTest, - Agc2FieldTrialDoNotSwitchToFullAgc2WithAgc1Agc2InputVolumeControllers) { - constexpr AudioProcessing::Config kOriginal{ - .gain_controller1{.enabled = true, - .analog_gain_controller{.enabled = true}}, - .gain_controller2{.enabled = true, - .input_volume_controller{.enabled = true}}, - }; - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:true/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); - EXPECT_EQ(adjusted.gain_controller1, kOriginal.gain_controller1); - EXPECT_EQ(adjusted.gain_controller2, kOriginal.gain_controller2); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(kOriginal); - adjusted = apm->GetConfig(); - EXPECT_EQ(adjusted.gain_controller1, kOriginal.gain_controller1); - EXPECT_EQ(adjusted.gain_controller2, kOriginal.gain_controller2); -} - -class Agc2FieldTrialParametrizedTest +class Agc2ParametrizedTest : public ::testing::TestWithParam {}; -TEST_P(Agc2FieldTrialParametrizedTest, DoNotChangeConfigIfDisabled) { - const AudioProcessing::Config original = GetParam(); - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Disabled/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(original).Create()->GetConfig(); - EXPECT_EQ(adjusted.gain_controller1, original.gain_controller1); - EXPECT_EQ(adjusted.gain_controller2, original.gain_controller2); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(original); - adjusted = apm->GetConfig(); - EXPECT_EQ(adjusted.gain_controller1, original.gain_controller1); - EXPECT_EQ(adjusted.gain_controller2, original.gain_controller2); -} - -TEST_P(Agc2FieldTrialParametrizedTest, DoNotChangeConfigIfNoOverride) { - const AudioProcessing::Config original = GetParam(); - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled," - "switch_to_agc2:false," - "disallow_transient_suppressor_usage:false/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(original).Create()->GetConfig(); - EXPECT_EQ(adjusted.gain_controller1, original.gain_controller1); - EXPECT_EQ(adjusted.gain_controller2, original.gain_controller2); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(original); - adjusted = apm->GetConfig(); - EXPECT_EQ(adjusted.gain_controller1, original.gain_controller1); - EXPECT_EQ(adjusted.gain_controller2, original.gain_controller2); -} - -TEST_P(Agc2FieldTrialParametrizedTest, DoNotSwitchToFullAgc2) { - const AudioProcessing::Config original = GetParam(); - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:false/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(original).Create()->GetConfig(); - EXPECT_EQ(adjusted.gain_controller1, original.gain_controller1); - EXPECT_EQ(adjusted.gain_controller2, original.gain_controller2); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(original); - adjusted = apm->GetConfig(); - EXPECT_EQ(adjusted.gain_controller1, original.gain_controller1); - EXPECT_EQ(adjusted.gain_controller2, original.gain_controller2); -} - -TEST_P(Agc2FieldTrialParametrizedTest, SwitchToFullAgc2) { - const AudioProcessing::Config original = GetParam(); - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:true/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(original).Create()->GetConfig(); - EXPECT_FALSE(adjusted.gain_controller1.enabled); - EXPECT_TRUE(adjusted.gain_controller2.enabled); - EXPECT_TRUE(adjusted.gain_controller2.input_volume_controller.enabled); - EXPECT_TRUE(adjusted.gain_controller2.adaptive_digital.enabled); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(original); - adjusted = apm->GetConfig(); - EXPECT_FALSE(adjusted.gain_controller1.enabled); - EXPECT_TRUE(adjusted.gain_controller2.enabled); - EXPECT_TRUE(adjusted.gain_controller2.input_volume_controller.enabled); - EXPECT_TRUE(adjusted.gain_controller2.adaptive_digital.enabled); -} - -TEST_P(Agc2FieldTrialParametrizedTest, - SwitchToFullAgc2AndOverrideInputVolumeControllerParameters) { - const AudioProcessing::Config original = GetParam(); - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:true," - "min_input_volume:123," - "clipped_level_min:20," - "clipped_level_step:30," - "clipped_ratio_threshold:0.4," - "clipped_wait_frames:50," - "enable_clipping_predictor:true," - "target_range_max_dbfs:-6," - "target_range_min_dbfs:-70," - "update_input_volume_wait_frames:80," - "speech_probability_threshold:0.9," - "speech_ratio_threshold:1.0/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(original).Create()->GetConfig(); - EXPECT_FALSE(adjusted.gain_controller1.enabled); - EXPECT_TRUE(adjusted.gain_controller2.enabled); - EXPECT_TRUE(adjusted.gain_controller2.input_volume_controller.enabled); - EXPECT_TRUE(adjusted.gain_controller2.adaptive_digital.enabled); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(original); - adjusted = apm->GetConfig(); - EXPECT_FALSE(adjusted.gain_controller1.enabled); - EXPECT_TRUE(adjusted.gain_controller2.enabled); - EXPECT_TRUE(adjusted.gain_controller2.input_volume_controller.enabled); - EXPECT_TRUE(adjusted.gain_controller2.adaptive_digital.enabled); -} - -TEST_P(Agc2FieldTrialParametrizedTest, - SwitchToFullAgc2AndOverrideAdaptiveDigitalControllerParameters) { - const AudioProcessing::Config original = GetParam(); - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled,switch_to_agc2:true," - "headroom_db:10," - "max_gain_db:20," - "initial_gain_db:7," - "max_gain_change_db_per_second:5," - "max_output_noise_level_dbfs:-40/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(original).Create()->GetConfig(); - EXPECT_FALSE(adjusted.gain_controller1.enabled); - EXPECT_TRUE(adjusted.gain_controller2.enabled); - EXPECT_TRUE(adjusted.gain_controller2.input_volume_controller.enabled); - EXPECT_TRUE(adjusted.gain_controller2.adaptive_digital.enabled); - ASSERT_NE(adjusted.gain_controller2.adaptive_digital, - original.gain_controller2.adaptive_digital); - EXPECT_EQ(adjusted.gain_controller2.adaptive_digital.headroom_db, 10); - EXPECT_EQ(adjusted.gain_controller2.adaptive_digital.max_gain_db, 20); - EXPECT_EQ(adjusted.gain_controller2.adaptive_digital.initial_gain_db, 7); - EXPECT_EQ( - adjusted.gain_controller2.adaptive_digital.max_gain_change_db_per_second, - 5); - EXPECT_EQ( - adjusted.gain_controller2.adaptive_digital.max_output_noise_level_dbfs, - -40); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(original); - adjusted = apm->GetConfig(); - EXPECT_FALSE(adjusted.gain_controller1.enabled); - EXPECT_TRUE(adjusted.gain_controller2.enabled); - EXPECT_TRUE(adjusted.gain_controller2.input_volume_controller.enabled); - EXPECT_TRUE(adjusted.gain_controller2.adaptive_digital.enabled); - ASSERT_NE(adjusted.gain_controller2.adaptive_digital, - original.gain_controller2.adaptive_digital); - EXPECT_EQ(adjusted.gain_controller2.adaptive_digital.headroom_db, 10); - EXPECT_EQ(adjusted.gain_controller2.adaptive_digital.max_gain_db, 20); - EXPECT_EQ(adjusted.gain_controller2.adaptive_digital.initial_gain_db, 7); - EXPECT_EQ( - adjusted.gain_controller2.adaptive_digital.max_gain_change_db_per_second, - 5); - EXPECT_EQ( - adjusted.gain_controller2.adaptive_digital.max_output_noise_level_dbfs, - -40); -} - -TEST_P(Agc2FieldTrialParametrizedTest, ProcessSucceedsWithTs) { - AudioProcessing::Config config = GetParam(); - if (!config.transient_suppression.enabled) { - GTEST_SKIP() << "TS is disabled, skip."; - } - - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Disabled/"); - auto apm = AudioProcessingBuilder().SetConfig(config).Create(); - - constexpr int kSampleRateHz = 48000; - constexpr int kNumChannels = 1; - std::array buffer; - float* channel_pointers[] = {buffer.data()}; - StreamConfig stream_config(kSampleRateHz, kNumChannels); - Random random_generator(2341U); - constexpr int kFramesToProcess = 10; - int volume = 100; - for (int i = 0; i < kFramesToProcess; ++i) { - SCOPED_TRACE(i); - RandomizeSampleVector(&random_generator, buffer); - apm->set_stream_analog_level(volume); - ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, - channel_pointers), - kNoErr); - volume = apm->recommended_stream_analog_level(); - } -} - -TEST_P(Agc2FieldTrialParametrizedTest, ProcessSucceedsWithoutTs) { - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled," - "switch_to_agc2:false," - "disallow_transient_suppressor_usage:true/"); +TEST_P(Agc2ParametrizedTest, ProcessSucceedsWhenOneAgcEnabled) { auto apm = AudioProcessingBuilder().SetConfig(GetParam()).Create(); - - constexpr int kSampleRateHz = 48000; - constexpr int kNumChannels = 1; - std::array buffer; - float* channel_pointers[] = {buffer.data()}; - StreamConfig stream_config(kSampleRateHz, kNumChannels); - Random random_generator(2341U); - constexpr int kFramesToProcess = 10; - int volume = 100; - for (int i = 0; i < kFramesToProcess; ++i) { - SCOPED_TRACE(i); - RandomizeSampleVector(&random_generator, buffer); - apm->set_stream_analog_level(volume); - ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, - channel_pointers), - kNoErr); - volume = apm->recommended_stream_analog_level(); - } -} - -TEST_P(Agc2FieldTrialParametrizedTest, - ProcessSucceedsWhenSwitchToFullAgc2WithTs) { - AudioProcessing::Config config = GetParam(); - if (!config.transient_suppression.enabled) { - GTEST_SKIP() << "TS is disabled, skip."; - } - - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled," - "switch_to_agc2:true," - "disallow_transient_suppressor_usage:false/"); - auto apm = AudioProcessingBuilder().SetConfig(config).Create(); - - constexpr int kSampleRateHz = 48000; - constexpr int kNumChannels = 1; - std::array buffer; - float* channel_pointers[] = {buffer.data()}; - StreamConfig stream_config(kSampleRateHz, kNumChannels); - Random random_generator(2341U); - constexpr int kFramesToProcess = 10; - int volume = 100; - for (int i = 0; i < kFramesToProcess; ++i) { - SCOPED_TRACE(i); - RandomizeSampleVector(&random_generator, buffer); - apm->set_stream_analog_level(volume); - ASSERT_EQ(apm->ProcessStream(channel_pointers, stream_config, stream_config, - channel_pointers), - kNoErr); - volume = apm->recommended_stream_analog_level(); - } -} - -TEST_P(Agc2FieldTrialParametrizedTest, - ProcessSucceedsWhenSwitchToFullAgc2WithoutTs) { - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled," - "switch_to_agc2:true," - "disallow_transient_suppressor_usage:true/"); - auto apm = AudioProcessingBuilder().SetConfig(GetParam()).Create(); - constexpr int kSampleRateHz = 48000; constexpr int kNumChannels = 1; std::array buffer; @@ -1395,7 +1028,7 @@ TEST_P(Agc2FieldTrialParametrizedTest, INSTANTIATE_TEST_SUITE_P( AudioProcessingImplTest, - Agc2FieldTrialParametrizedTest, + Agc2ParametrizedTest, ::testing::Values( // Full AGC1, TS disabled. AudioProcessing::Config{ @@ -1430,6 +1063,26 @@ INSTANTIATE_TEST_SUITE_P( .analog_gain_controller = {.enabled = true, .enable_digital_adaptive = false}}, .gain_controller2 = {.enabled = true, + .adaptive_digital = {.enabled = true}}}, + // Full AGC2, TS disabled. + AudioProcessing::Config{ + .transient_suppression = {.enabled = false}, + .gain_controller1 = + {.enabled = false, + .analog_gain_controller = {.enabled = false, + .enable_digital_adaptive = false}}, + .gain_controller2 = {.enabled = true, + .input_volume_controller = {.enabled = true}, + .adaptive_digital = {.enabled = true}}}, + // Full AGC2, TS enabled. + AudioProcessing::Config{ + .transient_suppression = {.enabled = true}, + .gain_controller1 = + {.enabled = false, + .analog_gain_controller = {.enabled = false, + .enable_digital_adaptive = false}}, + .gain_controller2 = {.enabled = true, + .input_volume_controller = {.enabled = true}, .adaptive_digital = {.enabled = true}}})); TEST(AudioProcessingImplTest, CanDisableTransientSuppressor) { @@ -1464,100 +1117,4 @@ TEST(AudioProcessingImplTest, CanEnableTs) { EXPECT_TRUE(adjusted.transient_suppression.enabled); } -TEST(AudioProcessingImplTest, CanDisableTsWithAgc2FieldTrialDisabled) { - constexpr AudioProcessing::Config kOriginal = { - .transient_suppression = {.enabled = false}}; - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Disabled/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); - EXPECT_FALSE(adjusted.transient_suppression.enabled); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(kOriginal); - adjusted = apm->GetConfig(); - EXPECT_FALSE(apm->GetConfig().transient_suppression.enabled); -} - -TEST(AudioProcessingImplTest, CanEnableTsWithAgc2FieldTrialDisabled) { - constexpr AudioProcessing::Config kOriginal = { - .transient_suppression = {.enabled = true}}; - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Disabled/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); - EXPECT_TRUE(adjusted.transient_suppression.enabled); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(kOriginal); - adjusted = apm->GetConfig(); - EXPECT_TRUE(adjusted.transient_suppression.enabled); -} - -TEST(AudioProcessingImplTest, - CanDisableTsWithAgc2FieldTrialEnabledAndUsageAllowed) { - constexpr AudioProcessing::Config kOriginal = { - .transient_suppression = {.enabled = false}}; - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled," - "disallow_transient_suppressor_usage:false/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); - EXPECT_FALSE(adjusted.transient_suppression.enabled); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(kOriginal); - adjusted = apm->GetConfig(); - EXPECT_FALSE(adjusted.transient_suppression.enabled); -} - -TEST(AudioProcessingImplTest, - CanEnableTsWithAgc2FieldTrialEnabledAndUsageAllowed) { - constexpr AudioProcessing::Config kOriginal = { - .transient_suppression = {.enabled = true}}; - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled," - "disallow_transient_suppressor_usage:false/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); - EXPECT_TRUE(adjusted.transient_suppression.enabled); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(kOriginal); - adjusted = apm->GetConfig(); - EXPECT_TRUE(adjusted.transient_suppression.enabled); -} - -TEST(AudioProcessingImplTest, - CannotEnableTsWithAgc2FieldTrialEnabledAndUsageDisallowed) { - constexpr AudioProcessing::Config kOriginal = { - .transient_suppression = {.enabled = true}}; - webrtc::test::ScopedFieldTrials field_trials( - "WebRTC-Audio-GainController2/Enabled," - "disallow_transient_suppressor_usage:true/"); - - // Test config application via `AudioProcessing` ctor. - auto adjusted = - AudioProcessingBuilder().SetConfig(kOriginal).Create()->GetConfig(); - EXPECT_FALSE(adjusted.transient_suppression.enabled); - - // Test config application via `AudioProcessing::ApplyConfig()`. - auto apm = AudioProcessingBuilder().Create(); - apm->ApplyConfig(kOriginal); - adjusted = apm->GetConfig(); - EXPECT_FALSE(apm->GetConfig().transient_suppression.enabled); -} - } // namespace webrtc 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 adb8891dfadb..9dd4d376cd4e 100644 --- a/third_party/libwebrtc/modules/audio_processing/audio_processing_unittest.cc +++ b/third_party/libwebrtc/modules/audio_processing/audio_processing_unittest.cc @@ -2173,8 +2173,8 @@ TEST_P(AudioProcessingTest, Formats) { // don't match. std::unique_ptr cmp_data(new float[ref_length]); - PushResampler resampler; - resampler.InitializeIfNeeded(out_rate, ref_rate, out_num); + PushResampler resampler(out_samples_per_channel, + ref_samples_per_channel, out_num); // Compute the resampling delay of the output relative to the reference, // to find the region over which we should search for the best SNR. diff --git a/third_party/libwebrtc/modules/audio_processing/gain_controller2.cc b/third_party/libwebrtc/modules/audio_processing/gain_controller2.cc index dd3521268ddf..9bfae527d761 100644 --- a/third_party/libwebrtc/modules/audio_processing/gain_controller2.cc +++ b/third_party/libwebrtc/modules/audio_processing/gain_controller2.cc @@ -13,6 +13,7 @@ #include #include +#include "api/audio/audio_frame.h" #include "common_audio/include/audio_util.h" #include "modules/audio_processing/agc2/agc2_common.h" #include "modules/audio_processing/agc2/cpu_features.h" @@ -63,11 +64,11 @@ struct SpeechLevel { }; // Computes the audio levels for the first channel in `frame`. -AudioLevels ComputeAudioLevels(AudioFrameView frame, +AudioLevels ComputeAudioLevels(DeinterleavedView frame, ApmDataDumper& data_dumper) { float peak = 0.0f; float rms = 0.0f; - for (const auto& x : frame.channel(0)) { + for (const auto& x : frame[0]) { peak = std::max(std::fabs(x), peak); rms += x * x; } @@ -94,7 +95,9 @@ GainController2::GainController2( fixed_gain_applier_( /*hard_clip_samples=*/false, /*initial_gain_factor=*/DbToRatio(config.fixed_digital.gain_db)), - limiter_(sample_rate_hz, &data_dumper_, /*histogram_name_prefix=*/"Agc2"), + limiter_(&data_dumper_, + SampleRateToDefaultChannelSize(sample_rate_hz), + /*histogram_name_prefix=*/"Agc2"), calls_since_last_limiter_log_(0) { RTC_DCHECK(Validate(config)); data_dumper_.InitiateNewSetOfRecordings(); @@ -179,8 +182,8 @@ void GainController2::Process(absl::optional speech_probability, saturation_protector_->Reset(); } - AudioFrameView float_frame(audio->channels(), audio->num_channels(), - audio->num_frames()); + DeinterleavedView float_frame = audio->view(); + // Compute speech probability. if (vad_) { // When the VAD component runs, `speech_probability` should not be specified diff --git a/third_party/libwebrtc/modules/audio_processing/gain_controller2_unittest.cc b/third_party/libwebrtc/modules/audio_processing/gain_controller2_unittest.cc index 71642dc38c07..bccab8d06061 100644 --- a/third_party/libwebrtc/modules/audio_processing/gain_controller2_unittest.cc +++ b/third_party/libwebrtc/modules/audio_processing/gain_controller2_unittest.cc @@ -16,7 +16,6 @@ #include #include -#include "api/array_view.h" #include "modules/audio_processing/agc2/agc2_testing_common.h" #include "modules/audio_processing/audio_buffer.h" #include "modules/audio_processing/test/audio_buffer_tools.h" @@ -596,9 +595,7 @@ TEST(GainController2, agc2_reference.Process(absl::nullopt, /*input_volume_changed=*/false, &audio_buffer_reference); test::CopyVectorToAudioBuffer(stream_config, frame, &audio_buffer); - float speech_probability = vad.Analyze(AudioFrameView( - audio_buffer.channels(), audio_buffer.num_channels(), - audio_buffer.num_frames())); + float speech_probability = vad.Analyze(audio_buffer.view()); agc2.Process(speech_probability, /*input_volume_changed=*/false, &audio_buffer); // Check the output buffer. diff --git a/third_party/libwebrtc/modules/audio_processing/include/audio_frame_view.h b/third_party/libwebrtc/modules/audio_processing/include/audio_frame_view.h index 1c717e923970..27e2009067d9 100644 --- a/third_party/libwebrtc/modules/audio_processing/include/audio_frame_view.h +++ b/third_party/libwebrtc/modules/audio_processing/include/audio_frame_view.h @@ -22,46 +22,44 @@ class AudioFrameView { // `num_channels` and `channel_size` describe the T** // `audio_samples`. `audio_samples` is assumed to point to a // two-dimensional |num_channels * channel_size| array of floats. + // + // Note: The implementation now only requires the first channel pointer. + // The previous implementation retained a pointer to externally owned array + // of channel pointers, but since the channel size and count are provided + // and the array is assumed to be a single two-dimensional array, the other + // channel pointers can be calculated based on that (which is what the class + // now uses `DeinterleavedView<>` internally for). AudioFrameView(T* const* audio_samples, int num_channels, int channel_size) - : audio_samples_(audio_samples), - num_channels_(num_channels), - channel_size_(channel_size) { - RTC_DCHECK_GE(num_channels_, 0); - RTC_DCHECK_GE(channel_size_, 0); + : view_(num_channels && channel_size ? audio_samples[0] : nullptr, + channel_size, + num_channels) { + RTC_DCHECK_GE(view_.num_channels(), 0); + RTC_DCHECK_GE(view_.samples_per_channel(), 0); } - // Implicit cast to allow converting Frame to - // Frame. + // Implicit cast to allow converting AudioFrameView to + // AudioFrameView. template - AudioFrameView(AudioFrameView other) - : audio_samples_(other.data()), - num_channels_(other.num_channels()), - channel_size_(other.samples_per_channel()) {} + AudioFrameView(AudioFrameView other) : view_(other.view()) {} + + // Allow constructing AudioFrameView from a DeinterleavedView. + template + explicit AudioFrameView(DeinterleavedView view) : view_(view) {} AudioFrameView() = delete; - int num_channels() const { return num_channels_; } + int num_channels() const { return view_.num_channels(); } + int samples_per_channel() const { return view_.samples_per_channel(); } + MonoView channel(int idx) { return view_[idx]; } + MonoView channel(int idx) const { return view_[idx]; } + MonoView operator[](int idx) { return view_[idx]; } + MonoView operator[](int idx) const { return view_[idx]; } - int samples_per_channel() const { return channel_size_; } - - MonoView channel(int idx) { - RTC_DCHECK_LE(0, idx); - RTC_DCHECK_LE(idx, num_channels_); - return MonoView(audio_samples_[idx], channel_size_); - } - - MonoView channel(int idx) const { - RTC_DCHECK_LE(0, idx); - RTC_DCHECK_LE(idx, num_channels_); - return MonoView(audio_samples_[idx], channel_size_); - } - - T* const* data() { return audio_samples_; } + DeinterleavedView view() { return view_; } + DeinterleavedView view() const { return view_; } private: - T* const* audio_samples_; - int num_channels_; - int channel_size_; + DeinterleavedView view_; }; } // namespace webrtc 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 a47e4b5be223..9bded526bd15 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 @@ -515,6 +515,10 @@ void AudioProcessingSimulator::ConfigureAudioProcessor() { apm_config.gain_controller2.adaptive_digital.enabled = *settings_.agc2_use_adaptive_gain; } + if (settings_.agc2_use_input_volume_controller) { + apm_config.gain_controller2.input_volume_controller.enabled = + *settings_.agc2_use_input_volume_controller; + } } if (settings_.use_pre_amplifier) { apm_config.pre_amplifier.enabled = *settings_.use_pre_amplifier; diff --git a/third_party/libwebrtc/modules/audio_processing/test/audio_processing_simulator.h b/third_party/libwebrtc/modules/audio_processing/test/audio_processing_simulator.h index 82e22cca4ffa..082ccb1e0bb5 100644 --- a/third_party/libwebrtc/modules/audio_processing/test/audio_processing_simulator.h +++ b/third_party/libwebrtc/modules/audio_processing/test/audio_processing_simulator.h @@ -113,6 +113,7 @@ struct SimulationSettings { absl::optional agc_compression_gain; absl::optional agc2_use_adaptive_gain; absl::optional agc2_fixed_gain_db; + absl::optional agc2_use_input_volume_controller; absl::optional pre_amplifier_gain_factor; absl::optional pre_gain_factor; absl::optional post_gain_factor; diff --git a/third_party/libwebrtc/modules/audio_processing/test/audioproc_float_impl.cc b/third_party/libwebrtc/modules/audio_processing/test/audioproc_float_impl.cc index 980b80a81ecd..5d3c75a495ba 100644 --- a/third_party/libwebrtc/modules/audio_processing/test/audioproc_float_impl.cc +++ b/third_party/libwebrtc/modules/audio_processing/test/audioproc_float_impl.cc @@ -150,6 +150,10 @@ ABSL_FLAG(float, agc2_fixed_gain_db, kParameterNotSpecifiedValue, "AGC2 fixed gain (dB) to apply"); +ABSL_FLAG(int, + agc2_enable_input_volume_controller, + kParameterNotSpecifiedValue, + "Activate (1) or deactivate (0) the AGC2 input volume adjustments"); ABSL_FLAG(float, pre_amplifier_gain_factor, kParameterNotSpecifiedValue, @@ -429,9 +433,10 @@ SimulationSettings CreateSettings() { &settings.agc_compression_gain); SetSettingIfFlagSet(absl::GetFlag(FLAGS_agc2_enable_adaptive_gain), &settings.agc2_use_adaptive_gain); - SetSettingIfSpecified(absl::GetFlag(FLAGS_agc2_fixed_gain_db), &settings.agc2_fixed_gain_db); + SetSettingIfFlagSet(absl::GetFlag(FLAGS_agc2_enable_input_volume_controller), + &settings.agc2_use_input_volume_controller); SetSettingIfSpecified(absl::GetFlag(FLAGS_pre_amplifier_gain_factor), &settings.pre_amplifier_gain_factor); SetSettingIfSpecified(absl::GetFlag(FLAGS_pre_gain_factor), diff --git a/third_party/libwebrtc/modules/audio_processing/test/test_utils.h b/third_party/libwebrtc/modules/audio_processing/test/test_utils.h index 04e980fb64f1..cc36f9a79973 100644 --- a/third_party/libwebrtc/modules/audio_processing/test/test_utils.h +++ b/third_party/libwebrtc/modules/audio_processing/test/test_utils.h @@ -16,7 +16,6 @@ #include #include #include -#include // no-presubmit-check TODO(webrtc:8982) #include #include @@ -149,22 +148,6 @@ float ComputeSNR(const T* ref, const T* test, size_t length, float* variance) { return snr; } -// Returns a vector parsed from whitespace delimited values in to_parse, -// or an empty vector if the string could not be parsed. -template -std::vector ParseList(absl::string_view to_parse) { - std::vector values; - - std::istringstream str( // no-presubmit-check TODO(webrtc:8982) - std::string{to_parse}); - std::copy( - std::istream_iterator(str), // no-presubmit-check TODO(webrtc:8982) - std::istream_iterator(), // no-presubmit-check TODO(webrtc:8982) - std::back_inserter(values)); - - return values; -} - } // namespace webrtc #endif // MODULES_AUDIO_PROCESSING_TEST_TEST_UTILS_H_ diff --git a/third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.cc b/third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.cc index 182e79358efa..ddc4aa2a5f7f 100644 --- a/third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.cc +++ b/third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.cc @@ -517,16 +517,7 @@ NetworkControlUpdate GoogCcNetworkController::OnTransportPacketsFeedback( if (network_estimator_) { network_estimator_->OnTransportPacketsFeedback(report); - auto prev_estimate = estimate_; - estimate_ = network_estimator_->GetCurrentEstimate(); - // TODO(srte): Make OnTransportPacketsFeedback signal whether the state - // changed to avoid the need for this check. - if (estimate_ && (!prev_estimate || estimate_->last_feed_time != - prev_estimate->last_feed_time)) { - env_.event_log().Log(std::make_unique( - estimate_->link_capacity_lower, estimate_->link_capacity_upper)); - probe_controller_->SetNetworkStateEstimate(*estimate_); - } + SetNetworkStateEstimate(network_estimator_->GetCurrentEstimate()); } absl::optional probe_bitrate = probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate(); @@ -603,10 +594,24 @@ NetworkControlUpdate GoogCcNetworkController::OnTransportPacketsFeedback( NetworkControlUpdate GoogCcNetworkController::OnNetworkStateEstimate( NetworkStateEstimate msg) { - estimate_ = msg; + if (!network_estimator_) { + SetNetworkStateEstimate(msg); + } return NetworkControlUpdate(); } +void GoogCcNetworkController::SetNetworkStateEstimate( + absl::optional estimate) { + auto prev_estimate = estimate_; + estimate_ = estimate; + if (estimate_ && (!prev_estimate || + estimate_->update_time != prev_estimate->update_time)) { + env_.event_log().Log(std::make_unique( + estimate_->link_capacity_lower, estimate_->link_capacity_upper)); + probe_controller_->SetNetworkStateEstimate(*estimate_); + } +} + NetworkControlUpdate GoogCcNetworkController::GetNetworkState( Timestamp at_time) const { NetworkControlUpdate update; diff --git a/third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.h b/third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.h index f2210c953fbe..46ef23f43209 100644 --- a/third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.h +++ b/third_party/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.h @@ -83,6 +83,7 @@ class GoogCcNetworkController : public NetworkControllerInterface { Timestamp at_time); void UpdateCongestionWindowSize(); PacerConfig GetPacingRates(Timestamp at_time) const; + void SetNetworkStateEstimate(absl::optional estimate); const Environment env_; const bool packet_feedback_only_; diff --git a/third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc b/third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc index 2f47ee0f1896..63d3ed3e42cd 100644 --- a/third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc +++ b/third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc @@ -52,37 +52,6 @@ bool IsValid(Timestamp timestamp) { double ToKiloBytes(DataSize datasize) { return datasize.bytes() / 1000.0; } -struct PacketResultsSummary { - int num_packets = 0; - int num_lost_packets = 0; - DataSize total_size = DataSize::Zero(); - DataSize lost_size = DataSize::Zero(); - Timestamp first_send_time = Timestamp::PlusInfinity(); - Timestamp last_send_time = Timestamp::MinusInfinity(); -}; - -// Returns a `PacketResultsSummary` where `first_send_time` is `PlusInfinity, -// and `last_send_time` is `MinusInfinity`, if `packet_results` is empty. -PacketResultsSummary GetPacketResultsSummary( - rtc::ArrayView packet_results) { - PacketResultsSummary packet_results_summary; - - packet_results_summary.num_packets = packet_results.size(); - for (const PacketResult& packet : packet_results) { - if (!packet.IsReceived()) { - packet_results_summary.num_lost_packets++; - packet_results_summary.lost_size += packet.sent_packet.size; - } - packet_results_summary.total_size += packet.sent_packet.size; - packet_results_summary.first_send_time = std::min( - packet_results_summary.first_send_time, packet.sent_packet.send_time); - packet_results_summary.last_send_time = std::max( - packet_results_summary.last_send_time, packet.sent_packet.send_time); - } - - return packet_results_summary; -} - double GetLossProbability(double inherent_loss, DataRate loss_limited_bandwidth, DataRate sending_rate) { @@ -173,7 +142,6 @@ LossBasedBweV2::Result LossBasedBweV2::GetLossBasedResult() const { : DataRate::PlusInfinity(), .state = LossBasedState::kDelayBasedEstimate}; } - return loss_based_result_; } @@ -545,69 +513,68 @@ absl::optional LossBasedBweV2::CreateConfig( key_value_config->Lookup("WebRTC-Bwe-LossBasedBweV2")); } - absl::optional config; if (!enabled.Get()) { - return config; + return absl::nullopt; } - config.emplace(Config()); - config->bandwidth_rampup_upper_bound_factor = + Config config; + config.bandwidth_rampup_upper_bound_factor = bandwidth_rampup_upper_bound_factor.Get(); - config->bandwidth_rampup_upper_bound_factor_in_hold = + config.bandwidth_rampup_upper_bound_factor_in_hold = bandwidth_rampup_upper_bound_factor_in_hold.Get(); - config->bandwidth_rampup_hold_threshold = + config.bandwidth_rampup_hold_threshold = bandwidth_rampup_hold_threshold.Get(); - config->rampup_acceleration_max_factor = rampup_acceleration_max_factor.Get(); - config->rampup_acceleration_maxout_time = + config.rampup_acceleration_max_factor = rampup_acceleration_max_factor.Get(); + config.rampup_acceleration_maxout_time = rampup_acceleration_maxout_time.Get(); - config->candidate_factors = candidate_factors.Get(); - config->higher_bandwidth_bias_factor = higher_bandwidth_bias_factor.Get(); - config->higher_log_bandwidth_bias_factor = + config.candidate_factors = candidate_factors.Get(); + config.higher_bandwidth_bias_factor = higher_bandwidth_bias_factor.Get(); + config.higher_log_bandwidth_bias_factor = higher_log_bandwidth_bias_factor.Get(); - config->inherent_loss_lower_bound = inherent_loss_lower_bound.Get(); - config->loss_threshold_of_high_bandwidth_preference = + config.inherent_loss_lower_bound = inherent_loss_lower_bound.Get(); + config.loss_threshold_of_high_bandwidth_preference = loss_threshold_of_high_bandwidth_preference.Get(); - config->bandwidth_preference_smoothing_factor = + config.bandwidth_preference_smoothing_factor = bandwidth_preference_smoothing_factor.Get(); - config->inherent_loss_upper_bound_bandwidth_balance = + config.inherent_loss_upper_bound_bandwidth_balance = inherent_loss_upper_bound_bandwidth_balance.Get(); - config->inherent_loss_upper_bound_offset = + config.inherent_loss_upper_bound_offset = inherent_loss_upper_bound_offset.Get(); - config->initial_inherent_loss_estimate = initial_inherent_loss_estimate.Get(); - config->newton_iterations = newton_iterations.Get(); - config->newton_step_size = newton_step_size.Get(); - config->append_acknowledged_rate_candidate = + config.initial_inherent_loss_estimate = initial_inherent_loss_estimate.Get(); + config.newton_iterations = newton_iterations.Get(); + config.newton_step_size = newton_step_size.Get(); + config.append_acknowledged_rate_candidate = append_acknowledged_rate_candidate.Get(); - config->append_delay_based_estimate_candidate = + config.append_delay_based_estimate_candidate = append_delay_based_estimate_candidate.Get(); - config->append_upper_bound_candidate_in_alr = + config.append_upper_bound_candidate_in_alr = append_upper_bound_candidate_in_alr.Get(); - config->observation_duration_lower_bound = + config.observation_duration_lower_bound = observation_duration_lower_bound.Get(); - config->observation_window_size = observation_window_size.Get(); - config->sending_rate_smoothing_factor = sending_rate_smoothing_factor.Get(); - config->instant_upper_bound_temporal_weight_factor = + config.observation_window_size = observation_window_size.Get(); + config.sending_rate_smoothing_factor = sending_rate_smoothing_factor.Get(); + config.instant_upper_bound_temporal_weight_factor = instant_upper_bound_temporal_weight_factor.Get(); - config->instant_upper_bound_bandwidth_balance = + config.instant_upper_bound_bandwidth_balance = instant_upper_bound_bandwidth_balance.Get(); - config->instant_upper_bound_loss_offset = + config.instant_upper_bound_loss_offset = instant_upper_bound_loss_offset.Get(); - config->temporal_weight_factor = temporal_weight_factor.Get(); - config->bandwidth_backoff_lower_bound_factor = + config.temporal_weight_factor = temporal_weight_factor.Get(); + config.bandwidth_backoff_lower_bound_factor = bandwidth_backoff_lower_bound_factor.Get(); - config->max_increase_factor = max_increase_factor.Get(); - config->delayed_increase_window = delayed_increase_window.Get(); - config->not_increase_if_inherent_loss_less_than_average_loss = + config.max_increase_factor = max_increase_factor.Get(); + config.delayed_increase_window = delayed_increase_window.Get(); + config.not_increase_if_inherent_loss_less_than_average_loss = not_increase_if_inherent_loss_less_than_average_loss.Get(); - config->not_use_acked_rate_in_alr = not_use_acked_rate_in_alr.Get(); - config->use_in_start_phase = use_in_start_phase.Get(); - config->min_num_observations = min_num_observations.Get(); - config->lower_bound_by_acked_rate_factor = + config.not_use_acked_rate_in_alr = not_use_acked_rate_in_alr.Get(); + config.use_in_start_phase = use_in_start_phase.Get(); + config.min_num_observations = min_num_observations.Get(); + config.lower_bound_by_acked_rate_factor = lower_bound_by_acked_rate_factor.Get(); - config->hold_duration_factor = hold_duration_factor.Get(); - config->use_byte_loss_rate = use_byte_loss_rate.Get(); - config->padding_duration = padding_duration.Get(); - config->bound_best_candidate = bound_best_candidate.Get(); - config->pace_at_loss_based_estimate = pace_at_loss_based_estimate.Get(); + config.hold_duration_factor = hold_duration_factor.Get(); + config.use_byte_loss_rate = use_byte_loss_rate.Get(); + config.padding_duration = padding_duration.Get(); + config.bound_best_candidate = bound_best_candidate.Get(); + config.pace_at_loss_based_estimate = pace_at_loss_based_estimate.Get(); return config; } @@ -1141,22 +1108,27 @@ bool LossBasedBweV2::PushBackObservation( return false; } - PacketResultsSummary packet_results_summary = - GetPacketResultsSummary(packet_results); - - partial_observation_.num_packets += packet_results_summary.num_packets; - partial_observation_.num_lost_packets += - packet_results_summary.num_lost_packets; - partial_observation_.size += packet_results_summary.total_size; - partial_observation_.lost_size += packet_results_summary.lost_size; + partial_observation_.num_packets += packet_results.size(); + Timestamp last_send_time = Timestamp::MinusInfinity(); + Timestamp first_send_time = Timestamp::PlusInfinity(); + for (const PacketResult& packet : packet_results) { + if (packet.IsReceived()) { + partial_observation_.lost_packets.erase( + packet.sent_packet.sequence_number); + } else { + partial_observation_.lost_packets.emplace( + packet.sent_packet.sequence_number, packet.sent_packet.size); + } + partial_observation_.size += packet.sent_packet.size; + last_send_time = std::max(last_send_time, packet.sent_packet.send_time); + first_send_time = std::min(first_send_time, packet.sent_packet.send_time); + } // This is the first packet report we have received. if (!IsValid(last_send_time_most_recent_observation_)) { - last_send_time_most_recent_observation_ = - packet_results_summary.first_send_time; + last_send_time_most_recent_observation_ = first_send_time; } - const Timestamp last_send_time = packet_results_summary.last_send_time; const TimeDelta observation_duration = last_send_time - last_send_time_most_recent_observation_; // Too small to be meaningful. @@ -1169,12 +1141,14 @@ bool LossBasedBweV2::PushBackObservation( Observation observation; observation.num_packets = partial_observation_.num_packets; - observation.num_lost_packets = partial_observation_.num_lost_packets; + observation.num_lost_packets = partial_observation_.lost_packets.size(); observation.num_received_packets = observation.num_packets - observation.num_lost_packets; observation.sending_rate = GetSendingRate(partial_observation_.size / observation_duration); - observation.lost_size = partial_observation_.lost_size; + for (auto const& [key, packet_size] : partial_observation_.lost_packets) { + observation.lost_size += packet_size; + } observation.size = partial_observation_.size; observation.id = num_observations_++; observations_[observation.id % config_->observation_window_size] = diff --git a/third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h b/third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h index 34c96c66d968..528a2328c181 100644 --- a/third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h +++ b/third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bwe_v2.h @@ -11,6 +11,8 @@ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_LOSS_BASED_BWE_V2_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_LOSS_BASED_BWE_V2_H_ +#include +#include #include #include "absl/types/optional.h" @@ -147,9 +149,8 @@ class LossBasedBweV2 { struct PartialObservation { int num_packets = 0; - int num_lost_packets = 0; + std::unordered_map lost_packets; DataSize size = DataSize::Zero(); - DataSize lost_size = DataSize::Zero(); }; struct PaddingInfo { diff --git a/third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc b/third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc index bb867f4fb020..40c1b4ec2578 100644 --- a/third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc +++ b/third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bwe_v2_test.cc @@ -10,6 +10,7 @@ #include "modules/congestion_controller/goog_cc/loss_based_bwe_v2.h" +#include #include #include @@ -91,6 +92,10 @@ class LossBasedBweV2Test : public ::testing::TestWithParam { std::vector CreatePacketResultsWithReceivedPackets( Timestamp first_packet_timestamp) { std::vector enough_feedback(2); + enough_feedback[0].sent_packet.sequence_number = + transport_sequence_number_++; + enough_feedback[1].sent_packet.sequence_number = + transport_sequence_number_++; enough_feedback[0].sent_packet.size = DataSize::Bytes(kPacketSize); enough_feedback[1].sent_packet.size = DataSize::Bytes(kPacketSize); enough_feedback[0].sent_packet.send_time = first_packet_timestamp; @@ -107,8 +112,9 @@ class LossBasedBweV2Test : public ::testing::TestWithParam { Timestamp first_packet_timestamp, DataSize lost_packet_size = DataSize::Bytes(kPacketSize)) { std::vector enough_feedback(10); - enough_feedback[0].sent_packet.size = DataSize::Bytes(kPacketSize); for (unsigned i = 0; i < enough_feedback.size(); ++i) { + enough_feedback[i].sent_packet.sequence_number = + transport_sequence_number_++; enough_feedback[i].sent_packet.size = DataSize::Bytes(kPacketSize); enough_feedback[i].sent_packet.send_time = first_packet_timestamp + @@ -125,6 +131,10 @@ class LossBasedBweV2Test : public ::testing::TestWithParam { std::vector CreatePacketResultsWith50pPacketLossRate( Timestamp first_packet_timestamp) { std::vector enough_feedback(2); + enough_feedback[0].sent_packet.sequence_number = + transport_sequence_number_++; + enough_feedback[1].sent_packet.sequence_number = + transport_sequence_number_++; enough_feedback[0].sent_packet.size = DataSize::Bytes(kPacketSize); enough_feedback[1].sent_packet.size = DataSize::Bytes(kPacketSize); enough_feedback[0].sent_packet.send_time = first_packet_timestamp; @@ -139,6 +149,10 @@ class LossBasedBweV2Test : public ::testing::TestWithParam { std::vector CreatePacketResultsWith100pLossRate( Timestamp first_packet_timestamp) { std::vector enough_feedback(2); + enough_feedback[0].sent_packet.sequence_number = + transport_sequence_number_++; + enough_feedback[1].sent_packet.sequence_number = + transport_sequence_number_++; enough_feedback[0].sent_packet.size = DataSize::Bytes(kPacketSize); enough_feedback[1].sent_packet.size = DataSize::Bytes(kPacketSize); enough_feedback[0].sent_packet.send_time = first_packet_timestamp; @@ -148,6 +162,9 @@ class LossBasedBweV2Test : public ::testing::TestWithParam { enough_feedback[1].receive_time = Timestamp::PlusInfinity(); return enough_feedback; } + + private: + int64_t transport_sequence_number_ = 0; }; TEST_F(LossBasedBweV2Test, EnabledWhenGivenValidConfigurationValues) { @@ -1812,5 +1829,61 @@ TEST_F(LossBasedBweV2Test, PaceAtLossBasedEstimate) { EXPECT_TRUE(loss_based_bandwidth_estimator.PaceAtLossBasedEstimate()); } +TEST_F(LossBasedBweV2Test, + EstimateDoesNotBackOffDueToPacketReorderingBetweenFeedback) { + ExplicitKeyValueConfig key_value_config(ShortObservationConfig("")); + LossBasedBweV2 loss_based_bandwidth_estimator(&key_value_config); + const DataRate kStartBitrate = DataRate::KilobitsPerSec(2500); + loss_based_bandwidth_estimator.SetBandwidthEstimate(kStartBitrate); + + std::vector feedback_1(3); + feedback_1[0].sent_packet.sequence_number = 1; + feedback_1[0].sent_packet.size = DataSize::Bytes(kPacketSize); + feedback_1[0].sent_packet.send_time = Timestamp::Zero(); + feedback_1[0].receive_time = + feedback_1[0].sent_packet.send_time + TimeDelta::Millis(10); + feedback_1[1].sent_packet.sequence_number = 2; + feedback_1[1].sent_packet.size = DataSize::Bytes(kPacketSize); + feedback_1[1].sent_packet.send_time = Timestamp::Zero(); + // Lost or reordered + feedback_1[1].receive_time = Timestamp::PlusInfinity(); + + feedback_1[2].sent_packet.sequence_number = 3; + feedback_1[2].sent_packet.size = DataSize::Bytes(kPacketSize); + feedback_1[2].sent_packet.send_time = Timestamp::Zero(); + feedback_1[2].receive_time = + feedback_1[2].sent_packet.send_time + TimeDelta::Millis(10); + + std::vector feedback_2(3); + feedback_2[0].sent_packet.sequence_number = 2; + feedback_2[0].sent_packet.size = DataSize::Bytes(kPacketSize); + feedback_2[0].sent_packet.send_time = Timestamp::Zero(); + feedback_2[0].receive_time = + feedback_1[0].sent_packet.send_time + TimeDelta::Millis(10); + feedback_2[1].sent_packet.sequence_number = 4; + feedback_2[1].sent_packet.size = DataSize::Bytes(kPacketSize); + feedback_2[1].sent_packet.send_time = + Timestamp::Zero() + kObservationDurationLowerBound; + feedback_2[1].receive_time = + feedback_2[1].sent_packet.send_time + TimeDelta::Millis(10); + feedback_2[2].sent_packet.sequence_number = 5; + feedback_2[2].sent_packet.size = DataSize::Bytes(kPacketSize); + feedback_2[2].sent_packet.send_time = Timestamp::Zero(); + feedback_2[2].receive_time = + feedback_2[2].sent_packet.send_time + TimeDelta::Millis(10); + + loss_based_bandwidth_estimator.UpdateBandwidthEstimate( + feedback_1, + /*delay_based_estimate=*/kStartBitrate, + /*in_alr=*/false); + loss_based_bandwidth_estimator.UpdateBandwidthEstimate( + feedback_2, + /*delay_based_estimate=*/kStartBitrate, + /*in_alr=*/false); + EXPECT_EQ( + loss_based_bandwidth_estimator.GetLossBasedResult().bandwidth_estimate, + kStartBitrate); +} + } // namespace } // namespace webrtc diff --git a/third_party/libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.cc b/third_party/libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.cc index e81f579c50b8..081e016b03f9 100644 --- a/third_party/libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.cc +++ b/third_party/libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.cc @@ -253,9 +253,10 @@ TransportFeedbackAdapter::ProcessTransportFeedbackInner( }); if (failed_lookups > 0) { - RTC_LOG(LS_WARNING) << "Failed to lookup send time for " << failed_lookups - << " packet" << (failed_lookups > 1 ? "s" : "") - << ". Send time history too small?"; + RTC_LOG(LS_WARNING) + << "Failed to lookup send time for " << failed_lookups << " packet" + << (failed_lookups > 1 ? "s" : "") + << ". Packets reordered or send time history too small?"; } if (ignored > 0) { RTC_LOG(LS_INFO) << "Ignoring " << ignored diff --git a/third_party/libwebrtc/modules/desktop_capture/BUILD.gn b/third_party/libwebrtc/modules/desktop_capture/BUILD.gn index e15ca63c2c50..d9276a97f5f8 100644 --- a/third_party/libwebrtc/modules/desktop_capture/BUILD.gn +++ b/third_party/libwebrtc/modules/desktop_capture/BUILD.gn @@ -67,7 +67,6 @@ if (rtc_include_tests) { "../../rtc_base/third_party/base64", "../../system_wrappers", "../../test:test_support", - "../../test:video_test_support", ] sources += [ "screen_capturer_integration_test.cc", diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.cc b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.cc index f2198bf5759f..6db4194d249e 100644 --- a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.cc +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.cc @@ -10,7 +10,7 @@ #include "modules/desktop_capture/screen_capturer_fuchsia.h" -#include +#include #include #include #include @@ -42,10 +42,10 @@ static constexpr uint32_t kFuchsiaBytesPerPixel = 4; static constexpr DesktopCapturer::SourceId kFuchsiaScreenId = 1; // 500 milliseconds static constexpr zx::duration kEventDelay = zx::msec(500); -static constexpr fuchsia::sysmem::ColorSpaceType kSRGBColorSpace = - fuchsia::sysmem::ColorSpaceType::SRGB; -static constexpr fuchsia::sysmem::PixelFormatType kBGRA32PixelFormatType = - fuchsia::sysmem::PixelFormatType::BGRA32; +static constexpr fuchsia::images2::ColorSpace kSRGBColorSpace = + fuchsia::images2::ColorSpace::SRGB; +static constexpr fuchsia::images2::PixelFormat kBGRA32PixelFormatType = + fuchsia::images2::PixelFormat::B8G8R8A8; // Round |value| up to the closest multiple of |multiple| size_t RoundUpToMultiple(size_t value, size_t multiple) { @@ -66,9 +66,10 @@ ScreenCapturerFuchsia::ScreenCapturerFuchsia() ScreenCapturerFuchsia::~ScreenCapturerFuchsia() { // unmap virtual memory mapped pointers uint32_t virt_mem_bytes = - buffer_collection_info_.settings.buffer_settings.size_bytes; + buffer_collection_info_.settings().buffer_settings().size_bytes(); for (uint32_t buffer_index = 0; - buffer_index < buffer_collection_info_.buffer_count; buffer_index++) { + buffer_index < buffer_collection_info_.buffers().size(); + buffer_index++) { uintptr_t address = reinterpret_cast(virtual_memory_mapped_addrs_[buffer_index]); zx_status_t status = zx::vmar::root_self()->unmap(address, virt_mem_bytes); @@ -132,7 +133,7 @@ void ScreenCapturerFuchsia::CaptureFrame() { new BasicDesktopFrame(DesktopSize(width_, height_))); uint32_t pixels_per_row = GetPixelsPerRow( - buffer_collection_info_.settings.image_format_constraints); + buffer_collection_info_.settings().image_format_constraints()); uint32_t stride = kFuchsiaBytesPerPixel * pixels_per_row; frame->CopyPixelsFrom(virtual_memory_mapped_addrs_[buffer_index], stride, DesktopRect::MakeWH(width_, height_)); @@ -167,34 +168,27 @@ bool ScreenCapturerFuchsia::SelectSource(SourceId id) { return false; } -fuchsia::sysmem::BufferCollectionConstraints +fuchsia::sysmem2::BufferCollectionConstraints ScreenCapturerFuchsia::GetBufferConstraints() { - fuchsia::sysmem::BufferCollectionConstraints constraints; - constraints.usage.cpu = - fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageWrite; - constraints.min_buffer_count = kMinBufferCount; + fuchsia::sysmem2::BufferCollectionConstraints constraints; + constraints.mutable_usage()->set_cpu(fuchsia::sysmem2::CPU_USAGE_READ | + fuchsia::sysmem2::CPU_USAGE_WRITE); + constraints.set_min_buffer_count(kMinBufferCount); - constraints.has_buffer_memory_constraints = true; - constraints.buffer_memory_constraints.ram_domain_supported = true; - constraints.buffer_memory_constraints.cpu_domain_supported = true; + auto& bmc = *constraints.mutable_buffer_memory_constraints(); + bmc.set_ram_domain_supported(true); + bmc.set_cpu_domain_supported(true); - constraints.image_format_constraints_count = 1; - fuchsia::sysmem::ImageFormatConstraints& image_constraints = - constraints.image_format_constraints[0]; - image_constraints.color_spaces_count = 1; - image_constraints.color_space[0] = - fuchsia::sysmem::ColorSpace{.type = kSRGBColorSpace}; - image_constraints.pixel_format.type = kBGRA32PixelFormatType; - image_constraints.pixel_format.has_format_modifier = true; - image_constraints.pixel_format.format_modifier.value = - fuchsia::sysmem::FORMAT_MODIFIER_LINEAR; + fuchsia::sysmem2::ImageFormatConstraints& ifc = + constraints.mutable_image_format_constraints()->emplace_back(); + ifc.mutable_color_spaces()->emplace_back(kSRGBColorSpace); + ifc.set_pixel_format(kBGRA32PixelFormatType); + ifc.set_pixel_format_modifier(fuchsia::images2::PixelFormatModifier::LINEAR); - image_constraints.required_min_coded_width = width_; - image_constraints.required_min_coded_height = height_; - image_constraints.required_max_coded_width = width_; - image_constraints.required_max_coded_height = height_; + ifc.set_required_min_size(fuchsia::math::SizeU{width_, height_}); + ifc.set_required_max_size(fuchsia::math::SizeU{width_, height_}); - image_constraints.bytes_per_row_divisor = kFuchsiaBytesPerPixel; + ifc.set_bytes_per_row_divisor(kFuchsiaBytesPerPixel); return constraints; } @@ -224,55 +218,62 @@ void ScreenCapturerFuchsia::SetupBuffers() { status = component_context_->svc()->Connect(sysmem_allocator_.NewRequest()); if (status != ZX_OK) { fatal_error_ = true; - RTC_LOG(LS_ERROR) << "Failed to connect to Sysmem Allocator: " << status; + RTC_LOG(LS_ERROR) << "Failed to connect to fuchsia.sysmem2.Allocator: " << status; return; } - fuchsia::sysmem::BufferCollectionTokenSyncPtr sysmem_token; - status = - sysmem_allocator_->AllocateSharedCollection(sysmem_token.NewRequest()); + fuchsia::sysmem2::BufferCollectionTokenSyncPtr sysmem_token; + status = sysmem_allocator_->AllocateSharedCollection( + std::move(fuchsia::sysmem2::AllocatorAllocateSharedCollectionRequest{} + .set_token_request(sysmem_token.NewRequest()))); if (status != ZX_OK) { fatal_error_ = true; RTC_LOG(LS_ERROR) - << "fuchsia.sysmem.Allocator.AllocateSharedCollection() failed: " + << "fuchsia.sysmem2.Allocator.AllocateSharedCollection() failed: " << status; return; } - fuchsia::sysmem::BufferCollectionTokenSyncPtr flatland_token; - status = sysmem_token->Duplicate(ZX_RIGHT_SAME_RIGHTS, - flatland_token.NewRequest()); + fuchsia::sysmem2::BufferCollectionTokenSyncPtr flatland_token; + status = sysmem_token->Duplicate( + std::move(fuchsia::sysmem2::BufferCollectionTokenDuplicateRequest{} + .set_rights_attenuation_mask(ZX_RIGHT_SAME_RIGHTS) + .set_token_request(flatland_token.NewRequest()))); if (status != ZX_OK) { fatal_error_ = true; RTC_LOG(LS_ERROR) - << "fuchsia.sysmem.BufferCollectionToken.Duplicate() failed: " + << "fuchsia.sysmem2.BufferCollectionToken.Duplicate() failed: " << status; return; } - status = sysmem_token->Sync(); + fuchsia::sysmem2::Node_Sync_Result sync_result; + status = sysmem_token->Sync(&sync_result); if (status != ZX_OK) { fatal_error_ = true; - RTC_LOG(LS_ERROR) << "fuchsia.sysmem.BufferCollectionToken.Sync() failed: " + RTC_LOG(LS_ERROR) << "fuchsia.sysmem2.BufferCollectionToken.Sync() failed: " << status; return; } - status = sysmem_allocator_->BindSharedCollection(std::move(sysmem_token), - collection_.NewRequest()); + status = sysmem_allocator_->BindSharedCollection( + std::move(fuchsia::sysmem2::AllocatorBindSharedCollectionRequest{} + .set_token(std::move(sysmem_token)) + .set_buffer_collection_request(collection_.NewRequest()))); if (status != ZX_OK) { fatal_error_ = true; RTC_LOG(LS_ERROR) - << "fuchsia.sysmem.Allocator.BindSharedCollection() failed: " << status; + << "fuchsia.sysmem2.Allocator.BindSharedCollection() failed: " << status; return; } - status = collection_->SetConstraints(/*has_constraints=*/true, - GetBufferConstraints()); + status = collection_->SetConstraints(std::move( + fuchsia::sysmem2::BufferCollectionSetConstraintsRequest{}.set_constraints( + GetBufferConstraints()))); if (status != ZX_OK) { fatal_error_ = true; RTC_LOG(LS_ERROR) - << "fuchsia.sysmem.BufferCollection.SetConstraints() failed: " + << "fuchsia.sysmem2.BufferCollection.SetConstraints() failed: " << status; return; } @@ -297,7 +298,9 @@ void ScreenCapturerFuchsia::SetupBuffers() { fuchsia::ui::composition::RegisterBufferCollectionArgs buffer_collection_args; buffer_collection_args.set_export_token(std::move(export_token)); - buffer_collection_args.set_buffer_collection_token(std::move(flatland_token)); + buffer_collection_args.set_buffer_collection_token( + fuchsia::sysmem::BufferCollectionTokenHandle( + flatland_token.Unbind().TakeChannel())); buffer_collection_args.set_usage( fuchsia::ui::composition::RegisterBufferCollectionUsage::SCREENSHOT); @@ -312,21 +315,31 @@ void ScreenCapturerFuchsia::SetupBuffers() { return; } - zx_status_t allocation_status; - status = collection_->WaitForBuffersAllocated(&allocation_status, - &buffer_collection_info_); + fuchsia::sysmem2::BufferCollection_WaitForAllBuffersAllocated_Result + wait_result; + status = collection_->WaitForAllBuffersAllocated(&wait_result); if (status != ZX_OK) { fatal_error_ = true; RTC_LOG(LS_ERROR) << "Failed to wait for buffer collection info: " << status; return; } - if (allocation_status != ZX_OK) { + if (!wait_result.is_response()) { + if (wait_result.is_framework_err()) { + RTC_LOG(LS_ERROR) + << "Failed to allocate buffer collection (framework_err): " + << fidl::ToUnderlying(wait_result.framework_err()); + } else { + RTC_LOG(LS_ERROR) << "Failed to allocate buffer collection (err): " + << static_cast(wait_result.err()); + } fatal_error_ = true; - RTC_LOG(LS_ERROR) << "Failed to allocate buffer collection: " << status; return; } - status = collection_->Close(); + buffer_collection_info_ = + std::move(*wait_result.response().mutable_buffer_collection_info()); + + status = collection_->Release(); if (status != ZX_OK) { fatal_error_ = true; RTC_LOG(LS_ERROR) << "Failed to close buffer collection token: " << status; @@ -343,7 +356,7 @@ void ScreenCapturerFuchsia::SetupBuffers() { // Configure buffers in ScreenCapture client. fuchsia::ui::composition::ScreenCaptureConfig configure_args; configure_args.set_import_token(std::move(import_token)); - configure_args.set_buffer_count(buffer_collection_info_.buffer_count); + configure_args.set_buffer_count(buffer_collection_info_.buffers().size()); configure_args.set_size({width_, height_}); fuchsia::ui::composition::ScreenCapture_Configure_Result configure_result; @@ -361,11 +374,13 @@ void ScreenCapturerFuchsia::SetupBuffers() { // onto a pointer stored in virtual_memory_mapped_addrs_ which we can use to // access this data. uint32_t virt_mem_bytes = - buffer_collection_info_.settings.buffer_settings.size_bytes; + buffer_collection_info_.settings().buffer_settings().size_bytes(); RTC_DCHECK(virt_mem_bytes > 0); for (uint32_t buffer_index = 0; - buffer_index < buffer_collection_info_.buffer_count; buffer_index++) { - const zx::vmo& virt_mem = buffer_collection_info_.buffers[buffer_index].vmo; + buffer_index < buffer_collection_info_.buffers().size(); + buffer_index++) { + const zx::vmo& virt_mem = + buffer_collection_info_.buffers()[buffer_index].vmo(); virtual_memory_mapped_addrs_[buffer_index] = nullptr; auto status = zx::vmar::root_self()->map( ZX_VM_PERM_READ, /*vmar_offset*/ 0, virt_mem, @@ -381,10 +396,10 @@ void ScreenCapturerFuchsia::SetupBuffers() { } uint32_t ScreenCapturerFuchsia::GetPixelsPerRow( - const fuchsia::sysmem::ImageFormatConstraints& constraints) { + const fuchsia::sysmem2::ImageFormatConstraints& constraints) { uint32_t stride = RoundUpToMultiple( - std::max(constraints.min_bytes_per_row, width_ * kFuchsiaBytesPerPixel), - constraints.bytes_per_row_divisor); + std::max(constraints.min_bytes_per_row(), width_ * kFuchsiaBytesPerPixel), + constraints.bytes_per_row_divisor()); uint32_t pixels_per_row = stride / kFuchsiaBytesPerPixel; return pixels_per_row; diff --git a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.h b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.h index 6e0f87cc58dc..614da82e8727 100644 --- a/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.h +++ b/third_party/libwebrtc/modules/desktop_capture/screen_capturer_fuchsia.h @@ -11,7 +11,7 @@ #ifndef MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_FUCHSIA_H_ #define MODULES_DESKTOP_CAPTURE_SCREEN_CAPTURER_FUCHSIA_H_ -#include +#include #include #include @@ -36,19 +36,19 @@ class ScreenCapturerFuchsia final : public DesktopCapturer { bool SelectSource(SourceId id) override; private: - fuchsia::sysmem::BufferCollectionConstraints GetBufferConstraints(); + fuchsia::sysmem2::BufferCollectionConstraints GetBufferConstraints(); void SetupBuffers(); uint32_t GetPixelsPerRow( - const fuchsia::sysmem::ImageFormatConstraints& constraints); + const fuchsia::sysmem2::ImageFormatConstraints& constraints); Callback* callback_ = nullptr; std::unique_ptr component_context_; - fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator_; + fuchsia::sysmem2::AllocatorSyncPtr sysmem_allocator_; fuchsia::ui::composition::AllocatorSyncPtr flatland_allocator_; fuchsia::ui::composition::ScreenCaptureSyncPtr screen_capture_; - fuchsia::sysmem::BufferCollectionSyncPtr collection_; - fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info_; + fuchsia::sysmem2::BufferCollectionSyncPtr collection_; + fuchsia::sysmem2::BufferCollectionInfo buffer_collection_info_; std::unordered_map virtual_memory_mapped_addrs_; bool fatal_error_; diff --git a/third_party/libwebrtc/modules/rtp_rtcp/BUILD.gn b/third_party/libwebrtc/modules/rtp_rtcp/BUILD.gn index 58317c12913c..d956a4fd00c6 100644 --- a/third_party/libwebrtc/modules/rtp_rtcp/BUILD.gn +++ b/third_party/libwebrtc/modules/rtp_rtcp/BUILD.gn @@ -233,7 +233,6 @@ rtc_library("rtp_rtcp") { "source/rtp_video_stream_receiver_frame_transformer_delegate.h", "source/source_tracker.cc", "source/source_tracker.h", - "source/time_util.h", "source/tmmbr_help.cc", "source/tmmbr_help.h", "source/ulpfec_generator.cc", 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 9c1dc4edb844..7e11add85b11 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 @@ -48,8 +48,7 @@ RtpPacketizerH264::RtpPacketizerH264(rtc::ArrayView payload, RTC_CHECK(packetization_mode == H264PacketizationMode::NonInterleaved || packetization_mode == H264PacketizationMode::SingleNalUnit); - for (const auto& nalu : - H264::FindNaluIndices(payload.data(), payload.size())) { + for (const auto& nalu : H264::FindNaluIndices(payload)) { input_fragments_.push_back( payload.subview(nalu.payload_start_offset, nalu.payload_size)); } diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_h265.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_h265.cc index 5f10120d81c3..901b68bfa3dd 100644 --- a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_h265.cc +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_h265.cc @@ -24,8 +24,11 @@ namespace webrtc { RtpPacketizerH265::RtpPacketizerH265(rtc::ArrayView payload, PayloadSizeLimits limits) : limits_(limits), num_packets_left_(0) { - for (const auto& nalu : - H264::FindNaluIndices(payload.data(), payload.size())) { + for (const auto& nalu : H264::FindNaluIndices(payload)) { + if (!nalu.payload_size) { + input_fragments_.clear(); + return; + } input_fragments_.push_back( payload.subview(nalu.payload_start_offset, nalu.payload_size)); } diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/time_util.h b/third_party/libwebrtc/modules/rtp_rtcp/source/time_util.h deleted file mode 100644 index f56786fd40fd..000000000000 --- a/third_party/libwebrtc/modules/rtp_rtcp/source/time_util.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2015 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 MODULES_RTP_RTCP_SOURCE_TIME_UTIL_H_ -#define MODULES_RTP_RTCP_SOURCE_TIME_UTIL_H_ - -// TODO: bugs.webrtc.org/343076000 - Remove this forwarding header when -// downstream projects are updated to use ntp_time_util directly. -#include "modules/rtp_rtcp/source/ntp_time_util.h" - -#endif // MODULES_RTP_RTCP_SOURCE_TIME_UTIL_H_ diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc index 4198c80106af..084b6ac203dc 100644 --- a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264.cc @@ -70,7 +70,7 @@ absl::optional ProcessStapAOrSingleNalu( parsed_payload->video_header.height = 0; parsed_payload->video_header.codec = kVideoCodecH264; parsed_payload->video_header.simulcastIdx = 0; - parsed_payload->video_header.is_first_packet_in_frame = true; + parsed_payload->video_header.is_first_packet_in_frame = false; auto& h264_header = parsed_payload->video_header.video_type_header .emplace(); @@ -116,12 +116,12 @@ absl::optional ProcessStapAOrSingleNalu( nalu.sps_id = -1; nalu.pps_id = -1; start_offset += H264::kNaluTypeSize; - + rtc::ArrayView nalu_data(&payload_data[start_offset], + end_offset - start_offset); switch (nalu.type) { case H264::NaluType::kSps: { // Check if VUI is present in SPS and if it needs to be modified to - // avoid - // excessive decoder latency. + // avoid excessive decoder latency. // Copy any previous data first (likely just the first header). rtc::Buffer output_buffer; @@ -131,8 +131,8 @@ absl::optional ProcessStapAOrSingleNalu( absl::optional sps; SpsVuiRewriter::ParseResult result = SpsVuiRewriter::ParseAndRewriteSps( - &payload_data[start_offset], end_offset - start_offset, &sps, - nullptr, &output_buffer, SpsVuiRewriter::Direction::kIncoming); + nalu_data, &sps, nullptr, &output_buffer, + SpsVuiRewriter::Direction::kIncoming); switch (result) { case SpsVuiRewriter::ParseResult::kFailure: RTC_LOG(LS_WARNING) << "Failed to parse SPS NAL unit."; @@ -174,14 +174,13 @@ absl::optional ProcessStapAOrSingleNalu( VideoFrameType::kVideoFrameKey; break; } + parsed_payload->video_header.is_first_packet_in_frame = true; break; } case H264::NaluType::kPps: { uint32_t pps_id; uint32_t sps_id; - if (PpsParser::ParsePpsIds(&payload_data[start_offset], - end_offset - start_offset, &pps_id, - &sps_id)) { + if (PpsParser::ParsePpsIds(nalu_data, &pps_id, &sps_id)) { nalu.pps_id = pps_id; nalu.sps_id = sps_id; } else { @@ -196,10 +195,13 @@ absl::optional ProcessStapAOrSingleNalu( VideoFrameType::kVideoFrameKey; [[fallthrough]]; case H264::NaluType::kSlice: { - absl::optional pps_id = PpsParser::ParsePpsIdFromSlice( - &payload_data[start_offset], end_offset - start_offset); - if (pps_id) { - nalu.pps_id = *pps_id; + absl::optional slice_header = + PpsParser::ParseSliceHeader(nalu_data); + if (slice_header) { + nalu.pps_id = slice_header->pic_parameter_set_id; + if (slice_header->first_mb_in_slice == 0) { + parsed_payload->video_header.is_first_packet_in_frame = true; + } } else { RTC_LOG(LS_WARNING) << "Failed to parse PPS id from slice of type: " << static_cast(nalu.type); @@ -220,13 +222,7 @@ absl::optional ProcessStapAOrSingleNalu( return absl::nullopt; } - if (h264_header.nalus_length == kMaxNalusPerPacket) { - RTC_LOG(LS_WARNING) - << "Received packet containing more than " << kMaxNalusPerPacket - << " NAL units. Will not keep track sps and pps ids for all of them."; - } else { - h264_header.nalus[h264_header.nalus_length++] = nalu; - } + h264_header.nalus.push_back(nalu); } return parsed_payload; @@ -243,21 +239,26 @@ absl::optional ParseFuaNalu( uint8_t fnri = rtp_payload.cdata()[0] & (kH264FBit | kH264NriMask); uint8_t original_nal_type = rtp_payload.cdata()[1] & kH264TypeMask; bool first_fragment = (rtp_payload.cdata()[1] & kH264SBit) > 0; + bool is_first_packet_in_frame = false; NaluInfo nalu; nalu.type = original_nal_type; nalu.sps_id = -1; nalu.pps_id = -1; if (first_fragment) { - absl::optional pps_id = - PpsParser::ParsePpsIdFromSlice(rtp_payload.cdata() + 2 * kNalHeaderSize, - rtp_payload.size() - 2 * kNalHeaderSize); - if (pps_id) { - nalu.pps_id = *pps_id; - } else { - RTC_LOG(LS_WARNING) - << "Failed to parse PPS from first fragment of FU-A NAL " - "unit with original type: " - << static_cast(nalu.type); + if (original_nal_type == H264::NaluType::kIdr || + original_nal_type == H264::NaluType::kSlice) { + absl::optional slice_header = + PpsParser::ParseSliceHeader(rtc::ArrayView(rtp_payload) + .subview(2 * kNalHeaderSize)); + if (slice_header) { + nalu.pps_id = slice_header->pic_parameter_set_id; + is_first_packet_in_frame = slice_header->first_mb_in_slice == 0; + } else { + RTC_LOG(LS_WARNING) + << "Failed to parse PPS from first fragment of FU-A NAL " + "unit with original type: " + << static_cast(nalu.type); + } } uint8_t original_nal_header = fnri | original_nal_type; rtp_payload = @@ -278,14 +279,14 @@ absl::optional ParseFuaNalu( parsed_payload->video_header.height = 0; parsed_payload->video_header.codec = kVideoCodecH264; parsed_payload->video_header.simulcastIdx = 0; - parsed_payload->video_header.is_first_packet_in_frame = first_fragment; + parsed_payload->video_header.is_first_packet_in_frame = + is_first_packet_in_frame; auto& h264_header = parsed_payload->video_header.video_type_header .emplace(); h264_header.packetization_type = kH264FuA; h264_header.nalu_type = original_nal_type; if (first_fragment) { - h264_header.nalus[h264_header.nalus_length] = nalu; - h264_header.nalus_length = 1; + h264_header.nalus = {nalu}; } return parsed_payload; } diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264_unittest.cc index 7cc410863214..d279d24ee86c 100644 --- a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264_unittest.cc +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h264_unittest.cc @@ -125,15 +125,7 @@ TEST(VideoRtpDepacketizerH264Test, StapAKey) { EXPECT_EQ(h264.packetization_type, kH264StapA); // NALU type for aggregated packets is the type of the first packet only. EXPECT_EQ(h264.nalu_type, kSps); - ASSERT_EQ(h264.nalus_length, 3u); - for (size_t i = 0; i < h264.nalus_length; ++i) { - EXPECT_EQ(h264.nalus[i].type, kExpectedNalus[i].type) - << "Failed parsing nalu " << i; - EXPECT_EQ(h264.nalus[i].sps_id, kExpectedNalus[i].sps_id) - << "Failed parsing nalu " << i; - EXPECT_EQ(h264.nalus[i].pps_id, kExpectedNalus[i].pps_id) - << "Failed parsing nalu " << i; - } + EXPECT_THAT(h264.nalus, ElementsAreArray(kExpectedNalus)); } TEST(VideoRtpDepacketizerH264Test, StapANaluSpsWithResolution) { @@ -327,7 +319,7 @@ TEST(VideoRtpDepacketizerH264Test, FuA) { absl::get(parsed1->video_header.video_type_header); EXPECT_EQ(h264.packetization_type, kH264FuA); EXPECT_EQ(h264.nalu_type, kIdr); - ASSERT_EQ(h264.nalus_length, 1u); + ASSERT_THAT(h264.nalus, SizeIs(1)); EXPECT_EQ(h264.nalus[0].type, static_cast(kIdr)); EXPECT_EQ(h264.nalus[0].sps_id, -1); EXPECT_EQ(h264.nalus[0].pps_id, 0); @@ -347,7 +339,7 @@ TEST(VideoRtpDepacketizerH264Test, FuA) { EXPECT_EQ(h264.packetization_type, kH264FuA); EXPECT_EQ(h264.nalu_type, kIdr); // NALU info is only expected for the first FU-A packet. - EXPECT_EQ(h264.nalus_length, 0u); + EXPECT_THAT(h264.nalus, IsEmpty()); } auto parsed3 = depacketizer.Parse(rtc::CopyOnWriteBuffer(packet3)); @@ -362,7 +354,7 @@ TEST(VideoRtpDepacketizerH264Test, FuA) { EXPECT_EQ(h264.packetization_type, kH264FuA); EXPECT_EQ(h264.nalu_type, kIdr); // NALU info is only expected for the first FU-A packet. - ASSERT_EQ(h264.nalus_length, 0u); + EXPECT_THAT(h264.nalus, IsEmpty()); } } @@ -409,7 +401,7 @@ TEST(VideoRtpDepacketizerH264Test, SeiPacket) { EXPECT_EQ(parsed->video_header.frame_type, VideoFrameType::kVideoFrameDelta); EXPECT_EQ(h264.packetization_type, kH264SingleNalu); EXPECT_EQ(h264.nalu_type, kSei); - ASSERT_EQ(h264.nalus_length, 1u); + ASSERT_THAT(h264.nalus, SizeIs(1)); EXPECT_EQ(h264.nalus[0].type, static_cast(kSei)); EXPECT_EQ(h264.nalus[0].sps_id, -1); EXPECT_EQ(h264.nalus[0].pps_id, -1); @@ -447,5 +439,69 @@ TEST(VideoRtpDepacketizerH264Test, BadSlice) { EXPECT_FALSE(depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload))); } +TEST(VideoRtpDepacketizerH264Test, StapASpsPpsMultiSlice) { + // A STAP-A containing a black 320x192 key frame with multiple slices. + const uint8_t kPayload[] = { + // clang-format off + 0x67, 0x42, 0xc0, 0x15, 0x8c, 0x68, 0x14, 0x19, // SPS. + 0x79, 0xe0, 0x1e, 0x11, 0x08, 0xd4, 0x00, 0x04, 0x68, 0xce, 0x3c, 0x80, + 0x00, 0x2e, // PPS. + // Slices. + 0x65, 0xb8, 0x00, 0x04, 0x08, 0x79, 0x31, 0x40, 0x00, 0x42, 0xae, 0x4d, + 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, + 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xd6, 0xeb, 0xae, 0xba, 0xeb, 0xae, + 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xbc, 0x00, 0x2f, + 0x65, 0x05, 0x2e, 0x00, 0x01, 0x02, 0x1e, 0x4c, 0x50, 0x00, 0x10, 0xab, + 0x93, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, + 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x75, 0xba, 0xeb, 0xae, 0xba, + 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xaf, 0x00, + 0x30, 0x65, 0x02, 0x8b, 0x80, 0x00, 0x40, 0x87, 0x93, 0x14, 0x00, 0x04, + 0x2a, 0xe4, 0xdc, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9d, 0x6e, 0xba, 0xeb, + 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, + 0xc0, 0x00, 0x30, 0x65, 0x03, 0xcb, 0x80, 0x00, 0x40, 0x87, 0x93, 0x14, + 0x00, 0x04, 0x2a, 0xe4, 0xdc, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9d, 0x6e, + 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, + 0xba, 0xeb, 0xc0, 0x00, 0x30, 0x65, 0x01, 0x42, 0xe0, 0x00, 0x10, 0x21, + 0xe4, 0xc5, 0x00, 0x01, 0x0a, 0xb9, 0x37, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x5b, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, + 0xba, 0xeb, 0xae, 0xba, 0xf0, 0x00, 0x30, 0x65, 0x01, 0x92, 0xe0, 0x00, + 0x10, 0x21, 0xe4, 0xc5, 0x00, 0x01, 0x0a, 0xb9, 0x37, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x5b, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, + 0xeb, 0xae, 0xba, 0xeb, 0xae, 0xba, 0xf0 + // clang-format on + }; + + VideoRtpDepacketizerH264 depacketizer; + absl::optional parsed = + depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload)); + ASSERT_TRUE(parsed); + EXPECT_TRUE(parsed->video_header.is_first_packet_in_frame); +} + +TEST(VideoRtpDepacketizerH264Test, SecondSliceIdrNalu) { + // First few bytes of a second slice of an IDR nalu with + // first_mb_in_slice = 480. + const uint8_t kPayload[] = { + // clang-format off + 0x65, 0x00, 0xf0, 0x88, 0x82, 0x01, 0x3b, 0xff, 0xdf, 0xfe, 0x0b, 0xbb, + 0xfc, 0xb4, 0x30, 0xd1, 0x00, 0xef, 0xfd, 0xef, 0x0e, 0x79, 0x8b, 0x74, + 0x9b, 0x44, 0xf3, 0xb8, 0x65, 0x8f, 0xa1, 0x92, 0x30, 0xf9, 0x40, 0x06, + 0xb0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x18, 0x87, 0x4f, 0x6a, 0xfe, 0x60, 0x03, 0x9f, 0xfe, 0xd8, + 0x8b, 0xa6, 0x67, 0x31 + // clang-format on + }; + + VideoRtpDepacketizerH264 depacketizer; + absl::optional parsed = + depacketizer.Parse(rtc::CopyOnWriteBuffer(kPayload)); + ASSERT_TRUE(parsed); + EXPECT_FALSE(parsed->video_header.is_first_packet_in_frame); +} + } // namespace } // namespace webrtc diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h265.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h265.cc index fdb5680a96ad..b415ae7b7fb8 100644 --- a/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h265.cc +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_h265.cc @@ -120,6 +120,8 @@ absl::optional ProcessApOrSingleNalu( uint8_t nalu_type = (payload_data[start_offset] & kH265TypeMask) >> 1; start_offset += kH265NalHeaderSizeBytes; + rtc::ArrayView nalu_data(&payload_data[start_offset], + end_offset - start_offset); switch (nalu_type) { case H265::NaluType::kBlaWLp: case H265::NaluType::kBlaWRadl: @@ -141,8 +143,8 @@ absl::optional ProcessApOrSingleNalu( if (start_offset) output_buffer->AppendData(payload_data, start_offset); - absl::optional sps = H265SpsParser::ParseSps( - &payload_data[start_offset], end_offset - start_offset); + absl::optional sps = + H265SpsParser::ParseSps(nalu_data); if (sps) { // TODO(bugs.webrtc.org/13485): Implement the size calculation taking diff --git a/third_party/libwebrtc/modules/video_capture/BUILD.gn b/third_party/libwebrtc/modules/video_capture/BUILD.gn index 115173f9c9c2..d9b347157272 100644 --- a/third_party/libwebrtc/modules/video_capture/BUILD.gn +++ b/third_party/libwebrtc/modules/video_capture/BUILD.gn @@ -81,7 +81,10 @@ if (!build_with_chromium || is_linux || is_chromeos) { "linux/video_capture_v4l2.cc", "linux/video_capture_v4l2.h", ] - deps += [ "../../media:rtc_media_base" ] + deps += [ + "../../media:rtc_media_base", + "../../rtc_base:sanitizer", + ] if (rtc_use_pipewire) { sources += [ diff --git a/third_party/libwebrtc/modules/video_capture/linux/pipewire_session.cc b/third_party/libwebrtc/modules/video_capture/linux/pipewire_session.cc index ac12d0437290..e17e28a65f4b 100644 --- a/third_party/libwebrtc/modules/video_capture/linux/pipewire_session.cc +++ b/third_party/libwebrtc/modules/video_capture/linux/pipewire_session.cc @@ -19,6 +19,7 @@ #include "common_video/libyuv/include/webrtc_libyuv.h" #include "modules/video_capture/device_info_impl.h" #include "rtc_base/logging.h" +#include "rtc_base/sanitizer.h" #include "rtc_base/string_encode.h" #include "rtc_base/string_to_number.h" @@ -65,6 +66,7 @@ PipeWireNode::PipeWireNodePtr PipeWireNode::Create(PipeWireSession* session, return PipeWireNodePtr(new PipeWireNode(session, id, props)); } +RTC_NO_SANITIZE("cfi-icall") PipeWireNode::PipeWireNode(PipeWireSession* session, uint32_t id, const spa_dict* props) @@ -87,6 +89,7 @@ PipeWireNode::PipeWireNode(PipeWireSession* session, } // static +RTC_NO_SANITIZE("cfi-icall") void PipeWireNode::OnNodeInfo(void* data, const pw_node_info* info) { PipeWireNode* that = static_cast(data); @@ -123,6 +126,7 @@ void PipeWireNode::OnNodeInfo(void* data, const pw_node_info* info) { } // static +RTC_NO_SANITIZE("cfi-icall") void PipeWireNode::OnNodeParam(void* data, int seq, uint32_t id, @@ -274,6 +278,7 @@ void PipeWireSession::InitPipeWire(int fd) { Finish(VideoCaptureOptions::Status::ERROR); } +RTC_NO_SANITIZE("cfi-icall") bool PipeWireSession::StartPipeWire(int fd) { pw_init(/*argc=*/nullptr, /*argv=*/nullptr); @@ -340,6 +345,7 @@ void PipeWireSession::StopPipeWire() { } } +RTC_NO_SANITIZE("cfi-icall") void PipeWireSession::PipeWireSync() { sync_seq_ = pw_core_sync(pw_core_, PW_ID_CORE, sync_seq_); } @@ -374,6 +380,7 @@ void PipeWireSession::OnCoreDone(void* data, uint32_t id, int seq) { } // static +RTC_NO_SANITIZE("cfi-icall") void PipeWireSession::OnRegistryGlobal(void* data, uint32_t id, uint32_t permissions, diff --git a/third_party/libwebrtc/modules/video_capture/linux/video_capture_pipewire.cc b/third_party/libwebrtc/modules/video_capture/linux/video_capture_pipewire.cc index 1672b7583f58..940db43fd55f 100644 --- a/third_party/libwebrtc/modules/video_capture/linux/video_capture_pipewire.cc +++ b/third_party/libwebrtc/modules/video_capture/linux/video_capture_pipewire.cc @@ -20,6 +20,7 @@ #include "common_video/libyuv/include/webrtc_libyuv.h" #include "modules/portal/pipewire_utils.h" #include "rtc_base/logging.h" +#include "rtc_base/sanitizer.h" #include "rtc_base/string_to_number.h" namespace webrtc { @@ -128,6 +129,7 @@ static spa_pod* BuildFormat(spa_pod_builder* builder, return static_cast(spa_pod_builder_pop(builder, &frames[0])); } +RTC_NO_SANITIZE("cfi-icall") int32_t VideoCaptureModulePipeWire::StartCapture( const VideoCaptureCapability& capability) { RTC_DCHECK_RUN_ON(&api_checker_); @@ -247,6 +249,7 @@ void VideoCaptureModulePipeWire::OnStreamParamChanged( that->OnFormatChanged(format); } +RTC_NO_SANITIZE("cfi-icall") void VideoCaptureModulePipeWire::OnFormatChanged(const struct spa_pod* format) { RTC_CHECK_RUNS_SERIALIZED(&capture_checker_); @@ -395,6 +398,7 @@ static VideoRotation VideorotationFromPipeWireTransform(uint32_t transform) { } } +RTC_NO_SANITIZE("cfi-icall") void VideoCaptureModulePipeWire::ProcessBuffers() { RTC_CHECK_RUNS_SERIALIZED(&capture_checker_); diff --git a/third_party/libwebrtc/modules/video_coding/BUILD.gn b/third_party/libwebrtc/modules/video_coding/BUILD.gn index fa93a074479d..7b8ac7c4c9bf 100644 --- a/third_party/libwebrtc/modules/video_coding/BUILD.gn +++ b/third_party/libwebrtc/modules/video_coding/BUILD.gn @@ -6,6 +6,7 @@ # in the file PATENTS. All contributing project authors may # be found in the AUTHORS file in the root of the source tree. +import("//build/config/linux/pkg_config.gni") import("//third_party/libaom/options.gni") import("../../webrtc.gni") @@ -366,6 +367,12 @@ rtc_source_set("codec_globals_headers") { deps = [ "../../rtc_base:checks" ] } +if (rtc_use_h264 && rtc_system_openh264) { + pkg_config("openh264") { + packages = [ "openh264" ] + } +} + rtc_library("video_coding_utility") { visibility = [ "*" ] sources = [ @@ -435,6 +442,7 @@ rtc_library("video_coding_utility") { "../../rtc_base/system:file_wrapper", "../../rtc_base/system:no_unique_address", "../../rtc_base/task_utils:repeating_task", + "../../video/config:encoder_config", "../rtp_rtcp:rtp_rtcp_format", "svc:scalability_mode_util", "//third_party/abseil-cpp/absl/numeric:bits", @@ -488,10 +496,12 @@ rtc_library("webrtc_h264") { ] if (rtc_use_h264) { - deps += [ - "//third_party/ffmpeg", - "//third_party/openh264:encoder", - ] + deps += [ "//third_party/ffmpeg" ] + if (rtc_system_openh264) { + configs += [ ":openh264" ] + } else { + deps += [ "//third_party/openh264:encoder" ] + } if (!build_with_mozilla) { deps += [ "../../media:rtc_media_base" ] } @@ -829,6 +839,7 @@ if (rtc_include_tests) { "../../rtc_base/synchronization:mutex", "../../rtc_base/system:no_unique_address", "../../test:test_support", + "../../test:video_frame_writer", "../../test:video_test_common", "../../test:video_test_support", "../rtp_rtcp:rtp_rtcp_format", @@ -916,6 +927,7 @@ if (rtc_include_tests) { "../../system_wrappers", "../../test:fileutils", "../../test:test_support", + "../../test:video_frame_writer", "../../test:video_test_common", "../../test:video_test_support", "../../video/config:encoder_config", diff --git a/third_party/libwebrtc/modules/video_coding/OWNERS b/third_party/libwebrtc/modules/video_coding/OWNERS index 2e4d968c98d2..5073079d3459 100644 --- a/third_party/libwebrtc/modules/video_coding/OWNERS +++ b/third_party/libwebrtc/modules/video_coding/OWNERS @@ -4,4 +4,5 @@ ilnik@webrtc.org marpan@webrtc.org philipel@webrtc.org sprang@webrtc.org +ssilkin@webrtc.org stefan@webrtc.org diff --git a/third_party/libwebrtc/modules/video_coding/codecs/av1/BUILD.gn b/third_party/libwebrtc/modules/video_coding/codecs/av1/BUILD.gn index 7d93de7a3a09..197e1f3e1950 100644 --- a/third_party/libwebrtc/modules/video_coding/codecs/av1/BUILD.gn +++ b/third_party/libwebrtc/modules/video_coding/codecs/av1/BUILD.gn @@ -60,6 +60,7 @@ rtc_library("libaom_av1_encoder") { "../../../../api/video_codecs:scalability_mode", "../../../../api/video_codecs:video_codecs_api", "../../../../common_video", + "../../../../modules/rtp_rtcp:rtp_rtcp_format", "../../../../rtc_base:checks", "../../../../rtc_base:logging", "../../../../rtc_base:rtc_numerics", @@ -104,6 +105,7 @@ if (rtc_include_tests) { "../../../../api/units:data_size", "../../../../api/units:time_delta", "../../../../api/video:video_frame", + "../../../../modules/rtp_rtcp:rtp_rtcp_format", "../../../../test:scoped_key_value_config", "../../svc:scalability_mode_util", "../../svc:scalability_structures", diff --git a/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_encoder.cc b/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_encoder.cc index 1bde9b37d793..db9238477d2f 100644 --- a/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_encoder.cc +++ b/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_encoder.cc @@ -30,6 +30,7 @@ #include "api/video_codecs/scalability_mode.h" #include "api/video_codecs/video_codec.h" #include "api/video_codecs/video_encoder.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "modules/video_coding/include/video_codec_interface.h" #include "modules/video_coding/include/video_error_codes.h" #include "modules/video_coding/svc/create_scalability_structure.h" @@ -58,14 +59,14 @@ namespace webrtc { namespace { // Encoder configuration parameters -constexpr int kQpMin = 10; +constexpr int kMinQp = 10; +constexpr int kMinQindex = 40; // Min qindex corresponding to kMinQp. constexpr int kUsageProfile = AOM_USAGE_REALTIME; -constexpr int kMinQindex = 145; // Min qindex threshold for QP scaling. -constexpr int kMaxQindex = 205; // Max qindex threshold for QP scaling. +constexpr int kLowQindex = 145; // Low qindex threshold for QP scaling. +constexpr int kHighQindex = 205; // High qindex threshold for QP scaling. constexpr int kBitDepth = 8; constexpr int kLagInFrames = 0; // No look ahead. -constexpr int kRtpTicksPerSecond = 90000; -constexpr double kMinimumFrameRate = 1.0; +constexpr double kMinFrameRateFps = 1.0; aom_superblock_size_t GetSuperblockSize(int width, int height, int threads) { int resolution = width * height; @@ -107,7 +108,8 @@ class LibaomAv1Encoder final : public VideoEncoder { bool SvcEnabled() const { return svc_params_.has_value(); } // Fills svc_params_ memeber value. Returns false on error. - bool SetSvcParams(ScalableVideoController::StreamLayersConfig svc_config); + bool SetSvcParams(ScalableVideoController::StreamLayersConfig svc_config, + const aom_codec_enc_cfg_t& encoder_config); // Configures the encoder with layer for the next frame. void SetSvcLayerId( const ScalableVideoController::LayerFrameConfig& layer_frame); @@ -128,9 +130,12 @@ class LibaomAv1Encoder final : public VideoEncoder { aom_codec_ctx_t ctx_; aom_codec_enc_cfg_t cfg_; EncodedImageCallback* encoded_image_callback_; + double framerate_fps_; // Current target frame rate. int64_t timestamp_; const LibaomAv1EncoderInfoSettings encoder_info_override_; - int max_consec_frame_drop_; + // TODO(webrtc:351644568): Remove this kill-switch after the feature is fully + // deployed. + bool adaptive_max_consec_drops_; }; int32_t VerifyCodecSettings(const VideoCodec& codec_settings) { @@ -155,18 +160,18 @@ int32_t VerifyCodecSettings(const VideoCodec& codec_settings) { if (codec_settings.maxFramerate < 1) { return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; } - if (codec_settings.qpMax < kQpMin || codec_settings.qpMax > 63) { + if (codec_settings.qpMax < kMinQp || codec_settings.qpMax > 63) { return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; } return WEBRTC_VIDEO_CODEC_OK; } -int GetMaxConsecutiveFrameDrop(const FieldTrialsView& field_trials) { - webrtc::FieldTrialParameter maxdrop("maxdrop", 0); - webrtc::ParseFieldTrial( - {&maxdrop}, - field_trials.Lookup("WebRTC-LibaomAv1Encoder-MaxConsecFrameDrop")); - return maxdrop; +int GetMaxConsecDrops(double framerate_fps) { + // Consecutive frame drops result in a video freeze. We want to minimize the + // max number of consecutive drops and, at the same time, keep the value high + // enough to let encoder drain the buffer at overshoot. + constexpr double kMaxFreezeSeconds = 0.25; + return std::ceil(kMaxFreezeSeconds * framerate_fps); } LibaomAv1Encoder::LibaomAv1Encoder(const Environment& env, @@ -176,9 +181,11 @@ LibaomAv1Encoder::LibaomAv1Encoder(const Environment& env, settings_(std::move(settings)), frame_for_encode_(nullptr), encoded_image_callback_(nullptr), + framerate_fps_(0), timestamp_(0), encoder_info_override_(env.field_trials()), - max_consec_frame_drop_(GetMaxConsecutiveFrameDrop(env.field_trials())) {} + adaptive_max_consec_drops_(!env.field_trials().IsDisabled( + "WebRTC-LibaomAv1Encoder-AdaptiveMaxConsecDrops")) {} LibaomAv1Encoder::~LibaomAv1Encoder() { Release(); @@ -223,10 +230,6 @@ int LibaomAv1Encoder::InitEncode(const VideoCodec* codec_settings, return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; } - if (!SetSvcParams(svc_controller_->StreamConfig())) { - return WEBRTC_VIDEO_CODEC_ERROR; - } - // Initialize encoder configuration structure with default values aom_codec_err_t ret = aom_codec_enc_config_default(aom_codec_av1_cx(), &cfg_, kUsageProfile); @@ -242,12 +245,12 @@ int LibaomAv1Encoder::InitEncode(const VideoCodec* codec_settings, cfg_.g_threads = NumberOfThreads(cfg_.g_w, cfg_.g_h, settings.number_of_cores); cfg_.g_timebase.num = 1; - cfg_.g_timebase.den = kRtpTicksPerSecond; + cfg_.g_timebase.den = kVideoPayloadTypeFrequency; cfg_.rc_target_bitrate = encoder_settings_.startBitrate; // kilobits/sec. cfg_.rc_dropframe_thresh = encoder_settings_.GetFrameDropEnabled() ? 30 : 0; cfg_.g_input_bit_depth = kBitDepth; cfg_.kf_mode = AOM_KF_DISABLED; - cfg_.rc_min_quantizer = kQpMin; + cfg_.rc_min_quantizer = kMinQp; cfg_.rc_max_quantizer = encoder_settings_.qpMax; cfg_.rc_undershoot_pct = 50; cfg_.rc_overshoot_pct = 50; @@ -276,6 +279,11 @@ int LibaomAv1Encoder::InitEncode(const VideoCodec* codec_settings, << " on aom_codec_enc_init."; return WEBRTC_VIDEO_CODEC_ERROR; } + + if (!SetSvcParams(svc_controller_->StreamConfig(), cfg_)) { + return WEBRTC_VIDEO_CODEC_ERROR; + } + inited_ = true; // Set control parameters @@ -299,12 +307,6 @@ int LibaomAv1Encoder::InitEncode(const VideoCodec* codec_settings, SET_ENCODER_PARAM_OR_RETURN_ERROR(AV1E_SET_ENABLE_PALETTE, 0); } - if (codec_settings->mode == VideoCodecMode::kRealtimeVideo && - encoder_settings_.GetFrameDropEnabled() && max_consec_frame_drop_ > 0) { - SET_ENCODER_PARAM_OR_RETURN_ERROR(AV1E_SET_MAX_CONSEC_FRAME_DROP_CBR, - max_consec_frame_drop_); - } - if (cfg_.g_threads == 8) { // Values passed to AV1E_SET_TILE_ROWS and AV1E_SET_TILE_COLUMNS are log2() // based. @@ -441,7 +443,8 @@ int LibaomAv1Encoder::NumberOfThreads(int width, } bool LibaomAv1Encoder::SetSvcParams( - ScalableVideoController::StreamLayersConfig svc_config) { + ScalableVideoController::StreamLayersConfig svc_config, + const aom_codec_enc_cfg_t& encoder_config) { bool svc_enabled = svc_config.num_spatial_layers > 1 || svc_config.num_temporal_layers > 1; if (!svc_enabled) { @@ -466,8 +469,8 @@ bool LibaomAv1Encoder::SetSvcParams( int num_layers = svc_config.num_spatial_layers * svc_config.num_temporal_layers; for (int i = 0; i < num_layers; ++i) { - svc_params.min_quantizers[i] = kQpMin; - svc_params.max_quantizers[i] = encoder_settings_.qpMax; + svc_params.min_quantizers[i] = encoder_config.rc_min_quantizer; + svc_params.max_quantizers[i] = encoder_config.rc_max_quantizer; } // Assume each temporal layer doubles framerate. @@ -653,8 +656,7 @@ int32_t LibaomAv1Encoder::Encode( return WEBRTC_VIDEO_CODEC_ENCODER_FAILURE; } - const uint32_t duration = - kRtpTicksPerSecond / static_cast(encoder_settings_.maxFramerate); + const uint32_t duration = kVideoPayloadTypeFrequency / framerate_fps_; timestamp_ += duration; const size_t num_spatial_layers = @@ -791,9 +793,9 @@ void LibaomAv1Encoder::SetRates(const RateControlParameters& parameters) { RTC_LOG(LS_WARNING) << "SetRates() while encoder is not initialized"; return; } - if (parameters.framerate_fps < kMinimumFrameRate) { + if (parameters.framerate_fps < kMinFrameRateFps) { RTC_LOG(LS_WARNING) << "Unsupported framerate (must be >= " - << kMinimumFrameRate + << kMinFrameRateFps << " ): " << parameters.framerate_fps; return; } @@ -816,26 +818,31 @@ void LibaomAv1Encoder::SetRates(const RateControlParameters& parameters) { if (SvcEnabled()) { for (int sid = 0; sid < svc_params_->number_spatial_layers; ++sid) { // libaom bitrate for spatial id S and temporal id T means bitrate - // of frames with spatial_id=S and temporal_id<=T - // while `parameters.bitrate` provdies bitrate of frames with - // spatial_id=S and temporal_id=T - int accumulated_bitrate_bps = 0; + // of frames with spatial_id=S and temporal_id<=T. for (int tid = 0; tid < svc_params_->number_temporal_layers; ++tid) { int layer_index = sid * svc_params_->number_temporal_layers + tid; - accumulated_bitrate_bps += parameters.bitrate.GetBitrate(sid, tid); // `svc_params_->layer_target_bitrate` expects bitrate in kbps. svc_params_->layer_target_bitrate[layer_index] = - accumulated_bitrate_bps / 1000; + parameters.bitrate.GetTemporalLayerSum(sid, tid) / 1000; } } SetEncoderControlParameters(AV1E_SET_SVC_PARAMS, &*svc_params_); } - rates_configured_ = true; + if (adaptive_max_consec_drops_ && + (!rates_configured_ || framerate_fps_ != parameters.framerate_fps)) { + int max_consec_drops = GetMaxConsecDrops(parameters.framerate_fps); + if (!SetEncoderControlParameters(AV1E_SET_MAX_CONSEC_FRAME_DROP_CBR, + max_consec_drops)) { + RTC_LOG(LS_WARNING) + << "Failed to set AV1E_SET_MAX_CONSEC_FRAME_DROP_CBR to " + << max_consec_drops; + } + } - // Set frame rate to closest integer value. - encoder_settings_.maxFramerate = - static_cast(parameters.framerate_fps + 0.5); + framerate_fps_ = parameters.framerate_fps; + + rates_configured_ = true; } VideoEncoder::EncoderInfo LibaomAv1Encoder::GetEncoderInfo() const { @@ -847,7 +854,7 @@ VideoEncoder::EncoderInfo LibaomAv1Encoder::GetEncoderInfo() const { info.scaling_settings = (inited_ && !encoder_settings_.AV1().automatic_resize_on) ? VideoEncoder::ScalingSettings::kOff - : VideoEncoder::ScalingSettings(kMinQindex, kMaxQindex); + : VideoEncoder::ScalingSettings(kLowQindex, kHighQindex); info.preferred_pixel_formats = {VideoFrameBuffer::Type::kI420, VideoFrameBuffer::Type::kNV12}; if (SvcEnabled()) { @@ -863,6 +870,8 @@ VideoEncoder::EncoderInfo LibaomAv1Encoder::GetEncoderInfo() const { info.resolution_bitrate_limits = encoder_info_override_.resolution_bitrate_limits(); } + + info.min_qp = kMinQindex; return info; } diff --git a/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_encoder_unittest.cc b/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_encoder_unittest.cc index abb6fce0cf16..a00b03aeda54 100644 --- a/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_encoder_unittest.cc +++ b/third_party/libwebrtc/modules/video_coding/codecs/av1/libaom_av1_encoder_unittest.cc @@ -10,6 +10,7 @@ #include "modules/video_coding/codecs/av1/libaom_av1_encoder.h" +#include #include #include #include @@ -22,6 +23,7 @@ #include "api/test/frame_generator_interface.h" #include "api/video_codecs/video_codec.h" #include "api/video_codecs/video_encoder.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "modules/video_coding/codecs/test/encoded_video_frame_producer.h" #include "modules/video_coding/include/video_error_codes.h" #include "test/gmock.h" @@ -37,6 +39,7 @@ using ::testing::Eq; using ::testing::Field; using ::testing::IsEmpty; using ::testing::SizeIs; +using ::testing::Values; VideoCodec DefaultCodecSettings() { VideoCodec codec_settings; @@ -199,32 +202,54 @@ TEST(LibaomAv1EncoderTest, CheckOddDimensionsWithSpatialLayers) { ASSERT_THAT(encoded_frames, SizeIs(6)); } -TEST(LibaomAv1EncoderTest, WithMaximumConsecutiveFrameDrop) { - auto field_trials = std::make_unique( - "WebRTC-LibaomAv1Encoder-MaxConsecFrameDrop/maxdrop:2/"); - const Environment env = CreateEnvironment(std::move(field_trials)); +class LibaomAv1EncoderMaxConsecDropTest + : public ::testing::TestWithParam {}; + +TEST_P(LibaomAv1EncoderMaxConsecDropTest, MaxConsecDrops) { VideoBitrateAllocation allocation; - allocation.SetBitrate(0, 0, 1000); // some very low bitrate - std::unique_ptr encoder = CreateLibaomAv1Encoder(env); + allocation.SetBitrate(0, 0, + 1000); // Very low bitrate to provoke frame drops. + std::unique_ptr encoder = + CreateLibaomAv1Encoder(CreateEnvironment()); VideoCodec codec_settings = DefaultCodecSettings(); codec_settings.SetFrameDropEnabled(true); codec_settings.SetScalabilityMode(ScalabilityMode::kL1T1); codec_settings.startBitrate = allocation.get_sum_kbps(); + codec_settings.maxFramerate = GetParam(); ASSERT_EQ(encoder->InitEncode(&codec_settings, DefaultEncoderSettings()), WEBRTC_VIDEO_CODEC_OK); encoder->SetRates(VideoEncoder::RateControlParameters( allocation, codec_settings.maxFramerate)); - EncodedVideoFrameProducer evfp(*encoder); - evfp.SetResolution( - RenderResolution{codec_settings.width, codec_settings.height}); - // We should code the first frame, skip two, then code another frame. std::vector encoded_frames = - evfp.SetNumInputFrames(4).Encode(); - ASSERT_THAT(encoded_frames, SizeIs(2)); - // The 4 frames have default Rtp-timestamps of 1000, 4000, 7000, 10000. - ASSERT_THAT(encoded_frames[1].encoded_image.RtpTimestamp(), 10000); + EncodedVideoFrameProducer(*encoder) + .SetNumInputFrames(60) + .SetFramerateFps(codec_settings.maxFramerate) + .SetResolution(RenderResolution{320, 180}) + .Encode(); + ASSERT_GE(encoded_frames.size(), 2u); + + int max_consec_drops = 0; + for (size_t i = 1; i < encoded_frames.size(); ++i) { + uint32_t frame_duration_rtp = + encoded_frames[i].encoded_image.RtpTimestamp() - + encoded_frames[i - 1].encoded_image.RtpTimestamp(); + // X consecutive drops result in a freeze of (X + 1) frame duration. + // Subtract 1 to get pure number of drops. + int num_drops = frame_duration_rtp * codec_settings.maxFramerate / + kVideoPayloadTypeFrequency - + 1; + max_consec_drops = std::max(max_consec_drops, num_drops); + } + + const int expected_max_consec_drops = + std::ceil(0.25 * codec_settings.maxFramerate); + EXPECT_EQ(max_consec_drops, expected_max_consec_drops); } +INSTANTIATE_TEST_SUITE_P(LibaomAv1EncoderMaxConsecDropTests, + LibaomAv1EncoderMaxConsecDropTest, + Values(1, 2, 5, 15, 30, 60)); + TEST(LibaomAv1EncoderTest, EncoderInfoWithoutResolutionBitrateLimits) { std::unique_ptr encoder = CreateLibaomAv1Encoder(CreateEnvironment()); diff --git a/third_party/libwebrtc/modules/video_coding/codecs/h264/include/h264_globals.h b/third_party/libwebrtc/modules/video_coding/codecs/h264/include/h264_globals.h index 6a1de382dce7..dac36dd734ce 100644 --- a/third_party/libwebrtc/modules/video_coding/codecs/h264/include/h264_globals.h +++ b/third_party/libwebrtc/modules/video_coding/codecs/h264/include/h264_globals.h @@ -16,6 +16,7 @@ #include #include +#include #include "modules/video_coding/codecs/interface/common_constants.h" #include "rtc_base/checks.h" @@ -72,8 +73,6 @@ struct NaluInfo { } }; -const size_t kMaxNalusPerPacket = 10; - struct RTPVideoHeaderH264 { // The NAL unit type. If this is a header for a // fragmented packet, it's the NAL unit type of @@ -83,8 +82,7 @@ struct RTPVideoHeaderH264 { uint8_t nalu_type; // The packetization type of this buffer - single, aggregated or fragmented. H264PacketizationTypes packetization_type; - NaluInfo nalus[kMaxNalusPerPacket]; - size_t nalus_length; + std::vector nalus; // The packetization mode of this transport. Packetization mode // determines which packetization types are allowed when packetizing. H264PacketizationMode packetization_mode; @@ -93,8 +91,7 @@ struct RTPVideoHeaderH264 { const RTPVideoHeaderH264& rhs) { return lhs.nalu_type == rhs.nalu_type && lhs.packetization_type == rhs.packetization_type && - std::equal(lhs.nalus, lhs.nalus + lhs.nalus_length, rhs.nalus, - rhs.nalus + rhs.nalus_length) && + lhs.nalus == rhs.nalus && lhs.packetization_mode == rhs.packetization_mode; } 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 3d07a3d1f73d..2b144ac5da12 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 @@ -582,7 +582,8 @@ TEST(VideoCodecTest, DISABLED_EncodeDecode) { uint32_t timestamp_rtp = 90000; std::map frame_settings; for (int frame_num = 0; frame_num < num_frames; ++frame_num) { - encoding_settings.keyframe = (frame_num % (key_interval + 1) == 0); + encoding_settings.keyframe = + (key_interval > 0 && (frame_num % key_interval) == 0); frame_settings.emplace(timestamp_rtp, encoding_settings); timestamp_rtp += k90kHz / framerate; } 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 9c708263a7ee..40569597f355 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 @@ -62,7 +62,7 @@ #include "test/testsupport/file_utils.h" #include "test/testsupport/frame_writer.h" #include "test/video_codec_settings.h" -#include "video/config/simulcast.h" +#include "video/config/encoder_stream_factory.h" #include "video/config/video_encoder_config.h" namespace webrtc { @@ -72,16 +72,21 @@ namespace { using VideoStatistics = VideoCodecTestStats::VideoStatistics; const int kBaseKeyFrameInterval = 3000; -const double kBitratePriority = 1.0; const int kDefaultMaxFramerateFps = 30; const int kMaxQp = 56; void ConfigureSimulcast(VideoCodec* codec_settings) { FieldTrialBasedConfig trials; - const std::vector streams = cricket::GetSimulcastConfig( - /*min_layer=*/1, codec_settings->numberOfSimulcastStreams, - codec_settings->width, codec_settings->height, kBitratePriority, kMaxQp, - /* is_screenshare = */ false, true, trials, webrtc::kVideoCodecVP8); + VideoEncoderConfig encoder_config; + encoder_config.codec_type = codec_settings->codecType; + encoder_config.number_of_streams = codec_settings->numberOfSimulcastStreams; + encoder_config.simulcast_layers.resize( + codec_settings->numberOfSimulcastStreams); + VideoEncoder::EncoderInfo encoder_info; + auto stream_factory = + rtc::make_ref_counted(encoder_info); + const std::vector streams = stream_factory->CreateEncoderStreams( + trials, codec_settings->width, codec_settings->height, encoder_config); for (size_t i = 0; i < streams.size(); ++i) { SimulcastStream* ss = &codec_settings->simulcastStream[i]; @@ -92,7 +97,7 @@ void ConfigureSimulcast(VideoCodec* codec_settings) { 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->qpMax = kMaxQp; ss->active = true; } } @@ -376,7 +381,7 @@ void VideoCodecTestFixtureImpl::H264KeyframeChecker::CheckEncodedFrame( bool contains_pps = false; bool contains_idr = false; const std::vector nalu_indices = - webrtc::H264::FindNaluIndices(encoded_frame.data(), encoded_frame.size()); + webrtc::H264::FindNaluIndices(encoded_frame); for (const webrtc::H264::NaluIndex& index : nalu_indices) { webrtc::H264::NaluType nalu_type = webrtc::H264::ParseNaluType( encoded_frame.data()[index.payload_start_offset]); diff --git a/third_party/libwebrtc/modules/video_coding/codecs/test/videoprocessor.cc b/third_party/libwebrtc/modules/video_coding/codecs/test/videoprocessor.cc index c5e7ed0ebb44..2133a94ab4fb 100644 --- a/third_party/libwebrtc/modules/video_coding/codecs/test/videoprocessor.cc +++ b/third_party/libwebrtc/modules/video_coding/codecs/test/videoprocessor.cc @@ -52,7 +52,7 @@ size_t GetMaxNaluSizeBytes(const EncodedImage& encoded_frame, return 0; std::vector nalu_indices = - webrtc::H264::FindNaluIndices(encoded_frame.data(), encoded_frame.size()); + webrtc::H264::FindNaluIndices(encoded_frame); RTC_CHECK(!nalu_indices.empty()); diff --git a/third_party/libwebrtc/modules/video_coding/codecs/vp8/libvpx_vp8_encoder.cc b/third_party/libwebrtc/modules/video_coding/codecs/vp8/libvpx_vp8_encoder.cc index 5efbf8806575..ad8bfa83b45b 100644 --- a/third_party/libwebrtc/modules/video_coding/codecs/vp8/libvpx_vp8_encoder.cc +++ b/third_party/libwebrtc/modules/video_coding/codecs/vp8/libvpx_vp8_encoder.cc @@ -68,6 +68,7 @@ constexpr char kVp8ForcePartitionResilience[] = // bitstream range of [0, 127] and not the user-level range of [0,63]. constexpr int kLowVp8QpThreshold = 29; constexpr int kHighVp8QpThreshold = 95; +constexpr int kScreenshareMinQp = 15; constexpr int kTokenPartitions = VP8_ONE_TOKENPARTITION; constexpr uint32_t kVp832ByteAlign = 32u; @@ -1352,6 +1353,10 @@ VideoEncoder::EncoderInfo LibvpxVp8Encoder::GetEncoderInfo() const { } } } + + if (codec_.mode == VideoCodecMode::kScreensharing) { + info.min_qp = kScreenshareMinQp; + } } return info; diff --git a/third_party/libwebrtc/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc b/third_party/libwebrtc/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc index 9c84b7768dc7..b4508a224e3b 100644 --- a/third_party/libwebrtc/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc +++ b/third_party/libwebrtc/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc @@ -1855,6 +1855,10 @@ VideoEncoder::EncoderInfo LibvpxVp9Encoder::GetEncoderInfo() const { info.preferred_pixel_formats = {VideoFrameBuffer::Type::kI420, VideoFrameBuffer::Type::kNV12}; } + + if (codec_.mode == VideoCodecMode::kScreensharing) { + info.min_qp = variable_framerate_screenshare::kMinQP; + } } if (!encoder_info_override_.resolution_bitrate_limits().empty()) { info.resolution_bitrate_limits = diff --git a/third_party/libwebrtc/modules/video_coding/deprecated/BUILD.gn b/third_party/libwebrtc/modules/video_coding/deprecated/BUILD.gn index 047541be4c10..dacde525e5fc 100644 --- a/third_party/libwebrtc/modules/video_coding/deprecated/BUILD.gn +++ b/third_party/libwebrtc/modules/video_coding/deprecated/BUILD.gn @@ -134,6 +134,7 @@ rtc_library("deprecated_session_info") { "../../../modules:module_api_public", "../../../modules/video_coding:codec_globals_headers", "../../../rtc_base:logging", + "//third_party/abseil-cpp/absl/algorithm:container", "//third_party/abseil-cpp/absl/types:variant", ] sources = [ diff --git a/third_party/libwebrtc/modules/video_coding/deprecated/jitter_buffer_unittest.cc b/third_party/libwebrtc/modules/video_coding/deprecated/jitter_buffer_unittest.cc index ee88f9c9cbb7..cc504454c67a 100644 --- a/third_party/libwebrtc/modules/video_coding/deprecated/jitter_buffer_unittest.cc +++ b/third_party/libwebrtc/modules/video_coding/deprecated/jitter_buffer_unittest.cc @@ -903,10 +903,8 @@ TEST_F(TestBasicJitterBuffer, SpsAndPpsHandling) { packet_->markerBit = true; packet_->video_header.codec = kVideoCodecH264; h264_header.nalu_type = H264::NaluType::kIdr; - h264_header.nalus[0].type = H264::NaluType::kIdr; - h264_header.nalus[0].sps_id = -1; - h264_header.nalus[0].pps_id = 0; - h264_header.nalus_length = 1; + h264_header.nalus = { + {.type = H264::NaluType::kIdr, .sps_id = -1, .pps_id = 0}}; bool retransmitted = false; EXPECT_EQ(kCompleteSession, jitter_buffer_->InsertPacket(*packet_, &retransmitted)); @@ -922,13 +920,9 @@ TEST_F(TestBasicJitterBuffer, SpsAndPpsHandling) { packet_->markerBit = false; packet_->video_header.codec = kVideoCodecH264; h264_header.nalu_type = H264::NaluType::kStapA; - h264_header.nalus[0].type = H264::NaluType::kSps; - h264_header.nalus[0].sps_id = 0; - h264_header.nalus[0].pps_id = -1; - h264_header.nalus[1].type = H264::NaluType::kPps; - h264_header.nalus[1].sps_id = 0; - h264_header.nalus[1].pps_id = 0; - h264_header.nalus_length = 2; + h264_header.nalus = { + {.type = H264::NaluType::kSps, .sps_id = 0, .pps_id = -1}, + {.type = H264::NaluType::kPps, .sps_id = 0, .pps_id = 0}}; // Not complete since the marker bit hasn't been received. EXPECT_EQ(kIncomplete, jitter_buffer_->InsertPacket(*packet_, &retransmitted)); @@ -940,10 +934,8 @@ TEST_F(TestBasicJitterBuffer, SpsAndPpsHandling) { packet_->markerBit = true; packet_->video_header.codec = kVideoCodecH264; h264_header.nalu_type = H264::NaluType::kIdr; - h264_header.nalus[0].type = H264::NaluType::kIdr; - h264_header.nalus[0].sps_id = -1; - h264_header.nalus[0].pps_id = 0; - h264_header.nalus_length = 1; + h264_header.nalus = { + {.type = H264::NaluType::kIdr, .sps_id = -1, .pps_id = 0}}; // Complete and decodable since the pps and sps are received in the first // packet of this frame. EXPECT_EQ(kCompleteSession, @@ -961,10 +953,9 @@ TEST_F(TestBasicJitterBuffer, SpsAndPpsHandling) { packet_->markerBit = true; packet_->video_header.codec = kVideoCodecH264; h264_header.nalu_type = H264::NaluType::kSlice; - h264_header.nalus[0].type = H264::NaluType::kSlice; - h264_header.nalus[0].sps_id = -1; - h264_header.nalus[0].pps_id = 0; - h264_header.nalus_length = 1; + h264_header.nalus = { + {.type = H264::NaluType::kIdr, .sps_id = -1, .pps_id = 0}}; + // Complete and decodable since sps, pps and key frame has been received. EXPECT_EQ(kCompleteSession, jitter_buffer_->InsertPacket(*packet_, &retransmitted)); diff --git a/third_party/libwebrtc/modules/video_coding/deprecated/session_info.cc b/third_party/libwebrtc/modules/video_coding/deprecated/session_info.cc index b15dc0a9ff10..ed0fc6496d7b 100644 --- a/third_party/libwebrtc/modules/video_coding/deprecated/session_info.cc +++ b/third_party/libwebrtc/modules/video_coding/deprecated/session_info.cc @@ -14,6 +14,7 @@ #include +#include "absl/algorithm/container.h" #include "absl/types/variant.h" #include "modules/include/module_common_types.h" #include "modules/include/module_common_types_public.h" @@ -139,9 +140,7 @@ std::vector VCMSessionInfo::GetNaluInfos() const { for (const VCMPacket& packet : packets_) { const auto& h264 = absl::get(packet.video_header.video_type_header); - for (size_t i = 0; i < h264.nalus_length; ++i) { - nalu_infos.push_back(h264.nalus[i]); - } + absl::c_copy(h264.nalus, std::back_inserter(nalu_infos)); } return nalu_infos; } diff --git a/third_party/libwebrtc/modules/video_coding/h264_sps_pps_tracker.cc b/third_party/libwebrtc/modules/video_coding/h264_sps_pps_tracker.cc index 5a7eae7b42c9..834a36d53ef0 100644 --- a/third_party/libwebrtc/modules/video_coding/h264_sps_pps_tracker.cc +++ b/third_party/libwebrtc/modules/video_coding/h264_sps_pps_tracker.cc @@ -58,8 +58,7 @@ H264SpsPpsTracker::FixedBitstream H264SpsPpsTracker::CopyAndFixBitstream( auto sps = sps_data_.end(); auto pps = pps_data_.end(); - for (size_t i = 0; i < h264_header.nalus_length; ++i) { - const NaluInfo& nalu = h264_header.nalus[i]; + for (const NaluInfo& nalu : h264_header.nalus) { switch (nalu.type) { case H264::NaluType::kSps: { SpsInfo& sps_info = sps_data_[nalu.sps_id]; @@ -140,7 +139,7 @@ H264SpsPpsTracker::FixedBitstream H264SpsPpsTracker::CopyAndFixBitstream( nalu_ptr += segment_length; } } else { - if (h264_header.nalus_length > 0) { + if (!h264_header.nalus.empty()) { required_size += sizeof(start_code_h264); } required_size += bitstream.size(); @@ -160,21 +159,11 @@ H264SpsPpsTracker::FixedBitstream H264SpsPpsTracker::CopyAndFixBitstream( fixed.bitstream.AppendData(pps->second.data.get(), pps->second.size); // Update codec header to reflect the newly added SPS and PPS. - NaluInfo sps_info; - sps_info.type = H264::NaluType::kSps; - sps_info.sps_id = sps->first; - sps_info.pps_id = -1; - NaluInfo pps_info; - pps_info.type = H264::NaluType::kPps; - pps_info.sps_id = sps->first; - pps_info.pps_id = pps->first; - if (h264_header.nalus_length + 2 <= kMaxNalusPerPacket) { - h264_header.nalus[h264_header.nalus_length++] = sps_info; - h264_header.nalus[h264_header.nalus_length++] = pps_info; - } else { - RTC_LOG(LS_WARNING) << "Not enough space in H.264 codec header to insert " - "SPS/PPS provided out-of-band."; - } + h264_header.nalus.push_back( + {.type = H264::NaluType::kSps, .sps_id = sps->first, .pps_id = -1}); + h264_header.nalus.push_back({.type = H264::NaluType::kPps, + .sps_id = sps->first, + .pps_id = pps->first}); } // Copy the rest of the bitstream and insert start codes. @@ -196,7 +185,7 @@ H264SpsPpsTracker::FixedBitstream H264SpsPpsTracker::CopyAndFixBitstream( nalu_ptr += segment_length; } } else { - if (h264_header.nalus_length > 0) { + if (!h264_header.nalus.empty()) { fixed.bitstream.AppendData(start_code_h264); } fixed.bitstream.AppendData(bitstream.data(), bitstream.size()); @@ -228,9 +217,9 @@ void H264SpsPpsTracker::InsertSpsPpsNalus(const std::vector& sps, return; } absl::optional parsed_sps = SpsParser::ParseSps( - sps.data() + kNaluHeaderOffset, sps.size() - kNaluHeaderOffset); + rtc::ArrayView(sps).subview(kNaluHeaderOffset)); absl::optional parsed_pps = PpsParser::ParsePps( - pps.data() + kNaluHeaderOffset, pps.size() - kNaluHeaderOffset); + rtc::ArrayView(pps).subview(kNaluHeaderOffset)); if (!parsed_sps) { RTC_LOG(LS_WARNING) << "Failed to parse SPS."; diff --git a/third_party/libwebrtc/modules/video_coding/h264_sps_pps_tracker.h b/third_party/libwebrtc/modules/video_coding/h264_sps_pps_tracker.h index 600e2ee397aa..3c0928d32489 100644 --- a/third_party/libwebrtc/modules/video_coding/h264_sps_pps_tracker.h +++ b/third_party/libwebrtc/modules/video_coding/h264_sps_pps_tracker.h @@ -66,8 +66,8 @@ class H264SpsPpsTracker { std::unique_ptr data; }; - std::map pps_data_; - std::map sps_data_; + std::map pps_data_; + std::map sps_data_; }; } // namespace video_coding diff --git a/third_party/libwebrtc/modules/video_coding/h264_sps_pps_tracker_unittest.cc b/third_party/libwebrtc/modules/video_coding/h264_sps_pps_tracker_unittest.cc index 76591e9f5c7d..86c63a38a2e8 100644 --- a/third_party/libwebrtc/modules/video_coding/h264_sps_pps_tracker_unittest.cc +++ b/third_party/libwebrtc/modules/video_coding/h264_sps_pps_tracker_unittest.cc @@ -26,6 +26,7 @@ namespace video_coding { namespace { using ::testing::ElementsAreArray; +using ::testing::SizeIs; const uint8_t start_code[] = {0, 0, 0, 1}; @@ -64,7 +65,6 @@ class H264VideoHeader : public RTPVideoHeader { codec = kVideoCodecH264; is_first_packet_in_frame = false; auto& h264_header = video_type_header.emplace(); - h264_header.nalus_length = 0; h264_header.packetization_type = kH264SingleNalu; } @@ -87,7 +87,7 @@ class TestH264SpsPpsTracker : public ::testing::Test { data->push_back(H264::NaluType::kSps); data->push_back(sps_id); // The sps data, just a single byte. - header->h264().nalus[header->h264().nalus_length++] = info; + header->h264().nalus.push_back(info); } void AddPps(H264VideoHeader* header, @@ -101,7 +101,7 @@ class TestH264SpsPpsTracker : public ::testing::Test { data->push_back(H264::NaluType::kPps); data->push_back(pps_id); // The pps data, just a single byte. - header->h264().nalus[header->h264().nalus_length++] = info; + header->h264().nalus.push_back(info); } void AddIdr(H264VideoHeader* header, int pps_id) { @@ -110,7 +110,7 @@ class TestH264SpsPpsTracker : public ::testing::Test { info.sps_id = -1; info.pps_id = pps_id; - header->h264().nalus[header->h264().nalus_length++] = info; + header->h264().nalus.push_back(info); } protected: @@ -133,7 +133,7 @@ TEST_F(TestH264SpsPpsTracker, FuAFirstPacket) { uint8_t data[] = {1, 2, 3}; H264VideoHeader header; header.h264().packetization_type = kH264FuA; - header.h264().nalus_length = 1; + header.h264().nalus.resize(1); header.is_first_packet_in_frame = true; H264SpsPpsTracker::FixedBitstream fixed = @@ -201,7 +201,7 @@ TEST_F(TestH264SpsPpsTracker, ConsecutiveStapA) { TEST_F(TestH264SpsPpsTracker, SingleNaluInsertStartCode) { uint8_t data[] = {1, 2, 3}; H264VideoHeader header; - header.h264().nalus_length = 1; + header.h264().nalus.resize(1); H264SpsPpsTracker::FixedBitstream fixed = tracker_.CopyAndFixBitstream(data, &header); @@ -217,8 +217,8 @@ TEST_F(TestH264SpsPpsTracker, NoStartCodeInsertedForSubsequentFuAPacket) { std::vector data = {1, 2, 3}; H264VideoHeader header; header.h264().packetization_type = kH264FuA; - // Since no NALU begin in this packet the nalus_length is zero. - header.h264().nalus_length = 0; + // Since no NALU begin in this packet the nalus are empty. + header.h264().nalus.clear(); H264SpsPpsTracker::FixedBitstream fixed = tracker_.CopyAndFixBitstream(data, &header); @@ -330,12 +330,12 @@ TEST_F(TestH264SpsPpsTracker, SpsPpsOutOfBand) { H264VideoHeader idr_header; idr_header.is_first_packet_in_frame = true; AddIdr(&idr_header, 0); - EXPECT_EQ(idr_header.h264().nalus_length, 1u); + EXPECT_THAT(idr_header.h264().nalus, SizeIs(1)); H264SpsPpsTracker::FixedBitstream fixed = tracker_.CopyAndFixBitstream(kData, &idr_header); - EXPECT_EQ(idr_header.h264().nalus_length, 3u); + EXPECT_THAT(idr_header.h264().nalus, SizeIs(3)); EXPECT_EQ(idr_header.width, 320u); EXPECT_EQ(idr_header.height, 240u); ExpectSpsPpsIdr(idr_header.h264(), 0, 0); diff --git a/third_party/libwebrtc/modules/video_coding/h26x_packet_buffer.cc b/third_party/libwebrtc/modules/video_coding/h26x_packet_buffer.cc index 133ab4708aa1..e9f6353bb8ba 100644 --- a/third_party/libwebrtc/modules/video_coding/h26x_packet_buffer.cc +++ b/third_party/libwebrtc/modules/video_coding/h26x_packet_buffer.cc @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -42,24 +43,15 @@ int64_t EuclideanMod(int64_t n, int64_t div) { return (n %= div) < 0 ? n + div : n; } -rtc::ArrayView GetNaluInfos( - const RTPVideoHeaderH264& h264_header) { - if (h264_header.nalus_length > kMaxNalusPerPacket) { - return {}; - } - - return rtc::MakeArrayView(h264_header.nalus, h264_header.nalus_length); -} - bool IsFirstPacketOfFragment(const RTPVideoHeaderH264& h264_header) { - return h264_header.nalus_length > 0; + return !h264_header.nalus.empty(); } bool BeginningOfIdr(const H26xPacketBuffer::Packet& packet) { const auto& h264_header = absl::get(packet.video_header.video_type_header); const bool contains_idr_nalu = - absl::c_any_of(GetNaluInfos(h264_header), [](const auto& nalu_info) { + absl::c_any_of(h264_header.nalus, [](const auto& nalu_info) { return nalu_info.type == H264::NaluType::kIdr; }); switch (h264_header.packetization_type) { @@ -76,15 +68,25 @@ bool BeginningOfIdr(const H26xPacketBuffer::Packet& packet) { bool HasSps(const H26xPacketBuffer::Packet& packet) { auto& h264_header = absl::get(packet.video_header.video_type_header); - return absl::c_any_of(GetNaluInfos(h264_header), [](const auto& nalu_info) { + return absl::c_any_of(h264_header.nalus, [](const auto& nalu_info) { return nalu_info.type == H264::NaluType::kSps; }); } +int64_t* GetContinuousSequence(rtc::ArrayView last_continuous, + int64_t unwrapped_seq_num) { + for (int64_t& last : last_continuous) { + if (unwrapped_seq_num - 1 == last) { + return &last; + } + } + return nullptr; +} + #ifdef RTC_ENABLE_H265 bool HasVps(const H26xPacketBuffer::Packet& packet) { - std::vector nalu_indices = H265::FindNaluIndices( - packet.video_payload.cdata(), packet.video_payload.size()); + std::vector nalu_indices = + H265::FindNaluIndices(packet.video_payload); return absl::c_any_of((nalu_indices), [&packet]( const H265::NaluIndex& nalu_index) { return H265::ParseNaluType( @@ -97,7 +99,9 @@ bool HasVps(const H26xPacketBuffer::Packet& packet) { } // namespace H26xPacketBuffer::H26xPacketBuffer(bool h264_idr_only_keyframes_allowed) - : h264_idr_only_keyframes_allowed_(h264_idr_only_keyframes_allowed) {} + : h264_idr_only_keyframes_allowed_(h264_idr_only_keyframes_allowed) { + last_continuous_in_sequence_.fill(std::numeric_limits::min()); +} H26xPacketBuffer::InsertResult H26xPacketBuffer::InsertPacket( std::unique_ptr packet) { @@ -106,7 +110,7 @@ H26xPacketBuffer::InsertResult H26xPacketBuffer::InsertPacket( InsertResult result; - int64_t unwrapped_seq_num = seq_num_unwrapper_.Unwrap(packet->seq_num); + int64_t unwrapped_seq_num = packet->sequence_number; auto& packet_slot = GetPacket(unwrapped_seq_num); if (packet_slot != nullptr && AheadOrAt(packet_slot->timestamp, packet->timestamp)) { @@ -147,27 +151,34 @@ H26xPacketBuffer::InsertResult H26xPacketBuffer::FindFrames( // Check if the packet is continuous or the beginning of a new coded video // sequence. - if (unwrapped_seq_num - 1 != last_continuous_unwrapped_seq_num_) { - if (unwrapped_seq_num <= last_continuous_unwrapped_seq_num_ || - !BeginningOfStream(*packet)) { + int64_t* last_continuous_unwrapped_seq_num = + GetContinuousSequence(last_continuous_in_sequence_, unwrapped_seq_num); + if (last_continuous_unwrapped_seq_num == nullptr) { + if (!BeginningOfStream(*packet)) { return result; } - last_continuous_unwrapped_seq_num_ = unwrapped_seq_num; + last_continuous_in_sequence_[last_continuous_in_sequence_index_] = + unwrapped_seq_num; + last_continuous_unwrapped_seq_num = + &last_continuous_in_sequence_[last_continuous_in_sequence_index_]; + last_continuous_in_sequence_index_ = + (last_continuous_in_sequence_index_ + 1) % + last_continuous_in_sequence_.size(); } for (int64_t seq_num = unwrapped_seq_num; seq_num < unwrapped_seq_num + kBufferSize;) { - RTC_DCHECK_GE(seq_num, *last_continuous_unwrapped_seq_num_); + RTC_DCHECK_GE(seq_num, *last_continuous_unwrapped_seq_num); // Packets that were never assembled into a completed frame will stay in // the 'buffer_'. Check that the `packet` sequence number match the expected // unwrapped sequence number. - if (static_cast(seq_num) != packet->seq_num) { + if (seq_num != packet->sequence_number) { return result; } - last_continuous_unwrapped_seq_num_ = seq_num; + *last_continuous_unwrapped_seq_num = seq_num; // Last packet of the frame, try to assemble the frame. if (packet->marker_bit) { uint32_t rtp_timestamp = packet->timestamp; @@ -219,7 +230,7 @@ bool H26xPacketBuffer::MaybeAssembleFrame(int64_t start_seq_num_unwrapped, if (packet->codec() == kVideoCodecH264) { const auto& h264_header = absl::get(packet->video_header.video_type_header); - for (const auto& nalu : GetNaluInfos(h264_header)) { + for (const auto& nalu : h264_header.nalus) { has_idr |= nalu.type == H264::NaluType::kIdr; has_sps |= nalu.type == H264::NaluType::kSps; has_pps |= nalu.type == H264::NaluType::kPps; @@ -231,8 +242,8 @@ bool H26xPacketBuffer::MaybeAssembleFrame(int64_t start_seq_num_unwrapped, } #ifdef RTC_ENABLE_H265 } else if (packet->codec() == kVideoCodecH265) { - std::vector nalu_indices = H265::FindNaluIndices( - packet->video_payload.cdata(), packet->video_payload.size()); + std::vector nalu_indices = + H265::FindNaluIndices(packet->video_payload); for (const auto& nalu_index : nalu_indices) { uint8_t nalu_type = H265::ParseNaluType( packet->video_payload.cdata()[nalu_index.payload_start_offset]); @@ -328,9 +339,9 @@ void H26xPacketBuffer::InsertSpsPpsNalus(const std::vector& sps, return; } absl::optional parsed_sps = SpsParser::ParseSps( - sps.data() + kNaluHeaderOffset, sps.size() - kNaluHeaderOffset); + rtc::ArrayView(sps).subview(kNaluHeaderOffset)); absl::optional parsed_pps = PpsParser::ParsePps( - pps.data() + kNaluHeaderOffset, pps.size() - kNaluHeaderOffset); + rtc::ArrayView(pps).subview(kNaluHeaderOffset)); if (!parsed_sps) { RTC_LOG(LS_WARNING) << "Failed to parse SPS."; @@ -383,8 +394,7 @@ bool H26xPacketBuffer::FixH264Packet(Packet& packet) { auto sps = sps_data_.end(); auto pps = pps_data_.end(); - for (size_t i = 0; i < h264_header.nalus_length; ++i) { - const NaluInfo& nalu = h264_header.nalus[i]; + for (const NaluInfo& nalu : h264_header.nalus) { switch (nalu.type) { case H264::NaluType::kSps: { SpsInfo& sps_info = sps_data_[nalu.sps_id]; @@ -456,22 +466,11 @@ bool H26xPacketBuffer::FixH264Packet(Packet& packet) { result.AppendData(pps->second.payload.get(), pps->second.size); // Update codec header to reflect the newly added SPS and PPS. - NaluInfo sps_info; - sps_info.type = H264::NaluType::kSps; - sps_info.sps_id = sps->first; - sps_info.pps_id = -1; - NaluInfo pps_info; - pps_info.type = H264::NaluType::kPps; - pps_info.sps_id = sps->first; - pps_info.pps_id = pps->first; - if (h264_header.nalus_length + 2 <= kMaxNalusPerPacket) { - h264_header.nalus[h264_header.nalus_length++] = sps_info; - h264_header.nalus[h264_header.nalus_length++] = pps_info; - } else { - RTC_LOG(LS_WARNING) - << "Not enough space in H.264 codec header to insert " - "SPS/PPS provided out-of-band."; - } + h264_header.nalus.push_back( + {.type = H264::NaluType::kSps, .sps_id = sps->first, .pps_id = -1}); + h264_header.nalus.push_back({.type = H264::NaluType::kPps, + .sps_id = sps->first, + .pps_id = pps->first}); } } diff --git a/third_party/libwebrtc/modules/video_coding/h26x_packet_buffer.h b/third_party/libwebrtc/modules/video_coding/h26x_packet_buffer.h index 8bfae71f7b70..f199341f25ab 100644 --- a/third_party/libwebrtc/modules/video_coding/h26x_packet_buffer.h +++ b/third_party/libwebrtc/modules/video_coding/h26x_packet_buffer.h @@ -72,6 +72,7 @@ class H26xPacketBuffer { }; static constexpr int kBufferSize = 2048; + static constexpr int kNumTrackedSequences = 5; std::unique_ptr& GetPacket(int64_t unwrapped_seq_num); bool BeginningOfStream(const Packet& packet) const; @@ -91,15 +92,15 @@ class H26xPacketBuffer { // Indicates whether IDR frames without SPS and PPS are allowed. const bool h264_idr_only_keyframes_allowed_; std::array, kBufferSize> buffer_; - absl::optional last_continuous_unwrapped_seq_num_; - SeqNumUnwrapper seq_num_unwrapper_; + std::array last_continuous_in_sequence_; + int64_t last_continuous_in_sequence_index_ = 0; // Map from pps_pic_parameter_set_id to the PPS payload associated with this // ID. - std::map pps_data_; + std::map pps_data_; // Map from sps_video_parameter_set_id to the SPS payload associated with this // ID. - std::map sps_data_; + std::map sps_data_; }; } // namespace webrtc diff --git a/third_party/libwebrtc/modules/video_coding/h26x_packet_buffer_unittest.cc b/third_party/libwebrtc/modules/video_coding/h26x_packet_buffer_unittest.cc index 8d6d69136df3..8659eb21c57e 100644 --- a/third_party/libwebrtc/modules/video_coding/h26x_packet_buffer_unittest.cc +++ b/third_party/libwebrtc/modules/video_coding/h26x_packet_buffer_unittest.cc @@ -76,7 +76,7 @@ class H264Packet { H264Packet& Marker(); H264Packet& AsFirstFragment(); H264Packet& Time(uint32_t rtp_timestamp); - H264Packet& SeqNum(uint16_t rtp_seq_num); + H264Packet& SeqNum(int64_t rtp_seq_num); std::unique_ptr Build(); @@ -97,7 +97,7 @@ class H264Packet { bool first_fragment_ = false; bool marker_bit_ = false; uint32_t rtp_timestamp_ = 0; - uint16_t rtp_seq_num_ = 0; + int64_t rtp_seq_num_ = 0; std::vector> nalu_payloads_; }; @@ -109,14 +109,14 @@ H264Packet& H264Packet::Idr(std::vector payload, int pps_id) { auto& h264_header = H264Header(); auto nalu_info = MakeNaluInfo(kIdr); nalu_info.pps_id = pps_id; - h264_header.nalus[h264_header.nalus_length++] = nalu_info; + h264_header.nalus.push_back(nalu_info); nalu_payloads_.push_back(std::move(payload)); return *this; } H264Packet& H264Packet::Slice(std::vector payload) { auto& h264_header = H264Header(); - h264_header.nalus[h264_header.nalus_length++] = MakeNaluInfo(kSlice); + h264_header.nalus.push_back(MakeNaluInfo(kSlice)); nalu_payloads_.push_back(std::move(payload)); return *this; } @@ -125,7 +125,7 @@ H264Packet& H264Packet::Sps(std::vector payload, int sps_id) { auto& h264_header = H264Header(); auto nalu_info = MakeNaluInfo(kSps); nalu_info.pps_id = sps_id; - h264_header.nalus[h264_header.nalus_length++] = nalu_info; + h264_header.nalus.push_back(nalu_info); nalu_payloads_.push_back(std::move(payload)); return *this; } @@ -133,7 +133,7 @@ H264Packet& H264Packet::Sps(std::vector payload, int sps_id) { H264Packet& H264Packet::SpsWithResolution(RenderResolution resolution, std::vector payload) { auto& h264_header = H264Header(); - h264_header.nalus[h264_header.nalus_length++] = MakeNaluInfo(kSps); + h264_header.nalus.push_back(MakeNaluInfo(kSps)); video_header_.width = resolution.Width(); video_header_.height = resolution.Height(); nalu_payloads_.push_back(std::move(payload)); @@ -147,14 +147,14 @@ H264Packet& H264Packet::Pps(std::vector payload, auto nalu_info = MakeNaluInfo(kPps); nalu_info.pps_id = pps_id; nalu_info.sps_id = sps_id; - h264_header.nalus[h264_header.nalus_length++] = nalu_info; + h264_header.nalus.push_back(nalu_info); nalu_payloads_.push_back(std::move(payload)); return *this; } H264Packet& H264Packet::Aud() { auto& h264_header = H264Header(); - h264_header.nalus[h264_header.nalus_length++] = MakeNaluInfo(kAud); + h264_header.nalus.push_back(MakeNaluInfo(kAud)); nalu_payloads_.push_back({}); return *this; } @@ -174,7 +174,7 @@ H264Packet& H264Packet::Time(uint32_t rtp_timestamp) { return *this; } -H264Packet& H264Packet::SeqNum(uint16_t rtp_seq_num) { +H264Packet& H264Packet::SeqNum(int64_t rtp_seq_num) { rtp_seq_num_ = rtp_seq_num; return *this; } @@ -185,32 +185,31 @@ std::unique_ptr H264Packet::Build() { auto& h264_header = H264Header(); switch (type_) { case kH264FuA: { - RTC_CHECK_EQ(h264_header.nalus_length, 1); + RTC_CHECK_EQ(h264_header.nalus.size(), 1); res->video_payload = BuildFuaPayload(); break; } case kH264SingleNalu: { - RTC_CHECK_EQ(h264_header.nalus_length, 1); + RTC_CHECK_EQ(h264_header.nalus.size(), 1); res->video_payload = BuildSingleNaluPayload(); break; } case kH264StapA: { - RTC_CHECK_GT(h264_header.nalus_length, 1); - RTC_CHECK_LE(h264_header.nalus_length, kMaxNalusPerPacket); + RTC_CHECK_GT(h264_header.nalus.size(), 1); res->video_payload = BuildStapAPayload(); break; } } if (type_ == kH264FuA && !first_fragment_) { - h264_header.nalus_length = 0; + h264_header.nalus.clear(); } h264_header.packetization_type = type_; res->marker_bit = marker_bit_; res->video_header = video_header_; res->timestamp = rtp_timestamp_; - res->seq_num = rtp_seq_num_; + res->sequence_number = rtp_seq_num_; res->video_header.codec = kVideoCodecH264; return res; @@ -235,7 +234,7 @@ rtc::CopyOnWriteBuffer H264Packet::BuildStapAPayload() const { res.AppendData(&indicator, 1); auto& h264_header = H264Header(); - for (size_t i = 0; i < h264_header.nalus_length; ++i) { + for (size_t i = 0; i < h264_header.nalus.size(); ++i) { // The two first bytes indicates the nalu segment size. uint8_t length_as_array[2] = { 0, static_cast(nalu_payloads_[i].size() + 1)}; @@ -264,7 +263,7 @@ class H265Packet { H265Packet& Marker(); H265Packet& AsFirstFragment(); H265Packet& Time(uint32_t rtp_timestamp); - H265Packet& SeqNum(uint16_t rtp_seq_num); + H265Packet& SeqNum(int64_t rtp_seq_num); std::unique_ptr Build(); @@ -330,7 +329,7 @@ std::unique_ptr H265Packet::Build() { res->marker_bit = marker_bit_; res->video_header = video_header_; res->timestamp = rtp_timestamp_; - res->seq_num = rtp_seq_num_; + res->sequence_number = rtp_seq_num_; res->video_header.codec = kVideoCodecH265; res->video_payload = rtc::CopyOnWriteBuffer(); for (const auto& payload : nalu_payloads_) { @@ -350,7 +349,7 @@ H265Packet& H265Packet::Time(uint32_t rtp_timestamp) { return *this; } -H265Packet& H265Packet::SeqNum(uint16_t rtp_seq_num) { +H265Packet& H265Packet::SeqNum(int64_t rtp_seq_num) { rtp_seq_num_ = rtp_seq_num; return *this; } @@ -925,13 +924,16 @@ TEST(H26xPacketBufferTest, RtpSeqNumWrap) { H264Packet(kH264StapA).Sps().Pps().SeqNum(0xffff).Time(0).Build())); RTC_UNUSED(packet_buffer.InsertPacket( - H264Packet(kH264FuA).Idr().SeqNum(0).Time(0).Build())); - EXPECT_THAT( - packet_buffer - .InsertPacket( - H264Packet(kH264FuA).Idr().SeqNum(1).Time(0).Marker().Build()) - .packets, - SizeIs(3)); + H264Packet(kH264FuA).Idr().SeqNum(0x1'0000).Time(0).Build())); + EXPECT_THAT(packet_buffer + .InsertPacket(H264Packet(kH264FuA) + .Idr() + .SeqNum(0x1'0001) + .Time(0) + .Marker() + .Build()) + .packets, + SizeIs(3)); } TEST(H26xPacketBufferTest, StapAFixedBitstream) { @@ -1044,22 +1046,70 @@ TEST(H26xPacketBufferTest, FullPacketBufferDoesNotBlockKeyframe) { SizeIs(1)); } -TEST(H26xPacketBufferTest, TooManyNalusInPacket) { +TEST(H26xPacketBufferTest, AssembleFrameAfterReordering) { H26xPacketBuffer packet_buffer(/*h264_allow_idr_only_keyframes=*/false); - std::unique_ptr packet(H264Packet(kH264StapA) - .Sps() - .Pps() - .Idr() - .SeqNum(1) - .Time(1) - .Marker() - .Build()); - auto& h264_header = - absl::get(packet->video_header.video_type_header); - h264_header.nalus_length = kMaxNalusPerPacket + 1; + EXPECT_THAT(packet_buffer + .InsertPacket(H264Packet(kH264StapA) + .Sps() + .Pps() + .Idr() + .SeqNum(2) + .Time(2) + .Marker() + .Build()) + .packets, + SizeIs(1)); - EXPECT_THAT(packet_buffer.InsertPacket(std::move(packet)).packets, IsEmpty()); + EXPECT_THAT(packet_buffer + .InsertPacket(H264Packet(kH264SingleNalu) + .Slice() + .SeqNum(1) + .Time(1) + .Marker() + .Build()) + .packets, + IsEmpty()); + + EXPECT_THAT(packet_buffer + .InsertPacket(H264Packet(kH264StapA) + .Sps() + .Pps() + .Idr() + .SeqNum(0) + .Time(0) + .Marker() + .Build()) + .packets, + SizeIs(2)); +} + +TEST(H26xPacketBufferTest, AssembleFrameAfterLoss) { + H26xPacketBuffer packet_buffer(/*h264_allow_idr_only_keyframes=*/false); + + EXPECT_THAT(packet_buffer + .InsertPacket(H264Packet(kH264StapA) + .Sps() + .Pps() + .Idr() + .SeqNum(0) + .Time(0) + .Marker() + .Build()) + .packets, + SizeIs(1)); + + EXPECT_THAT(packet_buffer + .InsertPacket(H264Packet(kH264StapA) + .Sps() + .Pps() + .Idr() + .SeqNum(2) + .Time(2) + .Marker() + .Build()) + .packets, + SizeIs(1)); } #ifdef RTC_ENABLE_H265 diff --git a/third_party/libwebrtc/modules/video_coding/nack_requester.cc b/third_party/libwebrtc/modules/video_coding/nack_requester.cc index b3e928d05e17..ac7c421ccd23 100644 --- a/third_party/libwebrtc/modules/video_coding/nack_requester.cc +++ b/third_party/libwebrtc/modules/video_coding/nack_requester.cc @@ -25,7 +25,9 @@ namespace { constexpr int kMaxPacketAge = 10'000; constexpr int kMaxNackPackets = 1000; constexpr TimeDelta kDefaultRtt = TimeDelta::Millis(100); -constexpr int kMaxNackRetries = 10; +// Number of times a packet can be nacked before giving up. Nack is sent at most +// every RTT. +constexpr int kMaxNackRetries = 100; constexpr int kMaxReorderedPackets = 128; constexpr int kNumReorderingBuckets = 10; constexpr TimeDelta kDefaultSendNackDelay = TimeDelta::Zero(); diff --git a/third_party/libwebrtc/modules/video_coding/packet_buffer.cc b/third_party/libwebrtc/modules/video_coding/packet_buffer.cc index 3a31d2104890..792c8452358e 100644 --- a/third_party/libwebrtc/modules/video_coding/packet_buffer.cc +++ b/third_party/libwebrtc/modules/video_coding/packet_buffer.cc @@ -35,13 +35,18 @@ namespace webrtc { namespace video_coding { PacketBuffer::Packet::Packet(const RtpPacketReceived& rtp_packet, + int64_t sequence_number, const RTPVideoHeader& video_header) : marker_bit(rtp_packet.Marker()), payload_type(rtp_packet.PayloadType()), - seq_num(rtp_packet.SequenceNumber()), + sequence_number(sequence_number), timestamp(rtp_packet.Timestamp()), times_nacked(-1), - video_header(video_header) {} + video_header(video_header) { + // Unwrapped sequence number should match the original wrapped one. + RTC_DCHECK_EQ(static_cast(sequence_number), + rtp_packet.SequenceNumber()); +} PacketBuffer::PacketBuffer(size_t start_buffer_size, size_t max_buffer_size) : max_size_(max_buffer_size), @@ -64,7 +69,7 @@ PacketBuffer::InsertResult PacketBuffer::InsertPacket( std::unique_ptr packet) { PacketBuffer::InsertResult result; - uint16_t seq_num = packet->seq_num; + uint16_t seq_num = packet->seq_num(); size_t index = seq_num % buffer_.size(); if (!first_packet_received_) { @@ -89,7 +94,7 @@ PacketBuffer::InsertResult PacketBuffer::InsertPacket( if (buffer_[index] != nullptr) { // Duplicate packet, just delete the payload. - if (buffer_[index]->seq_num == packet->seq_num) { + if (buffer_[index]->seq_num() == packet->seq_num()) { return result; } @@ -141,7 +146,7 @@ uint32_t PacketBuffer::ClearTo(uint16_t seq_num) { size_t iterations = std::min(diff, buffer_.size()); for (size_t i = 0; i < iterations; ++i) { auto& stored = buffer_[first_seq_num_ % buffer_.size()]; - if (stored != nullptr && AheadOf(seq_num, stored->seq_num)) { + if (stored != nullptr && AheadOf(seq_num, stored->seq_num())) { ++num_cleared_packets; stored = nullptr; } @@ -205,7 +210,7 @@ bool PacketBuffer::ExpandBufferSize() { std::vector> new_buffer(new_size); for (std::unique_ptr& entry : buffer_) { if (entry != nullptr) { - new_buffer[entry->seq_num % new_size] = std::move(entry); + new_buffer[entry->seq_num() % new_size] = std::move(entry); } } buffer_ = std::move(new_buffer); @@ -221,13 +226,13 @@ bool PacketBuffer::PotentialNewFrame(uint16_t seq_num) const { if (entry == nullptr) return false; - if (entry->seq_num != seq_num) + if (entry->seq_num() != seq_num) return false; if (entry->is_first_packet_in_frame()) return true; if (prev_entry == nullptr) return false; - if (prev_entry->seq_num != static_cast(entry->seq_num - 1)) + if (prev_entry->seq_num() != static_cast(entry->seq_num() - 1)) return false; if (prev_entry->timestamp != entry->timestamp) return false; @@ -291,15 +296,15 @@ std::vector> PacketBuffer::FindFrames( if (is_h264_descriptor) { const auto* h264_header = absl::get_if( &buffer_[start_index]->video_header.video_type_header); - if (!h264_header || h264_header->nalus_length >= kMaxNalusPerPacket) + if (!h264_header) return found_frames; - for (size_t j = 0; j < h264_header->nalus_length; ++j) { - if (h264_header->nalus[j].type == H264::NaluType::kSps) { + for (const NaluInfo& nalu : h264_header->nalus) { + if (nalu.type == H264::NaluType::kSps) { has_h264_sps = true; - } else if (h264_header->nalus[j].type == H264::NaluType::kPps) { + } else if (nalu.type == H264::NaluType::kPps) { has_h264_pps = true; - } else if (h264_header->nalus[j].type == H264::NaluType::kIdr) { + } else if (nalu.type == H264::NaluType::kIdr) { has_h264_idr = true; } } @@ -385,7 +390,7 @@ std::vector> PacketBuffer::FindFrames( for (uint16_t i = start_seq_num; i != end_seq_num; ++i) { std::unique_ptr& packet = buffer_[i % buffer_.size()]; RTC_DCHECK(packet); - RTC_DCHECK_EQ(i, packet->seq_num); + RTC_DCHECK_EQ(i, packet->seq_num()); // Ensure frame boundary flags are properly set. packet->video_header.is_first_packet_in_frame = (i == start_seq_num); packet->video_header.is_last_packet_in_frame = (i == seq_num); diff --git a/third_party/libwebrtc/modules/video_coding/packet_buffer.h b/third_party/libwebrtc/modules/video_coding/packet_buffer.h index 47b2ffe199da..87f87ca1014f 100644 --- a/third_party/libwebrtc/modules/video_coding/packet_buffer.h +++ b/third_party/libwebrtc/modules/video_coding/packet_buffer.h @@ -34,6 +34,7 @@ class PacketBuffer { struct Packet { Packet() = default; Packet(const RtpPacketReceived& rtp_packet, + int64_t sequence_number, const RTPVideoHeader& video_header); Packet(const Packet&) = delete; Packet(Packet&&) = delete; @@ -51,13 +52,14 @@ class PacketBuffer { bool is_last_packet_in_frame() const { return video_header.is_last_packet_in_frame; } + uint16_t seq_num() const { return static_cast(sequence_number); } // If all its previous packets have been inserted into the packet buffer. // Set and used internally by the PacketBuffer. bool continuous = false; bool marker_bit = false; uint8_t payload_type = 0; - uint16_t seq_num = 0; + int64_t sequence_number = 0; uint32_t timestamp = 0; int times_nacked = -1; diff --git a/third_party/libwebrtc/modules/video_coding/packet_buffer_unittest.cc b/third_party/libwebrtc/modules/video_coding/packet_buffer_unittest.cc index 607ec47dc45d..39b9eea6cdbc 100644 --- a/third_party/libwebrtc/modules/video_coding/packet_buffer_unittest.cc +++ b/third_party/libwebrtc/modules/video_coding/packet_buffer_unittest.cc @@ -49,7 +49,7 @@ std::vector StartSeqNums( for (const auto& packet : packets) { EXPECT_EQ(frame_boundary, packet->is_first_packet_in_frame()); if (packet->is_first_packet_in_frame()) { - result.push_back(packet->seq_num); + result.push_back(packet->seq_num()); } frame_boundary = packet->is_last_packet_in_frame(); } @@ -85,11 +85,11 @@ void PrintTo(const PacketBufferInsertResult& result, std::ostream* os) { for (const auto& packet : result.packets) { if (packet->is_first_packet_in_frame() && packet->is_last_packet_in_frame()) { - *os << "{sn: " << packet->seq_num << " }"; + *os << "{sn: " << packet->seq_num() << " }"; } else if (packet->is_first_packet_in_frame()) { - *os << "{sn: [" << packet->seq_num << "-"; + *os << "{sn: [" << packet->seq_num() << "-"; } else if (packet->is_last_packet_in_frame()) { - *os << packet->seq_num << "] }, "; + *os << packet->seq_num() << "] }, "; } } *os << " }"; @@ -108,7 +108,7 @@ class PacketBufferTest : public ::testing::Test { enum IsFirst { kFirst, kNotFirst }; enum IsLast { kLast, kNotLast }; - PacketBufferInsertResult Insert(uint16_t seq_num, // packet sequence number + PacketBufferInsertResult Insert(int64_t seq_num, // packet sequence number IsKeyFrame keyframe, // is keyframe IsFirst first, // is first packet of frame IsLast last, // is last packet of frame @@ -117,7 +117,7 @@ class PacketBufferTest : public ::testing::Test { auto packet = std::make_unique(); packet->video_header.codec = kVideoCodecGeneric; packet->timestamp = timestamp; - packet->seq_num = seq_num; + packet->sequence_number = seq_num; packet->video_header.frame_type = keyframe == kKeyFrame ? VideoFrameType::kVideoFrameKey : VideoFrameType::kVideoFrameDelta; @@ -134,12 +134,12 @@ class PacketBufferTest : public ::testing::Test { }; TEST_F(PacketBufferTest, InsertOnePacket) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); EXPECT_THAT(Insert(seq_num, kKeyFrame, kFirst, kLast).packets, SizeIs(1)); } TEST_F(PacketBufferTest, InsertMultiplePackets) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); EXPECT_THAT(Insert(seq_num, kKeyFrame, kFirst, kLast).packets, SizeIs(1)); EXPECT_THAT(Insert(seq_num + 1, kKeyFrame, kFirst, kLast).packets, SizeIs(1)); EXPECT_THAT(Insert(seq_num + 2, kKeyFrame, kFirst, kLast).packets, SizeIs(1)); @@ -147,7 +147,7 @@ TEST_F(PacketBufferTest, InsertMultiplePackets) { } TEST_F(PacketBufferTest, InsertDuplicatePacket) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); EXPECT_THAT(Insert(seq_num, kKeyFrame, kFirst, kNotLast).packets, IsEmpty()); EXPECT_THAT(Insert(seq_num, kKeyFrame, kFirst, kNotLast).packets, IsEmpty()); EXPECT_THAT(Insert(seq_num + 1, kKeyFrame, kNotFirst, kLast).packets, @@ -156,14 +156,14 @@ TEST_F(PacketBufferTest, InsertDuplicatePacket) { TEST_F(PacketBufferTest, SeqNumWrapOneFrame) { Insert(0xFFFF, kKeyFrame, kFirst, kNotLast); - EXPECT_THAT(Insert(0x0, kKeyFrame, kNotFirst, kLast), + EXPECT_THAT(Insert(0x1'0000, kKeyFrame, kNotFirst, kLast), StartSeqNumsAre(0xFFFF)); } TEST_F(PacketBufferTest, SeqNumWrapTwoFrames) { EXPECT_THAT(Insert(0xFFFF, kKeyFrame, kFirst, kLast), StartSeqNumsAre(0xFFFF)); - EXPECT_THAT(Insert(0x0, kKeyFrame, kFirst, kLast), StartSeqNumsAre(0x0)); + EXPECT_THAT(Insert(0x1'0000, kKeyFrame, kFirst, kLast), StartSeqNumsAre(0x0)); } TEST_F(PacketBufferTest, InsertOldPackets) { @@ -181,7 +181,7 @@ TEST_F(PacketBufferTest, InsertOldPackets) { } TEST_F(PacketBufferTest, FrameSize) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); uint8_t data1[5] = {}; uint8_t data2[5] = {}; uint8_t data3[5] = {}; @@ -198,7 +198,7 @@ TEST_F(PacketBufferTest, FrameSize) { } TEST_F(PacketBufferTest, ExpandBuffer) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); Insert(seq_num, kKeyFrame, kFirst, kNotLast); for (int i = 1; i < kStartSize; ++i) @@ -212,7 +212,7 @@ TEST_F(PacketBufferTest, ExpandBuffer) { } TEST_F(PacketBufferTest, SingleFrameExpandsBuffer) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); Insert(seq_num, kKeyFrame, kFirst, kNotLast); for (int i = 1; i < kStartSize; ++i) @@ -222,7 +222,7 @@ TEST_F(PacketBufferTest, SingleFrameExpandsBuffer) { } TEST_F(PacketBufferTest, ExpandBufferOverflow) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); EXPECT_FALSE(Insert(seq_num, kKeyFrame, kFirst, kNotLast).buffer_cleared); for (int i = 1; i < kMaxSize; ++i) @@ -236,13 +236,13 @@ TEST_F(PacketBufferTest, ExpandBufferOverflow) { } TEST_F(PacketBufferTest, OnePacketOneFrame) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); EXPECT_THAT(Insert(seq_num, kKeyFrame, kFirst, kLast), StartSeqNumsAre(seq_num)); } TEST_F(PacketBufferTest, TwoPacketsTwoFrames) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); EXPECT_THAT(Insert(seq_num, kKeyFrame, kFirst, kLast), StartSeqNumsAre(seq_num)); @@ -251,7 +251,7 @@ TEST_F(PacketBufferTest, TwoPacketsTwoFrames) { } TEST_F(PacketBufferTest, TwoPacketsOneFrames) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); EXPECT_THAT(Insert(seq_num, kKeyFrame, kFirst, kNotLast).packets, IsEmpty()); EXPECT_THAT(Insert(seq_num + 1, kKeyFrame, kNotFirst, kLast), @@ -259,7 +259,7 @@ TEST_F(PacketBufferTest, TwoPacketsOneFrames) { } TEST_F(PacketBufferTest, ThreePacketReorderingOneFrame) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); EXPECT_THAT(Insert(seq_num, kKeyFrame, kFirst, kNotLast).packets, IsEmpty()); EXPECT_THAT(Insert(seq_num + 2, kKeyFrame, kNotFirst, kLast).packets, @@ -269,7 +269,7 @@ TEST_F(PacketBufferTest, ThreePacketReorderingOneFrame) { } TEST_F(PacketBufferTest, Frames) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); EXPECT_THAT(Insert(seq_num, kKeyFrame, kFirst, kLast), StartSeqNumsAre(seq_num)); @@ -282,7 +282,7 @@ TEST_F(PacketBufferTest, Frames) { } TEST_F(PacketBufferTest, ClearSinglePacket) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); for (int i = 0; i < kMaxSize; ++i) Insert(seq_num + i, kDeltaFrame, kFirst, kLast); @@ -322,7 +322,7 @@ TEST_F(PacketBufferTest, DontClearNewerPacket) { } TEST_F(PacketBufferTest, OneIncompleteFrame) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); EXPECT_THAT(Insert(seq_num, kDeltaFrame, kFirst, kNotLast).packets, IsEmpty()); @@ -333,7 +333,7 @@ TEST_F(PacketBufferTest, OneIncompleteFrame) { } TEST_F(PacketBufferTest, TwoIncompleteFramesFullBuffer) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); for (int i = 1; i < kMaxSize - 1; ++i) Insert(seq_num + i, kDeltaFrame, kNotFirst, kNotLast); @@ -344,7 +344,7 @@ TEST_F(PacketBufferTest, TwoIncompleteFramesFullBuffer) { } TEST_F(PacketBufferTest, FramesReordered) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); EXPECT_THAT(Insert(seq_num + 1, kDeltaFrame, kFirst, kLast), StartSeqNumsAre(seq_num + 1)); @@ -357,14 +357,13 @@ TEST_F(PacketBufferTest, FramesReordered) { } TEST_F(PacketBufferTest, InsertPacketAfterSequenceNumberWrapAround) { - uint16_t kFirstSeqNum = 0; + int64_t kFirstSeqNum = 0; uint32_t kTimestampDelta = 100; uint32_t timestamp = 10000; - uint16_t seq_num = kFirstSeqNum; + int64_t seq_num = kFirstSeqNum; // Loop until seq_num wraps around. - SeqNumUnwrapper unwrapper; - while (unwrapper.Unwrap(seq_num) < std::numeric_limits::max()) { + while (seq_num < std::numeric_limits::max()) { Insert(seq_num++, kKeyFrame, kFirst, kNotLast, {}, timestamp); for (int i = 0; i < 5; ++i) { Insert(seq_num++, kKeyFrame, kNotFirst, kNotLast, {}, timestamp); @@ -399,7 +398,7 @@ class PacketBufferH264Test : public PacketBufferTest { } PacketBufferInsertResult InsertH264( - uint16_t seq_num, // packet sequence number + int64_t seq_num, // packet sequence number IsKeyFrame keyframe, // is keyframe IsFirst first, // is first packet of frame IsLast last, // is last packet of frame @@ -412,17 +411,15 @@ class PacketBufferH264Test : public PacketBufferTest { packet->video_header.codec = kVideoCodecH264; auto& h264_header = packet->video_header.video_type_header.emplace(); - packet->seq_num = seq_num; + packet->sequence_number = seq_num; packet->timestamp = timestamp; if (keyframe == kKeyFrame) { if (sps_pps_idr_is_keyframe_) { - h264_header.nalus[0].type = H264::NaluType::kSps; - h264_header.nalus[1].type = H264::NaluType::kPps; - h264_header.nalus[2].type = H264::NaluType::kIdr; - h264_header.nalus_length = 3; + h264_header.nalus = {{H264::NaluType::kSps}, + {H264::NaluType::kPps}, + {H264::NaluType::kIdr}}; } else { - h264_header.nalus[0].type = H264::NaluType::kIdr; - h264_header.nalus_length = 1; + h264_header.nalus = {{H264::NaluType::kIdr}}; } } packet->video_header.width = width; @@ -439,7 +436,7 @@ class PacketBufferH264Test : public PacketBufferTest { } PacketBufferInsertResult InsertH264KeyFrameWithAud( - uint16_t seq_num, // packet sequence number + int64_t seq_num, // packet sequence number IsKeyFrame keyframe, // is keyframe IsFirst first, // is first packet of frame IsLast last, // is last packet of frame @@ -451,15 +448,14 @@ class PacketBufferH264Test : public PacketBufferTest { packet->video_header.codec = kVideoCodecH264; auto& h264_header = packet->video_header.video_type_header.emplace(); - packet->seq_num = seq_num; + packet->sequence_number = seq_num; packet->timestamp = timestamp; // this should be the start of frame. RTC_CHECK(first == kFirst); // Insert a AUD NALU / packet without width/height. - h264_header.nalus[0].type = H264::NaluType::kAud; - h264_header.nalus_length = 1; + h264_header.nalus = {{H264::NaluType::kAud}}; packet->video_header.is_first_packet_in_frame = true; packet->video_header.is_last_packet_in_frame = false; IgnoreResult(packet_buffer_.InsertPacket(std::move(packet))); @@ -518,16 +514,15 @@ TEST_P(PacketBufferH264ParameterizedTest, GetBitstreamOneFrameFullBuffer) { } TEST_P(PacketBufferH264ParameterizedTest, GetBitstreamBufferPadding) { - uint16_t seq_num = Rand(); + int64_t seq_num = Rand(); rtc::CopyOnWriteBuffer data = "some plain old data"; auto packet = std::make_unique(); auto& h264_header = packet->video_header.video_type_header.emplace(); - h264_header.nalus_length = 1; - h264_header.nalus[0].type = H264::NaluType::kIdr; + h264_header.nalus = {{H264::NaluType::kIdr}}; h264_header.packetization_type = kH264SingleNalu; - packet->seq_num = seq_num; + packet->sequence_number = seq_num; packet->video_header.codec = kVideoCodecH264; packet->video_payload = data; packet->video_header.is_first_packet_in_frame = true; @@ -535,12 +530,12 @@ TEST_P(PacketBufferH264ParameterizedTest, GetBitstreamBufferPadding) { auto frames = packet_buffer_.InsertPacket(std::move(packet)).packets; ASSERT_THAT(frames, SizeIs(1)); - EXPECT_EQ(frames[0]->seq_num, seq_num); + EXPECT_EQ(frames[0]->sequence_number, seq_num); EXPECT_EQ(frames[0]->video_payload, data); } TEST_P(PacketBufferH264ParameterizedTest, FrameResolution) { - uint16_t seq_num = 100; + int64_t seq_num = 100; uint8_t data[] = "some plain old data"; uint32_t width = 640; uint32_t height = 360; @@ -556,7 +551,7 @@ TEST_P(PacketBufferH264ParameterizedTest, FrameResolution) { } TEST_P(PacketBufferH264ParameterizedTest, FrameResolutionNaluBeforeSPS) { - uint16_t seq_num = 100; + int64_t seq_num = 100; uint8_t data[] = "some plain old data"; uint32_t width = 640; uint32_t height = 360; @@ -572,7 +567,7 @@ TEST_P(PacketBufferH264ParameterizedTest, FrameResolutionNaluBeforeSPS) { } TEST_F(PacketBufferTest, FreeSlotsOnFrameCreation) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); Insert(seq_num, kKeyFrame, kFirst, kNotLast); Insert(seq_num + 1, kDeltaFrame, kNotFirst, kNotLast); @@ -588,7 +583,7 @@ TEST_F(PacketBufferTest, FreeSlotsOnFrameCreation) { } TEST_F(PacketBufferTest, Clear) { - const uint16_t seq_num = Rand(); + const int64_t seq_num = Rand(); Insert(seq_num, kKeyFrame, kFirst, kNotLast); Insert(seq_num + 1, kDeltaFrame, kNotFirst, kNotLast); @@ -630,7 +625,7 @@ TEST_F(PacketBufferTest, IncomingCodecChange) { packet->video_header.codec = kVideoCodecVP8; packet->video_header.video_type_header.emplace(); packet->timestamp = 1; - packet->seq_num = 1; + packet->sequence_number = 1; packet->video_header.frame_type = VideoFrameType::kVideoFrameKey; EXPECT_THAT(packet_buffer_.InsertPacket(std::move(packet)).packets, SizeIs(1)); @@ -641,9 +636,9 @@ TEST_F(PacketBufferTest, IncomingCodecChange) { packet->video_header.codec = kVideoCodecH264; auto& h264_header = packet->video_header.video_type_header.emplace(); - h264_header.nalus_length = 1; + h264_header.nalus.resize(1); packet->timestamp = 3; - packet->seq_num = 3; + packet->sequence_number = 3; packet->video_header.frame_type = VideoFrameType::kVideoFrameKey; EXPECT_THAT(packet_buffer_.InsertPacket(std::move(packet)).packets, IsEmpty()); @@ -654,27 +649,12 @@ TEST_F(PacketBufferTest, IncomingCodecChange) { packet->video_header.codec = kVideoCodecVP8; packet->video_header.video_type_header.emplace(); packet->timestamp = 2; - packet->seq_num = 2; + packet->sequence_number = 2; packet->video_header.frame_type = VideoFrameType::kVideoFrameDelta; EXPECT_THAT(packet_buffer_.InsertPacket(std::move(packet)).packets, SizeIs(2)); } -TEST_F(PacketBufferTest, TooManyNalusInPacket) { - auto packet = std::make_unique(); - packet->video_header.codec = kVideoCodecH264; - packet->timestamp = 1; - packet->seq_num = 1; - packet->video_header.frame_type = VideoFrameType::kVideoFrameKey; - packet->video_header.is_first_packet_in_frame = true; - packet->video_header.is_last_packet_in_frame = true; - auto& h264_header = - packet->video_header.video_type_header.emplace(); - h264_header.nalus_length = kMaxNalusPerPacket; - EXPECT_THAT(packet_buffer_.InsertPacket(std::move(packet)).packets, - IsEmpty()); -} - TEST_P(PacketBufferH264ParameterizedTest, OneFrameFillBuffer) { InsertH264(0, kKeyFrame, kFirst, kNotLast, 1000); for (int i = 1; i < kStartSize - 1; ++i) @@ -744,7 +724,7 @@ TEST_P(PacketBufferH264ParameterizedTest, FindFramesOnReorderedPadding) { class PacketBufferH264XIsKeyframeTest : public PacketBufferH264Test { protected: - const uint16_t kSeqNum = 5; + const int64_t kSeqNum = 5; explicit PacketBufferH264XIsKeyframeTest(bool sps_pps_idr_is_keyframe) : PacketBufferH264Test(sps_pps_idr_is_keyframe) {} @@ -752,7 +732,7 @@ class PacketBufferH264XIsKeyframeTest : public PacketBufferH264Test { std::unique_ptr CreatePacket() { auto packet = std::make_unique(); packet->video_header.codec = kVideoCodecH264; - packet->seq_num = kSeqNum; + packet->sequence_number = kSeqNum; packet->video_header.is_first_packet_in_frame = true; packet->video_header.is_last_packet_in_frame = true; @@ -771,8 +751,7 @@ TEST_F(PacketBufferH264IdrIsKeyframeTest, IdrIsKeyframe) { auto packet = CreatePacket(); auto& h264_header = packet->video_header.video_type_header.emplace(); - h264_header.nalus[0].type = H264::NaluType::kIdr; - h264_header.nalus_length = 1; + h264_header.nalus = {{H264::NaluType::kIdr}}; EXPECT_THAT(packet_buffer_.InsertPacket(std::move(packet)).packets, ElementsAre(KeyFrame())); } @@ -781,10 +760,8 @@ TEST_F(PacketBufferH264IdrIsKeyframeTest, SpsPpsIdrIsKeyframe) { auto packet = CreatePacket(); auto& h264_header = packet->video_header.video_type_header.emplace(); - h264_header.nalus[0].type = H264::NaluType::kSps; - h264_header.nalus[1].type = H264::NaluType::kPps; - h264_header.nalus[2].type = H264::NaluType::kIdr; - h264_header.nalus_length = 3; + h264_header.nalus = { + {H264::NaluType::kSps}, {H264::NaluType::kPps}, {H264::NaluType::kIdr}}; EXPECT_THAT(packet_buffer_.InsertPacket(std::move(packet)).packets, ElementsAre(KeyFrame())); @@ -801,8 +778,7 @@ TEST_F(PacketBufferH264SpsPpsIdrIsKeyframeTest, IdrIsNotKeyframe) { auto packet = CreatePacket(); auto& h264_header = packet->video_header.video_type_header.emplace(); - h264_header.nalus[0].type = H264::NaluType::kIdr; - h264_header.nalus_length = 1; + h264_header.nalus = {{H264::NaluType::kIdr}}; EXPECT_THAT(packet_buffer_.InsertPacket(std::move(packet)).packets, ElementsAre(DeltaFrame())); @@ -812,9 +788,7 @@ TEST_F(PacketBufferH264SpsPpsIdrIsKeyframeTest, SpsPpsIsNotKeyframe) { auto packet = CreatePacket(); auto& h264_header = packet->video_header.video_type_header.emplace(); - h264_header.nalus[0].type = H264::NaluType::kSps; - h264_header.nalus[1].type = H264::NaluType::kPps; - h264_header.nalus_length = 2; + h264_header.nalus = {{H264::NaluType::kSps}, {H264::NaluType::kPps}}; EXPECT_THAT(packet_buffer_.InsertPacket(std::move(packet)).packets, ElementsAre(DeltaFrame())); @@ -824,10 +798,8 @@ TEST_F(PacketBufferH264SpsPpsIdrIsKeyframeTest, SpsPpsIdrIsKeyframe) { auto packet = CreatePacket(); auto& h264_header = packet->video_header.video_type_header.emplace(); - h264_header.nalus[0].type = H264::NaluType::kSps; - h264_header.nalus[1].type = H264::NaluType::kPps; - h264_header.nalus[2].type = H264::NaluType::kIdr; - h264_header.nalus_length = 3; + h264_header.nalus = { + {H264::NaluType::kSps}, {H264::NaluType::kPps}, {H264::NaluType::kIdr}}; EXPECT_THAT(packet_buffer_.InsertPacket(std::move(packet)).packets, ElementsAre(KeyFrame())); diff --git a/third_party/libwebrtc/modules/video_coding/timing/timing_unittest.cc b/third_party/libwebrtc/modules/video_coding/timing/timing_unittest.cc index 4ba8c4dcd2ab..ffb831b2645c 100644 --- a/third_party/libwebrtc/modules/video_coding/timing/timing_unittest.cc +++ b/third_party/libwebrtc/modules/video_coding/timing/timing_unittest.cc @@ -38,30 +38,37 @@ MATCHER(HasConsistentVideoDelayTimings, "") { // Delays should be internally consistent. bool m1 = arg.minimum_delay <= arg.target_delay; if (!m1) { - *result_listener << "\nminimum_delay: " << arg.minimum_delay << ", " - << "target_delay: " << arg.target_delay << "\n"; + *result_listener << "\nminimum_delay: " << ToString(arg.minimum_delay) + << ", " << "target_delay: " << ToString(arg.target_delay) + << "\n"; } bool m2 = arg.minimum_delay <= arg.current_delay; if (!m2) { - *result_listener << "\nminimum_delay: " << arg.minimum_delay << ", " - << "current_delay: " << arg.current_delay; + *result_listener << "\nminimum_delay: " << ToString(arg.minimum_delay) + << ", " + << "current_delay: " << ToString(arg.current_delay); } bool m3 = arg.target_delay >= arg.min_playout_delay; if (!m3) { - *result_listener << "\ntarget_delay: " << arg.target_delay << ", " - << "min_playout_delay: " << arg.min_playout_delay << "\n"; + *result_listener << "\ntarget_delay: " << ToString(arg.target_delay) << ", " + << "min_playout_delay: " << ToString(arg.min_playout_delay) + << "\n"; } // TODO(crbug.com/webrtc/15197): Uncomment when this is guaranteed. // bool m4 = arg.target_delay <= arg.max_playout_delay; bool m5 = arg.current_delay >= arg.min_playout_delay; if (!m5) { - *result_listener << "\ncurrent_delay: " << arg.current_delay << ", " - << "min_playout_delay: " << arg.min_playout_delay << "\n"; + *result_listener << "\ncurrent_delay: " << ToString(arg.current_delay) + << ", " + << "min_playout_delay: " << ToString(arg.min_playout_delay) + << "\n"; } bool m6 = arg.current_delay <= arg.max_playout_delay; if (!m6) { - *result_listener << "\ncurrent_delay: " << arg.current_delay << ", " - << "max_playout_delay: " << arg.max_playout_delay << "\n"; + *result_listener << "\ncurrent_delay: " << ToString(arg.current_delay) + << ", " + << "max_playout_delay: " << ToString(arg.max_playout_delay) + << "\n"; } bool m = m1 && m2 && m3 && m5 && m6; diff --git a/third_party/libwebrtc/modules/video_coding/utility/simulcast_utility.cc b/third_party/libwebrtc/modules/video_coding/utility/simulcast_utility.cc index 824f4b0eac18..b637e7fdd013 100644 --- a/third_party/libwebrtc/modules/video_coding/utility/simulcast_utility.cc +++ b/third_party/libwebrtc/modules/video_coding/utility/simulcast_utility.cc @@ -75,7 +75,18 @@ bool SimulcastUtility::ValidSimulcastParameters(const VideoCodec& codec, bool SimulcastUtility::IsConferenceModeScreenshare(const VideoCodec& codec) { return codec.mode == VideoCodecMode::kScreensharing && - codec.legacy_conference_mode; + codec.legacy_conference_mode && + (codec.codecType == kVideoCodecVP8 || + codec.codecType == kVideoCodecH264); +} + +bool SimulcastUtility::IsConferenceModeScreenshare( + const VideoEncoderConfig& encoder_config) { + return encoder_config.content_type == + VideoEncoderConfig::ContentType::kScreen && + encoder_config.legacy_conference_mode && + (encoder_config.codec_type == webrtc::VideoCodecType::kVideoCodecVP8 || + encoder_config.codec_type == webrtc::VideoCodecType::kVideoCodecH264); } int SimulcastUtility::NumberOfTemporalLayers(const VideoCodec& codec, diff --git a/third_party/libwebrtc/modules/video_coding/utility/simulcast_utility.h b/third_party/libwebrtc/modules/video_coding/utility/simulcast_utility.h index e25a59436028..4270f9181dea 100644 --- a/third_party/libwebrtc/modules/video_coding/utility/simulcast_utility.h +++ b/third_party/libwebrtc/modules/video_coding/utility/simulcast_utility.h @@ -14,6 +14,7 @@ #include #include "api/video_codecs/video_codec.h" +#include "video/config/video_encoder_config.h" namespace webrtc { @@ -26,6 +27,8 @@ class SimulcastUtility { static int NumberOfTemporalLayers(const VideoCodec& codec, int spatial_id); // TODO(sprang): Remove this hack when ScreenshareLayers is gone. static bool IsConferenceModeScreenshare(const VideoCodec& codec); + static bool IsConferenceModeScreenshare( + const VideoEncoderConfig& encoder_config); }; } // namespace webrtc diff --git a/third_party/libwebrtc/modules/video_coding/video_codec_initializer.cc b/third_party/libwebrtc/modules/video_coding/video_codec_initializer.cc index 2ec419a76de9..2c6a25535520 100644 --- a/third_party/libwebrtc/modules/video_coding/video_codec_initializer.cc +++ b/third_party/libwebrtc/modules/video_coding/video_codec_initializer.cc @@ -301,6 +301,14 @@ VideoCodec VideoCodecInitializer::SetupCodec( streams.back().num_temporal_layers.value_or(1), /*num_spatial_layers=*/ std::max(config.spatial_layers.size(), 1))) { + // If min bitrate is set via RtpEncodingParameters, use this value on + // lowest spatial layer. + if (!config.simulcast_layers.empty() && + config.simulcast_layers[0].min_bitrate_bps > 0) { + video_codec.spatialLayers[0].minBitrate = std::min( + config.simulcast_layers[0].min_bitrate_bps / 1000, + static_cast(video_codec.spatialLayers[0].targetBitrate)); + } for (size_t i = 0; i < config.spatial_layers.size(); ++i) { video_codec.spatialLayers[i].active = config.spatial_layers[i].active; } @@ -337,7 +345,8 @@ VideoCodec VideoCodecInitializer::SetupCodec( rtc::saturated_cast(experimental_min_bitrate->kbps()); video_codec.minBitrate = experimental_min_bitrate_kbps; video_codec.simulcastStream[0].minBitrate = experimental_min_bitrate_kbps; - if (video_codec.codecType == kVideoCodecVP9) { + if (video_codec.codecType == kVideoCodecVP9 || + video_codec.codecType == kVideoCodecAV1) { video_codec.spatialLayers[0].minBitrate = experimental_min_bitrate_kbps; } } diff --git a/third_party/libwebrtc/modules/video_coding/video_codec_initializer_unittest.cc b/third_party/libwebrtc/modules/video_coding/video_codec_initializer_unittest.cc index 109b095b8325..de88a64e8567 100644 --- a/third_party/libwebrtc/modules/video_coding/video_codec_initializer_unittest.cc +++ b/third_party/libwebrtc/modules/video_coding/video_codec_initializer_unittest.cc @@ -535,6 +535,51 @@ TEST_F(VideoCodecInitializerTest, Av1TwoSpatialLayersBitratesAreConsistent) { codec.spatialLayers[1].maxBitrate); } +TEST_F(VideoCodecInitializerTest, Av1ConfiguredMinBitrateApplied) { + VideoEncoderConfig config; + config.simulcast_layers.resize(1); + config.simulcast_layers[0].min_bitrate_bps = 28000; + config.codec_type = VideoCodecType::kVideoCodecAV1; + std::vector streams = {DefaultStream()}; + streams[0].scalability_mode = ScalabilityMode::kL3T2; + + VideoCodec codec = + VideoCodecInitializer::SetupCodec(env_.field_trials(), config, streams); + + EXPECT_EQ(codec.spatialLayers[0].minBitrate, 28u); + EXPECT_GE(codec.spatialLayers[0].targetBitrate, + codec.spatialLayers[0].minBitrate); +} + +TEST_F(VideoCodecInitializerTest, + Av1ConfiguredMinBitrateLimitedByDefaultTargetBitrate) { + VideoEncoderConfig config; + config.simulcast_layers.resize(1); + config.simulcast_layers[0].min_bitrate_bps = 2228000; + config.codec_type = VideoCodecType::kVideoCodecAV1; + std::vector streams = {DefaultStream()}; + streams[0].scalability_mode = ScalabilityMode::kL3T2; + + VideoCodec codec = + VideoCodecInitializer::SetupCodec(env_.field_trials(), config, streams); + + EXPECT_GE(codec.spatialLayers[0].targetBitrate, + codec.spatialLayers[0].minBitrate); +} + +TEST_F(VideoCodecInitializerTest, Av1ConfiguredMinBitrateNotAppliedIfUnset) { + VideoEncoderConfig config; + config.simulcast_layers.resize(1); + config.codec_type = VideoCodecType::kVideoCodecAV1; + std::vector streams = {DefaultStream()}; + streams[0].scalability_mode = ScalabilityMode::kL3T2; + + VideoCodec codec = + VideoCodecInitializer::SetupCodec(env_.field_trials(), config, streams); + + EXPECT_GT(codec.spatialLayers[0].minBitrate, 0u); +} + TEST_F(VideoCodecInitializerTest, Av1TwoSpatialLayersActiveByDefault) { VideoEncoderConfig config; config.codec_type = VideoCodecType::kVideoCodecAV1; diff --git a/third_party/libwebrtc/moz-patch-stack/0001.patch b/third_party/libwebrtc/moz-patch-stack/0001.patch index 094a52747e6b..20dac1eb13c3 100644 --- a/third_party/libwebrtc/moz-patch-stack/0001.patch +++ b/third_party/libwebrtc/moz-patch-stack/0001.patch @@ -363,10 +363,10 @@ index 8813e0f4c2..b5e08cea2c 100644 RTPHeaderExtension::RTPHeaderExtension(const RTPHeaderExtension& other) = default; diff --git a/api/rtp_headers.h b/api/rtp_headers.h -index 7ededb94fc..bf1660d934 100644 +index 129ab5f499..5a867bbd77 100644 --- a/api/rtp_headers.h +++ b/api/rtp_headers.h -@@ -112,6 +112,19 @@ inline bool operator!=(const AbsoluteCaptureTime& lhs, +@@ -113,6 +113,19 @@ inline bool operator!=(const AbsoluteCaptureTime& lhs, return !(lhs == rhs); } @@ -386,7 +386,7 @@ index 7ededb94fc..bf1660d934 100644 struct RTPHeaderExtension { RTPHeaderExtension(); RTPHeaderExtension(const RTPHeaderExtension& other); -@@ -170,12 +183,12 @@ struct RTPHeaderExtension { +@@ -171,12 +184,12 @@ struct RTPHeaderExtension { absl::optional color_space; @@ -402,10 +402,10 @@ index 7ededb94fc..bf1660d934 100644 RTPHeader(); RTPHeader(const RTPHeader& other); diff --git a/api/rtp_parameters.cc b/api/rtp_parameters.cc -index 3ff4b58a2e..ad0f3c9396 100644 +index 283d238e77..da06138ce8 100644 --- a/api/rtp_parameters.cc +++ b/api/rtp_parameters.cc -@@ -160,7 +160,8 @@ bool RtpExtension::IsSupportedForAudio(absl::string_view uri) { +@@ -164,7 +164,8 @@ bool RtpExtension::IsSupportedForAudio(absl::string_view uri) { uri == webrtc::RtpExtension::kTransportSequenceNumberV2Uri || uri == webrtc::RtpExtension::kMidUri || uri == webrtc::RtpExtension::kRidUri || @@ -1437,7 +1437,7 @@ index 5ec1fd4a83..e46e050609 100644 int64_t _lastProcessFrameTimeNanos RTC_GUARDED_BY(capture_checker_); diff --git a/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc b/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc -index 21d46023f6..9c84b7768d 100644 +index fc3640cee0..b4508a224e 100644 --- a/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc +++ b/modules/video_coding/codecs/vp9/libvpx_vp9_encoder.cc @@ -255,6 +255,7 @@ LibvpxVp9Encoder::LibvpxVp9Encoder(const Environment& env, @@ -1543,7 +1543,7 @@ index 0134e3ea58..86d1bdda16 100644 std::unique_ptr svc_controller_; absl::optional scalability_mode_; diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn -index 85ec8edb9b..811256a88b 100644 +index f095b228ee..0cd307a646 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -465,6 +465,12 @@ rtc_library("logging") { @@ -1662,10 +1662,10 @@ index 0a9226ef6f..620c1c02f3 100644 vcm_ = nullptr; } diff --git a/webrtc.gni b/webrtc.gni -index 993b19c1f5..706d1794b2 100644 +index dfa7033bfd..3f0e7feccb 100644 --- a/webrtc.gni +++ b/webrtc.gni -@@ -130,7 +130,7 @@ declare_args() { +@@ -126,7 +126,7 @@ declare_args() { # Selects whether debug dumps for the audio processing module # should be generated. diff --git a/third_party/libwebrtc/moz-patch-stack/0010.patch b/third_party/libwebrtc/moz-patch-stack/0010.patch index a3740a83c9ee..2ffa333a401b 100644 --- a/third_party/libwebrtc/moz-patch-stack/0010.patch +++ b/third_party/libwebrtc/moz-patch-stack/0010.patch @@ -9,10 +9,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/92a7c3eee9f0c80ff 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/audio_coding/codecs/opus/audio_encoder_opus.cc b/modules/audio_coding/codecs/opus/audio_encoder_opus.cc -index 51b0fcd492..17e0e33b1d 100644 +index 8437706122..8e23b7b88f 100644 --- a/modules/audio_coding/codecs/opus/audio_encoder_opus.cc +++ b/modules/audio_coding/codecs/opus/audio_encoder_opus.cc -@@ -240,7 +240,7 @@ std::unique_ptr AudioEncoderOpusImpl::MakeAudioEncoder( +@@ -232,7 +232,7 @@ AudioCodecInfo AudioEncoderOpusImpl::QueryAudioEncoder( absl::optional AudioEncoderOpusImpl::SdpToConfig( const SdpAudioFormat& format) { if (!absl::EqualsIgnoreCase(format.name, "opus") || diff --git a/third_party/libwebrtc/moz-patch-stack/0014.patch b/third_party/libwebrtc/moz-patch-stack/0014.patch index 1b116c0bc7ab..ae4a7ce54f58 100644 --- a/third_party/libwebrtc/moz-patch-stack/0014.patch +++ b/third_party/libwebrtc/moz-patch-stack/0014.patch @@ -21,10 +21,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/e83c311e5293902be 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rtc_base/platform_thread.cc b/rtc_base/platform_thread.cc -index 6d369d747e..556204ac89 100644 +index 6433323fc2..26b23369bd 100644 --- a/rtc_base/platform_thread.cc +++ b/rtc_base/platform_thread.cc -@@ -189,15 +189,17 @@ PlatformThread PlatformThread::SpawnThread( +@@ -191,15 +191,17 @@ PlatformThread PlatformThread::SpawnThread( // Set the reserved stack stack size to 1M, which is the default on Windows // and Linux. DWORD thread_id = 0; diff --git a/third_party/libwebrtc/moz-patch-stack/0021.patch b/third_party/libwebrtc/moz-patch-stack/0021.patch index 7ba7d1cd025a..9d8615c29651 100644 --- a/third_party/libwebrtc/moz-patch-stack/0021.patch +++ b/third_party/libwebrtc/moz-patch-stack/0021.patch @@ -23,10 +23,10 @@ index 9a7a6f1498..86ec13fc1f 100644 #else constexpr bool kIsArm = false; diff --git a/modules/video_coding/codecs/vp8/libvpx_vp8_encoder.cc b/modules/video_coding/codecs/vp8/libvpx_vp8_encoder.cc -index 6dcdbfd2c8..5efbf88065 100644 +index 7b83e93e8f..ad8bfa83b4 100644 --- a/modules/video_coding/codecs/vp8/libvpx_vp8_encoder.cc +++ b/modules/video_coding/codecs/vp8/libvpx_vp8_encoder.cc -@@ -745,7 +745,7 @@ int LibvpxVp8Encoder::InitEncode(const VideoCodec* inst, +@@ -746,7 +746,7 @@ int LibvpxVp8Encoder::InitEncode(const VideoCodec* inst, } int LibvpxVp8Encoder::GetCpuSpeed(int width, int height) { @@ -35,7 +35,7 @@ index 6dcdbfd2c8..5efbf88065 100644 // On mobile platform, use a lower speed setting for lower resolutions for // CPUs with 4 or more cores. RTC_DCHECK_GT(number_of_cores_, 0); -@@ -773,6 +773,8 @@ int LibvpxVp8Encoder::GetCpuSpeed(int width, int height) { +@@ -774,6 +774,8 @@ int LibvpxVp8Encoder::GetCpuSpeed(int width, int height) { int LibvpxVp8Encoder::NumberOfThreads(int width, int height, int cpus) { #if defined(WEBRTC_ANDROID) if (android_specific_threading_settings_) { @@ -44,7 +44,7 @@ index 6dcdbfd2c8..5efbf88065 100644 if (width * height >= 320 * 180) { if (cpus >= 4) { // 3 threads for CPUs with 4 and more cores since most of times only 4 -@@ -785,7 +787,9 @@ int LibvpxVp8Encoder::NumberOfThreads(int width, int height, int cpus) { +@@ -786,7 +788,9 @@ int LibvpxVp8Encoder::NumberOfThreads(int width, int height, int cpus) { } } return 1; @@ -54,7 +54,7 @@ index 6dcdbfd2c8..5efbf88065 100644 #elif defined(WEBRTC_IOS) std::string trial_string = env_.field_trials().Lookup(kVP8IosMaxNumberOfThreadFieldTrial); -@@ -843,7 +847,7 @@ int LibvpxVp8Encoder::InitAndSetControlSettings() { +@@ -844,7 +848,7 @@ int LibvpxVp8Encoder::InitAndSetControlSettings() { // for getting the denoised frame from the encoder and using that // when encoding lower resolution streams. Would it work with the // multi-res encoding feature? diff --git a/third_party/libwebrtc/moz-patch-stack/0030.patch b/third_party/libwebrtc/moz-patch-stack/0030.patch index f5bc5a908f0e..6b97caff0f03 100644 --- a/third_party/libwebrtc/moz-patch-stack/0030.patch +++ b/third_party/libwebrtc/moz-patch-stack/0030.patch @@ -105,10 +105,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/b86cb7278bc4e5571 create mode 100644 common_audio/intrin.h diff --git a/.gn b/.gn -index 5de968946f..5796d30b7b 100644 +index 026382b0c7..2f2de25df6 100644 --- a/.gn +++ b/.gn -@@ -73,6 +73,8 @@ default_args = { +@@ -72,6 +72,8 @@ default_args = { # Prevent jsoncpp to pass -Wno-deprecated-declarations to users jsoncpp_no_deprecated_declarations = false @@ -118,7 +118,7 @@ index 5de968946f..5796d30b7b 100644 # TODO(https://bugs.webrtc.org/14437): Remove this section if general # Chromium fix resolves the problem. diff --git a/BUILD.gn b/BUILD.gn -index 7328df134a..c8bda37ad6 100644 +index 3c78142a25..dbc03c9f8b 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -33,7 +33,7 @@ if (is_android) { @@ -130,7 +130,7 @@ index 7328df134a..c8bda37ad6 100644 # This target should (transitively) cause everything to be built; if you run # 'ninja default' and then 'ninja all', the second build should do no work. group("default") { -@@ -158,6 +158,10 @@ config("common_inherited_config") { +@@ -154,6 +154,10 @@ config("common_inherited_config") { defines += [ "WEBRTC_ENABLE_OBJC_SYMBOL_EXPORT" ] } @@ -141,7 +141,7 @@ index 7328df134a..c8bda37ad6 100644 if (!rtc_builtin_ssl_root_certificates) { defines += [ "WEBRTC_EXCLUDE_BUILT_IN_SSL_ROOT_CERTS" ] } -@@ -513,9 +517,11 @@ config("common_config") { +@@ -509,9 +513,11 @@ config("common_config") { } } @@ -153,7 +153,7 @@ index 7328df134a..c8bda37ad6 100644 if (!build_with_chromium) { # Target to build all the WebRTC production code. -@@ -566,6 +572,34 @@ if (!build_with_chromium) { +@@ -562,6 +568,34 @@ if (!build_with_chromium) { "sdk", "video", ] @@ -188,7 +188,7 @@ index 7328df134a..c8bda37ad6 100644 if (rtc_include_builtin_audio_codecs) { deps += [ -@@ -578,6 +612,16 @@ if (!build_with_chromium) { +@@ -574,6 +608,16 @@ if (!build_with_chromium) { deps += [ "api/video:video_frame", "api/video:video_rtp_headers", @@ -206,10 +206,10 @@ index 7328df134a..c8bda37ad6 100644 } else { deps += [ diff --git a/api/BUILD.gn b/api/BUILD.gn -index 283d105163..89faaaa63f 100644 +index e537068ab5..2710b2476e 100644 --- a/api/BUILD.gn +++ b/api/BUILD.gn -@@ -40,6 +40,9 @@ rtc_source_set("enable_media") { +@@ -44,6 +44,9 @@ rtc_source_set("enable_media") { "../rtc_base/system:rtc_export", "environment", ] @@ -219,7 +219,7 @@ index 283d105163..89faaaa63f 100644 } rtc_source_set("enable_media_with_defaults") { -@@ -66,7 +69,7 @@ rtc_source_set("enable_media_with_defaults") { +@@ -76,7 +79,7 @@ rtc_source_set("enable_media_with_defaults") { ] } @@ -228,7 +228,7 @@ index 283d105163..89faaaa63f 100644 rtc_library("create_peerconnection_factory") { visibility = [ "*" ] allow_poison = [ "environment_construction" ] -@@ -217,6 +220,10 @@ rtc_source_set("ice_transport_interface") { +@@ -230,6 +233,10 @@ rtc_source_set("ice_transport_interface") { } rtc_library("dtls_transport_interface") { @@ -239,7 +239,7 @@ index 283d105163..89faaaa63f 100644 visibility = [ "*" ] sources = [ -@@ -233,6 +240,7 @@ rtc_library("dtls_transport_interface") { +@@ -247,6 +254,7 @@ rtc_library("dtls_transport_interface") { "//third_party/abseil-cpp/absl/types:optional", ] } @@ -247,7 +247,7 @@ index 283d105163..89faaaa63f 100644 rtc_library("dtmf_sender_interface") { visibility = [ "*" ] -@@ -245,6 +253,10 @@ rtc_library("dtmf_sender_interface") { +@@ -259,6 +267,10 @@ rtc_library("dtmf_sender_interface") { } rtc_library("rtp_sender_interface") { @@ -258,7 +258,7 @@ index 283d105163..89faaaa63f 100644 visibility = [ "*" ] sources = [ -@@ -259,6 +271,7 @@ rtc_library("rtp_sender_interface") { +@@ -273,6 +285,7 @@ rtc_library("rtp_sender_interface") { ":ref_count", ":rtc_error", ":rtp_parameters", @@ -266,7 +266,7 @@ index 283d105163..89faaaa63f 100644 ":scoped_refptr", "../rtc_base:checks", "../rtc_base/system:rtc_export", -@@ -267,8 +280,23 @@ rtc_library("rtp_sender_interface") { +@@ -281,8 +294,23 @@ rtc_library("rtp_sender_interface") { "//third_party/abseil-cpp/absl/functional:any_invocable", ] } @@ -290,7 +290,7 @@ index 283d105163..89faaaa63f 100644 visibility = [ "*" ] cflags = [] sources = [ -@@ -385,6 +413,7 @@ rtc_library("libjingle_peerconnection_api") { +@@ -405,6 +433,7 @@ rtc_library("libjingle_peerconnection_api") { "../rtc_base/system:rtc_export", ] } @@ -298,7 +298,7 @@ index 283d105163..89faaaa63f 100644 rtc_source_set("frame_transformer_interface") { visibility = [ "*" ] -@@ -560,6 +589,7 @@ rtc_source_set("peer_network_dependencies") { +@@ -580,6 +609,7 @@ rtc_source_set("peer_network_dependencies") { } rtc_source_set("peer_connection_quality_test_fixture_api") { @@ -306,7 +306,7 @@ index 283d105163..89faaaa63f 100644 visibility = [ "*" ] testonly = true sources = [ "test/peerconnection_quality_test_fixture.h" ] -@@ -606,6 +636,7 @@ rtc_source_set("peer_connection_quality_test_fixture_api") { +@@ -626,6 +656,7 @@ rtc_source_set("peer_connection_quality_test_fixture_api") { "//third_party/abseil-cpp/absl/types:optional", ] } @@ -314,7 +314,7 @@ index 283d105163..89faaaa63f 100644 rtc_source_set("frame_generator_api") { visibility = [ "*" ] -@@ -729,6 +760,7 @@ rtc_library("create_frame_generator") { +@@ -749,6 +780,7 @@ rtc_library("create_frame_generator") { ] } @@ -322,7 +322,7 @@ index 283d105163..89faaaa63f 100644 rtc_library("create_peer_connection_quality_test_frame_generator") { visibility = [ "*" ] testonly = true -@@ -745,6 +777,7 @@ rtc_library("create_peer_connection_quality_test_frame_generator") { +@@ -765,6 +797,7 @@ rtc_library("create_peer_connection_quality_test_frame_generator") { "//third_party/abseil-cpp/absl/types:optional", ] } @@ -330,7 +330,7 @@ index 283d105163..89faaaa63f 100644 rtc_source_set("libjingle_logging_api") { visibility = [ "*" ] -@@ -924,6 +957,7 @@ rtc_source_set("refcountedbase") { +@@ -945,6 +978,7 @@ rtc_source_set("refcountedbase") { ] } @@ -338,7 +338,7 @@ index 283d105163..89faaaa63f 100644 rtc_library("ice_transport_factory") { visibility = [ "*" ] sources = [ -@@ -947,6 +981,7 @@ rtc_library("ice_transport_factory") { +@@ -970,6 +1004,7 @@ rtc_library("ice_transport_factory") { "rtc_event_log:rtc_event_log", ] } @@ -581,7 +581,7 @@ index 0000000000..f6ff7f218f + #endif +#endif diff --git a/media/BUILD.gn b/media/BUILD.gn -index 8b5fd93338..868f662df9 100644 +index 3870a3b735..57b3d64265 100644 --- a/media/BUILD.gn +++ b/media/BUILD.gn @@ -77,7 +77,7 @@ rtc_library("rtc_media_base") { @@ -694,7 +694,7 @@ index 8b5fd93338..868f662df9 100644 rtc_library("rtc_simulcast_encoder_adapter") { visibility = [ "*" ] -@@ -505,6 +530,11 @@ rtc_library("rtc_internal_video_codecs") { +@@ -513,6 +538,11 @@ rtc_library("rtc_internal_video_codecs") { "//third_party/abseil-cpp/absl/strings", "//third_party/abseil-cpp/absl/types:optional", ] @@ -706,7 +706,7 @@ index 8b5fd93338..868f662df9 100644 if (enable_libaom) { defines += [ "RTC_USE_LIBAOM_AV1_ENCODER" ] -@@ -524,6 +554,13 @@ rtc_library("rtc_internal_video_codecs") { +@@ -532,6 +562,13 @@ rtc_library("rtc_internal_video_codecs") { "engine/internal_encoder_factory.cc", "engine/internal_encoder_factory.h", ] @@ -721,7 +721,7 @@ index 8b5fd93338..868f662df9 100644 rtc_library("rtc_audio_video") { diff --git a/media/base/media_channel.h b/media/base/media_channel.h -index 9e024d1ae3..fad10131e9 100644 +index d656304971..6e1f2b103f 100644 --- a/media/base/media_channel.h +++ b/media/base/media_channel.h @@ -65,10 +65,6 @@ class Timing; @@ -760,10 +760,10 @@ index 1c08382969..ff69ea62dc 100644 using webrtc::FrameDecryptorInterface; using webrtc::FrameEncryptorInterface; diff --git a/modules/audio_coding/BUILD.gn b/modules/audio_coding/BUILD.gn -index 655a9347fe..2ba849ff68 100644 +index 20c55108d7..021eb8e8f0 100644 --- a/modules/audio_coding/BUILD.gn +++ b/modules/audio_coding/BUILD.gn -@@ -546,7 +546,7 @@ rtc_library("webrtc_opus_wrapper") { +@@ -552,7 +552,7 @@ rtc_library("webrtc_opus_wrapper") { deps += [ rtc_opus_dir ] public_configs = [ "//third_party/opus:opus_config" ] } else if (build_with_mozilla) { @@ -908,10 +908,10 @@ index a0dd3942ee..47b9466a44 100644 } else { cflags = [ diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn -index dcde2c6b52..27c2675910 100644 +index ddbf3c3d58..1d6f6d567f 100644 --- a/modules/desktop_capture/BUILD.gn +++ b/modules/desktop_capture/BUILD.gn -@@ -348,37 +348,12 @@ rtc_library("desktop_capture") { +@@ -347,37 +347,12 @@ rtc_library("desktop_capture") { ] deps += [ ":desktop_capture_objc" ] } @@ -949,7 +949,7 @@ index dcde2c6b52..27c2675910 100644 } if (rtc_use_x11_extensions) { -@@ -537,9 +512,7 @@ rtc_library("desktop_capture") { +@@ -536,9 +511,7 @@ rtc_library("desktop_capture") { deps += [ "../../rtc_base:sanitizer" ] } @@ -1023,10 +1023,10 @@ index 8cefe5653c..b8d75865f7 100644 } } diff --git a/modules/video_capture/BUILD.gn b/modules/video_capture/BUILD.gn -index 59df8fe4f3..cea43c5c2e 100644 +index 29a7bea9d9..a8994aaa68 100644 --- a/modules/video_capture/BUILD.gn +++ b/modules/video_capture/BUILD.gn -@@ -126,21 +126,12 @@ if (!build_with_chromium || is_linux || is_chromeos) { +@@ -129,21 +129,12 @@ if (!build_with_chromium || is_linux || is_chromeos) { "strmiids.lib", "user32.lib", ] @@ -1050,7 +1050,7 @@ index 59df8fe4f3..cea43c5c2e 100644 "/config/external/nspr", "/nsprpub/lib/ds", diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn -index 811256a88b..9af3d47dd9 100644 +index 0cd307a646..4db5ed9ba6 100644 --- a/rtc_base/BUILD.gn +++ b/rtc_base/BUILD.gn @@ -334,6 +334,7 @@ rtc_library("sample_counter") { @@ -1069,7 +1069,7 @@ index 811256a88b..9af3d47dd9 100644 rtc_library("zero_memory") { visibility = [ "*" ] -@@ -853,7 +855,9 @@ rtc_library("rtc_json") { +@@ -854,7 +856,9 @@ rtc_library("rtc_json") { ":stringutils", "//third_party/abseil-cpp/absl/strings:string_view", ] @@ -1079,7 +1079,7 @@ index 811256a88b..9af3d47dd9 100644 if (rtc_build_json) { deps += [ "//third_party/jsoncpp" ] } else { -@@ -1205,6 +1209,7 @@ if (!build_with_chromium) { +@@ -1206,6 +1210,7 @@ if (!build_with_chromium) { } rtc_library("network") { @@ -1087,7 +1087,7 @@ index 811256a88b..9af3d47dd9 100644 visibility = [ "*" ] sources = [ "network.cc", -@@ -1242,16 +1247,20 @@ rtc_library("network") { +@@ -1243,16 +1248,20 @@ rtc_library("network") { deps += [ ":win32" ] } } @@ -1108,7 +1108,7 @@ index 811256a88b..9af3d47dd9 100644 visibility = [ "*" ] sources = [ "net_helper.cc", -@@ -1262,8 +1271,10 @@ rtc_library("net_helper") { +@@ -1263,8 +1272,10 @@ rtc_library("net_helper") { "//third_party/abseil-cpp/absl/strings:string_view", ] } @@ -1119,7 +1119,7 @@ index 811256a88b..9af3d47dd9 100644 visibility = [ "*" ] sources = [ "socket_adapters.cc", -@@ -1282,6 +1293,7 @@ rtc_library("socket_adapters") { +@@ -1283,6 +1294,7 @@ rtc_library("socket_adapters") { "//third_party/abseil-cpp/absl/strings:string_view", ] } @@ -1127,7 +1127,7 @@ index 811256a88b..9af3d47dd9 100644 rtc_library("network_route") { sources = [ -@@ -1296,6 +1308,7 @@ rtc_library("network_route") { +@@ -1297,6 +1309,7 @@ rtc_library("network_route") { } rtc_library("async_tcp_socket") { @@ -1135,7 +1135,7 @@ index 811256a88b..9af3d47dd9 100644 sources = [ "async_tcp_socket.cc", "async_tcp_socket.h", -@@ -1313,8 +1326,10 @@ rtc_library("async_tcp_socket") { +@@ -1314,8 +1327,10 @@ rtc_library("async_tcp_socket") { "network:sent_packet", ] } @@ -1146,7 +1146,7 @@ index 811256a88b..9af3d47dd9 100644 visibility = [ "*" ] sources = [ "async_udp_socket.cc", -@@ -1338,8 +1353,10 @@ rtc_library("async_udp_socket") { +@@ -1339,8 +1354,10 @@ rtc_library("async_udp_socket") { "//third_party/abseil-cpp/absl/types:optional", ] } @@ -1157,7 +1157,7 @@ index 811256a88b..9af3d47dd9 100644 visibility = [ "*" ] sources = [ "async_packet_socket.cc", -@@ -1359,6 +1376,7 @@ rtc_library("async_packet_socket") { +@@ -1360,6 +1377,7 @@ rtc_library("async_packet_socket") { "third_party/sigslot", ] } @@ -1165,7 +1165,7 @@ index 811256a88b..9af3d47dd9 100644 if (rtc_include_tests) { rtc_library("async_packet_socket_unittest") { -@@ -1414,6 +1432,7 @@ rtc_library("data_rate_limiter") { +@@ -1415,6 +1433,7 @@ rtc_library("data_rate_limiter") { } rtc_library("unique_id_generator") { @@ -1173,7 +1173,7 @@ index 811256a88b..9af3d47dd9 100644 sources = [ "unique_id_generator.cc", "unique_id_generator.h", -@@ -1428,6 +1447,7 @@ rtc_library("unique_id_generator") { +@@ -1429,6 +1448,7 @@ rtc_library("unique_id_generator") { "//third_party/abseil-cpp/absl/strings:string_view", ] } @@ -1181,7 +1181,7 @@ index 811256a88b..9af3d47dd9 100644 rtc_library("crc32") { sources = [ -@@ -1461,6 +1481,7 @@ rtc_library("stream") { +@@ -1462,6 +1482,7 @@ rtc_library("stream") { } rtc_library("rtc_certificate_generator") { @@ -1189,7 +1189,7 @@ index 811256a88b..9af3d47dd9 100644 visibility = [ "*" ] sources = [ "rtc_certificate_generator.cc", -@@ -1476,6 +1497,7 @@ rtc_library("rtc_certificate_generator") { +@@ -1477,6 +1498,7 @@ rtc_library("rtc_certificate_generator") { "//third_party/abseil-cpp/absl/types:optional", ] } @@ -1197,15 +1197,15 @@ index 811256a88b..9af3d47dd9 100644 rtc_source_set("ssl_header") { visibility = [ "*" ] -@@ -1532,6 +1554,7 @@ rtc_library("crypto_random") { +@@ -1533,6 +1555,7 @@ rtc_library("crypto_random") { } rtc_library("ssl") { +if (!build_with_mozilla) { visibility = [ "*" ] sources = [ - "helpers.h", -@@ -1616,6 +1639,7 @@ rtc_library("ssl") { + "openssl_key_pair.cc", +@@ -1605,6 +1628,7 @@ rtc_library("ssl") { deps += [ ":win32" ] } } @@ -1213,7 +1213,7 @@ index 811256a88b..9af3d47dd9 100644 rtc_library("ssl_adapter") { visibility = [ "*" ] -@@ -2224,7 +2248,7 @@ if (rtc_include_tests) { +@@ -2215,7 +2239,7 @@ if (rtc_include_tests) { } } @@ -1236,7 +1236,7 @@ index eef2754883..90f746e594 100644 deps += [ "..:logging", diff --git a/test/BUILD.gn b/test/BUILD.gn -index dd003f0e40..57b1c2be44 100644 +index f083e02bd6..3079aaa2a6 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -231,6 +231,7 @@ rtc_library("audio_test_common") { @@ -1268,7 +1268,7 @@ index dd003f0e40..57b1c2be44 100644 deps = [ "../api:array_view", -@@ -544,7 +550,9 @@ rtc_library("video_test_support") { +@@ -519,7 +525,9 @@ rtc_library("video_frame_writer") { ] if (!is_ios) { @@ -1278,7 +1278,7 @@ index dd003f0e40..57b1c2be44 100644 sources += [ "testsupport/jpeg_frame_writer.cc" ] } else { sources += [ "testsupport/jpeg_frame_writer_ios.cc" ] -@@ -1285,6 +1293,7 @@ if (!build_with_chromium) { +@@ -1301,6 +1309,7 @@ if (!build_with_chromium) { } } @@ -1286,7 +1286,7 @@ index dd003f0e40..57b1c2be44 100644 if (!build_with_chromium && is_android) { rtc_android_library("native_test_java") { testonly = true -@@ -1298,6 +1307,7 @@ if (!build_with_chromium && is_android) { +@@ -1314,6 +1323,7 @@ if (!build_with_chromium && is_android) { ] } } @@ -1295,7 +1295,7 @@ index dd003f0e40..57b1c2be44 100644 rtc_library("call_config_utils") { testonly = true diff --git a/video/BUILD.gn b/video/BUILD.gn -index e8449b580a..3fcc525546 100644 +index a23d4bb6ce..369894c236 100644 --- a/video/BUILD.gn +++ b/video/BUILD.gn @@ -17,7 +17,7 @@ rtc_library("video_stream_encoder_interface") { @@ -1307,7 +1307,7 @@ index e8449b580a..3fcc525546 100644 "../api:scoped_refptr", "../api/adaptation:resource_adaptation_api", "../api/units:data_rate", -@@ -400,7 +400,7 @@ rtc_library("video_stream_encoder_impl") { +@@ -406,7 +406,7 @@ rtc_library("video_stream_encoder_impl") { ":video_stream_encoder_interface", "../api:field_trials_view", "../api:rtp_parameters", @@ -1317,7 +1317,7 @@ index e8449b580a..3fcc525546 100644 "../api/adaptation:resource_adaptation_api", "../api/environment", diff --git a/webrtc.gni b/webrtc.gni -index 706d1794b2..0bf88b6e85 100644 +index 3f0e7feccb..c7d1b523a9 100644 --- a/webrtc.gni +++ b/webrtc.gni @@ -35,6 +35,11 @@ if (is_mac) { @@ -1344,7 +1344,7 @@ index 706d1794b2..0bf88b6e85 100644 declare_args() { # Setting this to true will make RTC_EXPORT (see rtc_base/system/rtc_export.h) # expand to code that will manage symbols visibility. -@@ -96,7 +106,7 @@ declare_args() { +@@ -92,7 +102,7 @@ declare_args() { # will tell the pre-processor to remove the default definition of the # SystemTimeNanos() which is defined in rtc_base/system_time.cc. In # that case a new implementation needs to be provided. @@ -1353,7 +1353,7 @@ index 706d1794b2..0bf88b6e85 100644 # Setting this to false will require the API user to pass in their own # SSLCertificateVerifier to verify the certificates presented from a -@@ -122,7 +132,7 @@ declare_args() { +@@ -118,7 +128,7 @@ declare_args() { # Used to specify an external OpenSSL include path when not compiling the # library that comes with WebRTC (i.e. rtc_build_ssl == 0). @@ -1362,7 +1362,7 @@ index 706d1794b2..0bf88b6e85 100644 # Enable when an external authentication mechanism is used for performing # packet authentication for RTP packets instead of libsrtp. -@@ -136,13 +146,13 @@ declare_args() { +@@ -132,13 +142,13 @@ declare_args() { rtc_exclude_audio_processing_module = false # Set this to false to skip building examples. @@ -1379,7 +1379,7 @@ index 706d1794b2..0bf88b6e85 100644 # Set this to use PipeWire on the Wayland display server. # By default it's only enabled on desktop Linux (excludes ChromeOS) and -@@ -153,9 +163,6 @@ declare_args() { +@@ -149,9 +159,6 @@ declare_args() { # Set this to link PipeWire and required libraries directly instead of using the dlopen. rtc_link_pipewire = false @@ -1389,7 +1389,7 @@ index 706d1794b2..0bf88b6e85 100644 # Experimental: enable use of Android AAudio which requires Android SDK 26 or above # and NDK r16 or above. rtc_enable_android_aaudio = false -@@ -288,7 +295,7 @@ declare_args() { +@@ -287,7 +294,7 @@ declare_args() { rtc_build_json = !build_with_mozilla rtc_build_libsrtp = !build_with_mozilla rtc_build_libvpx = !build_with_mozilla @@ -1398,7 +1398,7 @@ index 706d1794b2..0bf88b6e85 100644 rtc_build_opus = !build_with_mozilla rtc_build_ssl = !build_with_mozilla -@@ -307,7 +314,7 @@ declare_args() { +@@ -306,7 +313,7 @@ declare_args() { # Chromium uses its own IO handling, so the internal ADM is only built for # standalone WebRTC. @@ -1407,7 +1407,7 @@ index 706d1794b2..0bf88b6e85 100644 # Set this to true to enable the avx2 support in webrtc. # TODO: Make sure that AVX2 works also for non-clang compilers. -@@ -351,6 +358,9 @@ declare_args() { +@@ -350,6 +357,9 @@ declare_args() { rtc_enable_grpc = rtc_enable_protobuf && (is_linux || is_mac) } @@ -1417,7 +1417,7 @@ index 706d1794b2..0bf88b6e85 100644 # Make it possible to provide custom locations for some libraries (move these # up into declare_args should we need to actually use them for the GN build). rtc_libvpx_dir = "//third_party/libvpx" -@@ -1194,7 +1204,7 @@ if (is_mac || is_ios) { +@@ -1193,7 +1203,7 @@ if (is_mac || is_ios) { } } diff --git a/third_party/libwebrtc/moz-patch-stack/0032.patch b/third_party/libwebrtc/moz-patch-stack/0032.patch index 0d0ed8ec1a77..b0e486edd5b7 100644 --- a/third_party/libwebrtc/moz-patch-stack/0032.patch +++ b/third_party/libwebrtc/moz-patch-stack/0032.patch @@ -15,7 +15,7 @@ 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 1bf66a1fcc..04f0f02a5d 100644 +index 18f28c67eb..cfe194920c 100644 --- a/audio/audio_send_stream.cc +++ b/audio/audio_send_stream.cc @@ -413,6 +413,7 @@ webrtc::AudioSendStream::Stats AudioSendStream::GetStats( @@ -27,10 +27,10 @@ index 1bf66a1fcc..04f0f02a5d 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 3a8c9fe5c5..4f55416cca 100644 +index 4d59d2065f..37428ecc8a 100644 --- a/audio/channel_send.cc +++ b/audio/channel_send.cc -@@ -56,6 +56,31 @@ constexpr int64_t kMinRetransmissionWindowMs = 30; +@@ -57,6 +57,31 @@ constexpr int64_t kMinRetransmissionWindowMs = 30; class RtpPacketSenderProxy; class TransportSequenceNumberProxy; @@ -62,7 +62,7 @@ index 3a8c9fe5c5..4f55416cca 100644 class ChannelSend : public ChannelSendInterface, public AudioPacketizationCallback, // receive encoded // packets from the ACM -@@ -208,6 +233,8 @@ class ChannelSend : public ChannelSendInterface, +@@ -209,6 +234,8 @@ class ChannelSend : public ChannelSendInterface, bool input_mute_ RTC_GUARDED_BY(volume_settings_mutex_) = false; bool previous_frame_muted_ RTC_GUARDED_BY(encoder_queue_checker_) = false; @@ -71,7 +71,7 @@ index 3a8c9fe5c5..4f55416cca 100644 PacketRouter* packet_router_ RTC_GUARDED_BY(&worker_thread_checker_) = nullptr; const std::unique_ptr rtp_packet_pacer_proxy_; -@@ -399,6 +426,7 @@ ChannelSend::ChannelSend( +@@ -400,6 +427,7 @@ ChannelSend::ChannelSend( RtpTransportControllerSendInterface* transport_controller) : env_(env), ssrc_(ssrc), @@ -79,7 +79,7 @@ index 3a8c9fe5c5..4f55416cca 100644 rtp_packet_pacer_proxy_(new RtpPacketSenderProxy()), retransmission_rate_limiter_( new RateLimiter(&env_.clock(), kMaxRetransmissionWindowMs)), -@@ -423,6 +451,8 @@ ChannelSend::ChannelSend( +@@ -424,6 +452,8 @@ ChannelSend::ChannelSend( configuration.event_log = &env_.event_log(); configuration.rtt_stats = rtcp_rtt_stats; @@ -88,7 +88,7 @@ index 3a8c9fe5c5..4f55416cca 100644 if (env_.field_trials().IsDisabled("WebRTC-DisableRtxRateLimiter")) { configuration.retransmission_rate_limiter = retransmission_rate_limiter_.get(); -@@ -692,6 +722,7 @@ CallSendStatistics ChannelSend::GetRTCPStatistics() const { +@@ -693,6 +723,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/0033.patch b/third_party/libwebrtc/moz-patch-stack/0033.patch index a6b4d96942f7..76200b8a201e 100644 --- a/third_party/libwebrtc/moz-patch-stack/0033.patch +++ b/third_party/libwebrtc/moz-patch-stack/0033.patch @@ -25,10 +25,10 @@ index b6b62d7a75..5b793be18a 100644 // See LntfConfig for description. diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc -index ae9cb190a0..73fcdf5bc1 100644 +index caf373cd6a..cbfa512fd0 100644 --- a/video/rtp_video_stream_receiver2.cc +++ b/video/rtp_video_stream_receiver2.cc -@@ -1255,7 +1255,7 @@ void RtpVideoStreamReceiver2::StartReceive() { +@@ -1254,7 +1254,7 @@ void RtpVideoStreamReceiver2::StartReceive() { // Change REMB candidate egibility. packet_router_->RemoveReceiveRtpModule(rtp_rtcp_.get()); packet_router_->AddReceiveRtpModule(rtp_rtcp_.get(), diff --git a/third_party/libwebrtc/moz-patch-stack/0039.patch b/third_party/libwebrtc/moz-patch-stack/0039.patch index 2b7bbbff5162..91bbee00c427 100644 --- a/third_party/libwebrtc/moz-patch-stack/0039.patch +++ b/third_party/libwebrtc/moz-patch-stack/0039.patch @@ -10,7 +10,7 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/d881b16dd8a6813fe 1 file changed, 6 insertions(+) diff --git a/rtc_base/platform_thread.cc b/rtc_base/platform_thread.cc -index 556204ac89..71a9f1b224 100644 +index 26b23369bd..feb4338998 100644 --- a/rtc_base/platform_thread.cc +++ b/rtc_base/platform_thread.cc @@ -19,6 +19,8 @@ @@ -22,7 +22,7 @@ index 556204ac89..71a9f1b224 100644 namespace rtc { namespace { -@@ -181,6 +183,10 @@ PlatformThread PlatformThread::SpawnThread( +@@ -183,6 +185,10 @@ PlatformThread PlatformThread::SpawnThread( new std::function([thread_function = std::move(thread_function), name = std::string(name), attributes] { rtc::SetCurrentThreadName(name.c_str()); diff --git a/third_party/libwebrtc/moz-patch-stack/0040.patch b/third_party/libwebrtc/moz-patch-stack/0040.patch index bd68f08080e5..9a5a097d955c 100644 --- a/third_party/libwebrtc/moz-patch-stack/0040.patch +++ b/third_party/libwebrtc/moz-patch-stack/0040.patch @@ -20,10 +20,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/58f47eacaf10d12e2 11 files changed, 27 insertions(+), 27 deletions(-) diff --git a/BUILD.gn b/BUILD.gn -index c8bda37ad6..3b2ae1d40a 100644 +index dbc03c9f8b..7d60b75dc0 100644 --- a/BUILD.gn +++ b/BUILD.gn -@@ -456,12 +456,12 @@ config("common_config") { +@@ -452,12 +452,12 @@ config("common_config") { } } @@ -38,7 +38,7 @@ index c8bda37ad6..3b2ae1d40a 100644 defines += [ "WEBRTC_ARCH_ARM" ] if (arm_version >= 7) { defines += [ "WEBRTC_ARCH_ARM_V7" ] -@@ -471,7 +471,7 @@ config("common_config") { +@@ -467,7 +467,7 @@ config("common_config") { } } @@ -263,7 +263,7 @@ index 31ad61156d..6587fd14f3 100644 cflags = [ "-mfpu=neon" ] } diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn -index 27c2675910..23c66aec0a 100644 +index 1d6f6d567f..9b339c5ae0 100644 --- a/modules/desktop_capture/BUILD.gn +++ b/modules/desktop_capture/BUILD.gn @@ -10,7 +10,7 @@ import("//build/config/linux/gtk/gtk.gni") @@ -276,10 +276,10 @@ index 27c2675910..23c66aec0a 100644 config("x11_config") { if (rtc_use_x11_extensions) { diff --git a/webrtc.gni b/webrtc.gni -index 0bf88b6e85..ebc54e780d 100644 +index c7d1b523a9..75ba83eab1 100644 --- a/webrtc.gni +++ b/webrtc.gni -@@ -176,13 +176,13 @@ declare_args() { +@@ -172,13 +172,13 @@ declare_args() { # Selects fixed-point code where possible. rtc_prefer_fixed_point = false diff --git a/third_party/libwebrtc/moz-patch-stack/0041.patch b/third_party/libwebrtc/moz-patch-stack/0041.patch index dbd5c17f5308..be5702ac0d35 100644 --- a/third_party/libwebrtc/moz-patch-stack/0041.patch +++ b/third_party/libwebrtc/moz-patch-stack/0041.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 31e93b3c7d..e21feca1d6 100644 +index 0073930d9f..94dcced9c1 100644 --- a/audio/audio_receive_stream.cc +++ b/audio/audio_receive_stream.cc @@ -43,6 +43,8 @@ std::string AudioReceiveStreamInterface::Config::Rtp::ToString() const { @@ -115,7 +115,7 @@ index 61212f60fe..96e089585f 100644 } // namespace voe } // namespace webrtc diff --git a/call/audio_receive_stream.h b/call/audio_receive_stream.h -index 344a4b64e4..5a5a160c78 100644 +index f2118e81ab..35d80151f3 100644 --- a/call/audio_receive_stream.h +++ b/call/audio_receive_stream.h @@ -19,6 +19,7 @@ @@ -126,7 +126,7 @@ index 344a4b64e4..5a5a160c78 100644 #include "api/crypto/crypto_options.h" #include "api/rtp_parameters.h" #include "call/receive_stream.h" -@@ -118,6 +119,8 @@ class AudioReceiveStreamInterface : public MediaReceiveStreamInterface { +@@ -119,6 +120,8 @@ class AudioReceiveStreamInterface : public MediaReceiveStreamInterface { // See NackConfig for description. NackConfig nack; RtcpMode rtcp_mode = RtcpMode::kCompound; @@ -269,7 +269,7 @@ index e53bfbb58e..68c2b2ab56 100644 // DEPRECATED, transport_feedback_callback is no longer invoked by the RTP diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc -index 73fcdf5bc1..e8c13de119 100644 +index cbfa512fd0..3e2a995ada 100644 --- a/video/rtp_video_stream_receiver2.cc +++ b/video/rtp_video_stream_receiver2.cc @@ -83,7 +83,8 @@ std::unique_ptr CreateRtpRtcpModule( diff --git a/third_party/libwebrtc/moz-patch-stack/0043.patch b/third_party/libwebrtc/moz-patch-stack/0043.patch index 120314000534..92ef11d80834 100644 --- a/third_party/libwebrtc/moz-patch-stack/0043.patch +++ b/third_party/libwebrtc/moz-patch-stack/0043.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 e8c13de119..aae40d96e7 100644 +index 3e2a995ada..bbbbc2588f 100644 --- a/video/rtp_video_stream_receiver2.cc +++ b/video/rtp_video_stream_receiver2.cc -@@ -1051,6 +1051,16 @@ absl::optional RtpVideoStreamReceiver2::LastReceivedKeyframePacketMs() +@@ -1050,6 +1050,16 @@ absl::optional RtpVideoStreamReceiver2::LastReceivedKeyframePacketMs() return absl::nullopt; } @@ -53,10 +53,10 @@ index 8ea3ffd310..34f10334ba 100644 // Implements RtpVideoFrameReceiver. void ManageFrame(std::unique_ptr frame) override; diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc -index f3a7c329a6..507dd3a04c 100644 +index 92b4cb1500..ac9f458def 100644 --- a/video/video_receive_stream2.cc +++ b/video/video_receive_stream2.cc -@@ -581,6 +581,14 @@ VideoReceiveStreamInterface::Stats VideoReceiveStream2::GetStats() const { +@@ -580,6 +580,14 @@ VideoReceiveStreamInterface::Stats VideoReceiveStream2::GetStats() const { stats.sender_reports_bytes_sent = rtcp_sr_stats->bytes_sent; stats.sender_reports_reports_count = rtcp_sr_stats->reports_count; } diff --git a/third_party/libwebrtc/moz-patch-stack/0045.patch b/third_party/libwebrtc/moz-patch-stack/0045.patch index 8e4996ec8838..60f1554049d1 100644 --- a/third_party/libwebrtc/moz-patch-stack/0045.patch +++ b/third_party/libwebrtc/moz-patch-stack/0045.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 e21feca1d6..a677623843 100644 +index 94dcced9c1..5aabcf10d3 100644 --- a/audio/audio_receive_stream.cc +++ b/audio/audio_receive_stream.cc -@@ -383,7 +383,6 @@ int AudioReceiveStreamImpl::GetBaseMinimumPlayoutDelayMs() const { +@@ -386,7 +386,6 @@ int AudioReceiveStreamImpl::GetBaseMinimumPlayoutDelayMs() const { } std::vector AudioReceiveStreamImpl::GetSources() const { diff --git a/third_party/libwebrtc/moz-patch-stack/0049.patch b/third_party/libwebrtc/moz-patch-stack/0049.patch index 44cdd10debb6..cc91f0600a35 100644 --- a/third_party/libwebrtc/moz-patch-stack/0049.patch +++ b/third_party/libwebrtc/moz-patch-stack/0049.patch @@ -159,10 +159,10 @@ index 68c2b2ab56..b43919f18a 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 aae40d96e7..385a9d5ccf 100644 +index bbbbc2588f..d5da05aba2 100644 --- a/video/rtp_video_stream_receiver2.cc +++ b/video/rtp_video_stream_receiver2.cc -@@ -1056,9 +1056,10 @@ absl::optional RtpVideoStreamReceiver2::LastReceivedKeyframePacketMs() +@@ -1055,9 +1055,10 @@ absl::optional 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, @@ -190,10 +190,10 @@ index 34f10334ba..1355352cf6 100644 private: // Implements RtpVideoFrameReceiver. diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc -index 507dd3a04c..69943d11ee 100644 +index ac9f458def..25e836529d 100644 --- a/video/video_receive_stream2.cc +++ b/video/video_receive_stream2.cc -@@ -587,7 +587,8 @@ VideoReceiveStreamInterface::Stats VideoReceiveStream2::GetStats() const { +@@ -586,7 +586,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/0051.patch b/third_party/libwebrtc/moz-patch-stack/0051.patch index 38ee07c6fb06..d55c008a9b55 100644 --- a/third_party/libwebrtc/moz-patch-stack/0051.patch +++ b/third_party/libwebrtc/moz-patch-stack/0051.patch @@ -41,7 +41,7 @@ index a511eda7bd..75a4a1cac0 100644 DegradedCall::~DegradedCall() { RTC_DCHECK_RUN_ON(call_->worker_thread()); diff --git a/modules/audio_coding/acm2/acm_receiver.cc b/modules/audio_coding/acm2/acm_receiver.cc -index e6e5d69d40..964c9dc2ef 100644 +index e50545fa94..99c8277230 100644 --- a/modules/audio_coding/acm2/acm_receiver.cc +++ b/modules/audio_coding/acm2/acm_receiver.cc @@ -51,7 +51,7 @@ std::unique_ptr CreateNetEq( diff --git a/third_party/libwebrtc/moz-patch-stack/0052.patch b/third_party/libwebrtc/moz-patch-stack/0052.patch index 6cec647a055c..62fd45eaef20 100644 --- a/third_party/libwebrtc/moz-patch-stack/0052.patch +++ b/third_party/libwebrtc/moz-patch-stack/0052.patch @@ -18,10 +18,10 @@ Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/0300b32b7de70fb89 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/BUILD.gn b/BUILD.gn -index 3b2ae1d40a..7ac93125f8 100644 +index 7d60b75dc0..94f8bf41e0 100644 --- a/BUILD.gn +++ b/BUILD.gn -@@ -223,6 +223,9 @@ config("common_inherited_config") { +@@ -219,6 +219,9 @@ config("common_inherited_config") { if (is_linux || is_chromeos) { defines += [ "WEBRTC_LINUX" ] } @@ -32,7 +32,7 @@ index 3b2ae1d40a..7ac93125f8 100644 defines += [ "WEBRTC_MAC" ] } diff --git a/modules/video_capture/BUILD.gn b/modules/video_capture/BUILD.gn -index cea43c5c2e..cd4280d4c0 100644 +index a8994aaa68..a2bf27f645 100644 --- a/modules/video_capture/BUILD.gn +++ b/modules/video_capture/BUILD.gn @@ -72,7 +72,7 @@ if (!build_with_chromium || is_linux || is_chromeos) { @@ -74,10 +74,10 @@ index d64ea689bb..c3c6955a7b 100644 #endif // defined(WEBRTC_POSIX) } diff --git a/webrtc.gni b/webrtc.gni -index ebc54e780d..5c80e87995 100644 +index 75ba83eab1..2279e25fbe 100644 --- a/webrtc.gni +++ b/webrtc.gni -@@ -368,7 +368,7 @@ rtc_opus_dir = "//third_party/opus" +@@ -367,7 +367,7 @@ rtc_opus_dir = "//third_party/opus" # Desktop capturer is supported only on Windows, OSX and Linux. rtc_desktop_capture_supported = diff --git a/third_party/libwebrtc/moz-patch-stack/0058.patch b/third_party/libwebrtc/moz-patch-stack/0058.patch index 1ae0f2a8d8af..910cfb4b77c9 100644 --- a/third_party/libwebrtc/moz-patch-stack/0058.patch +++ b/third_party/libwebrtc/moz-patch-stack/0058.patch @@ -1,22 +1,24 @@ From: Michael Froman -Date: Wed, 1 Jun 2022 12:47:00 -0500 -Subject: Bug 1766646 - (fix-f137b75a4d) specify default constructor on - config.emplace(...) +Date: Tue, 21 Jun 2022 11:11:09 -0500 +Subject: Bug 1773223 - Generate webrtc moz.builds for all platforms at once. + r=mjf,firefox-build-system-reviewers,ahochheiden --- - modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) + build_overrides/build.gni | 4 ++++ + 1 file changed, 4 insertions(+) -diff --git a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc -index 7deeb7ad64..2f47ee0f18 100644 ---- a/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc -+++ b/modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc -@@ -549,7 +549,7 @@ absl::optional LossBasedBweV2::CreateConfig( - if (!enabled.Get()) { - return config; - } -- config.emplace(); -+ config.emplace(Config()); - config->bandwidth_rampup_upper_bound_factor = - bandwidth_rampup_upper_bound_factor.Get(); - config->bandwidth_rampup_upper_bound_factor_in_hold = +diff --git a/build_overrides/build.gni b/build_overrides/build.gni +index 33a535525e..7d91ab85a9 100644 +--- a/build_overrides/build.gni ++++ b/build_overrides/build.gni +@@ -50,6 +50,10 @@ if (host_os == "mac" || host_os == "linux") { + use_system_xcode = _result == 0 + } + ++use_system_xcode = false ++xcode_version = "10.15" ++mac_xcode_version = "default" ++ + declare_args() { + # WebRTC doesn't depend on //base from production code but only for testing + # purposes. In any case, it doesn't depend on //third_party/perfetto which diff --git a/third_party/libwebrtc/moz-patch-stack/0059.patch b/third_party/libwebrtc/moz-patch-stack/0059.patch index 910cfb4b77c9..ce88d2599a76 100644 --- a/third_party/libwebrtc/moz-patch-stack/0059.patch +++ b/third_party/libwebrtc/moz-patch-stack/0059.patch @@ -1,24 +1,26 @@ From: Michael Froman -Date: Tue, 21 Jun 2022 11:11:09 -0500 -Subject: Bug 1773223 - Generate webrtc moz.builds for all platforms at once. - r=mjf,firefox-build-system-reviewers,ahochheiden +Date: Tue, 21 Jun 2022 11:17:46 -0500 +Subject: Bug 1772380 - to upstream - ref count this in lambda capture +Bug 1876843 - (fix-23cecc1d43) drop rtc:: prefix on scoped_ptr + +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/2e4867d8cc9813869bd80f5201d3f7d84afcd412 --- - build_overrides/build.gni | 4 ++++ - 1 file changed, 4 insertions(+) + modules/video_capture/linux/video_capture_v4l2.cc | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) -diff --git a/build_overrides/build.gni b/build_overrides/build.gni -index 33a535525e..7d91ab85a9 100644 ---- a/build_overrides/build.gni -+++ b/build_overrides/build.gni -@@ -50,6 +50,10 @@ if (host_os == "mac" || host_os == "linux") { - use_system_xcode = _result == 0 - } - -+use_system_xcode = false -+xcode_version = "10.15" -+mac_xcode_version = "default" -+ - declare_args() { - # WebRTC doesn't depend on //base from production code but only for testing - # purposes. In any case, it doesn't depend on //third_party/perfetto which +diff --git a/modules/video_capture/linux/video_capture_v4l2.cc b/modules/video_capture/linux/video_capture_v4l2.cc +index a2e362bb94..6d8a5e463f 100644 +--- a/modules/video_capture/linux/video_capture_v4l2.cc ++++ b/modules/video_capture/linux/video_capture_v4l2.cc +@@ -303,8 +303,8 @@ int32_t VideoCaptureModuleV4L2::StartCapture( + if (_captureThread.empty()) { + quit_ = false; + _captureThread = rtc::PlatformThread::SpawnJoinable( +- [this] { +- while (CaptureProcess()) { ++ [self = scoped_refptr(this)] { ++ while (self->CaptureProcess()) { + } + }, + "CaptureThread", diff --git a/third_party/libwebrtc/moz-patch-stack/0060.patch b/third_party/libwebrtc/moz-patch-stack/0060.patch index ce88d2599a76..6d2adebd4dab 100644 --- a/third_party/libwebrtc/moz-patch-stack/0060.patch +++ b/third_party/libwebrtc/moz-patch-stack/0060.patch @@ -1,26 +1,48 @@ From: Michael Froman -Date: Tue, 21 Jun 2022 11:17:46 -0500 -Subject: Bug 1772380 - to upstream - ref count this in lambda capture +Date: Wed, 3 Aug 2022 20:21:25 -0500 +Subject: Bug 1780582 - work around generating VideoFrameBufferType;r=mjf -Bug 1876843 - (fix-23cecc1d43) drop rtc:: prefix on scoped_ptr - -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/2e4867d8cc9813869bd80f5201d3f7d84afcd412 --- - modules/video_capture/linux/video_capture_v4l2.cc | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) + .../api/org/webrtc/VideoFrameBufferType.java | 33 +++++++++++++++++++ + 1 file changed, 33 insertions(+) + create mode 100644 sdk/android/api/org/webrtc/VideoFrameBufferType.java -diff --git a/modules/video_capture/linux/video_capture_v4l2.cc b/modules/video_capture/linux/video_capture_v4l2.cc -index a2e362bb94..6d8a5e463f 100644 ---- a/modules/video_capture/linux/video_capture_v4l2.cc -+++ b/modules/video_capture/linux/video_capture_v4l2.cc -@@ -303,8 +303,8 @@ int32_t VideoCaptureModuleV4L2::StartCapture( - if (_captureThread.empty()) { - quit_ = false; - _captureThread = rtc::PlatformThread::SpawnJoinable( -- [this] { -- while (CaptureProcess()) { -+ [self = scoped_refptr(this)] { -+ while (self->CaptureProcess()) { - } - }, - "CaptureThread", +diff --git a/sdk/android/api/org/webrtc/VideoFrameBufferType.java b/sdk/android/api/org/webrtc/VideoFrameBufferType.java +new file mode 100644 +index 0000000000..7b05b88cba +--- /dev/null ++++ b/sdk/android/api/org/webrtc/VideoFrameBufferType.java +@@ -0,0 +1,33 @@ ++ ++// Copyright 2022 The Chromium 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 autogenerated by ++// java_cpp_enum.py ++// From ++// ../../api/video/video_frame_buffer.h ++ ++package org.webrtc; ++ ++import androidx.annotation.IntDef; ++ ++import java.lang.annotation.Retention; ++import java.lang.annotation.RetentionPolicy; ++ ++@IntDef({ ++ VideoFrameBufferType.NATIVE, VideoFrameBufferType.I420, VideoFrameBufferType.I420A, ++ VideoFrameBufferType.I422, VideoFrameBufferType.I444, VideoFrameBufferType.I010, ++ VideoFrameBufferType.I210, VideoFrameBufferType.NV12 ++}) ++@Retention(RetentionPolicy.SOURCE) ++public @interface VideoFrameBufferType { ++ int NATIVE = 0; ++ int I420 = 1; ++ int I420A = 2; ++ int I422 = 3; ++ int I444 = 4; ++ int I010 = 5; ++ int I210 = 6; ++ int NV12 = 7; ++} diff --git a/third_party/libwebrtc/moz-patch-stack/0061.patch b/third_party/libwebrtc/moz-patch-stack/0061.patch index 6d2adebd4dab..12bf71ee22c8 100644 --- a/third_party/libwebrtc/moz-patch-stack/0061.patch +++ b/third_party/libwebrtc/moz-patch-stack/0061.patch @@ -1,48 +1,53 @@ -From: Michael Froman -Date: Wed, 3 Aug 2022 20:21:25 -0500 -Subject: Bug 1780582 - work around generating VideoFrameBufferType;r=mjf +From: Andreas Pehrson +Date: Mon, 5 Sep 2022 13:56:00 +0000 +Subject: Bug 1786502 - Lock access to DeviceInfo devicechange callbacks. + r=webrtc-reviewers,jib +Differential Revision: https://phabricator.services.mozilla.com/D155365 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/e826dfadfe1264c59d9b13e3c17d6f75a40f5c33 --- - .../api/org/webrtc/VideoFrameBufferType.java | 33 +++++++++++++++++++ - 1 file changed, 33 insertions(+) - create mode 100644 sdk/android/api/org/webrtc/VideoFrameBufferType.java + modules/video_capture/video_capture.h | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) -diff --git a/sdk/android/api/org/webrtc/VideoFrameBufferType.java b/sdk/android/api/org/webrtc/VideoFrameBufferType.java -new file mode 100644 -index 0000000000..7b05b88cba ---- /dev/null -+++ b/sdk/android/api/org/webrtc/VideoFrameBufferType.java -@@ -0,0 +1,33 @@ -+ -+// Copyright 2022 The Chromium 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 autogenerated by -+// java_cpp_enum.py -+// From -+// ../../api/video/video_frame_buffer.h -+ -+package org.webrtc; -+ -+import androidx.annotation.IntDef; -+ -+import java.lang.annotation.Retention; -+import java.lang.annotation.RetentionPolicy; -+ -+@IntDef({ -+ VideoFrameBufferType.NATIVE, VideoFrameBufferType.I420, VideoFrameBufferType.I420A, -+ VideoFrameBufferType.I422, VideoFrameBufferType.I444, VideoFrameBufferType.I010, -+ VideoFrameBufferType.I210, VideoFrameBufferType.NV12 -+}) -+@Retention(RetentionPolicy.SOURCE) -+public @interface VideoFrameBufferType { -+ int NATIVE = 0; -+ int I420 = 1; -+ int I420A = 2; -+ int I422 = 3; -+ int I444 = 4; -+ int I010 = 5; -+ int I210 = 6; -+ int NV12 = 7; -+} +diff --git a/modules/video_capture/video_capture.h b/modules/video_capture/video_capture.h +index 7279bed476..db07580921 100644 +--- a/modules/video_capture/video_capture.h ++++ b/modules/video_capture/video_capture.h +@@ -16,6 +16,8 @@ + #include "modules/desktop_capture/desktop_capture_types.h" + #include "modules/video_capture/raw_video_sink_interface.h" + #include "modules/video_capture/video_capture_defines.h" ++#include "rtc_base/synchronization/mutex.h" ++#include "rtc_base/thread_annotations.h" + #include + + #if defined(ANDROID) +@@ -40,15 +42,18 @@ class VideoCaptureModule : public RefCountInterface { + virtual uint32_t NumberOfDevices() = 0; + virtual int32_t Refresh() = 0; + virtual void DeviceChange() { ++ MutexLock lock(&_inputCallbacksMutex); + for (auto inputCallBack : _inputCallBacks) { + inputCallBack->OnDeviceChange(); + } + } + virtual void RegisterVideoInputFeedBack(VideoInputFeedBack* callBack) { ++ MutexLock lock(&_inputCallbacksMutex); + _inputCallBacks.insert(callBack); + } + + virtual void DeRegisterVideoInputFeedBack(VideoInputFeedBack* callBack) { ++ MutexLock lock(&_inputCallbacksMutex); + auto it = _inputCallBacks.find(callBack); + if (it != _inputCallBacks.end()) { + _inputCallBacks.erase(it); +@@ -102,7 +107,8 @@ class VideoCaptureModule : public RefCountInterface { + + virtual ~DeviceInfo() {} + private: +- std::set _inputCallBacks; ++ Mutex _inputCallbacksMutex; ++ std::set _inputCallBacks RTC_GUARDED_BY(_inputCallbacksMutex); + }; + + // Register capture data callback diff --git a/third_party/libwebrtc/moz-patch-stack/0062.patch b/third_party/libwebrtc/moz-patch-stack/0062.patch index 12bf71ee22c8..f875b929a379 100644 --- a/third_party/libwebrtc/moz-patch-stack/0062.patch +++ b/third_party/libwebrtc/moz-patch-stack/0062.patch @@ -1,53 +1,49 @@ -From: Andreas Pehrson -Date: Mon, 5 Sep 2022 13:56:00 +0000 -Subject: Bug 1786502 - Lock access to DeviceInfo devicechange callbacks. - r=webrtc-reviewers,jib +From: Michael Froman +Date: Mon, 24 Oct 2022 13:00:00 -0500 +Subject: Bug 1797161 - pt1 - tweak BUILD.gn around task_queue_win usage. r?ng! -Differential Revision: https://phabricator.services.mozilla.com/D155365 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/e826dfadfe1264c59d9b13e3c17d6f75a40f5c33 +Add assurance that we will not build task_queue_win.cc to avoid +possible win32k API usage. + +Differential Revision: https://phabricator.services.mozilla.com/D160115 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/f097eb8cbd8b7686ce306a46a4db691194fd39c1 --- - modules/video_capture/video_capture.h | 8 +++++++- - 1 file changed, 7 insertions(+), 1 deletion(-) + api/task_queue/BUILD.gn | 5 +++++ + rtc_base/BUILD.gn | 4 ++++ + 2 files changed, 9 insertions(+) -diff --git a/modules/video_capture/video_capture.h b/modules/video_capture/video_capture.h -index 7279bed476..db07580921 100644 ---- a/modules/video_capture/video_capture.h -+++ b/modules/video_capture/video_capture.h -@@ -16,6 +16,8 @@ - #include "modules/desktop_capture/desktop_capture_types.h" - #include "modules/video_capture/raw_video_sink_interface.h" - #include "modules/video_capture/video_capture_defines.h" -+#include "rtc_base/synchronization/mutex.h" -+#include "rtc_base/thread_annotations.h" - #include +diff --git a/api/task_queue/BUILD.gn b/api/task_queue/BUILD.gn +index 7f0ab8faf0..aa6878a9e2 100644 +--- a/api/task_queue/BUILD.gn ++++ b/api/task_queue/BUILD.gn +@@ -29,6 +29,11 @@ rtc_library("task_queue") { + ] + } - #if defined(ANDROID) -@@ -40,15 +42,18 @@ class VideoCaptureModule : public RefCountInterface { - virtual uint32_t NumberOfDevices() = 0; - virtual int32_t Refresh() = 0; - virtual void DeviceChange() { -+ MutexLock lock(&_inputCallbacksMutex); - for (auto inputCallBack : _inputCallBacks) { - inputCallBack->OnDeviceChange(); - } - } - virtual void RegisterVideoInputFeedBack(VideoInputFeedBack* callBack) { -+ MutexLock lock(&_inputCallbacksMutex); - _inputCallBacks.insert(callBack); - } - - virtual void DeRegisterVideoInputFeedBack(VideoInputFeedBack* callBack) { -+ MutexLock lock(&_inputCallbacksMutex); - auto it = _inputCallBacks.find(callBack); - if (it != _inputCallBacks.end()) { - _inputCallBacks.erase(it); -@@ -102,7 +107,8 @@ class VideoCaptureModule : public RefCountInterface { - - virtual ~DeviceInfo() {} - private: -- std::set _inputCallBacks; -+ Mutex _inputCallbacksMutex; -+ std::set _inputCallBacks RTC_GUARDED_BY(_inputCallbacksMutex); - }; - - // Register capture data callback ++# Mozilla - we want to ensure that rtc_include_tests is set to false ++# to guarantee that default_task_queue_factory is not used so we ++# know that remaining win32k code in task_queue_win.cc is not built. ++# See Bug 1797161 for more info. ++assert(!rtc_include_tests, "Mozilla - verify rtc_include_tests is off") + if (rtc_include_tests) { + rtc_library("task_queue_test") { + visibility = [ "*" ] +diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn +index 4db5ed9ba6..d51059c140 100644 +--- a/rtc_base/BUILD.gn ++++ b/rtc_base/BUILD.gn +@@ -720,10 +720,14 @@ if (is_mac || is_ios) { + if (is_win) { + rtc_library("rtc_task_queue_win") { + visibility = [ "../api/task_queue:default_task_queue_factory" ] ++# See Bug 1797161 for more info. Remove from build until win32k ++# usage is removed. ++if (!build_with_mozilla) { + sources = [ + "task_queue_win.cc", + "task_queue_win.h", + ] ++} + deps = [ + ":checks", + ":logging", diff --git a/third_party/libwebrtc/moz-patch-stack/0063.patch b/third_party/libwebrtc/moz-patch-stack/0063.patch index d46eb982f381..85b4ae4747da 100644 --- a/third_party/libwebrtc/moz-patch-stack/0063.patch +++ b/third_party/libwebrtc/moz-patch-stack/0063.patch @@ -1,49 +1,31 @@ From: Michael Froman -Date: Mon, 24 Oct 2022 13:00:00 -0500 -Subject: Bug 1797161 - pt1 - tweak BUILD.gn around task_queue_win usage. r?ng! +Date: Mon, 24 Oct 2022 14:03:00 -0500 +Subject: Bug 1797161 - pt3 - add static_assert to ensure we don't include + task_queue_win.cc in Mozilla builds. r?ng! -Add assurance that we will not build task_queue_win.cc to avoid -possible win32k API usage. - -Differential Revision: https://phabricator.services.mozilla.com/D160115 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/f097eb8cbd8b7686ce306a46a4db691194fd39c1 +Differential Revision: https://phabricator.services.mozilla.com/D160117 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/50b15e036924203147e34ec20e2689fe4a847645 --- - api/task_queue/BUILD.gn | 5 +++++ - rtc_base/BUILD.gn | 4 ++++ - 2 files changed, 9 insertions(+) + rtc_base/task_queue_win.cc | 9 +++++++++ + 1 file changed, 9 insertions(+) -diff --git a/api/task_queue/BUILD.gn b/api/task_queue/BUILD.gn -index 7f0ab8faf0..aa6878a9e2 100644 ---- a/api/task_queue/BUILD.gn -+++ b/api/task_queue/BUILD.gn -@@ -29,6 +29,11 @@ rtc_library("task_queue") { - ] - } +diff --git a/rtc_base/task_queue_win.cc b/rtc_base/task_queue_win.cc +index 7e46d58e27..bf55a25c69 100644 +--- a/rtc_base/task_queue_win.cc ++++ b/rtc_base/task_queue_win.cc +@@ -8,6 +8,15 @@ + * be found in the AUTHORS file in the root of the source tree. + */ -+# Mozilla - we want to ensure that rtc_include_tests is set to false -+# to guarantee that default_task_queue_factory is not used so we -+# know that remaining win32k code in task_queue_win.cc is not built. -+# See Bug 1797161 for more info. -+assert(!rtc_include_tests, "Mozilla - verify rtc_include_tests is off") - if (rtc_include_tests) { - rtc_library("task_queue_test") { - visibility = [ "*" ] -diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn -index 9af3d47dd9..cb8e4a9175 100644 ---- a/rtc_base/BUILD.gn -+++ b/rtc_base/BUILD.gn -@@ -720,10 +720,14 @@ if (is_mac || is_ios) { - if (is_win) { - rtc_library("rtc_task_queue_win") { - visibility = [ "../api/task_queue:default_task_queue_factory" ] -+# See Bug 1797161 for more info. Remove from build until win32k -+# usage is removed. -+if (!build_with_mozilla) { - sources = [ - "task_queue_win.cc", - "task_queue_win.h", - ] -+} - deps = [ - ":checks", - ":logging", ++// Mozilla - this file should not be included in Mozilla builds until ++// win32k API usage is removed. This was once done in Bug 1395259, but ++// the upstreaming attempt stalled. Until win32k usage is officially ++// removed upstream, we have reverted to upstream's version of the file ++// (to reduce or elminate merge conflicts), and a static assert is ++// placed here to ensure this file isn't accidentally included in the ++// Mozilla build. ++static_assert(false, "This file should not be built, see Bug 1797161."); ++ + #include "rtc_base/task_queue_win.h" + + // clang-format off diff --git a/third_party/libwebrtc/moz-patch-stack/0064.patch b/third_party/libwebrtc/moz-patch-stack/0064.patch index 85b4ae4747da..8b5344a64382 100644 --- a/third_party/libwebrtc/moz-patch-stack/0064.patch +++ b/third_party/libwebrtc/moz-patch-stack/0064.patch @@ -1,31 +1,78 @@ -From: Michael Froman -Date: Mon, 24 Oct 2022 14:03:00 -0500 -Subject: Bug 1797161 - pt3 - add static_assert to ensure we don't include - task_queue_win.cc in Mozilla builds. r?ng! +From: Andreas Pehrson +Date: Mon, 12 Dec 2022 15:47:00 +0000 +Subject: Bug 1451394 - Expose mac camera capture backend in .gn and switch it + to gecko libyuv. r=webrtc-reviewers,mjf -Differential Revision: https://phabricator.services.mozilla.com/D160117 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/50b15e036924203147e34ec20e2689fe4a847645 +Differential Revision: https://phabricator.services.mozilla.com/D163682 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/b0658888969395dca938597783c8a377b9bea209 --- - rtc_base/task_queue_win.cc | 9 +++++++++ - 1 file changed, 9 insertions(+) + BUILD.gn | 4 ++++ + sdk/BUILD.gn | 6 ++++++ + 2 files changed, 10 insertions(+) -diff --git a/rtc_base/task_queue_win.cc b/rtc_base/task_queue_win.cc -index 7e46d58e27..bf55a25c69 100644 ---- a/rtc_base/task_queue_win.cc -+++ b/rtc_base/task_queue_win.cc -@@ -8,6 +8,15 @@ - * be found in the AUTHORS file in the root of the source tree. - */ +diff --git a/BUILD.gn b/BUILD.gn +index 94f8bf41e0..d213f92def 100644 +--- a/BUILD.gn ++++ b/BUILD.gn +@@ -632,6 +632,10 @@ if (!build_with_chromium) { + ] + } -+// Mozilla - this file should not be included in Mozilla builds until -+// win32k API usage is removed. This was once done in Bug 1395259, but -+// the upstreaming attempt stalled. Until win32k usage is officially -+// removed upstream, we have reverted to upstream's version of the file -+// (to reduce or elminate merge conflicts), and a static assert is -+// placed here to ensure this file isn't accidentally included in the -+// Mozilla build. -+static_assert(false, "This file should not be built, see Bug 1797161."); ++ if (build_with_mozilla && is_mac) { ++ deps += [ "sdk:videocapture_objc" ] ++ } + - #include "rtc_base/task_queue_win.h" + if (rtc_enable_protobuf) { + deps += [ "logging:rtc_event_log_proto" ] + } +diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn +index 683d8e5829..6fb3a79d2b 100644 +--- a/sdk/BUILD.gn ++++ b/sdk/BUILD.gn +@@ -528,6 +528,7 @@ if (is_ios || is_mac) { + } + } - // clang-format off ++ if (!build_with_mozilla) { + rtc_library("videosource_objc") { + sources = [ + "objc/api/peerconnection/RTCVideoSource+Private.h", +@@ -557,6 +558,7 @@ if (is_ios || is_mac) { + ":used_from_extension", + ] + } ++ } + + rtc_library("videoframebuffer_objc") { + visibility = [ "*" ] +@@ -589,6 +591,7 @@ if (is_ios || is_mac) { + ] + } + ++ if (!build_with_mozilla) { + rtc_library("metal_objc") { + visibility = [ "*" ] + allow_poison = [ +@@ -650,6 +653,7 @@ if (is_ios || is_mac) { + ":videoframebuffer_objc", + ] + } ++ } + + rtc_library("videocapture_objc") { + visibility = [ "*" ] +@@ -678,6 +682,7 @@ if (is_ios || is_mac) { + ] + } + ++ if (!build_with_mozilla) { + rtc_library("videocodec_objc") { + visibility = [ "*" ] + configs += [ "..:no_global_constructors" ] +@@ -1755,5 +1760,6 @@ if (is_ios || is_mac) { + "VideoToolbox.framework", + ] + } ++ } + } + } diff --git a/third_party/libwebrtc/moz-patch-stack/0065.patch b/third_party/libwebrtc/moz-patch-stack/0065.patch index 0059ca9fbd32..52302d9a8df6 100644 --- a/third_party/libwebrtc/moz-patch-stack/0065.patch +++ b/third_party/libwebrtc/moz-patch-stack/0065.patch @@ -1,78 +1,28 @@ From: Andreas Pehrson Date: Mon, 12 Dec 2022 15:47:00 +0000 -Subject: Bug 1451394 - Expose mac camera capture backend in .gn and switch it - to gecko libyuv. r=webrtc-reviewers,mjf +Subject: Bug 1451394 - Record video frame captures with PerformanceRecorder in + the new mac camera backend. r=padenot -Differential Revision: https://phabricator.services.mozilla.com/D163682 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/b0658888969395dca938597783c8a377b9bea209 +Also includes: +Bug 1806605 - Pass TrackingId instead of nsCString to CaptureStage. + +Differential Revision: https://phabricator.services.mozilla.com/D163687 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/a7362238c9e6fbe0d28200f6b41fc40a0c9a2158 --- - BUILD.gn | 4 ++++ - sdk/BUILD.gn | 6 ++++++ - 2 files changed, 10 insertions(+) + modules/video_capture/video_capture.h | 3 +++ + 1 file changed, 3 insertions(+) -diff --git a/BUILD.gn b/BUILD.gn -index 7ac93125f8..6003780e37 100644 ---- a/BUILD.gn -+++ b/BUILD.gn -@@ -636,6 +636,10 @@ if (!build_with_chromium) { - ] - } +diff --git a/modules/video_capture/video_capture.h b/modules/video_capture/video_capture.h +index db07580921..43a6a7f832 100644 +--- a/modules/video_capture/video_capture.h ++++ b/modules/video_capture/video_capture.h +@@ -154,6 +154,9 @@ class VideoCaptureModule : public RefCountInterface { + // Return whether the rotation is applied or left pending. + virtual bool GetApplyRotation() = 0; -+ if (build_with_mozilla && is_mac) { -+ deps += [ "sdk:videocapture_objc" ] -+ } ++ // Mozilla: TrackingId setter for use in profiler markers. ++ virtual void SetTrackingId(uint32_t aTrackingIdProcId) {} + - if (rtc_enable_protobuf) { - deps += [ "logging:rtc_event_log_proto" ] - } -diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn -index e7ec3e3614..ff4b6d1deb 100644 ---- a/sdk/BUILD.gn -+++ b/sdk/BUILD.gn -@@ -528,6 +528,7 @@ if (is_ios || is_mac) { - } - } - -+ if (!build_with_mozilla) { - rtc_library("videosource_objc") { - sources = [ - "objc/api/peerconnection/RTCVideoSource+Private.h", -@@ -557,6 +558,7 @@ if (is_ios || is_mac) { - ":used_from_extension", - ] - } -+ } - - rtc_library("videoframebuffer_objc") { - visibility = [ "*" ] -@@ -589,6 +591,7 @@ if (is_ios || is_mac) { - ] - } - -+ if (!build_with_mozilla) { - rtc_library("metal_objc") { - visibility = [ "*" ] - allow_poison = [ -@@ -650,6 +653,7 @@ if (is_ios || is_mac) { - ":videoframebuffer_objc", - ] - } -+ } - - rtc_library("videocapture_objc") { - visibility = [ "*" ] -@@ -678,6 +682,7 @@ if (is_ios || is_mac) { - ] - } - -+ if (!build_with_mozilla) { - rtc_library("videocodec_objc") { - visibility = [ "*" ] - configs += [ "..:no_global_constructors" ] -@@ -1750,5 +1755,6 @@ if (is_ios || is_mac) { - "VideoToolbox.framework", - ] - } -+ } - } - } + protected: + ~VideoCaptureModule() override {} + }; diff --git a/third_party/libwebrtc/moz-patch-stack/0066.patch b/third_party/libwebrtc/moz-patch-stack/0066.patch index 52302d9a8df6..dd75b0943f08 100644 --- a/third_party/libwebrtc/moz-patch-stack/0066.patch +++ b/third_party/libwebrtc/moz-patch-stack/0066.patch @@ -1,28 +1,346 @@ From: Andreas Pehrson -Date: Mon, 12 Dec 2022 15:47:00 +0000 -Subject: Bug 1451394 - Record video frame captures with PerformanceRecorder in - the new mac camera backend. r=padenot +Date: Tue, 23 Nov 2021 14:11:00 +0000 +Subject: Bug 1742181 - libwebrtc: Implement packetsDiscarded bookkeeping for + received video. r=ng + +Depends on D131707 + +Differential Revision: https://phabricator.services.mozilla.com/D131708 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/d0196a45a1f449874fc2a759e85e403c45c25575 Also includes: -Bug 1806605 - Pass TrackingId instead of nsCString to CaptureStage. -Differential Revision: https://phabricator.services.mozilla.com/D163687 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/a7362238c9e6fbe0d28200f6b41fc40a0c9a2158 +Bug 1804288 - (fix-de7ae5755b) reimplement Bug 1742181 - libwebrtc: Implement packetsDiscarded bookkeeping for received video. r=pehrsons + +Differential Revision: https://phabricator.services.mozilla.com/D163959 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/ee566d1bfb654d36e5d58dce637fb0580b989ac1 --- - modules/video_capture/video_capture.h | 3 +++ - 1 file changed, 3 insertions(+) + api/video/frame_buffer.cc | 25 ++++++++++++++++++++++--- + api/video/frame_buffer.h | 4 ++++ + call/video_receive_stream.h | 2 ++ + modules/video_coding/packet_buffer.cc | 10 +++++++--- + modules/video_coding/packet_buffer.h | 5 ++++- + video/receive_statistics_proxy.cc | 5 +++++ + video/receive_statistics_proxy.h | 1 + + video/rtp_video_stream_receiver2.cc | 5 ++++- + video/rtp_video_stream_receiver2.h | 3 +++ + video/video_receive_stream2.cc | 1 + + video/video_stream_buffer_controller.cc | 12 ++++++++++++ + video/video_stream_buffer_controller.h | 5 +++++ + 12 files changed, 70 insertions(+), 8 deletions(-) -diff --git a/modules/video_capture/video_capture.h b/modules/video_capture/video_capture.h -index db07580921..43a6a7f832 100644 ---- a/modules/video_capture/video_capture.h -+++ b/modules/video_capture/video_capture.h -@@ -154,6 +154,9 @@ class VideoCaptureModule : public RefCountInterface { - // Return whether the rotation is applied or left pending. - virtual bool GetApplyRotation() = 0; +diff --git a/api/video/frame_buffer.cc b/api/video/frame_buffer.cc +index 5e8fc0ff44..09ca53ac94 100644 +--- a/api/video/frame_buffer.cc ++++ b/api/video/frame_buffer.cc +@@ -140,14 +140,29 @@ void FrameBuffer::DropNextDecodableTemporalUnit() { + } -+ // Mozilla: TrackingId setter for use in profiler markers. -+ virtual void SetTrackingId(uint32_t aTrackingIdProcId) {} + auto end_it = std::next(next_decodable_temporal_unit_->last_frame); +- num_dropped_frames_ += std::count_if( +- frames_.begin(), end_it, +- [](const auto& f) { return f.second.encoded_frame != nullptr; }); + - protected: - ~VideoCaptureModule() override {} ++ UpdateDroppedFramesAndDiscardedPackets(frames_.begin(), end_it); + + frames_.erase(frames_.begin(), end_it); + FindNextAndLastDecodableTemporalUnit(); + } + ++void FrameBuffer::UpdateDroppedFramesAndDiscardedPackets(FrameIterator begin_it, ++ FrameIterator end_it) { ++ unsigned int num_discarded_packets = 0; ++ unsigned int num_dropped_frames = ++ std::count_if(begin_it, end_it, [&](const auto& f) { ++ if (f.second.encoded_frame) { ++ const auto& packetInfos = f.second.encoded_frame->PacketInfos(); ++ num_discarded_packets += packetInfos.size(); ++ } ++ return f.second.encoded_frame != nullptr; ++ }); ++ ++ num_dropped_frames_ += num_dropped_frames; ++ num_discarded_packets_ += num_discarded_packets; ++} ++ + absl::optional FrameBuffer::LastContinuousFrameId() const { + return last_continuous_frame_id_; + } +@@ -167,6 +182,9 @@ int FrameBuffer::GetTotalNumberOfContinuousTemporalUnits() const { + int FrameBuffer::GetTotalNumberOfDroppedFrames() const { + return num_dropped_frames_; + } ++int FrameBuffer::GetTotalNumberOfDiscardedPackets() const { ++ return num_discarded_packets_; ++} + + size_t FrameBuffer::CurrentSize() const { + return frames_.size(); +@@ -269,6 +287,7 @@ void FrameBuffer::FindNextAndLastDecodableTemporalUnit() { + } + + void FrameBuffer::Clear() { ++ UpdateDroppedFramesAndDiscardedPackets(frames_.begin(), frames_.end()); + frames_.clear(); + next_decodable_temporal_unit_.reset(); + decodable_temporal_units_info_.reset(); +diff --git a/api/video/frame_buffer.h b/api/video/frame_buffer.h +index 94edf64d5a..81fd12da58 100644 +--- a/api/video/frame_buffer.h ++++ b/api/video/frame_buffer.h +@@ -66,6 +66,7 @@ class FrameBuffer { + + int GetTotalNumberOfContinuousTemporalUnits() const; + int GetTotalNumberOfDroppedFrames() const; ++ int GetTotalNumberOfDiscardedPackets() const; + size_t CurrentSize() const; + + private: +@@ -87,6 +88,8 @@ class FrameBuffer { + void PropagateContinuity(const FrameIterator& frame_it); + void FindNextAndLastDecodableTemporalUnit(); + void Clear(); ++ void UpdateDroppedFramesAndDiscardedPackets(FrameIterator begin_it, ++ FrameIterator end_it); + + const bool legacy_frame_id_jump_behavior_; + const size_t max_size_; +@@ -99,6 +102,7 @@ class FrameBuffer { + + int num_continuous_temporal_units_ = 0; + int num_dropped_frames_ = 0; ++ int num_discarded_packets_ = 0; }; + + } // namespace webrtc +diff --git a/call/video_receive_stream.h b/call/video_receive_stream.h +index 40e8f85e8e..6e764f8c15 100644 +--- a/call/video_receive_stream.h ++++ b/call/video_receive_stream.h +@@ -112,6 +112,8 @@ class VideoReceiveStreamInterface : public MediaReceiveStreamInterface { + // https://www.w3.org/TR/webrtc-stats/#dom-rtcvideoreceiverstats-framesdropped + uint32_t frames_dropped = 0; + uint32_t frames_decoded = 0; ++ // https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsdiscarded ++ uint64_t packets_discarded = 0; + // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-totaldecodetime + TimeDelta total_decode_time = TimeDelta::Zero(); + // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-totalprocessingdelay +diff --git a/modules/video_coding/packet_buffer.cc b/modules/video_coding/packet_buffer.cc +index b30e84078c..792c845235 100644 +--- a/modules/video_coding/packet_buffer.cc ++++ b/modules/video_coding/packet_buffer.cc +@@ -127,25 +127,27 @@ PacketBuffer::InsertResult PacketBuffer::InsertPacket( + return result; + } + +-void PacketBuffer::ClearTo(uint16_t seq_num) { ++uint32_t PacketBuffer::ClearTo(uint16_t seq_num) { + // We have already cleared past this sequence number, no need to do anything. + if (is_cleared_to_first_seq_num_ && + AheadOf(first_seq_num_, seq_num)) { +- return; ++ return 0; + } + + // If the packet buffer was cleared between a frame was created and returned. + if (!first_packet_received_) +- return; ++ return 0; + + // Avoid iterating over the buffer more than once by capping the number of + // iterations to the `size_` of the buffer. + ++seq_num; ++ uint32_t num_cleared_packets = 0; + size_t diff = ForwardDiff(first_seq_num_, seq_num); + size_t iterations = std::min(diff, buffer_.size()); + for (size_t i = 0; i < iterations; ++i) { + auto& stored = buffer_[first_seq_num_ % buffer_.size()]; + if (stored != nullptr && AheadOf(seq_num, stored->seq_num())) { ++ ++num_cleared_packets; + stored = nullptr; + } + ++first_seq_num_; +@@ -161,6 +163,8 @@ void PacketBuffer::ClearTo(uint16_t seq_num) { + + received_padding_.erase(received_padding_.begin(), + received_padding_.lower_bound(seq_num)); ++ ++ return num_cleared_packets; + } + + void PacketBuffer::Clear() { +diff --git a/modules/video_coding/packet_buffer.h b/modules/video_coding/packet_buffer.h +index 9e143b8a85..87f87ca101 100644 +--- a/modules/video_coding/packet_buffer.h ++++ b/modules/video_coding/packet_buffer.h +@@ -80,7 +80,10 @@ class PacketBuffer { + ABSL_MUST_USE_RESULT InsertResult + InsertPacket(std::unique_ptr packet); + ABSL_MUST_USE_RESULT InsertResult InsertPadding(uint16_t seq_num); +- void ClearTo(uint16_t seq_num); ++ ++ // Clear all packets older than |seq_num|. Returns the number of packets ++ // cleared. ++ uint32_t ClearTo(uint16_t seq_num); + void Clear(); + + void ForceSpsPpsIdrIsH264Keyframe(); +diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc +index 75512a2465..8ef4d553ad 100644 +--- a/video/receive_statistics_proxy.cc ++++ b/video/receive_statistics_proxy.cc +@@ -799,6 +799,11 @@ void ReceiveStatisticsProxy::OnDroppedFrames(uint32_t frames_dropped) { + })); + } + ++void ReceiveStatisticsProxy::OnDiscardedPackets(uint32_t packets_discarded) { ++ RTC_DCHECK_RUN_ON(&main_thread_); ++ stats_.packets_discarded += packets_discarded; ++} ++ + void ReceiveStatisticsProxy::OnPreDecode(VideoCodecType codec_type, int qp) { + RTC_DCHECK_RUN_ON(&main_thread_); + last_codec_type_ = codec_type; +diff --git a/video/receive_statistics_proxy.h b/video/receive_statistics_proxy.h +index 8e4941f961..7bcfc7c057 100644 +--- a/video/receive_statistics_proxy.h ++++ b/video/receive_statistics_proxy.h +@@ -94,6 +94,7 @@ class ReceiveStatisticsProxy : public VideoStreamBufferControllerStatsObserver, + void OnDecodableFrame(TimeDelta jitter_buffer_delay, + TimeDelta target_delay, + TimeDelta minimum_delay) override; ++ void OnDiscardedPackets(uint32_t packets_discarded) override; + void OnFrameBufferTimingsUpdated(int estimated_max_decode_time_ms, + 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 d5da05aba2..dbfbeda4d5 100644 +--- a/video/rtp_video_stream_receiver2.cc ++++ b/video/rtp_video_stream_receiver2.cc +@@ -244,6 +244,7 @@ RtpVideoStreamReceiver2::RtpVideoStreamReceiver2( + RtcpPacketTypeCounterObserver* rtcp_packet_type_counter_observer, + RtcpCnameCallback* rtcp_cname_callback, + NackPeriodicProcessor* nack_periodic_processor, ++ VideoStreamBufferControllerStatsObserver* vcm_receive_statistics, + OnCompleteFrameCallback* complete_frame_callback, + rtc::scoped_refptr frame_decryptor, + rtc::scoped_refptr frame_transformer, +@@ -292,6 +293,7 @@ RtpVideoStreamReceiver2::RtpVideoStreamReceiver2( + &rtcp_feedback_buffer_, + &rtcp_feedback_buffer_, + field_trials_)), ++ vcm_receive_statistics_(vcm_receive_statistics), + packet_buffer_(kPacketBufferStartSize, + PacketBufferMaxSize(field_trials_)), + reference_finder_(std::make_unique()), +@@ -1244,7 +1246,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)); +- packet_buffer_.ClearTo(seq_num); ++ uint32_t num_packets_cleared = packet_buffer_.ClearTo(seq_num); ++ vcm_receive_statistics_->OnDiscardedPackets(num_packets_cleared); + reference_finder_->ClearTo(seq_num); + } + } +diff --git a/video/rtp_video_stream_receiver2.h b/video/rtp_video_stream_receiver2.h +index 1355352cf6..8f9f9db6db 100644 +--- a/video/rtp_video_stream_receiver2.h ++++ b/video/rtp_video_stream_receiver2.h +@@ -50,6 +50,7 @@ + #include "rtc_base/thread_annotations.h" + #include "video/buffered_frame_decryptor.h" + #include "video/unique_timestamp_counter.h" ++#include "video/video_stream_buffer_controller.h" + + namespace webrtc { + +@@ -92,6 +93,7 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender, + RtcpPacketTypeCounterObserver* rtcp_packet_type_counter_observer, + RtcpCnameCallback* rtcp_cname_callback, + NackPeriodicProcessor* nack_periodic_processor, ++ VideoStreamBufferControllerStatsObserver* vcm_receive_statistics, + // The KeyFrameRequestSender is optional; if not provided, key frame + // requests are sent via the internal RtpRtcp module. + OnCompleteFrameCallback* complete_frame_callback, +@@ -368,6 +370,7 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender, + std::unique_ptr loss_notification_controller_ + RTC_GUARDED_BY(packet_sequence_checker_); + ++ VideoStreamBufferControllerStatsObserver* const vcm_receive_statistics_; + video_coding::PacketBuffer packet_buffer_ + RTC_GUARDED_BY(packet_sequence_checker_); + // h26x_packet_buffer_ is nullptr if codec list doens't contain H.264 or +diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc +index 25e836529d..3741421a68 100644 +--- a/video/video_receive_stream2.cc ++++ b/video/video_receive_stream2.cc +@@ -209,6 +209,7 @@ VideoReceiveStream2::VideoReceiveStream2( + &stats_proxy_, + &stats_proxy_, + nack_periodic_processor, ++ &stats_proxy_, + this, // OnCompleteFrameCallback + std::move(config_.frame_decryptor), + std::move(config_.frame_transformer), +diff --git a/video/video_stream_buffer_controller.cc b/video/video_stream_buffer_controller.cc +index b7ed89656e..09aca28259 100644 +--- a/video/video_stream_buffer_controller.cc ++++ b/video/video_stream_buffer_controller.cc +@@ -259,6 +259,7 @@ void VideoStreamBufferController::OnFrameReady( + + // Update stats. + UpdateDroppedFrames(); ++ UpdateDiscardedPackets(); + UpdateFrameBufferTimings(min_receive_time, now); + UpdateTimingFrameInfo(); + +@@ -324,6 +325,17 @@ void VideoStreamBufferController::UpdateDroppedFrames() + buffer_->GetTotalNumberOfDroppedFrames(); + } + ++void VideoStreamBufferController::UpdateDiscardedPackets() ++ RTC_RUN_ON(&worker_sequence_checker_) { ++ const int discarded_packets = buffer_->GetTotalNumberOfDiscardedPackets() - ++ packets_discarded_before_last_new_frame_; ++ if (discarded_packets > 0) { ++ stats_proxy_->OnDiscardedPackets(discarded_packets); ++ } ++ packets_discarded_before_last_new_frame_ = ++ buffer_->GetTotalNumberOfDiscardedPackets(); ++} ++ + void VideoStreamBufferController::UpdateFrameBufferTimings( + Timestamp min_receive_time, + Timestamp now) { +diff --git a/video/video_stream_buffer_controller.h b/video/video_stream_buffer_controller.h +index f07b3eb274..6b9b67ca22 100644 +--- a/video/video_stream_buffer_controller.h ++++ b/video/video_stream_buffer_controller.h +@@ -52,6 +52,8 @@ class VideoStreamBufferControllerStatsObserver { + TimeDelta target_delay, + TimeDelta minimum_delay) = 0; + ++ virtual void OnDiscardedPackets(uint32_t packets_discarded) = 0; ++ + // Various jitter buffer delays determined by VCMTiming. + virtual void OnFrameBufferTimingsUpdated(int estimated_max_decode_time_ms, + int current_delay_ms, +@@ -94,6 +96,7 @@ class VideoStreamBufferController { + void OnTimeout(TimeDelta delay); + void FrameReadyForDecode(uint32_t rtp_timestamp, Timestamp render_time); + void UpdateDroppedFrames() RTC_RUN_ON(&worker_sequence_checker_); ++ void UpdateDiscardedPackets() RTC_RUN_ON(&worker_sequence_checker_); + void UpdateFrameBufferTimings(Timestamp min_receive_time, Timestamp now); + void UpdateTimingFrameInfo(); + bool IsTooManyFramesQueued() const RTC_RUN_ON(&worker_sequence_checker_); +@@ -120,6 +123,8 @@ class VideoStreamBufferController { + RTC_GUARDED_BY(&worker_sequence_checker_); + int frames_dropped_before_last_new_frame_ + RTC_GUARDED_BY(&worker_sequence_checker_) = 0; ++ int packets_discarded_before_last_new_frame_ ++ RTC_GUARDED_BY(&worker_sequence_checker_) = 0; + VCMVideoProtection protection_mode_ + RTC_GUARDED_BY(&worker_sequence_checker_) = kProtectionNack; + diff --git a/third_party/libwebrtc/moz-patch-stack/0067.patch b/third_party/libwebrtc/moz-patch-stack/0067.patch index 2560f193f51a..d2b461d7a225 100644 --- a/third_party/libwebrtc/moz-patch-stack/0067.patch +++ b/third_party/libwebrtc/moz-patch-stack/0067.patch @@ -1,346 +1,46 @@ From: Andreas Pehrson -Date: Tue, 23 Nov 2021 14:11:00 +0000 -Subject: Bug 1742181 - libwebrtc: Implement packetsDiscarded bookkeeping for - received video. r=ng +Date: Thu, 6 Jan 2022 00:16:00 +0000 +Subject: Bug 1748478 - Propagate calculated discarded packets to stats. r=bwc -Depends on D131707 - -Differential Revision: https://phabricator.services.mozilla.com/D131708 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/d0196a45a1f449874fc2a759e85e403c45c25575 - -Also includes: - -Bug 1804288 - (fix-de7ae5755b) reimplement Bug 1742181 - libwebrtc: Implement packetsDiscarded bookkeeping for received video. r=pehrsons - -Differential Revision: https://phabricator.services.mozilla.com/D163959 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/ee566d1bfb654d36e5d58dce637fb0580b989ac1 +Differential Revision: https://phabricator.services.mozilla.com/D135061 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/56fbf0469e25fa0d589c51ca112ce534a7c0ab91 --- - api/video/frame_buffer.cc | 25 ++++++++++++++++++++++--- - api/video/frame_buffer.h | 4 ++++ - call/video_receive_stream.h | 2 ++ - modules/video_coding/packet_buffer.cc | 10 +++++++--- - modules/video_coding/packet_buffer.h | 5 ++++- - video/receive_statistics_proxy.cc | 5 +++++ - video/receive_statistics_proxy.h | 1 + - video/rtp_video_stream_receiver2.cc | 5 ++++- - video/rtp_video_stream_receiver2.h | 3 +++ - video/video_receive_stream2.cc | 1 + - video/video_stream_buffer_controller.cc | 12 ++++++++++++ - video/video_stream_buffer_controller.h | 5 +++++ - 12 files changed, 70 insertions(+), 8 deletions(-) + video/receive_statistics_proxy.cc | 9 +++++++-- + video/rtp_video_stream_receiver2.cc | 4 +++- + 2 files changed, 10 insertions(+), 3 deletions(-) -diff --git a/api/video/frame_buffer.cc b/api/video/frame_buffer.cc -index 5e8fc0ff44..09ca53ac94 100644 ---- a/api/video/frame_buffer.cc -+++ b/api/video/frame_buffer.cc -@@ -140,14 +140,29 @@ void FrameBuffer::DropNextDecodableTemporalUnit() { - } - - auto end_it = std::next(next_decodable_temporal_unit_->last_frame); -- num_dropped_frames_ += std::count_if( -- frames_.begin(), end_it, -- [](const auto& f) { return f.second.encoded_frame != nullptr; }); -+ -+ UpdateDroppedFramesAndDiscardedPackets(frames_.begin(), end_it); - - frames_.erase(frames_.begin(), end_it); - FindNextAndLastDecodableTemporalUnit(); - } - -+void FrameBuffer::UpdateDroppedFramesAndDiscardedPackets(FrameIterator begin_it, -+ FrameIterator end_it) { -+ unsigned int num_discarded_packets = 0; -+ unsigned int num_dropped_frames = -+ std::count_if(begin_it, end_it, [&](const auto& f) { -+ if (f.second.encoded_frame) { -+ const auto& packetInfos = f.second.encoded_frame->PacketInfos(); -+ num_discarded_packets += packetInfos.size(); -+ } -+ return f.second.encoded_frame != nullptr; -+ }); -+ -+ num_dropped_frames_ += num_dropped_frames; -+ num_discarded_packets_ += num_discarded_packets; -+} -+ - absl::optional FrameBuffer::LastContinuousFrameId() const { - return last_continuous_frame_id_; - } -@@ -167,6 +182,9 @@ int FrameBuffer::GetTotalNumberOfContinuousTemporalUnits() const { - int FrameBuffer::GetTotalNumberOfDroppedFrames() const { - return num_dropped_frames_; - } -+int FrameBuffer::GetTotalNumberOfDiscardedPackets() const { -+ return num_discarded_packets_; -+} - - size_t FrameBuffer::CurrentSize() const { - return frames_.size(); -@@ -269,6 +287,7 @@ void FrameBuffer::FindNextAndLastDecodableTemporalUnit() { - } - - void FrameBuffer::Clear() { -+ UpdateDroppedFramesAndDiscardedPackets(frames_.begin(), frames_.end()); - frames_.clear(); - next_decodable_temporal_unit_.reset(); - decodable_temporal_units_info_.reset(); -diff --git a/api/video/frame_buffer.h b/api/video/frame_buffer.h -index 94edf64d5a..81fd12da58 100644 ---- a/api/video/frame_buffer.h -+++ b/api/video/frame_buffer.h -@@ -66,6 +66,7 @@ class FrameBuffer { - - int GetTotalNumberOfContinuousTemporalUnits() const; - int GetTotalNumberOfDroppedFrames() const; -+ int GetTotalNumberOfDiscardedPackets() const; - size_t CurrentSize() const; - - private: -@@ -87,6 +88,8 @@ class FrameBuffer { - void PropagateContinuity(const FrameIterator& frame_it); - void FindNextAndLastDecodableTemporalUnit(); - void Clear(); -+ void UpdateDroppedFramesAndDiscardedPackets(FrameIterator begin_it, -+ FrameIterator end_it); - - const bool legacy_frame_id_jump_behavior_; - const size_t max_size_; -@@ -99,6 +102,7 @@ class FrameBuffer { - - int num_continuous_temporal_units_ = 0; - int num_dropped_frames_ = 0; -+ int num_discarded_packets_ = 0; - }; - - } // namespace webrtc -diff --git a/call/video_receive_stream.h b/call/video_receive_stream.h -index 40e8f85e8e..6e764f8c15 100644 ---- a/call/video_receive_stream.h -+++ b/call/video_receive_stream.h -@@ -112,6 +112,8 @@ class VideoReceiveStreamInterface : public MediaReceiveStreamInterface { - // https://www.w3.org/TR/webrtc-stats/#dom-rtcvideoreceiverstats-framesdropped - uint32_t frames_dropped = 0; - uint32_t frames_decoded = 0; -+ // https://w3c.github.io/webrtc-stats/#dom-rtcreceivedrtpstreamstats-packetsdiscarded -+ uint64_t packets_discarded = 0; - // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-totaldecodetime - TimeDelta total_decode_time = TimeDelta::Zero(); - // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-totalprocessingdelay -diff --git a/modules/video_coding/packet_buffer.cc b/modules/video_coding/packet_buffer.cc -index 420a200ba9..3a31d21048 100644 ---- a/modules/video_coding/packet_buffer.cc -+++ b/modules/video_coding/packet_buffer.cc -@@ -122,25 +122,27 @@ PacketBuffer::InsertResult PacketBuffer::InsertPacket( - return result; - } - --void PacketBuffer::ClearTo(uint16_t seq_num) { -+uint32_t PacketBuffer::ClearTo(uint16_t seq_num) { - // We have already cleared past this sequence number, no need to do anything. - if (is_cleared_to_first_seq_num_ && - AheadOf(first_seq_num_, seq_num)) { -- return; -+ return 0; - } - - // If the packet buffer was cleared between a frame was created and returned. - if (!first_packet_received_) -- return; -+ return 0; - - // Avoid iterating over the buffer more than once by capping the number of - // iterations to the `size_` of the buffer. - ++seq_num; -+ uint32_t num_cleared_packets = 0; - size_t diff = ForwardDiff(first_seq_num_, seq_num); - size_t iterations = std::min(diff, buffer_.size()); - for (size_t i = 0; i < iterations; ++i) { - auto& stored = buffer_[first_seq_num_ % buffer_.size()]; - if (stored != nullptr && AheadOf(seq_num, stored->seq_num)) { -+ ++num_cleared_packets; - stored = nullptr; - } - ++first_seq_num_; -@@ -156,6 +158,8 @@ void PacketBuffer::ClearTo(uint16_t seq_num) { - - received_padding_.erase(received_padding_.begin(), - received_padding_.lower_bound(seq_num)); -+ -+ return num_cleared_packets; - } - - void PacketBuffer::Clear() { -diff --git a/modules/video_coding/packet_buffer.h b/modules/video_coding/packet_buffer.h -index 53e08c95a1..47b2ffe199 100644 ---- a/modules/video_coding/packet_buffer.h -+++ b/modules/video_coding/packet_buffer.h -@@ -78,7 +78,10 @@ class PacketBuffer { - ABSL_MUST_USE_RESULT InsertResult - InsertPacket(std::unique_ptr packet); - ABSL_MUST_USE_RESULT InsertResult InsertPadding(uint16_t seq_num); -- void ClearTo(uint16_t seq_num); -+ -+ // Clear all packets older than |seq_num|. Returns the number of packets -+ // cleared. -+ uint32_t ClearTo(uint16_t seq_num); - void Clear(); - - void ForceSpsPpsIdrIsH264Keyframe(); diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc -index 75512a2465..8ef4d553ad 100644 +index 8ef4d553ad..a0f19999d8 100644 --- a/video/receive_statistics_proxy.cc +++ b/video/receive_statistics_proxy.cc -@@ -799,6 +799,11 @@ void ReceiveStatisticsProxy::OnDroppedFrames(uint32_t frames_dropped) { - })); +@@ -800,8 +800,13 @@ void ReceiveStatisticsProxy::OnDroppedFrames(uint32_t frames_dropped) { + } + + void ReceiveStatisticsProxy::OnDiscardedPackets(uint32_t packets_discarded) { +- RTC_DCHECK_RUN_ON(&main_thread_); +- stats_.packets_discarded += packets_discarded; ++ // Can be called on either the decode queue or the worker thread ++ // See FrameBuffer2 for more details. ++ worker_thread_->PostTask( ++ SafeTask(task_safety_.flag(), [packets_discarded, this]() { ++ RTC_DCHECK_RUN_ON(&main_thread_); ++ stats_.packets_discarded += packets_discarded; ++ })); } -+void ReceiveStatisticsProxy::OnDiscardedPackets(uint32_t packets_discarded) { -+ RTC_DCHECK_RUN_ON(&main_thread_); -+ stats_.packets_discarded += packets_discarded; -+} -+ void ReceiveStatisticsProxy::OnPreDecode(VideoCodecType codec_type, int qp) { - RTC_DCHECK_RUN_ON(&main_thread_); - last_codec_type_ = codec_type; -diff --git a/video/receive_statistics_proxy.h b/video/receive_statistics_proxy.h -index 8e4941f961..7bcfc7c057 100644 ---- a/video/receive_statistics_proxy.h -+++ b/video/receive_statistics_proxy.h -@@ -94,6 +94,7 @@ class ReceiveStatisticsProxy : public VideoStreamBufferControllerStatsObserver, - void OnDecodableFrame(TimeDelta jitter_buffer_delay, - TimeDelta target_delay, - TimeDelta minimum_delay) override; -+ void OnDiscardedPackets(uint32_t packets_discarded) override; - void OnFrameBufferTimingsUpdated(int estimated_max_decode_time_ms, - 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 385a9d5ccf..a83e6ee860 100644 +index dbfbeda4d5..1ca4cfb991 100644 --- a/video/rtp_video_stream_receiver2.cc +++ b/video/rtp_video_stream_receiver2.cc -@@ -244,6 +244,7 @@ RtpVideoStreamReceiver2::RtpVideoStreamReceiver2( - RtcpPacketTypeCounterObserver* rtcp_packet_type_counter_observer, - RtcpCnameCallback* rtcp_cname_callback, - NackPeriodicProcessor* nack_periodic_processor, -+ VideoStreamBufferControllerStatsObserver* vcm_receive_statistics, - OnCompleteFrameCallback* complete_frame_callback, - rtc::scoped_refptr frame_decryptor, - rtc::scoped_refptr frame_transformer, -@@ -292,6 +293,7 @@ RtpVideoStreamReceiver2::RtpVideoStreamReceiver2( - &rtcp_feedback_buffer_, - &rtcp_feedback_buffer_, - field_trials_)), -+ vcm_receive_statistics_(vcm_receive_statistics), - packet_buffer_(kPacketBufferStartSize, - PacketBufferMaxSize(field_trials_)), - reference_finder_(std::make_unique()), -@@ -1245,7 +1247,8 @@ void RtpVideoStreamReceiver2::FrameDecoded(int64_t picture_id) { - int64_t unwrapped_rtp_seq_num = rtp_seq_num_unwrapper_.Unwrap(seq_num); +@@ -1247,7 +1247,9 @@ void RtpVideoStreamReceiver2::FrameDecoded(int64_t picture_id) { packet_infos_.erase(packet_infos_.begin(), packet_infos_.upper_bound(unwrapped_rtp_seq_num)); -- packet_buffer_.ClearTo(seq_num); -+ uint32_t num_packets_cleared = packet_buffer_.ClearTo(seq_num); -+ vcm_receive_statistics_->OnDiscardedPackets(num_packets_cleared); + uint32_t num_packets_cleared = packet_buffer_.ClearTo(seq_num); +- vcm_receive_statistics_->OnDiscardedPackets(num_packets_cleared); ++ if (num_packets_cleared > 0) { ++ vcm_receive_statistics_->OnDiscardedPackets(num_packets_cleared); ++ } reference_finder_->ClearTo(seq_num); } } -diff --git a/video/rtp_video_stream_receiver2.h b/video/rtp_video_stream_receiver2.h -index 1355352cf6..8f9f9db6db 100644 ---- a/video/rtp_video_stream_receiver2.h -+++ b/video/rtp_video_stream_receiver2.h -@@ -50,6 +50,7 @@ - #include "rtc_base/thread_annotations.h" - #include "video/buffered_frame_decryptor.h" - #include "video/unique_timestamp_counter.h" -+#include "video/video_stream_buffer_controller.h" - - namespace webrtc { - -@@ -92,6 +93,7 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender, - RtcpPacketTypeCounterObserver* rtcp_packet_type_counter_observer, - RtcpCnameCallback* rtcp_cname_callback, - NackPeriodicProcessor* nack_periodic_processor, -+ VideoStreamBufferControllerStatsObserver* vcm_receive_statistics, - // The KeyFrameRequestSender is optional; if not provided, key frame - // requests are sent via the internal RtpRtcp module. - OnCompleteFrameCallback* complete_frame_callback, -@@ -368,6 +370,7 @@ class RtpVideoStreamReceiver2 : public LossNotificationSender, - std::unique_ptr loss_notification_controller_ - RTC_GUARDED_BY(packet_sequence_checker_); - -+ VideoStreamBufferControllerStatsObserver* const vcm_receive_statistics_; - video_coding::PacketBuffer packet_buffer_ - RTC_GUARDED_BY(packet_sequence_checker_); - // h26x_packet_buffer_ is nullptr if codec list doens't contain H.264 or -diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc -index 69943d11ee..23d8c17094 100644 ---- a/video/video_receive_stream2.cc -+++ b/video/video_receive_stream2.cc -@@ -209,6 +209,7 @@ VideoReceiveStream2::VideoReceiveStream2( - &stats_proxy_, - &stats_proxy_, - nack_periodic_processor, -+ &stats_proxy_, - this, // OnCompleteFrameCallback - std::move(config_.frame_decryptor), - std::move(config_.frame_transformer), -diff --git a/video/video_stream_buffer_controller.cc b/video/video_stream_buffer_controller.cc -index b7ed89656e..09aca28259 100644 ---- a/video/video_stream_buffer_controller.cc -+++ b/video/video_stream_buffer_controller.cc -@@ -259,6 +259,7 @@ void VideoStreamBufferController::OnFrameReady( - - // Update stats. - UpdateDroppedFrames(); -+ UpdateDiscardedPackets(); - UpdateFrameBufferTimings(min_receive_time, now); - UpdateTimingFrameInfo(); - -@@ -324,6 +325,17 @@ void VideoStreamBufferController::UpdateDroppedFrames() - buffer_->GetTotalNumberOfDroppedFrames(); - } - -+void VideoStreamBufferController::UpdateDiscardedPackets() -+ RTC_RUN_ON(&worker_sequence_checker_) { -+ const int discarded_packets = buffer_->GetTotalNumberOfDiscardedPackets() - -+ packets_discarded_before_last_new_frame_; -+ if (discarded_packets > 0) { -+ stats_proxy_->OnDiscardedPackets(discarded_packets); -+ } -+ packets_discarded_before_last_new_frame_ = -+ buffer_->GetTotalNumberOfDiscardedPackets(); -+} -+ - void VideoStreamBufferController::UpdateFrameBufferTimings( - Timestamp min_receive_time, - Timestamp now) { -diff --git a/video/video_stream_buffer_controller.h b/video/video_stream_buffer_controller.h -index f07b3eb274..6b9b67ca22 100644 ---- a/video/video_stream_buffer_controller.h -+++ b/video/video_stream_buffer_controller.h -@@ -52,6 +52,8 @@ class VideoStreamBufferControllerStatsObserver { - TimeDelta target_delay, - TimeDelta minimum_delay) = 0; - -+ virtual void OnDiscardedPackets(uint32_t packets_discarded) = 0; -+ - // Various jitter buffer delays determined by VCMTiming. - virtual void OnFrameBufferTimingsUpdated(int estimated_max_decode_time_ms, - int current_delay_ms, -@@ -94,6 +96,7 @@ class VideoStreamBufferController { - void OnTimeout(TimeDelta delay); - void FrameReadyForDecode(uint32_t rtp_timestamp, Timestamp render_time); - void UpdateDroppedFrames() RTC_RUN_ON(&worker_sequence_checker_); -+ void UpdateDiscardedPackets() RTC_RUN_ON(&worker_sequence_checker_); - void UpdateFrameBufferTimings(Timestamp min_receive_time, Timestamp now); - void UpdateTimingFrameInfo(); - bool IsTooManyFramesQueued() const RTC_RUN_ON(&worker_sequence_checker_); -@@ -120,6 +123,8 @@ class VideoStreamBufferController { - RTC_GUARDED_BY(&worker_sequence_checker_); - int frames_dropped_before_last_new_frame_ - RTC_GUARDED_BY(&worker_sequence_checker_) = 0; -+ int packets_discarded_before_last_new_frame_ -+ RTC_GUARDED_BY(&worker_sequence_checker_) = 0; - VCMVideoProtection protection_mode_ - RTC_GUARDED_BY(&worker_sequence_checker_) = kProtectionNack; - diff --git a/third_party/libwebrtc/moz-patch-stack/0068.patch b/third_party/libwebrtc/moz-patch-stack/0068.patch index ae8b39bdd7eb..6036a71d9279 100644 --- a/third_party/libwebrtc/moz-patch-stack/0068.patch +++ b/third_party/libwebrtc/moz-patch-stack/0068.patch @@ -1,46 +1,221 @@ From: Andreas Pehrson Date: Thu, 6 Jan 2022 00:16:00 +0000 -Subject: Bug 1748478 - Propagate calculated discarded packets to stats. r=bwc +Subject: Bug 1748458 - Add TRACE_EVENTs for dropped frames and packets for + received video. r=bwc -Differential Revision: https://phabricator.services.mozilla.com/D135061 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/56fbf0469e25fa0d589c51ca112ce534a7c0ab91 +This lets us see in the profiler how many received frames and packets we decide +to drop and the reasons why. + +Differential Revision: https://phabricator.services.mozilla.com/D135062 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/08e252da94c4752eccfd845eef13d8517953cc6a + +Also includes: + +Bug 1804288 - (fix-de7ae5755b) reimplement Bug 1748458 - Add TRACE_EVENTs for dropped frames and packets for received video. r=pehrsons + +Differential Revision: https://phabricator.services.mozilla.com/D163960 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/8e9a326a99cd5eaa6e447ff57c01ad9d79a09744 --- - video/receive_statistics_proxy.cc | 9 +++++++-- - video/rtp_video_stream_receiver2.cc | 4 +++- - 2 files changed, 10 insertions(+), 3 deletions(-) + api/video/frame_buffer.cc | 33 +++++++++++++++++++++++++ + video/receive_statistics_proxy.cc | 11 +++++++++ + video/rtp_video_stream_receiver2.cc | 4 +++ + video/video_stream_buffer_controller.cc | 7 ++++++ + 4 files changed, 55 insertions(+) +diff --git a/api/video/frame_buffer.cc b/api/video/frame_buffer.cc +index 09ca53ac94..8d61025912 100644 +--- a/api/video/frame_buffer.cc ++++ b/api/video/frame_buffer.cc +@@ -16,6 +16,7 @@ + #include "absl/container/inlined_vector.h" + #include "rtc_base/logging.h" + #include "rtc_base/numerics/sequence_number_util.h" ++#include "rtc_base/trace_event.h" + + namespace webrtc { + namespace { +@@ -68,7 +69,12 @@ FrameBuffer::FrameBuffer(int max_size, + decoded_frame_history_(max_decode_history) {} + + bool FrameBuffer::InsertFrame(std::unique_ptr frame) { ++ const uint32_t ssrc = ++ frame->PacketInfos().empty() ? 0 : frame->PacketInfos()[0].ssrc(); + if (!ValidReferences(*frame)) { ++ TRACE_EVENT2("webrtc", ++ "FrameBuffer::InsertFrame Frame dropped (Invalid references)", ++ "remote_ssrc", ssrc, "frame_id", frame->Id()); + RTC_DLOG(LS_WARNING) << "Frame " << frame->Id() + << " has invalid references, dropping frame."; + return false; +@@ -78,23 +84,35 @@ bool FrameBuffer::InsertFrame(std::unique_ptr frame) { + if (legacy_frame_id_jump_behavior_ && frame->is_keyframe() && + AheadOf(frame->RtpTimestamp(), + *decoded_frame_history_.GetLastDecodedFrameTimestamp())) { ++ TRACE_EVENT2("webrtc", ++ "FrameBuffer::InsertFrame Frames dropped (OOO + PicId jump)", ++ "remote_ssrc", ssrc, "frame_id", frame->Id()); + RTC_DLOG(LS_WARNING) + << "Keyframe " << frame->Id() + << " has newer timestamp but older picture id, clearing buffer."; + Clear(); + } else { + // Already decoded past this frame. ++ TRACE_EVENT2("webrtc", ++ "FrameBuffer::InsertFrame Frame dropped (Out of order)", ++ "remote_ssrc", ssrc, "frame_id", frame->Id()); + return false; + } + } + + if (frames_.size() == max_size_) { + if (frame->is_keyframe()) { ++ TRACE_EVENT2("webrtc", ++ "FrameBuffer::InsertFrame Frames dropped (KF + Full buffer)", ++ "remote_ssrc", ssrc, "frame_id", frame->Id()); + RTC_DLOG(LS_WARNING) << "Keyframe " << frame->Id() + << " inserted into full buffer, clearing buffer."; + Clear(); + } else { + // No space for this frame. ++ TRACE_EVENT2("webrtc", ++ "FrameBuffer::InsertFrame Frame dropped (Full buffer)", ++ "remote_ssrc", ssrc, "frame_id", frame->Id()); + return false; + } + } +@@ -149,16 +167,31 @@ void FrameBuffer::DropNextDecodableTemporalUnit() { + + void FrameBuffer::UpdateDroppedFramesAndDiscardedPackets(FrameIterator begin_it, + FrameIterator end_it) { ++ uint32_t dropped_ssrc = 0; ++ int64_t dropped_frame_id = 0; + unsigned int num_discarded_packets = 0; + unsigned int num_dropped_frames = + std::count_if(begin_it, end_it, [&](const auto& f) { + if (f.second.encoded_frame) { + const auto& packetInfos = f.second.encoded_frame->PacketInfos(); ++ dropped_frame_id = f.first; ++ if (!packetInfos.empty()) { ++ dropped_ssrc = packetInfos[0].ssrc(); ++ } + num_discarded_packets += packetInfos.size(); + } + return f.second.encoded_frame != nullptr; + }); + ++ if (num_dropped_frames > 0) { ++ TRACE_EVENT2("webrtc", "FrameBuffer Dropping Old Frames", "remote_ssrc", ++ dropped_ssrc, "frame_id", dropped_frame_id); ++ } ++ if (num_discarded_packets > 0) { ++ TRACE_EVENT2("webrtc", "FrameBuffer Discarding Old Packets", "remote_ssrc", ++ dropped_ssrc, "frame_id", dropped_frame_id); ++ } ++ + num_dropped_frames_ += num_dropped_frames; + num_discarded_packets_ += num_discarded_packets; + } diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc -index 8ef4d553ad..a0f19999d8 100644 +index a0f19999d8..1764308c0a 100644 --- a/video/receive_statistics_proxy.cc +++ b/video/receive_statistics_proxy.cc -@@ -800,8 +800,13 @@ void ReceiveStatisticsProxy::OnDroppedFrames(uint32_t frames_dropped) { - } +@@ -20,6 +20,7 @@ + #include "rtc_base/strings/string_builder.h" + #include "rtc_base/thread.h" + #include "rtc_base/time_utils.h" ++#include "rtc_base/trace_event.h" + #include "system_wrappers/include/clock.h" + #include "system_wrappers/include/metrics.h" + #include "video/video_receive_stream2.h" +@@ -767,6 +768,9 @@ void ReceiveStatisticsProxy::OnCompleteFrame(bool is_keyframe, + VideoContentType content_type) { + RTC_DCHECK_RUN_ON(&main_thread_); ++ TRACE_EVENT2("webrtc", "ReceiveStatisticsProxy::OnCompleteFrame", ++ "remote_ssrc", remote_ssrc_, "is_keyframe", is_keyframe); ++ + // Content type extension is set only for keyframes and should be propagated + // for all the following delta frames. Here we may receive frames out of order + // and miscategorise some delta frames near the layer switch. +@@ -792,6 +796,8 @@ void ReceiveStatisticsProxy::OnCompleteFrame(bool is_keyframe, + void ReceiveStatisticsProxy::OnDroppedFrames(uint32_t frames_dropped) { + // Can be called on either the decode queue or the worker thread + // See FrameBuffer2 for more details. ++ TRACE_EVENT2("webrtc", "ReceiveStatisticsProxy::OnDroppedFrames", ++ "remote_ssrc", remote_ssrc_, "frames_dropped", frames_dropped); + worker_thread_->PostTask( + SafeTask(task_safety_.flag(), [frames_dropped, this]() { + RTC_DCHECK_RUN_ON(&main_thread_); +@@ -802,6 +808,9 @@ void ReceiveStatisticsProxy::OnDroppedFrames(uint32_t frames_dropped) { void ReceiveStatisticsProxy::OnDiscardedPackets(uint32_t packets_discarded) { -- RTC_DCHECK_RUN_ON(&main_thread_); -- stats_.packets_discarded += packets_discarded; -+ // Can be called on either the decode queue or the worker thread -+ // See FrameBuffer2 for more details. -+ worker_thread_->PostTask( -+ SafeTask(task_safety_.flag(), [packets_discarded, this]() { -+ RTC_DCHECK_RUN_ON(&main_thread_); -+ stats_.packets_discarded += packets_discarded; -+ })); + // Can be called on either the decode queue or the worker thread + // See FrameBuffer2 for more details. ++ TRACE_EVENT2("webrtc", "ReceiveStatisticsProxy::OnDiscardedPackets", ++ "remote_ssrc", remote_ssrc_, "packets_discarded", ++ packets_discarded); + worker_thread_->PostTask( + SafeTask(task_safety_.flag(), [packets_discarded, this]() { + RTC_DCHECK_RUN_ON(&main_thread_); +@@ -830,6 +839,8 @@ void ReceiveStatisticsProxy::OnStreamInactive() { + + void ReceiveStatisticsProxy::OnRttUpdate(int64_t avg_rtt_ms) { + RTC_DCHECK_RUN_ON(&main_thread_); ++ TRACE_EVENT2("webrtc", "ReceiveStatisticsProxy::OnRttUpdate", ++ "remote_ssrc", remote_ssrc_, "avg_rtt_ms", avg_rtt_ms); + avg_rtt_ms_ = avg_rtt_ms; } - 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 a83e6ee860..4d5ddd55fe 100644 +index 1ca4cfb991..b62185053a 100644 --- a/video/rtp_video_stream_receiver2.cc +++ b/video/rtp_video_stream_receiver2.cc -@@ -1248,7 +1248,9 @@ void RtpVideoStreamReceiver2::FrameDecoded(int64_t picture_id) { - packet_infos_.erase(packet_infos_.begin(), +@@ -44,6 +44,7 @@ + #include "rtc_base/checks.h" + #include "rtc_base/logging.h" + #include "rtc_base/strings/string_builder.h" ++#include "rtc_base/trace_event.h" + #include "system_wrappers/include/metrics.h" + #include "system_wrappers/include/ntp_time.h" + +@@ -1248,6 +1249,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); -- vcm_receive_statistics_->OnDiscardedPackets(num_packets_cleared); -+ if (num_packets_cleared > 0) { -+ vcm_receive_statistics_->OnDiscardedPackets(num_packets_cleared); -+ } + if (num_packets_cleared > 0) { ++ TRACE_EVENT2("webrtc", ++ "RtpVideoStreamReceiver2::FrameDecoded Cleared Old Packets", ++ "remote_ssrc", config_.rtp.remote_ssrc, "seq_num", seq_num); + vcm_receive_statistics_->OnDiscardedPackets(num_packets_cleared); + } reference_finder_->ClearTo(seq_num); - } - } +diff --git a/video/video_stream_buffer_controller.cc b/video/video_stream_buffer_controller.cc +index 09aca28259..252ff7aa14 100644 +--- a/video/video_stream_buffer_controller.cc ++++ b/video/video_stream_buffer_controller.cc +@@ -31,6 +31,7 @@ + #include "rtc_base/checks.h" + #include "rtc_base/logging.h" + #include "rtc_base/thread_annotations.h" ++#include "rtc_base/trace_event.h" + #include "video/frame_decode_scheduler.h" + #include "video/frame_decode_timing.h" + #include "video/task_queue_frame_decode_scheduler.h" +@@ -152,6 +153,9 @@ absl::optional VideoStreamBufferController::InsertFrame( + std::unique_ptr frame) { + RTC_DCHECK_RUN_ON(&worker_sequence_checker_); + FrameMetadata metadata(*frame); ++ const uint32_t ssrc = ++ frame->PacketInfos().empty() ? 0 : frame->PacketInfos()[0].ssrc(); ++ const int64_t frameId = frame->Id(); + int complete_units = buffer_->GetTotalNumberOfContinuousTemporalUnits(); + if (buffer_->InsertFrame(std::move(frame))) { + RTC_DCHECK(metadata.receive_time) << "Frame receive time must be set!"; +@@ -162,6 +166,9 @@ absl::optional VideoStreamBufferController::InsertFrame( + *metadata.receive_time); + } + if (complete_units < buffer_->GetTotalNumberOfContinuousTemporalUnits()) { ++ TRACE_EVENT2("webrtc", ++ "VideoStreamBufferController::InsertFrame Frame Complete", ++ "remote_ssrc", ssrc, "frame_id", frameId); + stats_proxy_->OnCompleteFrame(metadata.is_keyframe, metadata.size, + metadata.contentType); + MaybeScheduleFrameForRelease(); diff --git a/third_party/libwebrtc/moz-patch-stack/0069.patch b/third_party/libwebrtc/moz-patch-stack/0069.patch index cf3bdd9dfa6c..1b60c758bf73 100644 --- a/third_party/libwebrtc/moz-patch-stack/0069.patch +++ b/third_party/libwebrtc/moz-patch-stack/0069.patch @@ -1,221 +1,27 @@ From: Andreas Pehrson Date: Thu, 6 Jan 2022 00:16:00 +0000 -Subject: Bug 1748458 - Add TRACE_EVENTs for dropped frames and packets for - received video. r=bwc +Subject: Bug 1748458 - Add TRACE_EVENT for keyframe requests. r=bwc -This lets us see in the profiler how many received frames and packets we decide -to drop and the reasons why. - -Differential Revision: https://phabricator.services.mozilla.com/D135062 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/08e252da94c4752eccfd845eef13d8517953cc6a - -Also includes: - -Bug 1804288 - (fix-de7ae5755b) reimplement Bug 1748458 - Add TRACE_EVENTs for dropped frames and packets for received video. r=pehrsons - -Differential Revision: https://phabricator.services.mozilla.com/D163960 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/8e9a326a99cd5eaa6e447ff57c01ad9d79a09744 +Differential Revision: https://phabricator.services.mozilla.com/D135113 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/5b2a7894ef1cf096d0e8977754507c0820e757fc --- - api/video/frame_buffer.cc | 33 +++++++++++++++++++++++++ - video/receive_statistics_proxy.cc | 11 +++++++++ - video/rtp_video_stream_receiver2.cc | 4 +++ - video/video_stream_buffer_controller.cc | 7 ++++++ - 4 files changed, 55 insertions(+) + video/rtp_video_stream_receiver2.cc | 6 ++++++ + 1 file changed, 6 insertions(+) -diff --git a/api/video/frame_buffer.cc b/api/video/frame_buffer.cc -index 09ca53ac94..8d61025912 100644 ---- a/api/video/frame_buffer.cc -+++ b/api/video/frame_buffer.cc -@@ -16,6 +16,7 @@ - #include "absl/container/inlined_vector.h" - #include "rtc_base/logging.h" - #include "rtc_base/numerics/sequence_number_util.h" -+#include "rtc_base/trace_event.h" - - namespace webrtc { - namespace { -@@ -68,7 +69,12 @@ FrameBuffer::FrameBuffer(int max_size, - decoded_frame_history_(max_decode_history) {} - - bool FrameBuffer::InsertFrame(std::unique_ptr frame) { -+ const uint32_t ssrc = -+ frame->PacketInfos().empty() ? 0 : frame->PacketInfos()[0].ssrc(); - if (!ValidReferences(*frame)) { -+ TRACE_EVENT2("webrtc", -+ "FrameBuffer::InsertFrame Frame dropped (Invalid references)", -+ "remote_ssrc", ssrc, "frame_id", frame->Id()); - RTC_DLOG(LS_WARNING) << "Frame " << frame->Id() - << " has invalid references, dropping frame."; - return false; -@@ -78,23 +84,35 @@ bool FrameBuffer::InsertFrame(std::unique_ptr frame) { - if (legacy_frame_id_jump_behavior_ && frame->is_keyframe() && - AheadOf(frame->RtpTimestamp(), - *decoded_frame_history_.GetLastDecodedFrameTimestamp())) { -+ TRACE_EVENT2("webrtc", -+ "FrameBuffer::InsertFrame Frames dropped (OOO + PicId jump)", -+ "remote_ssrc", ssrc, "frame_id", frame->Id()); - RTC_DLOG(LS_WARNING) - << "Keyframe " << frame->Id() - << " has newer timestamp but older picture id, clearing buffer."; - Clear(); - } else { - // Already decoded past this frame. -+ TRACE_EVENT2("webrtc", -+ "FrameBuffer::InsertFrame Frame dropped (Out of order)", -+ "remote_ssrc", ssrc, "frame_id", frame->Id()); - return false; - } - } - - if (frames_.size() == max_size_) { - if (frame->is_keyframe()) { -+ TRACE_EVENT2("webrtc", -+ "FrameBuffer::InsertFrame Frames dropped (KF + Full buffer)", -+ "remote_ssrc", ssrc, "frame_id", frame->Id()); - RTC_DLOG(LS_WARNING) << "Keyframe " << frame->Id() - << " inserted into full buffer, clearing buffer."; - Clear(); - } else { - // No space for this frame. -+ TRACE_EVENT2("webrtc", -+ "FrameBuffer::InsertFrame Frame dropped (Full buffer)", -+ "remote_ssrc", ssrc, "frame_id", frame->Id()); - return false; - } - } -@@ -149,16 +167,31 @@ void FrameBuffer::DropNextDecodableTemporalUnit() { - - void FrameBuffer::UpdateDroppedFramesAndDiscardedPackets(FrameIterator begin_it, - FrameIterator end_it) { -+ uint32_t dropped_ssrc = 0; -+ int64_t dropped_frame_id = 0; - unsigned int num_discarded_packets = 0; - unsigned int num_dropped_frames = - std::count_if(begin_it, end_it, [&](const auto& f) { - if (f.second.encoded_frame) { - const auto& packetInfos = f.second.encoded_frame->PacketInfos(); -+ dropped_frame_id = f.first; -+ if (!packetInfos.empty()) { -+ dropped_ssrc = packetInfos[0].ssrc(); -+ } - num_discarded_packets += packetInfos.size(); - } - return f.second.encoded_frame != nullptr; - }); - -+ if (num_dropped_frames > 0) { -+ TRACE_EVENT2("webrtc", "FrameBuffer Dropping Old Frames", "remote_ssrc", -+ dropped_ssrc, "frame_id", dropped_frame_id); -+ } -+ if (num_discarded_packets > 0) { -+ TRACE_EVENT2("webrtc", "FrameBuffer Discarding Old Packets", "remote_ssrc", -+ dropped_ssrc, "frame_id", dropped_frame_id); -+ } -+ - num_dropped_frames_ += num_dropped_frames; - num_discarded_packets_ += num_discarded_packets; - } -diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc -index a0f19999d8..1764308c0a 100644 ---- a/video/receive_statistics_proxy.cc -+++ b/video/receive_statistics_proxy.cc -@@ -20,6 +20,7 @@ - #include "rtc_base/strings/string_builder.h" - #include "rtc_base/thread.h" - #include "rtc_base/time_utils.h" -+#include "rtc_base/trace_event.h" - #include "system_wrappers/include/clock.h" - #include "system_wrappers/include/metrics.h" - #include "video/video_receive_stream2.h" -@@ -767,6 +768,9 @@ void ReceiveStatisticsProxy::OnCompleteFrame(bool is_keyframe, - VideoContentType content_type) { - RTC_DCHECK_RUN_ON(&main_thread_); - -+ TRACE_EVENT2("webrtc", "ReceiveStatisticsProxy::OnCompleteFrame", -+ "remote_ssrc", remote_ssrc_, "is_keyframe", is_keyframe); -+ - // Content type extension is set only for keyframes and should be propagated - // for all the following delta frames. Here we may receive frames out of order - // and miscategorise some delta frames near the layer switch. -@@ -792,6 +796,8 @@ void ReceiveStatisticsProxy::OnCompleteFrame(bool is_keyframe, - void ReceiveStatisticsProxy::OnDroppedFrames(uint32_t frames_dropped) { - // Can be called on either the decode queue or the worker thread - // See FrameBuffer2 for more details. -+ TRACE_EVENT2("webrtc", "ReceiveStatisticsProxy::OnDroppedFrames", -+ "remote_ssrc", remote_ssrc_, "frames_dropped", frames_dropped); - worker_thread_->PostTask( - SafeTask(task_safety_.flag(), [frames_dropped, this]() { - RTC_DCHECK_RUN_ON(&main_thread_); -@@ -802,6 +808,9 @@ void ReceiveStatisticsProxy::OnDroppedFrames(uint32_t frames_dropped) { - void ReceiveStatisticsProxy::OnDiscardedPackets(uint32_t packets_discarded) { - // Can be called on either the decode queue or the worker thread - // See FrameBuffer2 for more details. -+ TRACE_EVENT2("webrtc", "ReceiveStatisticsProxy::OnDiscardedPackets", -+ "remote_ssrc", remote_ssrc_, "packets_discarded", -+ packets_discarded); - worker_thread_->PostTask( - SafeTask(task_safety_.flag(), [packets_discarded, this]() { - RTC_DCHECK_RUN_ON(&main_thread_); -@@ -830,6 +839,8 @@ void ReceiveStatisticsProxy::OnStreamInactive() { - - void ReceiveStatisticsProxy::OnRttUpdate(int64_t avg_rtt_ms) { - RTC_DCHECK_RUN_ON(&main_thread_); -+ TRACE_EVENT2("webrtc", "ReceiveStatisticsProxy::OnRttUpdate", -+ "remote_ssrc", remote_ssrc_, "avg_rtt_ms", avg_rtt_ms); - avg_rtt_ms_ = avg_rtt_ms; - } - diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc -index 4d5ddd55fe..ff55d0fefd 100644 +index b62185053a..4650aa0082 100644 --- a/video/rtp_video_stream_receiver2.cc +++ b/video/rtp_video_stream_receiver2.cc -@@ -44,6 +44,7 @@ - #include "rtc_base/checks.h" - #include "rtc_base/logging.h" - #include "rtc_base/strings/string_builder.h" -+#include "rtc_base/trace_event.h" - #include "system_wrappers/include/metrics.h" - #include "system_wrappers/include/ntp_time.h" +@@ -728,6 +728,12 @@ void RtpVideoStreamReceiver2::OnRtpPacket(const RtpPacketReceived& packet) { -@@ -1249,6 +1250,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) { -+ TRACE_EVENT2("webrtc", -+ "RtpVideoStreamReceiver2::FrameDecoded Cleared Old Packets", -+ "remote_ssrc", config_.rtp.remote_ssrc, "seq_num", seq_num); - vcm_receive_statistics_->OnDiscardedPackets(num_packets_cleared); - } - reference_finder_->ClearTo(seq_num); -diff --git a/video/video_stream_buffer_controller.cc b/video/video_stream_buffer_controller.cc -index 09aca28259..252ff7aa14 100644 ---- a/video/video_stream_buffer_controller.cc -+++ b/video/video_stream_buffer_controller.cc -@@ -31,6 +31,7 @@ - #include "rtc_base/checks.h" - #include "rtc_base/logging.h" - #include "rtc_base/thread_annotations.h" -+#include "rtc_base/trace_event.h" - #include "video/frame_decode_scheduler.h" - #include "video/frame_decode_timing.h" - #include "video/task_queue_frame_decode_scheduler.h" -@@ -152,6 +153,9 @@ absl::optional VideoStreamBufferController::InsertFrame( - std::unique_ptr frame) { - RTC_DCHECK_RUN_ON(&worker_sequence_checker_); - FrameMetadata metadata(*frame); -+ const uint32_t ssrc = -+ frame->PacketInfos().empty() ? 0 : frame->PacketInfos()[0].ssrc(); -+ const int64_t frameId = frame->Id(); - int complete_units = buffer_->GetTotalNumberOfContinuousTemporalUnits(); - if (buffer_->InsertFrame(std::move(frame))) { - RTC_DCHECK(metadata.receive_time) << "Frame receive time must be set!"; -@@ -162,6 +166,9 @@ absl::optional VideoStreamBufferController::InsertFrame( - *metadata.receive_time); - } - if (complete_units < buffer_->GetTotalNumberOfContinuousTemporalUnits()) { -+ TRACE_EVENT2("webrtc", -+ "VideoStreamBufferController::InsertFrame Frame Complete", -+ "remote_ssrc", ssrc, "frame_id", frameId); - stats_proxy_->OnCompleteFrame(metadata.is_keyframe, metadata.size, - metadata.contentType); - MaybeScheduleFrameForRelease(); + void RtpVideoStreamReceiver2::RequestKeyFrame() { + RTC_DCHECK_RUN_ON(&worker_task_checker_); ++ TRACE_EVENT2("webrtc", "RtpVideoStreamReceiver2::RequestKeyFrame", ++ "remote_ssrc", config_.rtp.remote_ssrc, "method", ++ keyframe_request_method_ == KeyFrameReqMethod::kPliRtcp ? "PLI" ++ : keyframe_request_method_ == KeyFrameReqMethod::kFirRtcp ? "FIR" ++ : keyframe_request_method_ == KeyFrameReqMethod::kNone ? "None" ++ : "Other"); + // TODO(bugs.webrtc.org/10336): Allow the sender to ignore key frame requests + // issued by anything other than the LossNotificationController if it (the + // sender) is relying on LNTF alone. diff --git a/third_party/libwebrtc/moz-patch-stack/0070.patch b/third_party/libwebrtc/moz-patch-stack/0070.patch index ead57dd197b0..e4804b668fee 100644 --- a/third_party/libwebrtc/moz-patch-stack/0070.patch +++ b/third_party/libwebrtc/moz-patch-stack/0070.patch @@ -1,27 +1,23 @@ From: Andreas Pehrson -Date: Thu, 6 Jan 2022 00:16:00 +0000 -Subject: Bug 1748458 - Add TRACE_EVENT for keyframe requests. r=bwc +Date: Wed, 11 Jan 2023 22:42:00 +0000 +Subject: Bug 1800942 - Add DCHECKs to + TimestampExtrapolator::ExtrapolateLocalTime. r=mjf -Differential Revision: https://phabricator.services.mozilla.com/D135113 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/5b2a7894ef1cf096d0e8977754507c0820e757fc +Differential Revision: https://phabricator.services.mozilla.com/D166536 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/c5df7f40392464ffc63f44a53ddcaab2091741e0 --- - video/rtp_video_stream_receiver2.cc | 6 ++++++ - 1 file changed, 6 insertions(+) + modules/video_coding/timing/timestamp_extrapolator.cc | 1 + + 1 file changed, 1 insertion(+) -diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc -index ff55d0fefd..a5184d8ac0 100644 ---- a/video/rtp_video_stream_receiver2.cc -+++ b/video/rtp_video_stream_receiver2.cc -@@ -728,6 +728,12 @@ void RtpVideoStreamReceiver2::OnRtpPacket(const RtpPacketReceived& packet) { +diff --git a/modules/video_coding/timing/timestamp_extrapolator.cc b/modules/video_coding/timing/timestamp_extrapolator.cc +index cc7eff97ba..cbe3754875 100644 +--- a/modules/video_coding/timing/timestamp_extrapolator.cc ++++ b/modules/video_coding/timing/timestamp_extrapolator.cc +@@ -126,6 +126,7 @@ void TimestampExtrapolator::Update(Timestamp now, uint32_t ts90khz) { + absl::optional TimestampExtrapolator::ExtrapolateLocalTime( + uint32_t timestamp90khz) const { + int64_t unwrapped_ts90khz = unwrapper_.PeekUnwrap(timestamp90khz); ++ RTC_DCHECK_GE(unwrapped_ts90khz, 0); - void RtpVideoStreamReceiver2::RequestKeyFrame() { - RTC_DCHECK_RUN_ON(&worker_task_checker_); -+ TRACE_EVENT2("webrtc", "RtpVideoStreamReceiver2::RequestKeyFrame", -+ "remote_ssrc", config_.rtp.remote_ssrc, "method", -+ keyframe_request_method_ == KeyFrameReqMethod::kPliRtcp ? "PLI" -+ : keyframe_request_method_ == KeyFrameReqMethod::kFirRtcp ? "FIR" -+ : keyframe_request_method_ == KeyFrameReqMethod::kNone ? "None" -+ : "Other"); - // TODO(bugs.webrtc.org/10336): Allow the sender to ignore key frame requests - // issued by anything other than the LossNotificationController if it (the - // sender) is relying on LNTF alone. + if (!first_unwrapped_timestamp_) { + return absl::nullopt; diff --git a/third_party/libwebrtc/moz-patch-stack/0071.patch b/third_party/libwebrtc/moz-patch-stack/0071.patch index e4804b668fee..d05a43bc8999 100644 --- a/third_party/libwebrtc/moz-patch-stack/0071.patch +++ b/third_party/libwebrtc/moz-patch-stack/0071.patch @@ -1,23 +1,25 @@ From: Andreas Pehrson -Date: Wed, 11 Jan 2023 22:42:00 +0000 -Subject: Bug 1800942 - Add DCHECKs to - TimestampExtrapolator::ExtrapolateLocalTime. r=mjf +Date: Wed, 8 Feb 2023 08:01:00 +0000 +Subject: Bug 1814692 - Don't attempt realtime scheduling rtc::PlatformThreads. + r=webrtc-reviewers,bwc -Differential Revision: https://phabricator.services.mozilla.com/D166536 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/c5df7f40392464ffc63f44a53ddcaab2091741e0 +Differential Revision: https://phabricator.services.mozilla.com/D169036 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/9e64a965e26c8379261466e5273c3b383164b2c7 --- - modules/video_coding/timing/timestamp_extrapolator.cc | 1 + - 1 file changed, 1 insertion(+) + rtc_base/platform_thread.cc | 3 +++ + 1 file changed, 3 insertions(+) -diff --git a/modules/video_coding/timing/timestamp_extrapolator.cc b/modules/video_coding/timing/timestamp_extrapolator.cc -index cc7eff97ba..cbe3754875 100644 ---- a/modules/video_coding/timing/timestamp_extrapolator.cc -+++ b/modules/video_coding/timing/timestamp_extrapolator.cc -@@ -126,6 +126,7 @@ void TimestampExtrapolator::Update(Timestamp now, uint32_t ts90khz) { - absl::optional TimestampExtrapolator::ExtrapolateLocalTime( - uint32_t timestamp90khz) const { - int64_t unwrapped_ts90khz = unwrapper_.PeekUnwrap(timestamp90khz); -+ RTC_DCHECK_GE(unwrapped_ts90khz, 0); - - if (!first_unwrapped_timestamp_) { - return absl::nullopt; +diff --git a/rtc_base/platform_thread.cc b/rtc_base/platform_thread.cc +index feb4338998..614860f8a0 100644 +--- a/rtc_base/platform_thread.cc ++++ b/rtc_base/platform_thread.cc +@@ -52,6 +52,9 @@ bool SetPriority(ThreadPriority priority) { + // TODO(tommi): Switch to the same mechanism as Chromium uses for changing + // thread priorities. + return true; ++#elif defined(WEBRTC_MOZILLA_BUILD) && defined(WEBRTC_LINUX) ++ // Only realtime audio uses realtime scheduling in Firefox. ++ return true; + #else + const int policy = SCHED_FIFO; + const int min_prio = sched_get_priority_min(policy); diff --git a/third_party/libwebrtc/moz-patch-stack/0072.patch b/third_party/libwebrtc/moz-patch-stack/0072.patch index c1633db5ce53..92dd89515305 100644 --- a/third_party/libwebrtc/moz-patch-stack/0072.patch +++ b/third_party/libwebrtc/moz-patch-stack/0072.patch @@ -1,25 +1,74 @@ -From: Andreas Pehrson -Date: Wed, 8 Feb 2023 08:01:00 +0000 -Subject: Bug 1814692 - Don't attempt realtime scheduling rtc::PlatformThreads. - r=webrtc-reviewers,bwc +From: Jan Grulich +Date: Fri, 10 Mar 2023 09:21:00 +0000 +Subject: Bug 1819035 - get EGL display based on the used platform in the + browser r=webrtc-reviewers,ng -Differential Revision: https://phabricator.services.mozilla.com/D169036 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/9e64a965e26c8379261466e5273c3b383164b2c7 +Because of a possible misconfiguration or a possible driver issue it +might happen that the browser will use a different driver on X11 and +end up using yet another one for wayland/gbm, which might lead to not +working screen sharing in the better case, but also to a crash in the +other driver (Nvidia). This adds a check for platform the browser runs +on, if it's XWayland or Wayland and based on that query EGL display for +that specific platform, rather than going for the Wayland one only. + +Differential Revision: https://phabricator.services.mozilla.com/D171858 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/c8606497de1f461a6352456e0e511c2ae498d526 --- - rtc_base/platform_thread.cc | 3 +++ - 1 file changed, 3 insertions(+) + .../linux/wayland/egl_dmabuf.cc | 30 +++++++++++++++++-- + 1 file changed, 28 insertions(+), 2 deletions(-) -diff --git a/rtc_base/platform_thread.cc b/rtc_base/platform_thread.cc -index 71a9f1b224..bcbb784b97 100644 ---- a/rtc_base/platform_thread.cc -+++ b/rtc_base/platform_thread.cc -@@ -50,6 +50,9 @@ bool SetPriority(ThreadPriority priority) { - // TODO(tommi): Switch to the same mechanism as Chromium uses for changing - // thread priorities. - return true; -+#elif defined(WEBRTC_MOZILLA_BUILD) && defined(WEBRTC_LINUX) -+ // Only realtime audio uses realtime scheduling in Firefox. -+ return true; - #else - const int policy = SCHED_FIFO; - const int min_prio = sched_get_priority_min(policy); +diff --git a/modules/desktop_capture/linux/wayland/egl_dmabuf.cc b/modules/desktop_capture/linux/wayland/egl_dmabuf.cc +index b529077c6d..6a019c64b4 100644 +--- a/modules/desktop_capture/linux/wayland/egl_dmabuf.cc ++++ b/modules/desktop_capture/linux/wayland/egl_dmabuf.cc +@@ -13,6 +13,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -217,6 +218,26 @@ static void CloseLibrary(void* library) { + } + } + ++static bool IsWaylandDisplay() { ++ static auto sGdkWaylandDisplayGetType = ++ (GType (*)(void))dlsym(RTLD_DEFAULT, "gdk_wayland_display_get_type"); ++ if (!sGdkWaylandDisplayGetType) { ++ return false; ++ } ++ return (G_TYPE_CHECK_INSTANCE_TYPE ((gdk_display_get_default()), ++ sGdkWaylandDisplayGetType())); ++} ++ ++static bool IsX11Display() { ++ static auto sGdkX11DisplayGetType = ++ (GType (*)(void))dlsym(RTLD_DEFAULT, "gdk_x11_display_get_type"); ++ if (!sGdkX11DisplayGetType) { ++ return false; ++ } ++ return (G_TYPE_CHECK_INSTANCE_TYPE ((gdk_display_get_default()), ++ sGdkX11DisplayGetType())); ++} ++ + static void* g_lib_egl = nullptr; + + RTC_NO_SANITIZE("cfi-icall") +@@ -362,8 +383,13 @@ EglDmaBuf::EglDmaBuf() { + return; + } + +- egl_.display = EglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, +- (void*)EGL_DEFAULT_DISPLAY, nullptr); ++ if (IsWaylandDisplay()) { ++ egl_.display = EglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, ++ (void*)EGL_DEFAULT_DISPLAY, nullptr); ++ } else if (IsX11Display()) { ++ egl_.display = EglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, ++ (void*)EGL_DEFAULT_DISPLAY, nullptr); ++ } + + if (egl_.display == EGL_NO_DISPLAY) { + RTC_LOG(LS_ERROR) << "Failed to obtain default EGL display: " diff --git a/third_party/libwebrtc/moz-patch-stack/0073.patch b/third_party/libwebrtc/moz-patch-stack/0073.patch index 92dd89515305..56e0f5333146 100644 --- a/third_party/libwebrtc/moz-patch-stack/0073.patch +++ b/third_party/libwebrtc/moz-patch-stack/0073.patch @@ -1,74 +1,31 @@ -From: Jan Grulich -Date: Fri, 10 Mar 2023 09:21:00 +0000 -Subject: Bug 1819035 - get EGL display based on the used platform in the - browser r=webrtc-reviewers,ng +From: Andreas Pehrson +Date: Mon, 27 Feb 2023 16:22:00 +0000 +Subject: Bug 1817024 - (fix-fdcfefa708) In PhysicalSocket avoid a non-trivial + designated initializer. r=mjf,webrtc-reviewers -Because of a possible misconfiguration or a possible driver issue it -might happen that the browser will use a different driver on X11 and -end up using yet another one for wayland/gbm, which might lead to not -working screen sharing in the better case, but also to a crash in the -other driver (Nvidia). This adds a check for platform the browser runs -on, if it's XWayland or Wayland and based on that query EGL display for -that specific platform, rather than going for the Wayland one only. +This fixes a build failure in the base-toolchain job with GCC 7.5.0: + In file included from Unified_cpp_threading_gn0.cpp:38:0: + .../third_party/libwebrtc/rtc_base/physical_socket_server.cc: In member function 'int rtc::PhysicalSocket::DoReadFromSocket(void*, size_t, rtc::SocketAddress*, int64_t*)': + .../third_party/libwebrtc/rtc_base/physical_socket_server.cc:463:51: sorry, unimplemented: non-trivial designated initializers not supported + msghdr msg = {.msg_iov = &iov, .msg_iovlen = 1}; + ^ -Differential Revision: https://phabricator.services.mozilla.com/D171858 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/c8606497de1f461a6352456e0e511c2ae498d526 +Differential Revision: https://phabricator.services.mozilla.com/D171057 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/a3447f709befd84a282ca40f29b7a5ea76d5b68d --- - .../linux/wayland/egl_dmabuf.cc | 30 +++++++++++++++++-- - 1 file changed, 28 insertions(+), 2 deletions(-) + rtc_base/physical_socket_server.cc | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) -diff --git a/modules/desktop_capture/linux/wayland/egl_dmabuf.cc b/modules/desktop_capture/linux/wayland/egl_dmabuf.cc -index b529077c6d..6a019c64b4 100644 ---- a/modules/desktop_capture/linux/wayland/egl_dmabuf.cc -+++ b/modules/desktop_capture/linux/wayland/egl_dmabuf.cc -@@ -13,6 +13,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -217,6 +218,26 @@ static void CloseLibrary(void* library) { - } - } - -+static bool IsWaylandDisplay() { -+ static auto sGdkWaylandDisplayGetType = -+ (GType (*)(void))dlsym(RTLD_DEFAULT, "gdk_wayland_display_get_type"); -+ if (!sGdkWaylandDisplayGetType) { -+ return false; -+ } -+ return (G_TYPE_CHECK_INSTANCE_TYPE ((gdk_display_get_default()), -+ sGdkWaylandDisplayGetType())); -+} -+ -+static bool IsX11Display() { -+ static auto sGdkX11DisplayGetType = -+ (GType (*)(void))dlsym(RTLD_DEFAULT, "gdk_x11_display_get_type"); -+ if (!sGdkX11DisplayGetType) { -+ return false; -+ } -+ return (G_TYPE_CHECK_INSTANCE_TYPE ((gdk_display_get_default()), -+ sGdkX11DisplayGetType())); -+} -+ - static void* g_lib_egl = nullptr; - - RTC_NO_SANITIZE("cfi-icall") -@@ -362,8 +383,13 @@ EglDmaBuf::EglDmaBuf() { - return; - } - -- egl_.display = EglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, -- (void*)EGL_DEFAULT_DISPLAY, nullptr); -+ if (IsWaylandDisplay()) { -+ egl_.display = EglGetPlatformDisplay(EGL_PLATFORM_WAYLAND_KHR, -+ (void*)EGL_DEFAULT_DISPLAY, nullptr); -+ } else if (IsX11Display()) { -+ egl_.display = EglGetPlatformDisplay(EGL_PLATFORM_X11_KHR, -+ (void*)EGL_DEFAULT_DISPLAY, nullptr); -+ } - - if (egl_.display == EGL_NO_DISPLAY) { - RTC_LOG(LS_ERROR) << "Failed to obtain default EGL display: " +diff --git a/rtc_base/physical_socket_server.cc b/rtc_base/physical_socket_server.cc +index 5a75fbb707..809efb8814 100644 +--- a/rtc_base/physical_socket_server.cc ++++ b/rtc_base/physical_socket_server.cc +@@ -520,7 +520,7 @@ int PhysicalSocket::DoReadFromSocket(void* buffer, + #if defined(WEBRTC_POSIX) + int received = 0; + iovec iov = {.iov_base = buffer, .iov_len = length}; +- msghdr msg = {.msg_iov = &iov, .msg_iovlen = 1}; ++ msghdr msg = {.msg_name = nullptr, .msg_namelen = 0, .msg_iov = &iov, .msg_iovlen = 1}; + if (out_addr) { + out_addr->Clear(); + msg.msg_name = addr; diff --git a/third_party/libwebrtc/moz-patch-stack/0074.patch b/third_party/libwebrtc/moz-patch-stack/0074.patch index 56e0f5333146..43f29876082d 100644 --- a/third_party/libwebrtc/moz-patch-stack/0074.patch +++ b/third_party/libwebrtc/moz-patch-stack/0074.patch @@ -1,31 +1,24 @@ -From: Andreas Pehrson -Date: Mon, 27 Feb 2023 16:22:00 +0000 -Subject: Bug 1817024 - (fix-fdcfefa708) In PhysicalSocket avoid a non-trivial - designated initializer. r=mjf,webrtc-reviewers +From: Byron Campen +Date: Fri, 7 Apr 2023 20:28:00 +0000 +Subject: Bug 1819048: Remove this bad assertion. r=webrtc-reviewers,jib -This fixes a build failure in the base-toolchain job with GCC 7.5.0: - In file included from Unified_cpp_threading_gn0.cpp:38:0: - .../third_party/libwebrtc/rtc_base/physical_socket_server.cc: In member function 'int rtc::PhysicalSocket::DoReadFromSocket(void*, size_t, rtc::SocketAddress*, int64_t*)': - .../third_party/libwebrtc/rtc_base/physical_socket_server.cc:463:51: sorry, unimplemented: non-trivial designated initializers not supported - msghdr msg = {.msg_iov = &iov, .msg_iovlen = 1}; - ^ - -Differential Revision: https://phabricator.services.mozilla.com/D171057 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/a3447f709befd84a282ca40f29b7a5ea76d5b68d +Differential Revision: https://phabricator.services.mozilla.com/D174978 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/5a52e1b0c808edfda82f0abea668699eb68098dc --- - rtc_base/physical_socket_server.cc | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) + video/task_queue_frame_decode_scheduler.cc | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) -diff --git a/rtc_base/physical_socket_server.cc b/rtc_base/physical_socket_server.cc -index 5a75fbb707..809efb8814 100644 ---- a/rtc_base/physical_socket_server.cc -+++ b/rtc_base/physical_socket_server.cc -@@ -520,7 +520,7 @@ int PhysicalSocket::DoReadFromSocket(void* buffer, - #if defined(WEBRTC_POSIX) - int received = 0; - iovec iov = {.iov_base = buffer, .iov_len = length}; -- msghdr msg = {.msg_iov = &iov, .msg_iovlen = 1}; -+ msghdr msg = {.msg_name = nullptr, .msg_namelen = 0, .msg_iov = &iov, .msg_iovlen = 1}; - if (out_addr) { - out_addr->Clear(); - msg.msg_name = addr; +diff --git a/video/task_queue_frame_decode_scheduler.cc b/video/task_queue_frame_decode_scheduler.cc +index cd109c2932..6dd7b47f17 100644 +--- a/video/task_queue_frame_decode_scheduler.cc ++++ b/video/task_queue_frame_decode_scheduler.cc +@@ -37,7 +37,8 @@ void TaskQueueFrameDecodeScheduler::ScheduleFrame( + uint32_t rtp, + FrameDecodeTiming::FrameSchedule schedule, + FrameReleaseCallback cb) { +- RTC_DCHECK(!stopped_) << "Can not schedule frames after stopped."; ++ // Mozilla modification, until https://bugs.webrtc.org/14944 is fixed ++ //RTC_DCHECK(!stopped_) << "Can not schedule frames after stopped."; + RTC_DCHECK(!scheduled_rtp_.has_value()) + << "Can not schedule two frames for release at the same time."; + RTC_DCHECK(cb); diff --git a/third_party/libwebrtc/moz-patch-stack/0075.patch b/third_party/libwebrtc/moz-patch-stack/0075.patch index 2bbf85400133..b1d62b5a34c7 100644 --- a/third_party/libwebrtc/moz-patch-stack/0075.patch +++ b/third_party/libwebrtc/moz-patch-stack/0075.patch @@ -1,22 +1,57 @@ -From: Byron Campen -Date: Tue, 4 Apr 2023 16:34:00 -0500 -Subject: Bug 1822194 - (fix-3b51cd328e) - Add missing designated initializer - that gcc is sad about. +From: Nico Grunbaum +Date: Tue, 6 Jun 2023 16:37:00 -0700 +Subject: Bug 1833237 - (fix-f0be3bee1f) remove reference to + portal:pipewire_base;r?pehrsons -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/108046c7cbb21c6cf19320c0804e9aee1a3eb4bf +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/8ff886a4d366b4be35b329d1ef733a6df542067c --- - modules/audio_processing/audio_processing_impl.cc | 1 + - 1 file changed, 1 insertion(+) + modules/video_capture/BUILD.gn | 4 ++++ + modules/video_capture/linux/device_info_pipewire.cc | 4 ++-- + modules/video_capture/linux/device_info_pipewire.h | 3 ++- + 3 files changed, 8 insertions(+), 3 deletions(-) -diff --git a/modules/audio_processing/audio_processing_impl.cc b/modules/audio_processing/audio_processing_impl.cc -index 0d11e418e6..5f6dd59d02 100644 ---- a/modules/audio_processing/audio_processing_impl.cc -+++ b/modules/audio_processing/audio_processing_impl.cc -@@ -452,6 +452,7 @@ AudioProcessingImpl::GetGainController2ExperimentParams() { - }, - .adaptive_digital_controller = - { -+ .enabled = false, - .headroom_db = static_cast(headroom_db.Get()), - .max_gain_db = static_cast(max_gain_db.Get()), - .initial_gain_db = +diff --git a/modules/video_capture/BUILD.gn b/modules/video_capture/BUILD.gn +index a2bf27f645..d9b3471572 100644 +--- a/modules/video_capture/BUILD.gn ++++ b/modules/video_capture/BUILD.gn +@@ -108,6 +108,10 @@ if (!build_with_chromium || is_linux || is_chromeos) { + "../../media:rtc_media_base", + "../portal", + ] ++ if (build_with_mozilla) { ++ configs -= [ "../portal:pipewire_base" ] ++ public_deps = [ "//third_party/pipewire" ] ++ } + } + } + if (is_win) { +diff --git a/modules/video_capture/linux/device_info_pipewire.cc b/modules/video_capture/linux/device_info_pipewire.cc +index b608648194..ef1cf67d54 100644 +--- a/modules/video_capture/linux/device_info_pipewire.cc ++++ b/modules/video_capture/linux/device_info_pipewire.cc +@@ -49,9 +49,9 @@ int32_t DeviceInfoPipeWire::GetDeviceName(uint32_t deviceNumber, + char* deviceUniqueIdUTF8, + uint32_t deviceUniqueIdUTF8Length, + char* productUniqueIdUTF8, +- uint32_t productUniqueIdUTF8Length) { ++ uint32_t productUniqueIdUTF8Length, ++ pid_t* pid) { + RTC_CHECK(pipewire_session_); +- + if (deviceNumber >= NumberOfDevices()) + return -1; + +diff --git a/modules/video_capture/linux/device_info_pipewire.h b/modules/video_capture/linux/device_info_pipewire.h +index 4da0c7a90b..8a33d75892 100644 +--- a/modules/video_capture/linux/device_info_pipewire.h ++++ b/modules/video_capture/linux/device_info_pipewire.h +@@ -29,7 +29,8 @@ class DeviceInfoPipeWire : public DeviceInfoImpl { + char* deviceUniqueIdUTF8, + uint32_t deviceUniqueIdUTF8Length, + char* productUniqueIdUTF8 = nullptr, +- uint32_t productUniqueIdUTF8Length = 0) override; ++ uint32_t productUniqueIdUTF8Length = 0, ++ pid_t* pid = 0) override; + /* + * Fills the membervariable _captureCapabilities with capabilites for the + * given device name. diff --git a/third_party/libwebrtc/moz-patch-stack/0076.patch b/third_party/libwebrtc/moz-patch-stack/0076.patch index 43f29876082d..d9666b3a3c7f 100644 --- a/third_party/libwebrtc/moz-patch-stack/0076.patch +++ b/third_party/libwebrtc/moz-patch-stack/0076.patch @@ -1,24 +1,29 @@ -From: Byron Campen -Date: Fri, 7 Apr 2023 20:28:00 +0000 -Subject: Bug 1819048: Remove this bad assertion. r=webrtc-reviewers,jib +From: Jan-Ivar Bruaroey +Date: Wed, 28 Jun 2023 20:45:00 -0400 +Subject: Bug 1839451 - (fix-0f43da2248) Keep mozilla's + RTCPReceiver::RemoteRTCPSenderInfo function working. -Differential Revision: https://phabricator.services.mozilla.com/D174978 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/5a52e1b0c808edfda82f0abea668699eb68098dc +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/154c9cdb386d0f50c5e1549270e1af6ab4969602 --- - video/task_queue_frame_decode_scheduler.cc | 3 ++- - 1 file changed, 2 insertions(+), 1 deletion(-) + modules/rtp_rtcp/source/rtcp_receiver.cc | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) -diff --git a/video/task_queue_frame_decode_scheduler.cc b/video/task_queue_frame_decode_scheduler.cc -index cd109c2932..6dd7b47f17 100644 ---- a/video/task_queue_frame_decode_scheduler.cc -+++ b/video/task_queue_frame_decode_scheduler.cc -@@ -37,7 +37,8 @@ void TaskQueueFrameDecodeScheduler::ScheduleFrame( - uint32_t rtp, - FrameDecodeTiming::FrameSchedule schedule, - FrameReleaseCallback cb) { -- RTC_DCHECK(!stopped_) << "Can not schedule frames after stopped."; -+ // Mozilla modification, until https://bugs.webrtc.org/14944 is fixed -+ //RTC_DCHECK(!stopped_) << "Can not schedule frames after stopped."; - RTC_DCHECK(!scheduled_rtp_.has_value()) - << "Can not schedule two frames for release at the same time."; - RTC_DCHECK(cb); +diff --git a/modules/rtp_rtcp/source/rtcp_receiver.cc b/modules/rtp_rtcp/source/rtcp_receiver.cc +index d8dee29ef3..5125f715a0 100644 +--- a/modules/rtp_rtcp/source/rtcp_receiver.cc ++++ b/modules/rtp_rtcp/source/rtcp_receiver.cc +@@ -375,10 +375,10 @@ void RTCPReceiver::RemoteRTCPSenderInfo(uint32_t* packet_count, + int64_t* ntp_timestamp_ms, + int64_t* remote_ntp_timestamp_ms) const { + MutexLock lock(&rtcp_receiver_lock_); +- *packet_count = remote_sender_packet_count_; +- *octet_count = remote_sender_octet_count_; +- *ntp_timestamp_ms = last_received_sr_ntp_.ToMs(); +- *remote_ntp_timestamp_ms = remote_sender_ntp_time_.ToMs(); ++ *packet_count = remote_sender_.packets_sent; ++ *octet_count = remote_sender_.bytes_sent; ++ *ntp_timestamp_ms = remote_sender_.last_arrival_timestamp.ToMs(); ++ *remote_ntp_timestamp_ms = remote_sender_.last_remote_timestamp.ToMs(); + } + + std::vector RTCPReceiver::GetLatestReportBlockData() const { diff --git a/third_party/libwebrtc/moz-patch-stack/0077.patch b/third_party/libwebrtc/moz-patch-stack/0077.patch index d66f8364c798..25beff291598 100644 --- a/third_party/libwebrtc/moz-patch-stack/0077.patch +++ b/third_party/libwebrtc/moz-patch-stack/0077.patch @@ -1,57 +1,33 @@ From: Nico Grunbaum -Date: Tue, 6 Jun 2023 16:37:00 -0700 -Subject: Bug 1833237 - (fix-f0be3bee1f) remove reference to - portal:pipewire_base;r?pehrsons +Date: Thu, 22 Jun 2023 16:23:00 +0000 +Subject: Bug 1837918 - libwebrtc update broke the build on + OpenBSD;r=mjf,webrtc-reviewers -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/8ff886a4d366b4be35b329d1ef733a6df542067c +Differential Revision: https://phabricator.services.mozilla.com/D181791 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/2a6a838b7021bb285f9485c2ceda6ba2543e0d6f --- - modules/video_capture/BUILD.gn | 4 ++++ - modules/video_capture/linux/device_info_pipewire.cc | 4 ++-- - modules/video_capture/linux/device_info_pipewire.h | 3 ++- - 3 files changed, 8 insertions(+), 3 deletions(-) + modules/video_capture/video_capture_options.h | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) -diff --git a/modules/video_capture/BUILD.gn b/modules/video_capture/BUILD.gn -index cd4280d4c0..115173f9c9 100644 ---- a/modules/video_capture/BUILD.gn -+++ b/modules/video_capture/BUILD.gn -@@ -105,6 +105,10 @@ if (!build_with_chromium || is_linux || is_chromeos) { - "../../media:rtc_media_base", - "../portal", - ] -+ if (build_with_mozilla) { -+ configs -= [ "../portal:pipewire_base" ] -+ public_deps = [ "//third_party/pipewire" ] -+ } - } - } - if (is_win) { -diff --git a/modules/video_capture/linux/device_info_pipewire.cc b/modules/video_capture/linux/device_info_pipewire.cc -index b608648194..ef1cf67d54 100644 ---- a/modules/video_capture/linux/device_info_pipewire.cc -+++ b/modules/video_capture/linux/device_info_pipewire.cc -@@ -49,9 +49,9 @@ int32_t DeviceInfoPipeWire::GetDeviceName(uint32_t deviceNumber, - char* deviceUniqueIdUTF8, - uint32_t deviceUniqueIdUTF8Length, - char* productUniqueIdUTF8, -- uint32_t productUniqueIdUTF8Length) { -+ uint32_t productUniqueIdUTF8Length, -+ pid_t* pid) { - RTC_CHECK(pipewire_session_); -- - if (deviceNumber >= NumberOfDevices()) - return -1; +diff --git a/modules/video_capture/video_capture_options.h b/modules/video_capture/video_capture_options.h +index 6f72f7927e..37965305d9 100644 +--- a/modules/video_capture/video_capture_options.h ++++ b/modules/video_capture/video_capture_options.h +@@ -55,7 +55,7 @@ class RTC_EXPORT VideoCaptureOptions { -diff --git a/modules/video_capture/linux/device_info_pipewire.h b/modules/video_capture/linux/device_info_pipewire.h -index 4da0c7a90b..8a33d75892 100644 ---- a/modules/video_capture/linux/device_info_pipewire.h -+++ b/modules/video_capture/linux/device_info_pipewire.h -@@ -29,7 +29,8 @@ class DeviceInfoPipeWire : public DeviceInfoImpl { - char* deviceUniqueIdUTF8, - uint32_t deviceUniqueIdUTF8Length, - char* productUniqueIdUTF8 = nullptr, -- uint32_t productUniqueIdUTF8Length = 0) override; -+ uint32_t productUniqueIdUTF8Length = 0, -+ pid_t* pid = 0) override; - /* - * Fills the membervariable _captureCapabilities with capabilites for the - * given device name. + void Init(Callback* callback); + +-#if defined(WEBRTC_LINUX) ++#if defined(WEBRTC_LINUX) || defined(WEBRTC_BSD) + bool allow_v4l2() const { return allow_v4l2_; } + void set_allow_v4l2(bool allow) { allow_v4l2_ = allow; } + #endif +@@ -68,7 +68,7 @@ class RTC_EXPORT VideoCaptureOptions { + #endif + + private: +-#if defined(WEBRTC_LINUX) ++#if defined(WEBRTC_LINUX) || defined(WEBRTC_BSD) + bool allow_v4l2_ = false; + #endif + #if defined(WEBRTC_USE_PIPEWIRE) diff --git a/third_party/libwebrtc/moz-patch-stack/0078.patch b/third_party/libwebrtc/moz-patch-stack/0078.patch index d9666b3a3c7f..aebc8c18d5b5 100644 --- a/third_party/libwebrtc/moz-patch-stack/0078.patch +++ b/third_party/libwebrtc/moz-patch-stack/0078.patch @@ -1,29 +1,24 @@ -From: Jan-Ivar Bruaroey -Date: Wed, 28 Jun 2023 20:45:00 -0400 -Subject: Bug 1839451 - (fix-0f43da2248) Keep mozilla's - RTCPReceiver::RemoteRTCPSenderInfo function working. +From: Michael Froman +Date: Wed, 5 Jul 2023 19:15:00 +0000 +Subject: Bug 1841864 - upstream commit 4baea5b07f should properly check size + of encoder_config_.simulcast_layers. r=jib -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/154c9cdb386d0f50c5e1549270e1af6ab4969602 +Differential Revision: https://phabricator.services.mozilla.com/D182813 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/a7179d8d75313b6c9c76a496e10d102da019ff4f --- - modules/rtp_rtcp/source/rtcp_receiver.cc | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) + video/video_stream_encoder.cc | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) -diff --git a/modules/rtp_rtcp/source/rtcp_receiver.cc b/modules/rtp_rtcp/source/rtcp_receiver.cc -index d8dee29ef3..5125f715a0 100644 ---- a/modules/rtp_rtcp/source/rtcp_receiver.cc -+++ b/modules/rtp_rtcp/source/rtcp_receiver.cc -@@ -375,10 +375,10 @@ void RTCPReceiver::RemoteRTCPSenderInfo(uint32_t* packet_count, - int64_t* ntp_timestamp_ms, - int64_t* remote_ntp_timestamp_ms) const { - MutexLock lock(&rtcp_receiver_lock_); -- *packet_count = remote_sender_packet_count_; -- *octet_count = remote_sender_octet_count_; -- *ntp_timestamp_ms = last_received_sr_ntp_.ToMs(); -- *remote_ntp_timestamp_ms = remote_sender_ntp_time_.ToMs(); -+ *packet_count = remote_sender_.packets_sent; -+ *octet_count = remote_sender_.bytes_sent; -+ *ntp_timestamp_ms = remote_sender_.last_arrival_timestamp.ToMs(); -+ *remote_ntp_timestamp_ms = remote_sender_.last_remote_timestamp.ToMs(); - } +diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc +index a9391e0cea..ef3d85f8b4 100644 +--- a/video/video_stream_encoder.cc ++++ b/video/video_stream_encoder.cc +@@ -1368,7 +1368,7 @@ void VideoStreamEncoder::ReconfigureEncoder() { - std::vector RTCPReceiver::GetLatestReportBlockData() const { + bool is_svc = false; + bool single_stream_or_non_first_inactive = true; +- for (size_t i = 1; i < encoder_config_.number_of_streams; ++i) { ++ for (size_t i = 1; i < encoder_config_.simulcast_layers.size(); ++i) { + if (encoder_config_.simulcast_layers[i].active) { + single_stream_or_non_first_inactive = false; + break; diff --git a/third_party/libwebrtc/moz-patch-stack/0079.patch b/third_party/libwebrtc/moz-patch-stack/0079.patch index 25beff291598..7d71d308af3b 100644 --- a/third_party/libwebrtc/moz-patch-stack/0079.patch +++ b/third_party/libwebrtc/moz-patch-stack/0079.patch @@ -1,33 +1,24 @@ -From: Nico Grunbaum -Date: Thu, 22 Jun 2023 16:23:00 +0000 -Subject: Bug 1837918 - libwebrtc update broke the build on - OpenBSD;r=mjf,webrtc-reviewers +From: Mike Hommey +Date: Fri, 7 Jul 2023 00:58:00 +0000 +Subject: Bug 1841577 - Don't set WEBRTC_ENABLE_AVX2 on platforms that don't + have AVX2. r=mjf,webrtc-reviewers -Differential Revision: https://phabricator.services.mozilla.com/D181791 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/2a6a838b7021bb285f9485c2ceda6ba2543e0d6f +Differential Revision: https://phabricator.services.mozilla.com/D182695 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/46fb51c90709be64c35946a8cf69195121441024 --- - modules/video_capture/video_capture_options.h | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) + webrtc.gni | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) -diff --git a/modules/video_capture/video_capture_options.h b/modules/video_capture/video_capture_options.h -index 6f72f7927e..37965305d9 100644 ---- a/modules/video_capture/video_capture_options.h -+++ b/modules/video_capture/video_capture_options.h -@@ -55,7 +55,7 @@ class RTC_EXPORT VideoCaptureOptions { +diff --git a/webrtc.gni b/webrtc.gni +index 2279e25fbe..7d3bbdda0d 100644 +--- a/webrtc.gni ++++ b/webrtc.gni +@@ -317,7 +317,7 @@ declare_args() { - void Init(Callback* callback); - --#if defined(WEBRTC_LINUX) -+#if defined(WEBRTC_LINUX) || defined(WEBRTC_BSD) - bool allow_v4l2() const { return allow_v4l2_; } - void set_allow_v4l2(bool allow) { allow_v4l2_ = allow; } - #endif -@@ -68,7 +68,7 @@ class RTC_EXPORT VideoCaptureOptions { - #endif - - private: --#if defined(WEBRTC_LINUX) -+#if defined(WEBRTC_LINUX) || defined(WEBRTC_BSD) - bool allow_v4l2_ = false; - #endif - #if defined(WEBRTC_USE_PIPEWIRE) + # Set this to true to enable the avx2 support in webrtc. + # TODO: Make sure that AVX2 works also for non-clang compilers. +- if (is_clang == true) { ++ if (is_clang == true && (target_cpu == "x86" || target_cpu == "x64")) { + rtc_enable_avx2 = true + } else { + rtc_enable_avx2 = false diff --git a/third_party/libwebrtc/moz-patch-stack/0080.patch b/third_party/libwebrtc/moz-patch-stack/0080.patch index 3560847a178e..24954eee4aa9 100644 --- a/third_party/libwebrtc/moz-patch-stack/0080.patch +++ b/third_party/libwebrtc/moz-patch-stack/0080.patch @@ -1,24 +1,26 @@ -From: Michael Froman -Date: Wed, 5 Jul 2023 19:15:00 +0000 -Subject: Bug 1841864 - upstream commit 4baea5b07f should properly check size - of encoder_config_.simulcast_layers. r=jib +From: Byron Campen +Date: Thu, 20 Jul 2023 14:24:00 +0000 +Subject: Bug 1838080: Remove this duplicate init (that's also on the wrong + thread). r=pehrsons,webrtc-reviewers -Differential Revision: https://phabricator.services.mozilla.com/D182813 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/a7179d8d75313b6c9c76a496e10d102da019ff4f +This was causing assertions. + +Differential Revision: https://phabricator.services.mozilla.com/D179731 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/6ac6592a04a839a6152d5ad5f0778f63dbbd6b1b --- - video/video_stream_encoder.cc | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) + audio/channel_send.cc | 2 -- + 1 file changed, 2 deletions(-) -diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc -index 3c0c78e42f..7fb64fa9e2 100644 ---- a/video/video_stream_encoder.cc -+++ b/video/video_stream_encoder.cc -@@ -1372,7 +1372,7 @@ void VideoStreamEncoder::ReconfigureEncoder() { +diff --git a/audio/channel_send.cc b/audio/channel_send.cc +index 37428ecc8a..9eb35b3d50 100644 +--- a/audio/channel_send.cc ++++ b/audio/channel_send.cc +@@ -476,8 +476,6 @@ ChannelSend::ChannelSend( - bool is_svc = false; - bool single_stream_or_non_first_inactive = true; -- for (size_t i = 1; i < encoder_config_.number_of_streams; ++i) { -+ for (size_t i = 1; i < encoder_config_.simulcast_layers.size(); ++i) { - if (encoder_config_.simulcast_layers[i].active) { - single_stream_or_non_first_inactive = false; - break; + int error = audio_coding_->RegisterTransportCallback(this); + RTC_DCHECK_EQ(0, error); +- if (frame_transformer) +- InitFrameTransformerDelegate(std::move(frame_transformer)); + } + + ChannelSend::~ChannelSend() { diff --git a/third_party/libwebrtc/moz-patch-stack/0081.patch b/third_party/libwebrtc/moz-patch-stack/0081.patch index c3e498784aa5..e62f0d76477a 100644 --- a/third_party/libwebrtc/moz-patch-stack/0081.patch +++ b/third_party/libwebrtc/moz-patch-stack/0081.patch @@ -1,24 +1,39 @@ -From: Mike Hommey -Date: Fri, 7 Jul 2023 00:58:00 +0000 -Subject: Bug 1841577 - Don't set WEBRTC_ENABLE_AVX2 on platforms that don't - have AVX2. r=mjf,webrtc-reviewers +From: Byron Campen +Date: Thu, 20 Jul 2023 14:24:00 +0000 +Subject: Bug 1838080: Work around a race in + ChannelSendFrameTransformerDelegate. r=pehrsons,webrtc-reviewers -Differential Revision: https://phabricator.services.mozilla.com/D182695 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/46fb51c90709be64c35946a8cf69195121441024 +This variable can be null when a ChannelSendFrameTransformerDelegate is in use, +because that does an async dispatch to the encoder queue in the handling for +transformed frames. If this is unset while that dispatch is in flight, we +nullptr crash. + +Differential Revision: https://phabricator.services.mozilla.com/D180735 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/56555ecee7f36ae73abff1cbbd06807c2b65fc19 --- - webrtc.gni | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) + audio/channel_send.cc | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) -diff --git a/webrtc.gni b/webrtc.gni -index 5c80e87995..6cae8d9770 100644 ---- a/webrtc.gni -+++ b/webrtc.gni -@@ -318,7 +318,7 @@ declare_args() { +diff --git a/audio/channel_send.cc b/audio/channel_send.cc +index 9eb35b3d50..d753613a90 100644 +--- a/audio/channel_send.cc ++++ b/audio/channel_send.cc +@@ -284,12 +284,16 @@ class RtpPacketSenderProxy : public RtpPacketSender { + void EnqueuePackets( + std::vector> packets) override { + MutexLock lock(&mutex_); +- rtp_packet_pacer_->EnqueuePackets(std::move(packets)); ++ if (rtp_packet_pacer_) { ++ rtp_packet_pacer_->EnqueuePackets(std::move(packets)); ++ } + } - # Set this to true to enable the avx2 support in webrtc. - # TODO: Make sure that AVX2 works also for non-clang compilers. -- if (is_clang == true) { -+ if (is_clang == true && (target_cpu == "x86" || target_cpu == "x64")) { - rtc_enable_avx2 = true - } else { - rtc_enable_avx2 = false + void RemovePacketsForSsrc(uint32_t ssrc) override { + MutexLock lock(&mutex_); +- rtp_packet_pacer_->RemovePacketsForSsrc(ssrc); ++ if (rtp_packet_pacer_) { ++ rtp_packet_pacer_->RemovePacketsForSsrc(ssrc); ++ } + } + + private: diff --git a/third_party/libwebrtc/moz-patch-stack/0082.patch b/third_party/libwebrtc/moz-patch-stack/0082.patch index 0f870fcda1e3..9bcead2420f6 100644 --- a/third_party/libwebrtc/moz-patch-stack/0082.patch +++ b/third_party/libwebrtc/moz-patch-stack/0082.patch @@ -1,26 +1,66 @@ From: Byron Campen Date: Thu, 20 Jul 2023 14:24:00 +0000 -Subject: Bug 1838080: Remove this duplicate init (that's also on the wrong - thread). r=pehrsons,webrtc-reviewers +Subject: Bug 1838080: Use the current TaskQueue, instead of the current + thread, to init this. r=pehrsons,webrtc-reviewers -This was causing assertions. +There are situations where the current thread is not set, but the current +TaskQueue is (but not vice versa). -Differential Revision: https://phabricator.services.mozilla.com/D179731 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/6ac6592a04a839a6152d5ad5f0778f63dbbd6b1b +Differential Revision: https://phabricator.services.mozilla.com/D180736 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/62e71a2f745c4b98d5ee7ce9e6386aa1b657be9b --- - audio/channel_send.cc | 2 -- - 1 file changed, 2 deletions(-) + .../rtp_video_stream_receiver_frame_transformer_delegate.cc | 3 +-- + .../rtp_video_stream_receiver_frame_transformer_delegate.h | 5 ++--- + video/rtp_video_stream_receiver2.cc | 2 +- + 3 files changed, 4 insertions(+), 6 deletions(-) -diff --git a/audio/channel_send.cc b/audio/channel_send.cc -index 4f55416cca..d1485a5468 100644 ---- a/audio/channel_send.cc -+++ b/audio/channel_send.cc -@@ -475,8 +475,6 @@ ChannelSend::ChannelSend( +diff --git a/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc b/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc +index 5ef0a80492..de74684a1f 100644 +--- a/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc ++++ b/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc +@@ -96,8 +96,7 @@ RtpVideoStreamReceiverFrameTransformerDelegate:: + RtpVideoFrameReceiver* receiver, + Clock* clock, + rtc::scoped_refptr frame_transformer, +- rtc::Thread* network_thread, +- uint32_t ssrc) ++ TaskQueueBase* network_thread, uint32_t ssrc) + : receiver_(receiver), + frame_transformer_(std::move(frame_transformer)), + network_thread_(network_thread), +diff --git a/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.h b/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.h +index f08fc692dd..02f2e53923 100644 +--- a/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.h ++++ b/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.h +@@ -41,8 +41,7 @@ class RtpVideoStreamReceiverFrameTransformerDelegate + RtpVideoFrameReceiver* receiver, + Clock* clock, + rtc::scoped_refptr frame_transformer, +- rtc::Thread* network_thread, +- uint32_t ssrc); ++ TaskQueueBase* network_thread, uint32_t ssrc); - int error = audio_coding_->RegisterTransportCallback(this); - RTC_DCHECK_EQ(0, error); -- if (frame_transformer) -- InitFrameTransformerDelegate(std::move(frame_transformer)); - } - - ChannelSend::~ChannelSend() { + void Init(); + void Reset(); +@@ -71,7 +70,7 @@ class RtpVideoStreamReceiverFrameTransformerDelegate + RtpVideoFrameReceiver* receiver_ RTC_GUARDED_BY(network_sequence_checker_); + rtc::scoped_refptr frame_transformer_ + RTC_GUARDED_BY(network_sequence_checker_); +- rtc::Thread* const network_thread_; ++ TaskQueueBase* const network_thread_; + const uint32_t ssrc_; + 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 4650aa0082..5b5538711a 100644 +--- a/video/rtp_video_stream_receiver2.cc ++++ b/video/rtp_video_stream_receiver2.cc +@@ -345,7 +345,7 @@ RtpVideoStreamReceiver2::RtpVideoStreamReceiver2( + if (frame_transformer) { + frame_transformer_delegate_ = + rtc::make_ref_counted( +- this, clock_, std::move(frame_transformer), rtc::Thread::Current(), ++ this, clock_, std::move(frame_transformer), TaskQueueBase::Current(), + config_.rtp.remote_ssrc); + frame_transformer_delegate_->Init(); + } diff --git a/third_party/libwebrtc/moz-patch-stack/0083.patch b/third_party/libwebrtc/moz-patch-stack/0083.patch index 8b5672aeafc1..47a6830ef207 100644 --- a/third_party/libwebrtc/moz-patch-stack/0083.patch +++ b/third_party/libwebrtc/moz-patch-stack/0083.patch @@ -1,39 +1,165 @@ -From: Byron Campen -Date: Thu, 20 Jul 2023 14:24:00 +0000 -Subject: Bug 1838080: Work around a race in - ChannelSendFrameTransformerDelegate. r=pehrsons,webrtc-reviewers +From: Michael Froman +Date: Thu, 27 Jul 2023 12:42:44 -0500 +Subject: Bug 1838080: Store the rid in TransformableVideoSenderFrame. + r=ng,webrtc-reviewers -This variable can be null when a ChannelSendFrameTransformerDelegate is in use, -because that does an async dispatch to the encoder queue in the handling for -transformed frames. If this is unset while that dispatch is in flight, we -nullptr crash. +This is necessary to reliably detect what rid a given keyframe is for, for the +purposes of resolving promises from RTCRtpScriptTransformer.generateKeyFrame. -Differential Revision: https://phabricator.services.mozilla.com/D180735 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/56555ecee7f36ae73abff1cbbd06807c2b65fc19 +Differential Revision: https://phabricator.services.mozilla.com/D180737 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/2f1a0ba74bf71cfa0bc4e77714b8a5276a70cc36 --- - audio/channel_send.cc | 8 ++++++-- - 1 file changed, 6 insertions(+), 2 deletions(-) + api/frame_transformer_interface.h | 1 + + modules/rtp_rtcp/source/rtp_sender.h | 4 ++++ + modules/rtp_rtcp/source/rtp_sender_video.cc | 1 + + ...rtp_sender_video_frame_transformer_delegate.cc | 15 +++++++++++---- + .../rtp_sender_video_frame_transformer_delegate.h | 2 ++ + ..._stream_receiver_frame_transformer_delegate.cc | 5 +++++ + 6 files changed, 24 insertions(+), 4 deletions(-) -diff --git a/audio/channel_send.cc b/audio/channel_send.cc -index d1485a5468..9a15793ed0 100644 ---- a/audio/channel_send.cc -+++ b/audio/channel_send.cc -@@ -283,12 +283,16 @@ class RtpPacketSenderProxy : public RtpPacketSender { - void EnqueuePackets( - std::vector> packets) override { - MutexLock lock(&mutex_); -- rtp_packet_pacer_->EnqueuePackets(std::move(packets)); -+ if (rtp_packet_pacer_) { -+ rtp_packet_pacer_->EnqueuePackets(std::move(packets)); -+ } - } - - void RemovePacketsForSsrc(uint32_t ssrc) override { - MutexLock lock(&mutex_); -- rtp_packet_pacer_->RemovePacketsForSsrc(ssrc); -+ if (rtp_packet_pacer_) { -+ rtp_packet_pacer_->RemovePacketsForSsrc(ssrc); -+ } +diff --git a/api/frame_transformer_interface.h b/api/frame_transformer_interface.h +index 6f632bcb72..c606328e2b 100644 +--- a/api/frame_transformer_interface.h ++++ b/api/frame_transformer_interface.h +@@ -77,6 +77,7 @@ class TransformableVideoFrameInterface : public TransformableFrameInterface { + RTC_EXPORT explicit TransformableVideoFrameInterface(Passkey passkey); + virtual ~TransformableVideoFrameInterface() = default; + virtual bool IsKeyFrame() const = 0; ++ virtual const std::string& GetRid() const = 0; + + virtual VideoFrameMetadata Metadata() const = 0; + +diff --git a/modules/rtp_rtcp/source/rtp_sender.h b/modules/rtp_rtcp/source/rtp_sender.h +index a398f16d46..8136730e4c 100644 +--- a/modules/rtp_rtcp/source/rtp_sender.h ++++ b/modules/rtp_rtcp/source/rtp_sender.h +@@ -140,6 +140,10 @@ class RTPSender { + + uint32_t SSRC() const RTC_LOCKS_EXCLUDED(send_mutex_) { return ssrc_; } + ++ const std::string& Rid() const RTC_LOCKS_EXCLUDED(send_mutex_) { ++ return rid_; ++ } ++ + absl::optional FlexfecSsrc() const RTC_LOCKS_EXCLUDED(send_mutex_) { + return flexfec_ssrc_; + } +diff --git a/modules/rtp_rtcp/source/rtp_sender_video.cc b/modules/rtp_rtcp/source/rtp_sender_video.cc +index 2ca5e4bb1e..1d8f29c73b 100644 +--- a/modules/rtp_rtcp/source/rtp_sender_video.cc ++++ b/modules/rtp_rtcp/source/rtp_sender_video.cc +@@ -156,6 +156,7 @@ RTPSenderVideo::RTPSenderVideo(const Config& config) + this, + config.frame_transformer, + rtp_sender_->SSRC(), ++ rtp_sender_->Rid(), + config.task_queue_factory) + : nullptr), + enable_av1_even_split_( +diff --git a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc +index 8c24160f38..b49a6fead5 100644 +--- a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc ++++ b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc +@@ -55,7 +55,8 @@ class TransformableVideoSenderFrame : public TransformableVideoFrameInterface { + uint32_t rtp_timestamp, + TimeDelta expected_retransmission_time, + uint32_t ssrc, +- std::vector csrcs) ++ std::vector csrcs, ++ const std::string& rid) + : TransformableVideoFrameInterface(Passkey()), + encoded_data_(encoded_image.GetEncodedData()), + pre_transform_payload_size_(encoded_image.size()), +@@ -68,7 +69,8 @@ class TransformableVideoSenderFrame : public TransformableVideoFrameInterface { + capture_time_identifier_(encoded_image.CaptureTimeIdentifier()), + expected_retransmission_time_(expected_retransmission_time), + ssrc_(ssrc), +- csrcs_(csrcs) { ++ csrcs_(csrcs), ++ rid_(rid) { + RTC_DCHECK_GE(payload_type_, 0); + RTC_DCHECK_LE(payload_type_, 127); + } +@@ -131,6 +133,8 @@ class TransformableVideoSenderFrame : public TransformableVideoFrameInterface { + return mime_type + CodecTypeToPayloadString(*codec_type_); } ++ const std::string& GetRid() const override { return rid_; } ++ private: + rtc::scoped_refptr encoded_data_; + const size_t pre_transform_payload_size_; +@@ -145,16 +149,19 @@ class TransformableVideoSenderFrame : public TransformableVideoFrameInterface { + + uint32_t ssrc_; + std::vector csrcs_; ++ const std::string rid_; + }; + + RTPSenderVideoFrameTransformerDelegate::RTPSenderVideoFrameTransformerDelegate( + RTPVideoFrameSenderInterface* sender, + rtc::scoped_refptr frame_transformer, + uint32_t ssrc, ++ const std::string& rid, + TaskQueueFactory* task_queue_factory) + : sender_(sender), + frame_transformer_(std::move(frame_transformer)), + ssrc_(ssrc), ++ rid_(rid), + transformation_queue_(task_queue_factory->CreateTaskQueue( + "video_frame_transformer", + TaskQueueFactory::Priority::NORMAL)) {} +@@ -185,7 +192,7 @@ bool RTPSenderVideoFrameTransformerDelegate::TransformFrame( + frame_transformer_->Transform(std::make_unique( + encoded_image, video_header, payload_type, codec_type, rtp_timestamp, + expected_retransmission_time, ssrc_, +- /*csrcs=*/std::vector())); ++ /*csrcs=*/std::vector(), rid_)); + return true; + } + +@@ -287,7 +294,7 @@ std::unique_ptr CloneSenderVideoFrame( + return std::make_unique( + encoded_image, new_header, original->GetPayloadType(), new_header.codec, + original->GetTimestamp(), kDefaultRetransmissionsTime, +- original->GetSsrc(), metadata.GetCsrcs()); ++ original->GetSsrc(), metadata.GetCsrcs(), original->GetRid()); + } + + } // namespace webrtc +diff --git a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h +index a32c7084ea..ef6f128899 100644 +--- a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h ++++ b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h +@@ -60,6 +60,7 @@ class RTPSenderVideoFrameTransformerDelegate : public TransformedFrameCallback { + RTPVideoFrameSenderInterface* sender, + rtc::scoped_refptr frame_transformer, + uint32_t ssrc, ++ const std::string& rid, + TaskQueueFactory* send_transport_queue); + + void Init(); +@@ -108,6 +109,7 @@ class RTPSenderVideoFrameTransformerDelegate : public TransformedFrameCallback { + RTPVideoFrameSenderInterface* sender_ RTC_GUARDED_BY(sender_lock_); + rtc::scoped_refptr frame_transformer_; + const uint32_t ssrc_; ++ const std::string rid_; + // Used when the encoded frames arrives without a current task queue. This can + // happen if a hardware encoder was used. + std::unique_ptr transformation_queue_; +diff --git a/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc b/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc +index de74684a1f..7e0754284a 100644 +--- a/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc ++++ b/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc +@@ -59,6 +59,11 @@ class TransformableVideoReceiverFrame + return frame_->FrameType() == VideoFrameType::kVideoFrameKey; + } + ++ const std::string& GetRid() const override { ++ static const std::string empty; ++ return empty; ++ } ++ + VideoFrameMetadata Metadata() const override { return metadata_; } + + void SetMetadata(const VideoFrameMetadata& metadata) override { diff --git a/third_party/libwebrtc/moz-patch-stack/0084.patch b/third_party/libwebrtc/moz-patch-stack/0084.patch index bde3f2d2ce9f..ca6a2d482f24 100644 --- a/third_party/libwebrtc/moz-patch-stack/0084.patch +++ b/third_party/libwebrtc/moz-patch-stack/0084.patch @@ -1,66 +1,38 @@ From: Byron Campen Date: Thu, 20 Jul 2023 14:24:00 +0000 -Subject: Bug 1838080: Use the current TaskQueue, instead of the current - thread, to init this. r=pehrsons,webrtc-reviewers +Subject: Bug 1838080: Ensure that last ref to transformation_queue_ is not + released on itself. r=pehrsons,webrtc-reviewers -There are situations where the current thread is not set, but the current -TaskQueue is (but not vice versa). - -Differential Revision: https://phabricator.services.mozilla.com/D180736 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/62e71a2f745c4b98d5ee7ce9e6386aa1b657be9b +Differential Revision: https://phabricator.services.mozilla.com/D181699 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/91d9e8b6a5c430a73561ffd2330865f04fcb1a6d --- - .../rtp_video_stream_receiver_frame_transformer_delegate.cc | 3 +-- - .../rtp_video_stream_receiver_frame_transformer_delegate.h | 5 ++--- - video/rtp_video_stream_receiver2.cc | 2 +- - 3 files changed, 4 insertions(+), 6 deletions(-) + .../rtp_sender_video_frame_transformer_delegate.cc | 9 +++++++++ + 1 file changed, 9 insertions(+) -diff --git a/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc b/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc -index 5ef0a80492..de74684a1f 100644 ---- a/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc -+++ b/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc -@@ -96,8 +96,7 @@ RtpVideoStreamReceiverFrameTransformerDelegate:: - RtpVideoFrameReceiver* receiver, - Clock* clock, - rtc::scoped_refptr frame_transformer, -- rtc::Thread* network_thread, -- uint32_t ssrc) -+ TaskQueueBase* network_thread, uint32_t ssrc) - : receiver_(receiver), - frame_transformer_(std::move(frame_transformer)), - network_thread_(network_thread), -diff --git a/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.h b/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.h -index f08fc692dd..02f2e53923 100644 ---- a/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.h -+++ b/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.h -@@ -41,8 +41,7 @@ class RtpVideoStreamReceiverFrameTransformerDelegate - RtpVideoFrameReceiver* receiver, - Clock* clock, - rtc::scoped_refptr frame_transformer, -- rtc::Thread* network_thread, -- uint32_t ssrc); -+ TaskQueueBase* network_thread, uint32_t ssrc); +diff --git a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc +index b49a6fead5..71777069f7 100644 +--- a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc ++++ b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc +@@ -34,6 +34,7 @@ + #include "api/video_codecs/video_codec.h" + #include "modules/rtp_rtcp/source/rtp_video_header.h" + #include "rtc_base/checks.h" ++#include "rtc_base/event.h" + #include "rtc_base/synchronization/mutex.h" - void Init(); - void Reset(); -@@ -71,7 +70,7 @@ class RtpVideoStreamReceiverFrameTransformerDelegate - RtpVideoFrameReceiver* receiver_ RTC_GUARDED_BY(network_sequence_checker_); - rtc::scoped_refptr frame_transformer_ - RTC_GUARDED_BY(network_sequence_checker_); -- rtc::Thread* const network_thread_; -+ TaskQueueBase* const network_thread_; - const uint32_t ssrc_; - 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 a5184d8ac0..863894d5a8 100644 ---- a/video/rtp_video_stream_receiver2.cc -+++ b/video/rtp_video_stream_receiver2.cc -@@ -345,7 +345,7 @@ RtpVideoStreamReceiver2::RtpVideoStreamReceiver2( - if (frame_transformer) { - frame_transformer_delegate_ = - rtc::make_ref_counted( -- this, clock_, std::move(frame_transformer), rtc::Thread::Current(), -+ this, clock_, std::move(frame_transformer), TaskQueueBase::Current(), - config_.rtp.remote_ssrc); - frame_transformer_delegate_->Init(); + namespace webrtc { +@@ -274,6 +275,14 @@ void RTPSenderVideoFrameTransformerDelegate::Reset() { + MutexLock lock(&sender_lock_); + sender_ = nullptr; } ++ // Wait until all pending tasks are executed, to ensure that the last ref ++ // standing is not on the transformation queue. ++ rtc::Event flush; ++ transformation_queue_->PostTask([this, &flush]() { ++ RTC_DCHECK_RUN_ON(transformation_queue_.get()); ++ flush.Set(); ++ }); ++ flush.Wait(rtc::Event::kForever); + } + + std::unique_ptr CloneSenderVideoFrame( diff --git a/third_party/libwebrtc/moz-patch-stack/0085.patch b/third_party/libwebrtc/moz-patch-stack/0085.patch index 47a6830ef207..364a0dee88fa 100644 --- a/third_party/libwebrtc/moz-patch-stack/0085.patch +++ b/third_party/libwebrtc/moz-patch-stack/0085.patch @@ -1,165 +1,38 @@ -From: Michael Froman -Date: Thu, 27 Jul 2023 12:42:44 -0500 -Subject: Bug 1838080: Store the rid in TransformableVideoSenderFrame. - r=ng,webrtc-reviewers +From: stransky +Date: Tue, 29 Aug 2023 12:43:00 +0000 +Subject: Bug 1821629 [DMABuf] Don't use DMABuf if it's disabled by Firefox gfx + config r=ng,webrtc-reviewers -This is necessary to reliably detect what rid a given keyframe is for, for the -purposes of resolving promises from RTCRtpScriptTransformer.generateKeyFrame. - -Differential Revision: https://phabricator.services.mozilla.com/D180737 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/2f1a0ba74bf71cfa0bc4e77714b8a5276a70cc36 +Differential Revision: https://phabricator.services.mozilla.com/D172224 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/158a888cad8869a2f0026fa7cfaaa13ecbfcf2ed --- - api/frame_transformer_interface.h | 1 + - modules/rtp_rtcp/source/rtp_sender.h | 4 ++++ - modules/rtp_rtcp/source/rtp_sender_video.cc | 1 + - ...rtp_sender_video_frame_transformer_delegate.cc | 15 +++++++++++---- - .../rtp_sender_video_frame_transformer_delegate.h | 2 ++ - ..._stream_receiver_frame_transformer_delegate.cc | 5 +++++ - 6 files changed, 24 insertions(+), 4 deletions(-) + .../linux/wayland/shared_screencast_stream.cc | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) -diff --git a/api/frame_transformer_interface.h b/api/frame_transformer_interface.h -index 6f632bcb72..c606328e2b 100644 ---- a/api/frame_transformer_interface.h -+++ b/api/frame_transformer_interface.h -@@ -77,6 +77,7 @@ class TransformableVideoFrameInterface : public TransformableFrameInterface { - RTC_EXPORT explicit TransformableVideoFrameInterface(Passkey passkey); - virtual ~TransformableVideoFrameInterface() = default; - virtual bool IsKeyFrame() const = 0; -+ virtual const std::string& GetRid() const = 0; +diff --git a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc +index 047ad5a857..61fe3a8137 100644 +--- a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc ++++ b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc +@@ -27,6 +27,13 @@ + #include "rtc_base/synchronization/mutex.h" + #include "rtc_base/time_utils.h" - virtual VideoFrameMetadata Metadata() const = 0; - -diff --git a/modules/rtp_rtcp/source/rtp_sender.h b/modules/rtp_rtcp/source/rtp_sender.h -index a398f16d46..8136730e4c 100644 ---- a/modules/rtp_rtcp/source/rtp_sender.h -+++ b/modules/rtp_rtcp/source/rtp_sender.h -@@ -140,6 +140,10 @@ class RTPSender { - - uint32_t SSRC() const RTC_LOCKS_EXCLUDED(send_mutex_) { return ssrc_; } - -+ const std::string& Rid() const RTC_LOCKS_EXCLUDED(send_mutex_) { -+ return rid_; -+ } ++// Wrapper for gfxVars::UseDMABuf() as we can't include gfxVars here. ++// We don't want to use dmabuf of known broken systems. ++// See FEATURE_DMABUF for details. ++namespace mozilla::gfx { ++bool IsDMABufEnabled(); ++} + - absl::optional FlexfecSsrc() const RTC_LOCKS_EXCLUDED(send_mutex_) { - return flexfec_ssrc_; - } -diff --git a/modules/rtp_rtcp/source/rtp_sender_video.cc b/modules/rtp_rtcp/source/rtp_sender_video.cc -index 2ca5e4bb1e..1d8f29c73b 100644 ---- a/modules/rtp_rtcp/source/rtp_sender_video.cc -+++ b/modules/rtp_rtcp/source/rtp_sender_video.cc -@@ -156,6 +156,7 @@ RTPSenderVideo::RTPSenderVideo(const Config& config) - this, - config.frame_transformer, - rtp_sender_->SSRC(), -+ rtp_sender_->Rid(), - config.task_queue_factory) - : nullptr), - enable_av1_even_split_( -diff --git a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc -index 8c24160f38..b49a6fead5 100644 ---- a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc -+++ b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc -@@ -55,7 +55,8 @@ class TransformableVideoSenderFrame : public TransformableVideoFrameInterface { - uint32_t rtp_timestamp, - TimeDelta expected_retransmission_time, - uint32_t ssrc, -- std::vector csrcs) -+ std::vector csrcs, -+ const std::string& rid) - : TransformableVideoFrameInterface(Passkey()), - encoded_data_(encoded_image.GetEncodedData()), - pre_transform_payload_size_(encoded_image.size()), -@@ -68,7 +69,8 @@ class TransformableVideoSenderFrame : public TransformableVideoFrameInterface { - capture_time_identifier_(encoded_image.CaptureTimeIdentifier()), - expected_retransmission_time_(expected_retransmission_time), - ssrc_(ssrc), -- csrcs_(csrcs) { -+ csrcs_(csrcs), -+ rid_(rid) { - RTC_DCHECK_GE(payload_type_, 0); - RTC_DCHECK_LE(payload_type_, 127); - } -@@ -131,6 +133,8 @@ class TransformableVideoSenderFrame : public TransformableVideoFrameInterface { - return mime_type + CodecTypeToPayloadString(*codec_type_); - } + namespace webrtc { -+ const std::string& GetRid() const override { return rid_; } -+ - private: - rtc::scoped_refptr encoded_data_; - const size_t pre_transform_payload_size_; -@@ -145,16 +149,19 @@ class TransformableVideoSenderFrame : public TransformableVideoFrameInterface { + const int kBytesPerPixel = 4; +@@ -268,7 +275,7 @@ void SharedScreenCastStreamPrivate::OnStreamParamChanged( + that->modifier_ = + has_modifier ? that->spa_video_format_.modifier : DRM_FORMAT_MOD_INVALID; + std::vector params; +- const int buffer_types = has_modifier ++ const int buffer_types = has_modifier && mozilla::gfx::IsDMABufEnabled() + ? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) + : (1 << SPA_DATA_MemFd); - uint32_t ssrc_; - std::vector csrcs_; -+ const std::string rid_; - }; - - RTPSenderVideoFrameTransformerDelegate::RTPSenderVideoFrameTransformerDelegate( - RTPVideoFrameSenderInterface* sender, - rtc::scoped_refptr frame_transformer, - uint32_t ssrc, -+ const std::string& rid, - TaskQueueFactory* task_queue_factory) - : sender_(sender), - frame_transformer_(std::move(frame_transformer)), - ssrc_(ssrc), -+ rid_(rid), - transformation_queue_(task_queue_factory->CreateTaskQueue( - "video_frame_transformer", - TaskQueueFactory::Priority::NORMAL)) {} -@@ -185,7 +192,7 @@ bool RTPSenderVideoFrameTransformerDelegate::TransformFrame( - frame_transformer_->Transform(std::make_unique( - encoded_image, video_header, payload_type, codec_type, rtp_timestamp, - expected_retransmission_time, ssrc_, -- /*csrcs=*/std::vector())); -+ /*csrcs=*/std::vector(), rid_)); - return true; - } - -@@ -287,7 +294,7 @@ std::unique_ptr CloneSenderVideoFrame( - return std::make_unique( - encoded_image, new_header, original->GetPayloadType(), new_header.codec, - original->GetTimestamp(), kDefaultRetransmissionsTime, -- original->GetSsrc(), metadata.GetCsrcs()); -+ original->GetSsrc(), metadata.GetCsrcs(), original->GetRid()); - } - - } // namespace webrtc -diff --git a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h -index a32c7084ea..ef6f128899 100644 ---- a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h -+++ b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.h -@@ -60,6 +60,7 @@ class RTPSenderVideoFrameTransformerDelegate : public TransformedFrameCallback { - RTPVideoFrameSenderInterface* sender, - rtc::scoped_refptr frame_transformer, - uint32_t ssrc, -+ const std::string& rid, - TaskQueueFactory* send_transport_queue); - - void Init(); -@@ -108,6 +109,7 @@ class RTPSenderVideoFrameTransformerDelegate : public TransformedFrameCallback { - RTPVideoFrameSenderInterface* sender_ RTC_GUARDED_BY(sender_lock_); - rtc::scoped_refptr frame_transformer_; - const uint32_t ssrc_; -+ const std::string rid_; - // Used when the encoded frames arrives without a current task queue. This can - // happen if a hardware encoder was used. - std::unique_ptr transformation_queue_; -diff --git a/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc b/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc -index de74684a1f..7e0754284a 100644 ---- a/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc -+++ b/modules/rtp_rtcp/source/rtp_video_stream_receiver_frame_transformer_delegate.cc -@@ -59,6 +59,11 @@ class TransformableVideoReceiverFrame - return frame_->FrameType() == VideoFrameType::kVideoFrameKey; - } - -+ const std::string& GetRid() const override { -+ static const std::string empty; -+ return empty; -+ } -+ - VideoFrameMetadata Metadata() const override { return metadata_; } - - void SetMetadata(const VideoFrameMetadata& metadata) override { diff --git a/third_party/libwebrtc/moz-patch-stack/0086.patch b/third_party/libwebrtc/moz-patch-stack/0086.patch index ca6a2d482f24..421b47ef4d01 100644 --- a/third_party/libwebrtc/moz-patch-stack/0086.patch +++ b/third_party/libwebrtc/moz-patch-stack/0086.patch @@ -1,38 +1,47 @@ -From: Byron Campen -Date: Thu, 20 Jul 2023 14:24:00 +0000 -Subject: Bug 1838080: Ensure that last ref to transformation_queue_ is not - released on itself. r=pehrsons,webrtc-reviewers +From: stransky +Date: Tue, 29 Aug 2023 12:43:00 +0000 +Subject: Bug 1821629 [Pipewire/DMABuf] Don't create dmabuf backend if it's + disabled r=ng,webrtc-reviewers -Differential Revision: https://phabricator.services.mozilla.com/D181699 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/91d9e8b6a5c430a73561ffd2330865f04fcb1a6d +Depends on D172224 + +Differential Revision: https://phabricator.services.mozilla.com/D172229 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/237d19fe96dd7d25b6a817415ee4e6854678d648 --- - .../rtp_sender_video_frame_transformer_delegate.cc | 9 +++++++++ - 1 file changed, 9 insertions(+) + .../linux/wayland/shared_screencast_stream.cc | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) -diff --git a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc -index b49a6fead5..71777069f7 100644 ---- a/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc -+++ b/modules/rtp_rtcp/source/rtp_sender_video_frame_transformer_delegate.cc -@@ -34,6 +34,7 @@ - #include "api/video_codecs/video_codec.h" - #include "modules/rtp_rtcp/source/rtp_video_header.h" - #include "rtc_base/checks.h" -+#include "rtc_base/event.h" - #include "rtc_base/synchronization/mutex.h" - - namespace webrtc { -@@ -274,6 +275,14 @@ void RTPSenderVideoFrameTransformerDelegate::Reset() { - MutexLock lock(&sender_lock_); - sender_ = nullptr; +diff --git a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc +index 61fe3a8137..b8cac318ff 100644 +--- a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc ++++ b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc +@@ -406,7 +406,9 @@ bool SharedScreenCastStreamPrivate::StartScreenCastStream( + RTC_LOG(LS_ERROR) << "Unable to open PipeWire library"; + return false; + } +- egl_dmabuf_ = std::make_unique(); ++ if (mozilla::gfx::IsDMABufEnabled()) { ++ egl_dmabuf_ = std::make_unique(); ++ } + + pw_stream_node_id_ = stream_node_id; + +@@ -495,7 +497,8 @@ bool SharedScreenCastStreamPrivate::StartScreenCastStream( + for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, + SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) { + // Modifiers can be used with PipeWire >= 0.3.33 +- if (has_required_pw_client_version && has_required_pw_server_version) { ++ if (egl_dmabuf_ && ++ has_required_pw_client_version && has_required_pw_server_version) { + modifiers_ = egl_dmabuf_->QueryDmaBufModifiers(format); + + if (!modifiers_.empty()) { +@@ -938,7 +941,7 @@ bool SharedScreenCastStreamPrivate::ProcessDMABuffer( + + const uint n_planes = spa_buffer->n_datas; + +- if (!n_planes) { ++ if (!n_planes || !egl_dmabuf_) { + return false; } -+ // Wait until all pending tasks are executed, to ensure that the last ref -+ // standing is not on the transformation queue. -+ rtc::Event flush; -+ transformation_queue_->PostTask([this, &flush]() { -+ RTC_DCHECK_RUN_ON(transformation_queue_.get()); -+ flush.Set(); -+ }); -+ flush.Wait(rtc::Event::kForever); - } - std::unique_ptr CloneSenderVideoFrame( diff --git a/third_party/libwebrtc/moz-patch-stack/0087.patch b/third_party/libwebrtc/moz-patch-stack/0087.patch index 96a428b22892..a5098babea44 100644 --- a/third_party/libwebrtc/moz-patch-stack/0087.patch +++ b/third_party/libwebrtc/moz-patch-stack/0087.patch @@ -1,38 +1,56 @@ -From: stransky -Date: Tue, 29 Aug 2023 12:43:00 +0000 -Subject: Bug 1821629 [DMABuf] Don't use DMABuf if it's disabled by Firefox gfx - config r=ng,webrtc-reviewers +From: Michael Froman +Date: Thu, 28 Sep 2023 14:12:00 +0000 +Subject: Bug 1832465 - remove libXtst usage from libwebrtc. + r=ng,webrtc-reviewers -Differential Revision: https://phabricator.services.mozilla.com/D172224 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/158a888cad8869a2f0026fa7cfaaa13ecbfcf2ed +Differential Revision: https://phabricator.services.mozilla.com/D189386 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/0ec1b33b95dbb2d39355f28b2812fe25b4ad9f20 --- - .../linux/wayland/shared_screencast_stream.cc | 9 ++++++++- - 1 file changed, 8 insertions(+), 1 deletion(-) + modules/desktop_capture/BUILD.gn | 3 +++ + modules/desktop_capture/linux/x11/shared_x_display.cc | 4 ++++ + 2 files changed, 7 insertions(+) -diff --git a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc -index 90273aba3e..134a265974 100644 ---- a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc -+++ b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc -@@ -27,6 +27,13 @@ - #include "rtc_base/synchronization/mutex.h" - #include "rtc_base/time_utils.h" +diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn +index 9b339c5ae0..d9276a97f5 100644 +--- a/modules/desktop_capture/BUILD.gn ++++ b/modules/desktop_capture/BUILD.gn +@@ -388,6 +388,9 @@ rtc_library("desktop_capture") { + "Xrandr", + "Xtst", + ] ++ if (build_with_mozilla) { ++ libs -= [ "Xtst" ] ++ } + } -+// Wrapper for gfxVars::UseDMABuf() as we can't include gfxVars here. -+// We don't want to use dmabuf of known broken systems. -+// See FEATURE_DMABUF for details. -+namespace mozilla::gfx { -+bool IsDMABufEnabled(); -+} -+ - namespace webrtc { + if (!is_win && !is_mac && !rtc_use_x11_extensions && !rtc_use_pipewire && +diff --git a/modules/desktop_capture/linux/x11/shared_x_display.cc b/modules/desktop_capture/linux/x11/shared_x_display.cc +index d690b0e2ba..3f3617b074 100644 +--- a/modules/desktop_capture/linux/x11/shared_x_display.cc ++++ b/modules/desktop_capture/linux/x11/shared_x_display.cc +@@ -11,7 +11,9 @@ + #include "modules/desktop_capture/linux/x11/shared_x_display.h" - const int kBytesPerPixel = 4; -@@ -268,7 +275,7 @@ void SharedScreenCastStreamPrivate::OnStreamParamChanged( - that->modifier_ = - has_modifier ? that->spa_video_format_.modifier : DRM_FORMAT_MOD_INVALID; - std::vector params; -- const int buffer_types = has_modifier -+ const int buffer_types = has_modifier && mozilla::gfx::IsDMABufEnabled() - ? (1 << SPA_DATA_DmaBuf) | (1 << SPA_DATA_MemFd) - : (1 << SPA_DATA_MemFd); + #include ++#if !defined(WEBRTC_MOZILLA_BUILD) + #include ++#endif + #include + +@@ -95,6 +97,7 @@ void SharedXDisplay::ProcessPendingXEvents() { + } + + void SharedXDisplay::IgnoreXServerGrabs() { ++#if !defined(WEBRTC_MOZILLA_BUILD) + int test_event_base = 0; + int test_error_base = 0; + int major = 0; +@@ -103,6 +106,7 @@ void SharedXDisplay::IgnoreXServerGrabs() { + &minor)) { + XTestGrabControl(display(), true); + } ++#endif + } + + } // namespace webrtc diff --git a/third_party/libwebrtc/moz-patch-stack/0088.patch b/third_party/libwebrtc/moz-patch-stack/0088.patch index e701009102f7..3fcbcd0f9449 100644 --- a/third_party/libwebrtc/moz-patch-stack/0088.patch +++ b/third_party/libwebrtc/moz-patch-stack/0088.patch @@ -1,47 +1,30 @@ -From: stransky -Date: Tue, 29 Aug 2023 12:43:00 +0000 -Subject: Bug 1821629 [Pipewire/DMABuf] Don't create dmabuf backend if it's - disabled r=ng,webrtc-reviewers +From: Michael Froman +Date: Thu, 5 Oct 2023 14:21:00 +0000 +Subject: Bug 1857037 - pt1 - add shim gni files to limit BUILD.gn changes. + r=ng,webrtc-reviewers -Depends on D172224 - -Differential Revision: https://phabricator.services.mozilla.com/D172229 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/237d19fe96dd7d25b6a817415ee4e6854678d648 +Differential Revision: https://phabricator.services.mozilla.com/D190104 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/a84d39db037cbe34aa19588b0d18335eb5e2d79b --- - .../linux/wayland/shared_screencast_stream.cc | 9 ++++++--- - 1 file changed, 6 insertions(+), 3 deletions(-) + testing/libfuzzer/fuzzer_test.gni | 2 ++ + testing/test.gni | 2 ++ + 2 files changed, 4 insertions(+) + create mode 100644 testing/libfuzzer/fuzzer_test.gni + create mode 100644 testing/test.gni -diff --git a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc -index 134a265974..ab9054f1a1 100644 ---- a/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc -+++ b/modules/desktop_capture/linux/wayland/shared_screencast_stream.cc -@@ -406,7 +406,9 @@ bool SharedScreenCastStreamPrivate::StartScreenCastStream( - RTC_LOG(LS_ERROR) << "Unable to open PipeWire library"; - return false; - } -- egl_dmabuf_ = std::make_unique(); -+ if (mozilla::gfx::IsDMABufEnabled()) { -+ egl_dmabuf_ = std::make_unique(); -+ } - - pw_stream_node_id_ = stream_node_id; - -@@ -495,7 +497,8 @@ bool SharedScreenCastStreamPrivate::StartScreenCastStream( - for (uint32_t format : {SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, - SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx}) { - // Modifiers can be used with PipeWire >= 0.3.33 -- if (has_required_pw_client_version && has_required_pw_server_version) { -+ if (egl_dmabuf_ && -+ has_required_pw_client_version && has_required_pw_server_version) { - modifiers_ = egl_dmabuf_->QueryDmaBufModifiers(format); - - if (!modifiers_.empty()) { -@@ -932,7 +935,7 @@ bool SharedScreenCastStreamPrivate::ProcessDMABuffer( - - const uint n_planes = spa_buffer->n_datas; - -- if (!n_planes) { -+ if (!n_planes || !egl_dmabuf_) { - return false; - } - +diff --git a/testing/libfuzzer/fuzzer_test.gni b/testing/libfuzzer/fuzzer_test.gni +new file mode 100644 +index 0000000000..8fdf3cdad2 +--- /dev/null ++++ b/testing/libfuzzer/fuzzer_test.gni +@@ -0,0 +1,2 @@ ++# "empty" file in place of importing new testing/libfuzzer ++# to allow BUILD.gn imports to succeed. +diff --git a/testing/test.gni b/testing/test.gni +new file mode 100644 +index 0000000000..f46fa82778 +--- /dev/null ++++ b/testing/test.gni +@@ -0,0 +1,2 @@ ++# "empty" file in place of importing new testing/test.gni ++# to allow BUILD.gn imports to succeed. diff --git a/third_party/libwebrtc/moz-patch-stack/0089.patch b/third_party/libwebrtc/moz-patch-stack/0089.patch index a383f0859ee5..b0cdd2d54086 100644 --- a/third_party/libwebrtc/moz-patch-stack/0089.patch +++ b/third_party/libwebrtc/moz-patch-stack/0089.patch @@ -1,56 +1,40 @@ From: Michael Froman -Date: Thu, 28 Sep 2023 14:12:00 +0000 -Subject: Bug 1832465 - remove libXtst usage from libwebrtc. - r=ng,webrtc-reviewers +Date: Tue, 14 Feb 2023 03:27:00 +0000 +Subject: Bug 1816173 - pt12 - add shim config for + third_party/libwebrtc/testing/{gmock|gtest} r=ng -Differential Revision: https://phabricator.services.mozilla.com/D189386 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/0ec1b33b95dbb2d39355f28b2812fe25b4ad9f20 +We don't vendor third_party/libwebrtc/third_party/gmock + third_party/libwebrtc/third_party/gtest, so: +- add BUILD.gn to avoid scattered BUILD.gn changes + +Differential Revision: https://phabricator.services.mozilla.com/D169674 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/4ea9d2db79c42a144235e45c47c71adf1dd01fdc --- - modules/desktop_capture/BUILD.gn | 3 +++ - modules/desktop_capture/linux/x11/shared_x_display.cc | 4 ++++ - 2 files changed, 7 insertions(+) + testing/gmock/BUILD.gn | 5 +++++ + testing/gtest/BUILD.gn | 5 +++++ + 2 files changed, 10 insertions(+) + create mode 100644 testing/gmock/BUILD.gn + create mode 100644 testing/gtest/BUILD.gn -diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn -index 23c66aec0a..4fb4db0907 100644 ---- a/modules/desktop_capture/BUILD.gn -+++ b/modules/desktop_capture/BUILD.gn -@@ -389,6 +389,9 @@ rtc_library("desktop_capture") { - "Xrandr", - "Xtst", - ] -+ if (build_with_mozilla) { -+ libs -= [ "Xtst" ] -+ } - } - - if (!is_win && !is_mac && !rtc_use_x11_extensions && !rtc_use_pipewire && -diff --git a/modules/desktop_capture/linux/x11/shared_x_display.cc b/modules/desktop_capture/linux/x11/shared_x_display.cc -index d690b0e2ba..3f3617b074 100644 ---- a/modules/desktop_capture/linux/x11/shared_x_display.cc -+++ b/modules/desktop_capture/linux/x11/shared_x_display.cc -@@ -11,7 +11,9 @@ - #include "modules/desktop_capture/linux/x11/shared_x_display.h" - - #include -+#if !defined(WEBRTC_MOZILLA_BUILD) - #include -+#endif - - #include - -@@ -95,6 +97,7 @@ void SharedXDisplay::ProcessPendingXEvents() { - } - - void SharedXDisplay::IgnoreXServerGrabs() { -+#if !defined(WEBRTC_MOZILLA_BUILD) - int test_event_base = 0; - int test_error_base = 0; - int major = 0; -@@ -103,6 +106,7 @@ void SharedXDisplay::IgnoreXServerGrabs() { - &minor)) { - XTestGrabControl(display(), true); - } -+#endif - } - - } // namespace webrtc +diff --git a/testing/gmock/BUILD.gn b/testing/gmock/BUILD.gn +new file mode 100644 +index 0000000000..a2a1efdea9 +--- /dev/null ++++ b/testing/gmock/BUILD.gn +@@ -0,0 +1,5 @@ ++import("//third_party/libaom/options.gni") ++import("../../webrtc.gni") ++ ++rtc_library("gmock") { ++} +diff --git a/testing/gtest/BUILD.gn b/testing/gtest/BUILD.gn +new file mode 100644 +index 0000000000..c9c2703c37 +--- /dev/null ++++ b/testing/gtest/BUILD.gn +@@ -0,0 +1,5 @@ ++import("//third_party/libaom/options.gni") ++import("../../webrtc.gni") ++ ++rtc_library("gtest") { ++} diff --git a/third_party/libwebrtc/moz-patch-stack/0090.patch b/third_party/libwebrtc/moz-patch-stack/0090.patch index 3fcbcd0f9449..36e7778d1a1e 100644 --- a/third_party/libwebrtc/moz-patch-stack/0090.patch +++ b/third_party/libwebrtc/moz-patch-stack/0090.patch @@ -1,30 +1,28 @@ -From: Michael Froman -Date: Thu, 5 Oct 2023 14:21:00 +0000 -Subject: Bug 1857037 - pt1 - add shim gni files to limit BUILD.gn changes. - r=ng,webrtc-reviewers +From: Andreas Pehrson +Date: Wed, 18 Oct 2023 17:25:00 +0000 +Subject: Bug 1857862 - (fix-32a8169a65) Don't call non-constexpr + RTC_CHECK_NOTREACHED from constexpr VideoFrameTypeToString under gcc-8. + r=webrtc-reviewers,mjf -Differential Revision: https://phabricator.services.mozilla.com/D190104 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/a84d39db037cbe34aa19588b0d18335eb5e2d79b +Differential Revision: https://phabricator.services.mozilla.com/D191308 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/8a4449ba24fa3192b44863ed8ba96f6f94a6e88d --- - testing/libfuzzer/fuzzer_test.gni | 2 ++ - testing/test.gni | 2 ++ - 2 files changed, 4 insertions(+) - create mode 100644 testing/libfuzzer/fuzzer_test.gni - create mode 100644 testing/test.gni + api/video/video_frame_type.h | 4 ++++ + 1 file changed, 4 insertions(+) -diff --git a/testing/libfuzzer/fuzzer_test.gni b/testing/libfuzzer/fuzzer_test.gni -new file mode 100644 -index 0000000000..8fdf3cdad2 ---- /dev/null -+++ b/testing/libfuzzer/fuzzer_test.gni -@@ -0,0 +1,2 @@ -+# "empty" file in place of importing new testing/libfuzzer -+# to allow BUILD.gn imports to succeed. -diff --git a/testing/test.gni b/testing/test.gni -new file mode 100644 -index 0000000000..f46fa82778 ---- /dev/null -+++ b/testing/test.gni -@@ -0,0 +1,2 @@ -+# "empty" file in place of importing new testing/test.gni -+# to allow BUILD.gn imports to succeed. +diff --git a/api/video/video_frame_type.h b/api/video/video_frame_type.h +index 9079829ff8..3665a80cd8 100644 +--- a/api/video/video_frame_type.h ++++ b/api/video/video_frame_type.h +@@ -34,7 +34,11 @@ inline constexpr absl::string_view VideoFrameTypeToString( + case VideoFrameType::kVideoFrameDelta: + return "delta"; + } ++// Mozilla: ++// gcc-8 complains about a constexpr function calling a non-constexpr ditto. ++#if defined(__clang__) || (defined(__GNUC__) && __GNUC__ >= 9) + RTC_CHECK_NOTREACHED(); ++#endif + return ""; + } + diff --git a/third_party/libwebrtc/moz-patch-stack/0091.patch b/third_party/libwebrtc/moz-patch-stack/0091.patch index b0cdd2d54086..989e8f8d73f4 100644 --- a/third_party/libwebrtc/moz-patch-stack/0091.patch +++ b/third_party/libwebrtc/moz-patch-stack/0091.patch @@ -1,40 +1,41 @@ -From: Michael Froman -Date: Tue, 14 Feb 2023 03:27:00 +0000 -Subject: Bug 1816173 - pt12 - add shim config for - third_party/libwebrtc/testing/{gmock|gtest} r=ng +From: Andreas Pehrson +Date: Wed, 18 Oct 2023 17:21:00 +0000 +Subject: Bug 1859786 - Fix lock annotation warning in Mozilla-specific edit on + top of video_capture_impl.cc. r=webrtc-reviewers,mjf -We don't vendor third_party/libwebrtc/third_party/gmock - third_party/libwebrtc/third_party/gtest, so: -- add BUILD.gn to avoid scattered BUILD.gn changes +The annotations were added in M116: +https://hg.mozilla.org/mozilla-central/rev/9cd372df013948ad822ae936752d725d77474fb5 -Differential Revision: https://phabricator.services.mozilla.com/D169674 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/4ea9d2db79c42a144235e45c47c71adf1dd01fdc +Note that this was never unsafe, since _dataCallbacks is only written on the +same thread that we are patching here. This patch however, adds helpful static +analysis. + +Differential Revision: https://phabricator.services.mozilla.com/D191301 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/56ff441b644400f09d2d0453dbd8991ea25db7b1 --- - testing/gmock/BUILD.gn | 5 +++++ - testing/gtest/BUILD.gn | 5 +++++ - 2 files changed, 10 insertions(+) - create mode 100644 testing/gmock/BUILD.gn - create mode 100644 testing/gtest/BUILD.gn + modules/video_capture/video_capture_impl.cc | 11 +++++++---- + 1 file changed, 7 insertions(+), 4 deletions(-) -diff --git a/testing/gmock/BUILD.gn b/testing/gmock/BUILD.gn -new file mode 100644 -index 0000000000..a2a1efdea9 ---- /dev/null -+++ b/testing/gmock/BUILD.gn -@@ -0,0 +1,5 @@ -+import("//third_party/libaom/options.gni") -+import("../../webrtc.gni") -+ -+rtc_library("gmock") { -+} -diff --git a/testing/gtest/BUILD.gn b/testing/gtest/BUILD.gn -new file mode 100644 -index 0000000000..c9c2703c37 ---- /dev/null -+++ b/testing/gtest/BUILD.gn -@@ -0,0 +1,5 @@ -+import("//third_party/libaom/options.gni") -+import("../../webrtc.gni") -+ -+rtc_library("gtest") { -+} +diff --git a/modules/video_capture/video_capture_impl.cc b/modules/video_capture/video_capture_impl.cc +index 1a12020b64..dad8bee1f8 100644 +--- a/modules/video_capture/video_capture_impl.cc ++++ b/modules/video_capture/video_capture_impl.cc +@@ -119,11 +119,14 @@ void VideoCaptureImpl::DeRegisterCaptureDataCallback( + } + + int32_t VideoCaptureImpl::StopCaptureIfAllClientsClose() { +- if (_dataCallBacks.empty()) { +- return StopCapture(); +- } else { +- return 0; ++ RTC_DCHECK_RUN_ON(&api_checker_); ++ { ++ MutexLock lock(&api_lock_); ++ if (!_dataCallBacks.empty()) { ++ return 0; ++ } + } ++ return StopCapture(); + } + + int32_t VideoCaptureImpl::DeliverCapturedFrame(VideoFrame& captureFrame) { diff --git a/third_party/libwebrtc/moz-patch-stack/0092.patch b/third_party/libwebrtc/moz-patch-stack/0092.patch index 36e7778d1a1e..daf4f0a16f34 100644 --- a/third_party/libwebrtc/moz-patch-stack/0092.patch +++ b/third_party/libwebrtc/moz-patch-stack/0092.patch @@ -1,28 +1,27 @@ From: Andreas Pehrson -Date: Wed, 18 Oct 2023 17:25:00 +0000 -Subject: Bug 1857862 - (fix-32a8169a65) Don't call non-constexpr - RTC_CHECK_NOTREACHED from constexpr VideoFrameTypeToString under gcc-8. +Date: Wed, 18 Oct 2023 17:21:00 +0000 +Subject: Bug 1859786 - Fix clang-tidy warning in video_capture_impl.cc. r=webrtc-reviewers,mjf -Differential Revision: https://phabricator.services.mozilla.com/D191308 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/8a4449ba24fa3192b44863ed8ba96f6f94a6e88d ---- - api/video/video_frame_type.h | 4 ++++ - 1 file changed, 4 insertions(+) +clang-tidy says: + 'auto dataCallBack' can be declared as 'auto *dataCallBack' -diff --git a/api/video/video_frame_type.h b/api/video/video_frame_type.h -index 9079829ff8..3665a80cd8 100644 ---- a/api/video/video_frame_type.h -+++ b/api/video/video_frame_type.h -@@ -34,7 +34,11 @@ inline constexpr absl::string_view VideoFrameTypeToString( - case VideoFrameType::kVideoFrameDelta: - return "delta"; - } -+// Mozilla: -+// gcc-8 complains about a constexpr function calling a non-constexpr ditto. -+#if defined(__clang__) || (defined(__GNUC__) && __GNUC__ >= 9) - RTC_CHECK_NOTREACHED(); -+#endif - return ""; - } +Differential Revision: https://phabricator.services.mozilla.com/D191302 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/26c84d214137a1b0de0902c7038756964e5786f4 +--- + modules/video_capture/video_capture_impl.cc | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/modules/video_capture/video_capture_impl.cc b/modules/video_capture/video_capture_impl.cc +index dad8bee1f8..46fff89a52 100644 +--- a/modules/video_capture/video_capture_impl.cc ++++ b/modules/video_capture/video_capture_impl.cc +@@ -134,7 +134,7 @@ int32_t VideoCaptureImpl::DeliverCapturedFrame(VideoFrame& captureFrame) { + + UpdateFrameCount(); // frame count used for local frame rate callback. + +- for (auto dataCallBack : _dataCallBacks) { ++ for (auto* dataCallBack : _dataCallBacks) { + dataCallBack->OnFrame(captureFrame); + } diff --git a/third_party/libwebrtc/moz-patch-stack/0093.patch b/third_party/libwebrtc/moz-patch-stack/0093.patch index 989e8f8d73f4..1faafdf8cf61 100644 --- a/third_party/libwebrtc/moz-patch-stack/0093.patch +++ b/third_party/libwebrtc/moz-patch-stack/0093.patch @@ -1,41 +1,35362 @@ -From: Andreas Pehrson -Date: Wed, 18 Oct 2023 17:21:00 +0000 -Subject: Bug 1859786 - Fix lock annotation warning in Mozilla-specific edit on - top of video_capture_impl.cc. r=webrtc-reviewers,mjf +From: Nico Grunbaum +Date: Fri, 30 Apr 2021 21:51:00 +0000 +Subject: Bug 1654112 - Add grit dep for building webrtc on android; r=mjf -The annotations were added in M116: -https://hg.mozilla.org/mozilla-central/rev/9cd372df013948ad822ae936752d725d77474fb5 - -Note that this was never unsafe, since _dataCallbacks is only written on the -same thread that we are patching here. This patch however, adds helpful static -analysis. - -Differential Revision: https://phabricator.services.mozilla.com/D191301 -Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/56ff441b644400f09d2d0453dbd8991ea25db7b1 +Differential Revision: https://phabricator.services.mozilla.com/D114027 +Mercurial Revision: https://hg.mozilla.org/mozilla-central/rev/3cce5e6938f0df87bd9ab12a5f556aceb93dfa1d --- - modules/video_capture/video_capture_impl.cc | 11 +++++++---- - 1 file changed, 7 insertions(+), 4 deletions(-) + 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/modules/video_capture/video_capture_impl.cc b/modules/video_capture/video_capture_impl.cc -index 1a12020b64..dad8bee1f8 100644 ---- a/modules/video_capture/video_capture_impl.cc -+++ b/modules/video_capture/video_capture_impl.cc -@@ -119,11 +119,14 @@ void VideoCaptureImpl::DeRegisterCaptureDataCallback( - } - - int32_t VideoCaptureImpl::StopCaptureIfAllClientsClose() { -- if (_dataCallBacks.empty()) { -- return StopCapture(); -- } else { -- return 0; -+ RTC_DCHECK_RUN_ON(&api_checker_); -+ { -+ MutexLock lock(&api_lock_); -+ if (!_dataCallBacks.empty()) { +diff --git a/tools/grit/.gitignore b/tools/grit/.gitignore +new file mode 100644 +index 0000000000..0d20b6487c +--- /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 , 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 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''' ++ ++ ++ ++ ++ Hello %sJoi, how are you doing today? ++ ++ ++ ++ ++ ++ ++ ++'''), 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''' ++ ++ ++ ++ ++ ++ ++'''), 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 element is allowed for each 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 element.''' ++ pass ++ ++ ++class InvalidCharactersInsidePhNode(Base): ++ '''Invalid characters found inside 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 ++ ++ ... ++ ++ ... ++ ++ ++for a grd file with the following messages: ++ ++ Hello ++ world ++ ++and there existed an appropriate xtb file containing the Spanish translations, ++then the output would be: ++ ++ ++ ++ "Hola" ++ "mundo" ++ ++ ++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: ++ ++ Hello ++ ++To generate Android plurals (aka "quantity strings"), use the ICU plural syntax ++in the grd file. This will automatically be transformed into a element ++in the output xml file. For example: ++ ++ ++ {NUM_CATS, plural, ++ =1 {1 cat} ++ other {# cats}} ++ ++ ++ will produce ++ ++ ++ 1 Katze ++ %d Katzen ++ ++""" ++ ++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[A-Z0-9_]+)\Z') ++ ++# Most strings are output as a element. Note the double quotes ++# around the value to preserve whitespace. ++_STRING_TEMPLATE = u'"%s"\n' ++ ++# Some strings are output as a element. ++_PLURALS_TEMPLATE = '\n%s\n' ++_PLURALS_ITEM_TEMPLATE = ' %s\n' ++ ++# Matches e.g. "{HELLO, plural, HOW ARE YOU DOING}", while capturing ++# "HOW ARE YOU DOING" in . ++_PLURALS_PATTERN = lazy_re.compile(r'\{[A-Z_]+,\s*plural,(?P.*)\}$', ++ flags=re.S) ++ ++# Repeatedly matched against the capture in _PLURALS_PATTERN, ++# to match "{}". ++_PLURALS_ITEM_PATTERN = lazy_re.compile(r'(?P\S+?)\s*' ++ r'\{(?P.*?)\}') ++_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 ('\n' ++ '\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 '\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 element. ++ ++ 1. In a .grd file, we can write a plural string like this: ++ ++ ++ {NUM_THINGS, plural, ++ =1 {1 thing} ++ other {# things}} ++ ++ ++ 2. The Android equivalent looks like this: ++ ++ ++ 1 thing ++ %d things ++ ++ ++ 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 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 < > & 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""" ++ ++ ++ Martha ++ ++ sat and wondered ++ ++ out loud, "Why don't I build a flying car?" ++ ++ ++ She gathered ++wood, charcoal, and ++a sledge hammer. ++ ++ ++ ''' How old fashioned -- she thought. ''' ++ ++ ++ I'll buy a %d200 nm laser at %sthe grocery store. ++ ++ ++ {NUM_THINGS, plural, ++ =1 {Maybe I'll get one laser.} ++ other {Maybe I'll get # lasers.}} ++ ++ ++ {NUM_MISSISSIPPIS, plural, ++ =1{OneMississippi}other{ManyMississippis}} ++ ++ ++ """) ++ ++ buf = StringIO() ++ build.RcBuilder.ProcessNode(root, DummyOutput('android', 'en'), buf) ++ output = buf.getvalue() ++ expected = r""" ++ ++ ++"Martha" ++"sat and wondered" ++"out loud, \"Why don\'t I build a flying car?\"" ++"She gathered ++wood, charcoal, and ++a sledge hammer." ++" How old fashioned -- she thought. " ++"I\'ll buy a %d nm laser at %s." ++ ++ "Maybe I\'ll get one laser." ++ "Maybe I\'ll get %d lasers." ++ ++ ++ "OneMississippi" ++ "ManyMississippis" ++ ++ ++""" ++ self.assertEqual(output.strip(), expected.strip()) ++ ++ ++ def testConflictingPlurals(self): ++ root = util.ParseGrdForUnittest(r""" ++ ++ ++ {NUM_THINGS, plural, ++ =1 {Maybe I'll get one laser.} ++ one {Maybe I'll get one laser.} ++ other {Maybe I'll get # lasers.}} ++ ++ ++ """) ++ ++ buf = StringIO() ++ build.RcBuilder.ProcessNode(root, DummyOutput('android', 'en'), buf) ++ output = buf.getvalue() ++ expected = r""" ++ ++ ++ ++ "Maybe I\'ll get one laser." ++ "Maybe I\'ll get %d lasers." ++ ++ ++""" ++ self.assertEqual(output.strip(), expected.strip()) ++ ++ ++ def testTaggedOnly(self): ++ root = util.ParseGrdForUnittest(r""" ++ ++ ++ Hello ++ ++ ++ world ++ ++ ++ """) ++ ++ 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 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%s)(?P%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""" ++ ++ Do you want to play questions? ++ ++ "What's in a name, %sBrandon?" ++ ++ ++ Was that rhetoric? ++No. ++Statement. Two all. Game point. ++ ++ ++ \u00f5\\xc2\\xa4\\\u00a4\\\\xc3\\xb5\u4924 ++ ++ ++ """) ++ ++ 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""" ++ ++ ++ Simple message. ++ ++ ++ element\u2019s \u201c%sname\u201d attribute ++ ++ ++ %1$d1 error, %2$d1 warning ++ ++ ++ $1atest$2b ++ ++ ++ ''' (%d2) ++ ++ ++ (%d2) ''' ++ ++ ++ ''' (%d2) ''' ++ ++ ++ A "double quoted" message. ++ ++ ++ \\ ++ ++ ++ """) ++ ++ 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" ++ } + } - } -+ return StopCapture(); - } - - int32_t VideoCaptureImpl::DeliverCapturedFrame(VideoFrame& captureFrame) { ++ }, ++ "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(""" ++ ++ Hello! ++ Hello %s ++ Joi ++ ++ """) ++ ++ 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 = """ ++ ++ ++ ++ ++ ++ Hello not translated ++ ++ ++""" ++ 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""" ++ ++ ++ $1atest$2b ++ ++ ++ """) ++ ++ 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('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('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 ") ++ 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'' ++ b'') ++ ++ 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'' ++ b'') ++ ++ 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']*?expr=("(?P[^">]*)"|\'(?P[^\'>]*)\')[^>]*?>') ++ ++# Matches ending of an "if" block. ++_END_IF_BLOCK = lazy_re.compile(r'') ++ ++# Used by DoInline to replace various links with inline content. ++_STYLESHEET_RE = lazy_re.compile( ++ r']+?href="(?P[^"]*)".*?>(\s*)?', ++ re.DOTALL) ++_INCLUDE_RE = lazy_re.compile( ++ r'(?P\/\/ )?]+?' ++ r'src=("(?P[^">]*)"|\'(?P[^\'>]*)\').*?>(\s*)?', ++ re.DOTALL) ++_SRC_RE = lazy_re.compile( ++ r'<(?!script)(?:[^>]+?\s)src="(?!\[\[|{{)(?P[^"\']*)"', ++ re.MULTILINE) ++# This re matches ']*?\s)srcset="(?!\[\[|{{|\$i18n{)' ++ r'(?P[^"\']*)"', ++ 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[^,\s]\S+[^,\s])' ++ r'(?:\s+(?P[\deE.-]+[wx]))?\s*' ++ r'(?P,|$)', ++ re.MULTILINE) ++_ICON_RE = lazy_re.compile( ++ r']+?\s)?' ++ r'href=(?P")(?P[^"\']*)\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 and . ++ 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 ') ++ 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 ') ++ ++ 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, '%s', ++ 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"|\'|)(?P[^"\'()]*)' ++ 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'', ++ InlineScript, ++ flat_text) ++ ++ flat_text = _STYLESHEET_RE.sub( ++ lambda m: InlineCSSFile(m, ''), ++ 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 ++ ++ ++ ''' ++ ++ 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': ''' ++ ++ ++ ++ ++ ++ ++ ++ ++
++
++
++
++ ++ ++ ''', ++ ++ '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 = ''' ++ ++ ++ ++ ++ ++ ++ ++ ++
++
++
++
++ ++ ++ ''' ++ ++ 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': ''' ++ ++ ++ ++ ++ ++ ''', ++ ++ 'foo.css': '''''', ++ ++ 'style.css': ''' ++ ++ blink { ++ display: none; ++ } ++ ''', ++ 'style2.css': '''h1 {}''', ++ } ++ ++ expected_inlined = ''' ++ ++ ++ ++ ++ ++ ''' ++ ++ 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': ''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ''', ++ ++ 'test.css': ''' ++ ++ ''', ++ ++ 'test2.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': ''' ++ ++ ++ ++ ++ ++ ++ ''', ++ ++ 'foo.css': ''' ++ @import url(chrome://resources/blurp.css); ++ blink { ++ display: none; ++ } ++ ''', ++ } ++ ++ expected_inlined = ''' ++ ++ ++ ++ ++ ++ ++ ''' ++ ++ 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': ''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ''', ++ 'style1.css': '''h1 {}''', ++ 'tmpl1.html': '''

''', ++ 'script1.js': '''console.log('hello');''', ++ 'img1.png': '''abc''', ++ } ++ ++ expected_inlined = ''' ++ ++ ++ ++ ++ ++

++ ++ ++ ''' ++ ++ 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': ''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ''', ++ 'style1.css': '''h1 {}''', ++ 'style2.css': '''h2 {}''', ++ 'tmpl1.html': '''

''', ++ 'tmpl2.html': '''

''', ++ 'script1.js': '''console.log('hello');''', ++ 'img1.png': '''abc''', ++ } ++ ++ expected_inlined = ''' ++ ++ ++ ++ ++ ++ ++ ++

++

++

++ ++ ++ ++ ''' ++ ++ 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 works inside a comment.''' ++ ++ files = { ++ 'include.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 works inside a comment.''' ++ ++ files = { ++ 'if.js': ''' ++ // ++ yep(); ++ // ++ ++ // ++ nope(); ++ // ++ ''', ++ } ++ ++ 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': ''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ''', ++ '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 = ''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ''' ++ ++ 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 = ''' ++ ++ ++ ++ ++ ++ ++ ''' ++ ++ 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': ''' ++ ++ ++ ++ ++ ++ ++ ++ ''', ++ '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 = ''' ++ ++ ++ ++ ++ ++ ++ ++ ''' ++ ++ 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 macros.''' ++ ++ files = { ++ 'index.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 = ''' ++ ++ ++ ++ ++ ++ ''' ++ ++ 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 and only. ''' ++ ++ files = { ++ 'index.html': ''' ++ ++ ++ ++ ++ ++
++ ++
++ ++
++ ++ ++
++ ++
++    ++
++ ++
++
++ ''')) ++ html.Parse() ++ trans = html.Translate('en') ++ if (html.GetText() != trans): ++ self.fail() ++ ++ ++ def testHtmlToMessageWithBlockTags(self): ++ msg = tr_html.HtmlToMessage( ++ 'Hello

Howdiebingo', True) ++ result = msg.GetPresentableContent() ++ self.failUnless( ++ result == 'HelloBEGIN_PARAGRAPHHowdieBEGIN_BLOCKbingoEND_BLOCK') ++ ++ msg = tr_html.HtmlToMessage( ++ 'Hello

Howdie', 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 = '©  & "<hello>"' ++ unescaped = util.UnescapeHtml(text) ++ self.failUnless(unescaped == u'\u00a9\u00a0 & ""') ++ escaped_unescaped = util.EscapeHtml(unescaped, True) ++ self.failUnless(escaped_unescaped == ++ u'\u00a9\u00a0 & "<hello>"') ++ ++ 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( ++ '''''')) ++ 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 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 ") ++ if attrs: ++ raise exception.UnexpectedAttribute( ++ " 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 root node. Note that the first_ids_file ++ parameter should be relative to the cwd, even though the first_ids_file ++ attribute of the 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 ") ++ ++ 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''' ++ ++ ++ ++ ++ ++ ++ ++ ++ Hello %sJoi, how are you doing today? ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++''' ++ 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''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++''' ++ pseudo_file = StringIO(input) ++ tree = grd_reader.Parse(pseudo_file, '.', stop_after='outputs') ++ # only an child ++ self.failUnless(len(tree.children) == 1) ++ self.failUnless(tree.children[0].name == 'outputs') ++ ++ def testLongLinesWithComments(self): ++ input = u''' ++ ++ ++ ++ ++ This is a very long line with no linebreaks yes yes it stretches on and on and on! ++ ++ ++ ++''' ++ 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''' ++ ++ ++ ++ ++ test ++ ++ ++ ++''' % 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''' ++ ++ ++ ++ ++ test ++ ++ ++ ++ ++ test2 ++ ++ ++ ++''' ++ 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''' ++ ++ ++ ++ ++ ++ Hello! ++ ++ ++ ++ ++''' ++ 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''' ++ ++ ++ ++ ++ ++ ++ Hello! ++ ++ ++ ++ ++ Yellow! ++ ++ ++ ++ ++ ++''' ++ 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'''\ ++ ++ test5 ++ ''' ++ tmp_dir = util.TempDir({'arbitrary_path.grp': arbitrary_path_grd}) ++ arbitrary_path_grd_file = tmp_dir.GetPath('arbitrary_path.grp') ++ top_grd = u'''\ ++ ++ ++ ++ ++ test ++ ++ ++ ++ ++ ++ ''' % arbitrary_path_grd_file ++ sub_grd = u'''\ ++ ++ test2 ++ ++ test3 ++ ''' ++ subsub_grd = u'''\ ++ ++ test4 ++ ''' ++ expected_output = u'''\ ++ ++ ++ ++ ++ test ++ ++ ++ ++ test2 ++ ++ ++ ++ test4 ++ ++ ++ ++ test3 ++ ++ ++ ++ ++ test5 ++ ++ ++ ++ ++ ''' % 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''' ++ ++ ++ %s ++ ++ ''' ++ ++ part_failures = [ ++ (exception.UnexpectedContent, u'fnord'), ++ (exception.UnexpectedChild, ++ u''), ++ (exception.FileNotFound, u''), ++ ] ++ for raises, data in part_failures: ++ data = StringIO(template % data) ++ self.assertRaises(raises, grd_reader.Parse, data, '.') ++ ++ gritpart_failures = [ ++ (exception.UnexpectedAttribute, u''), ++ (exception.MissingElement, u''), ++ ] ++ for raises, data in gritpart_failures: ++ top_grd = StringIO(template % u'') ++ 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''' ++ ++ ++ ++ ++ foo ++ ++ ++ ++ boo ++ ++ ++ ++ ''' % 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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +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 .''' ++ 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'\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 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' % (self.name, attribs, inside_content, ++ self.name) ++ elif content_one_line: ++ return u'%s<%s%s>\n%s %s\n%s' % ( ++ indent, self.name, attribs, ++ indent, inside_content, ++ indent, self.name) ++ else: ++ return u'%s<%s%s>\n%s\n%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='', 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 element). ++ - 'context' is the current output context ++ (the 'context' attribute of the 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 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(" ') ++ ++ 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'\n Hello ' ++ u'<young> $1Joi' ++ u'\n') ++ ++ indented_xml = node.FormatXml(u' ') ++ self.failUnless(indented_xml == u' \n Hello ' ++ u'<young> $1Joi' ++ u'\n ') ++ ++ 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 ") ++ ++ 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"\n ''' Hello" ++ u' <young> $1Joi' ++ u" yessiree '''\n") ++ ++ indented_xml = node.FormatXml(u' ') ++ self.failUnless(indented_xml == ++ u" \n ''' Hello" ++ u' <young> $1Joi' ++ u" yessiree '''\n ") ++ ++ self.failUnless(node.GetNodeById('name')) ++ ++ def testXmlFormatContentWithEntities(self): ++ '''Tests a bug where   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', '', 'bla'), ++ tclib.Placeholder('WHITESPACE', ' ', 'bla'), ++ tclib.Placeholder('END_BOLD', '', 'bla')]), ++ 'BINGOBONGO') ++ xml = msg_node.FormatXml() ++ self.failUnless(xml.find(' ') == -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 = ''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Hello! ++ ++ ++ ''' ++ 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 (, , ++ and ).''' ++ def DefaultAttributes(self): ++ return { ++ 'first_id' : '', ++ 'comment' : '', ++ 'fallback_to_english' : 'false', ++ 'fallback_to_low_resolution' : 'false', ++ } ++ ++ ++class IncludesNode(GroupingNode): ++ '''The element.''' ++ def _IsValidChild(self, child): ++ return isinstance(child, (include.IncludeNode, misc.IfNode, misc.PartNode)) ++ ++ ++class MessagesNode(GroupingNode): ++ '''The element.''' ++ def _IsValidChild(self, child): ++ return isinstance(child, (message.MessageNode, misc.IfNode, misc.PartNode)) ++ ++ ++class StructuresNode(GroupingNode): ++ '''The element.''' ++ def _IsValidChild(self, child): ++ return isinstance(child, (structure.StructureNode, ++ misc.IfNode, misc.PartNode)) ++ ++ ++class TranslationsNode(base.Node): ++ '''The element.''' ++ def _IsValidChild(self, child): ++ return isinstance(child, (node_io.FileNode, misc.IfNode, misc.PartNode)) ++ ++ ++class OutputsNode(base.Node): ++ '''The element.''' ++ def _IsValidChild(self, child): ++ return isinstance(child, (node_io.OutputNode, misc.IfNode, misc.PartNode)) ++ ++ ++class IdentifiersNode(GroupingNode): ++ '''The 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 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 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 ++ and . ++ 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( ++ ''' ++ ++ ++ ''' % (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(''' ++ ++ ++ ++ ++ ''', 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( ++ ''' ++ ++ ++ ''', ++ 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 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*)(?P.+?)(?P\s*)\Z', ++ re.DOTALL | re.MULTILINE) ++ ++# placeholder elements should contain the special character formatters ++# used to format 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 () element. ++_FORMATTERS = lazy_re.compile( ++ '(%s)|(%s)|(%s)' % (_ANDROID_FORMAT, _CHROME_FORMAT, _OTHER_FORMAT)) ++_BAD_PLACEHOLDER_MSG = ('ERROR: Placeholder formatter found outside of ' ++ 'tag in message "%s" in %s.') ++_INVALID_PH_CHAR_MSG = ('ERROR: Invalid format characters found in message ' ++ '"%s" 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 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 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 element: fail if any formatters are detected. ++ if _FORMATTERS.search(item): ++ print(_BAD_PLACEHOLDER_MSG % (item, self.source)) ++ raise exception.PlaceholderNotInsidePhNode ++ text += item ++ else: ++ # Extract the element components. ++ presentation = item.attrs['name'].upper() ++ text += presentation ++ ex = ' ' # example element cdata if present. ++ if len(item.children): ++ ex = item.children[0].GetCdata() ++ original = item.GetCdata() ++ ++ # Sanity check the 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 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 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 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 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(''' ++ ++ ++ Hello %sJoi, how are you doing today? ++ ++ ''') ++ 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("""\ ++ ++ ++ ''' Hello there %s ''' ++ ++ """) ++ 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("""\ ++ ++ ++ Text ++ ++ """) ++ 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(''' ++ ++ ++ A...B.... %sA... B... C... ++ ++ ''') ++ 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''' ++ ++ ++ \uFEFFThis\uFEFF i\uFEFFs OK\uFEFF ++ ++ ''') ++ msg, = root.GetChildrenOfType(message.MessageNode) ++ content = msg.Translate('en') ++ self.failUnlessEqual(u'This is OK', content) ++ ++ def testPlaceholderHasTooManyExamples(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ Hi $1JoiJoy ++ ++ """) ++ except exception.TooManyExamples: ++ return ++ self.fail('Should have gotten exception') ++ ++ def testPlaceholderHasInvalidName(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ Hi $1 ++ ++ """) ++ except exception.InvalidPlaceholderName: ++ return ++ self.fail('Should have gotten exception') ++ ++ def testChromeLocalizedFormatIsInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ This message is missing the ph node: $1 ++ ++ """) ++ except exception.PlaceholderNotInsidePhNode: ++ return ++ self.fail('Should have gotten exception') ++ ++ def testAndroidStringFormatIsInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ This message is missing a ph node: %1$s ++ ++ """) ++ except exception.PlaceholderNotInsidePhNode: ++ return ++ self.fail('Should have gotten exception') ++ ++ def testAndroidIntegerFormatIsInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ This message is missing a ph node: %2$d ++ ++ """) ++ except exception.PlaceholderNotInsidePhNode: ++ return ++ self.fail('Should have gotten exception') ++ ++ def testAndroidIntegerWidthFormatIsInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ This message is missing a ph node: %2$3d ++ ++ """) ++ except exception.PlaceholderNotInsidePhNode: ++ return ++ self.fail('Should have gotten exception') ++ ++ def testValidAndroidIntegerWidthFormatInPhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ %2$3d042 ++ ++ """) ++ except: ++ self.fail('Should not have gotten exception') ++ ++ def testAndroidFloatFormatIsInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ This message is missing a ph node: %3$4.5f ++ ++ """) ++ except exception.PlaceholderNotInsidePhNode: ++ return ++ self.fail('Should have gotten exception') ++ ++ def testGritStringFormatIsInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ This message is missing the ph node: %s ++ ++ """) ++ except exception.PlaceholderNotInsidePhNode: ++ return ++ self.fail('Should have gotten exception') ++ ++ def testGritIntegerFormatIsInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ This message is missing the ph node: %d ++ ++ """) ++ except exception.PlaceholderNotInsidePhNode: ++ return ++ self.fail('Should have gotten exception') ++ ++ def testWindowsETWIntegerFormatIsInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ This message is missing the ph node: %1 ++ ++ """) ++ except exception.PlaceholderNotInsidePhNode: ++ return ++ self.fail('Should have gotten exception') ++ ++ def testValidMultipleFormattersInsidePhNodes(self): ++ root = util.ParseGrdForUnittest("""\ ++ ++ ++ %1$d1 error, %2$d1 warning ++ ++ """) ++ 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("""\ ++ ++ ++ %1$d error, %2$d warning ++ ++ """) ++ except exception.PlaceholderNotInsidePhNode: ++ failed = False ++ if failed: ++ self.fail('Should have gotten exception') ++ return ++ ++ failed = True ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ %1$d1 error, %2$d warning ++ ++ """) ++ except exception.PlaceholderNotInsidePhNode: ++ failed = False ++ if failed: ++ self.fail('Should have gotten exception') ++ return ++ ++ failed = True ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ %1$d %2$d ++ ++ """) ++ except exception.InvalidCharactersInsidePhNode: ++ failed = False ++ if failed: ++ self.fail('Should have gotten exception') ++ return ++ ++ def testValidHTMLFormatInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ <span>$1</span>1 ++ ++ """) ++ except: ++ self.fail('Should not have gotten exception') ++ ++ def testValidHTMLWithAttributesFormatInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ <span attribute="js:$this %">$2</span>2 ++ ++ """) ++ except: ++ self.fail('Should not have gotten exception') ++ ++ def testValidHTMLEntityFormatInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ >%1$d<1 ++ ++ """) ++ except: ++ self.fail('Should not have gotten exception') ++ ++ def testValidMultipleDollarFormatInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ $$1 ++ ++ """) ++ except: ++ self.fail('Should not have gotten exception') ++ ++ def testInvalidDollarCharacterInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ %1$d $ ++ ++ """) ++ except exception.InvalidCharactersInsidePhNode: ++ return ++ self.fail('Should have gotten exception') ++ ++ def testInvalidPercentCharacterInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ %1$d % ++ ++ """) ++ except exception.InvalidCharactersInsidePhNode: ++ return ++ self.fail('Should have gotten exception') ++ ++ def testInvalidMixedFormatCharactersInsidePhNode(self): ++ try: ++ util.ParseGrdForUnittest("""\ ++ ++ ++ %1$2 ++ ++ """) ++ 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 and 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( ++ ' element must be ......') ++ 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 with an empty ++ return super(IfNode, self).ActiveChildren() if cond else [] ++ ++ ++class ThenNode(SplicingNode): ++ """A node. Can only appear directly inside an node.""" ++ pass ++ ++ ++class ElseNode(SplicingNode): ++ """An node. Can only appear directly inside an 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 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 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 nodes. ++ """ ++ unique_names = {} ++ duplicate_names = [] ++ # To avoid false positives from mutually exclusive 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 nodes that are descendants of this node's ++ child and are not enclosed by unsatisfied 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 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 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 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 = ''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ''' % 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 = ''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ''' % 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(''' ++ ++ bar ++ ++ bar ++ ++ ''') ++ 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, ''' ++ ++ ++ ++ ++ ++ ++ Hello %sJoi, how are you doing today? ++ ++ Frubegfrums ++ ''') ++ ++ def testImplicitOverlapsPreexisting(self): ++ # second message in will overlap preexisting range ++ self.assertRaises(grit.exception.IdRangeOverlap, ++ util.ParseGrdForUnittest, ''' ++ ++ ++ ++ ++ ++ ++ Hello %sJoi, how are you doing today? ++ ++ Frubegfrums ++ ''') ++ ++ def testPredeterminedIds(self): ++ with _MakeTempPredeterminedIdsFile('IDS_A 101\nIDS_B 102') as ids_file: ++ grd = util.ParseGrdForUnittest(''' ++ ++ ++ ++ ++ ++ Hello %sJoi, how are you doing today? ++ ++ ++ Bongo! ++ ++ ''', 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, ''' ++ ++ ++ ++ ++ ++ Hello %sJoi, how are you doing today? ++ ++ ++ Bongo! ++ ++ ''', predetermined_ids_file=ids_file) ++ os.remove(ids_file) ++ ++ ++class IfNodeUnittest(unittest.TestCase): ++ def testIffyness(self): ++ grd = grd_reader.Parse(StringIO(''' ++ ++ ++ ++ ++ ++ Bingo! ++ ++ ++ ++ ++ Hello! ++ ++ ++ ++ ++ Good morning ++ ++ ++ ++ is_win ++ ++ ++ ++ '''), 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(''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ''') ++ 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(''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ '''), 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''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Bingo! ++ ++ ++ ++ Bingo! ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ '''), dir='.') ++ ++ def testIfBadChildrenNesting(self): ++ # includes ++ xml = StringIO(r''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ''') ++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) ++ # messages ++ xml = StringIO(r''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ''') ++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) ++ # structures ++ xml = StringIO(''' ++ ++ ++ ++ ++ Bingo! ++ ++ ++ ++ ''') ++ # translations ++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) ++ xml = StringIO(''' ++ ++ ++ ++ Bingo! ++ ++ ++ ''') ++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) ++ # same with nesting ++ xml = StringIO(r''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ''') ++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) ++ xml = StringIO(r''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ''') ++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) ++ xml = StringIO(''' ++ ++ ++ ++ ++ ++ Bingo! ++ ++ ++ ++ ++ ''') ++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) ++ xml = StringIO(''' ++ ++ ++ ++ ++ Bingo! ++ ++ ++ ++ ''') ++ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) ++ ++ ++class ReleaseNodeUnittest(unittest.TestCase): ++ def testPseudoControl(self): ++ grd = grd_reader.Parse(StringIO(''' ++ ++ ++ ++ ++ Hello ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Bingo ++ ++ ++ ++ ++ ++ ++ '''), 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 and elements. ++''' ++ ++from __future__ import print_function ++ ++import os ++ ++from grit import xtb_reader ++from grit.node import base ++ ++ ++class FileNode(base.Node): ++ '''A 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 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 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 = ''' ++ ++ ++ ++ ++ ++ ++ Hello! ++ Hello %sJoi ++ ++ ++ ''' ++ 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(''' ++ ++ ++ ++ ++ ++ ++ ++ ++ Hello! ++ Hello %sJoi ++ ++ ++ '''), 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 = ''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Hello! ++ Hello %s ++ Joi ++ ++ ++ ''' ++ grd = grd_reader.Parse(StringIO(xml), ++ util.PathFromRoot('grit/testdata')) ++ grd.SetOutputLanguage('en') ++ grd.RunGatherers() ++ self.VerifyCliquesContainEnglishAndFrenchAndNothingElse(_GetAllCliques(grd)) ++ ++ def testConditionalOutput(self): ++ xml = ''' ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Hello! ++ ++ ++ ''' ++ 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(''' ++ ++ ++ ++ ++ ++ ++ ++ ++ '''), 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 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 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 and . ++ '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 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( ++ ''' ++ ++ ++ ''' % (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(''' ++ ++ ++ ++ ++ ''', 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(''' ++ ++ ++ ''', 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(('

Hello!

\n' ++ 'Some cool things are foo, bar, baz.\n' ++ 'Did you know that 2+2==4?\n' ++ '

\n' ++ ' Hello!\n' ++ '

\n'), result) ++ os.remove(filepath) ++ ++ def testGetPath(self): ++ base_dir = util.PathFromRoot('grit/testdata') ++ grd = util.ParseGrdForUnittest(''' ++ ++ ++ ''', 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(''' ++ ++ ++ ''', 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( ++ ''' ++ ++ ++ ''', ++ 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(' ++ ++ ''', 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 element. ++''' ++ ++from __future__ import print_function ++ ++from grit.node import base ++ ++ ++class SkeletonNode(base.Node): ++ '''A 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']+|"[^\"]*"|\'[^\']*\'))?' # 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. ++ # ++ # 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='" ++EnterpriseTabResults="Search results URL" ++EnterpriseTabResultsQuery="Check if search results page supports '&&q='" ++ ++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] ++
++
 [TITLE]
++
Google Desktop Search: Search your own computer.

++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
  Outlook Email   Netscape Mail / Thunderbird
  Outlook Express   Netscape / Firefox / Mozilla
  Word   PDF
  Excel   Music
  PowerPoint   Images
  Internet Explorer   Video
  AOL Instant Messenger   Even more with these plug-ins
  Text and others
++
++

++ ++ ++ ++ ++ ++
Getting Started - Learn more about using Google Desktop Search
Online Help - Up-to-date answers to your questions
Privacy - A few words about privacy and Google Desktop Search
Uninstall - How to uninstall Google Desktop Search
Submit Feedback - Send us your comments and ideas

Google Desktop Search [$~BUILDNUMBER~$]

++[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 @@ ++ ++ ++ ++ ++ ++ Open %s? ++ ++ ++ ++ A simple string. ++ ++ ++ Contains a comment. ++ ++ ++ Another simple string. ++ ++ ++ Do not translate me. ++ +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 @@ ++

We're sorry, but we don't seem to be compatible.

++

Our software suggests that you're using a browser incompatible with Google Desktop Search. ++ Google Desktop Search currently supports the following:

++ ++ ++

You may click here 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. ++

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 @@ ++ ++[$~TITLE~$] ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
[$~IMAGE~$] ++   ++ ++ ++ ++ ++
++ ++ ++ ++ ++
 [$~CHROME_TITLE~$]
++
++ ++ ++ ++ ++ ++
++ [$~BODY~$] ++
++[$~FOOTER~$] ++ +\ 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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Copyright 2008 Google Inc. All Rights Reserved. ++ ++ ++ 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. ++ ++ ++ ++ +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 @@ ++ ++ ++ ++ ++ ++ ++
++ ++ ++
This is one version of ++[URL-DISP] from your personal cache.
++The page may have changed since that time. Click here for the current page.
++Since this page is stored on your computer, publicly linking to this page will not work.[$~EXTRA~$]

++Google may not be affiliated with the authors of this page nor responsible for its content. This page may be protected by copyright. ++
++ ++ ++


++ +\ 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 @@ ++ ++ ++ ++ ++ ++ ++
++ ++ ++
This is one version of [URL-DISP] ++from your personal cache.
++The file may have changed since that time. Click here for the current file.
++Since this file is stored on your computer, publicly linking to it will not work.[$~EXTRA~$]

++Google may not be affiliated with the authors of this page nor responsible for its content. This page may be protected by copyright. ++
++
++ ++ ++
++ +\ 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] ++ ++ ++
[$~STARTCHAT~$]
++
++
++   [$~TITLE~$] ++

Participants: [USERNAME], [BUDDYNAME]
++Date: [TIME]
++
++ ++
++ ++ ++
[$~STARTCHAT~$]
++ ++ ++[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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ New background app installed ++ ++ ++ $1Background App will launch at system startup and continue to run in the background even once you've closed all other $2Google Chrome windows. ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +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 @@ ++ ++ +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 @@ ++ ++ ++ ++
 Remove checked results and return to search.Check all - Uncheck all   ++ ++
++

[$~BOTTOMLINE~$] - ©2005 Google
++ +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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
Go to Google Desktop Search  ++ ++ ++ ++ ++
++ ++ ++ ++ ++ ++
 Remove Specific ItemsHelp  
++
++ ++ ++ ++ ++ ++ ++
 Remove checked results and return to search.Check all - ++Uncheck all  
++
++ ++ ++ ++ ++
 Remove ++checked items from Google Desktop Search. Other copies of the same items will not be ++affected.
++ If you view the item again, it will be added back to Google Desktop Search.
++
+\ 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 @@ ++ ++Database Deleted ++ ++ ++ ++ ++ ++ ++ ++
++ ++
Google Desktop Search ++

++
The database has been deleted. Click here to continue.
++ ++ ++
[$~BOTTOMLINE~$]

++

©2005 Google

+\ 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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +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 ++ ++

This documentation is not yet available

++

++[$~BOTTOMLINE~$] - ©2005 Google ++
+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 @@ ++ ++ ++ ++ ++ ++ Hello %sJoi, how are you doing today? ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +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] ++ ++ ++
[CONV] ++Reply | Reply to All[$~FORWARD_URL~$] | Compose[$~OUTLOOKVIEW~$] ++
++
++
++   [SUBJECT] ++

[FROM-DISP] ++[TO-DISP] ++[CC-DISP] ++[BCC-DISP] ++[REPLYTO-DISP] ++[DATE-DISP] ++[VIEW-DISP] ++[$~ATTACH~$] ++

++

++ ++
++ ++
[CONV] ++Reply | Reply to All[$~FORWARD_URL~$] | Compose[$~OUTLOOKVIEW~$] ++
++ ++ ++[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] ++
++   [SUBJECT]

++ ++[CONTENTS] ++
++
++[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] ++
++
++[ERROR]

++If you think this is an error, please contact us. ++
++[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] ++ ++[WEB_TOP_CHROME] ++[$~STATUS~$] ++[$~MESSAGE~$] ++[WEB_FILES] ++
[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 @@ ++


++
++ ++ ++

++ ++ ++ ++
[$~BOTTOM~$]

++
++

++[$~BOTTOMLINE~$] - ©2005 Google
++[SCRIPT] ++ +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 @@ ++ ++ ++ ++Salut! ++Salut ++Supprime&r ++Activer la barre de favoris ++Déconnexion du réseau privé ++ sur  ++Déconnecter ce compte... ++&Vérifier l'orthographe dans ce champ ++Aucune donnée reçue. ++Une erreur s'est produite lors de la tentative de lecture du fichier : . ++Le mot de passe multiterme est obligatoire. ++Importer les données d'un autre navigateur... ++Saisie automatique ++API P2P ++Exécuter automatiquement (recommandé) ++Le certificat de sécurité du site a expiré ! ++Clé publique de l'objet ++Importer ++Afficher dan&s un onglet ++ID : ++Le certificat n'indique aucun mécanisme permettant de vérifier s'il a été révoqué. ++Touches de modification... ++Signé par : ++Utiliser un service Web pour résoudre les erreurs de navigation ++Guillemet ++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é. ++Autoriser le stockage des données locales (recommandé) ++Ouvrir dans une fenêtre ++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. ++&Rechercher : ++Échec de génération de clé privée RSA aléatoire ++Certificat en attente ++Technologie réseau : ++Le certificat du serveur ne figure pas dans le DNS. ++Demander le mot de passe au retour de veille ++Désactiver la synchronisation ++Base de données indexée ++Ne pas enregistrer ++ 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. ++Le délai imparti à l'opération est dépassé. ++Nordique ++Créer des raccourcis vers des applications ++Pas encore chargé ++Confirmer le nouveau code PIN : ++L2TP/IPSec + Certificat utilisateur ++Préfecture ++Extraction de l'image de récupération... ++Terminé ++Il se peut que la page Web à l'adresse soit temporairement inaccessible ou qu'elle ait été déplacée de façon permanente à une autre adresse Web. ++Cache des scripts ++Barre d'outils Google ++Importés depuis Safari ++Le plug-in n'est pas autorisé. ++Vous exécutez à 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. ++Certificat du serveur SSL ++Z&oom arrière ++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. ++Mise à jour du système : % téléchargés ++Les informations d'identification associées au partage de vos imprimantes via sont arrivées à expiration. Cliquez ici pour saisir à nouveau votre nom d'utilisateur et votre mot de passe. ++Erreur de définition du paramètre de confiance du certificat ++ peut maintenant synchroniser vos mots de passe. ++Sélectionnez le certificat à présenter pour l'identification : ++ a planté. Cliquez sur cette info-bulle pour actualiser l'extension. ++Compatibilité expérimentale avec des méthodes Wi-Fi Extensible Authentication Protocol supplémentaires, telles que EAP-TLS et LEAP. ++Échec de lecture de la clé privée ++Les plug-ins suivants ont été bloqués sur cette page : ++Configuration de la synchronisation ++Case d'option cochée ++Très petite ++URL de révocation de l'autorité de certification Netscape ++Style de pavé numérique ++Active les feuilles de style CSS 3D et la composition graphique haute performance des pages Web via le processeur graphique. ++&Rétablir ++Redémarrer ++La connexion n'est pas compressée. ++Fin ++&Nouvelle fenêtre ++Configuration automatique du proxy ++Afficher l'orthographe et la grammaire ++Aucune imprimante n'a été trouvée. Veuillez en installer une. ++Saisissez les caractères visibles dans l'image ci-dessous. ++Certificat d'authentification de client SSL incorrect ++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. ++Rechercher le précédent ++I&nspecter l'élément ++État d'itinérance : ++&Non ++Effacer les données de navigation... ++Nombre maximal de suggestions ++L'accessibilité est désactivée. ++Sélectionner par domaine ++Tout réduire... ++Ne jamais traduire les pages rédigées en ++Non confirmé ++Avant de vous connecter, démarrez une session en tant qu'invité afin d'activer le réseau . ++La gravure de l'image est terminée. ++Si vous supprimez le certificat d'une autorité de certification, votre navigateur ne fera plus confiance aux certificats émis par cette autorité de certification. ++Synchronisez toutes les données de cet ordinateur ou sélectionnez celles que vous souhaitez synchroniser. ++ hours ago ++Domaine : ++Aperçu ++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. ++Ignorer le verrouillage des majuscules et saisir des minuscules par défaut ++Aucune parole détectée ++Changer de moteur de recherche par défaut ++Cliquer pour revenir en arrière, maintenir pour voir l'historique ++ secondes restantes ++Unicode ++Ouverture à la fin du téléchargement ++Les extensions, les applications et les thèmes peuvent endommager votre ordinateur. Voulez-vous vraiment continuer ? ++Schéma du pinyin double ++Déconnecter ce compte... ++&Fichier ++Microsoft Internet Explorer ++Aucune correspondance trouvée ++État de votre commentaire ++Mise à jour terminée. Veuillez redémarrer le système. ++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é. ++Essayez d'ajouter ++ ++ 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. ++Réseaux sans fil ++Masquer ++Zoom arrière ++Méthode EAP : ++Développer ++Veuillez vous connecter ++de n'importe quand ++Désactiver la validation des formulaires interactifs HTML5 ++Le suivi de votre position géographique sur cette page a été bloqué pour les sites suivants : ++La gravure de l'image a été interrompue. ++&Afficher dans le dossier ++Continuer à bloquer JavaScript ++Enregistrer la page sous... ++Le serveur à l'adresse requiert un nom d'utilisateur et un mot de passe. ++Exceptions de géolocalisation ++13px ++Contenu : ++Le plug-in a été bloqué, car il n'est plus à jour. ++Taille ré&elle ++Échec de la connexion au serveur proxy. ++Vérification de pilote matériel Microsoft Windows ++Paysage ++Détecter automatiquement ++Page ++Nom d'utilisateur : ++Nous aider à améliorer en envoyant automatiquement les statistiques d'utilisation et les rapports d'erreur à Google ++Réseau ++Connexion en cours ++Ajouter tous les onglets aux favoris... ++Onglets ou fenêtres ++Sites récemment consultés ++Ouvrir dans une fenêtre de navigation privée ++Préférences de saisie automatique... ++Compteur d'images par seconde ++Mot de passe ++Afficher le compte ++ : ++Le suivi de votre position géographique a été bloqué pour cette page. ++Connexion à l'aide de votre compte Google ++Le mot de passe multiterme entré est incorrect. ++télécopie : # ++Le navigateur par défaut est actuellement . ++ secondes restantes ++Mot de passe précédent ++Code PIN incorrect ++Modifier l'adresse ++Zoom avant ++Micrologiciel ++Une erreur s'est produite lors de l'affichage de cette page Web. Pour continuer, actualisez cette page ou ouvrez-en une autre. ++Récupération de clé Microsoft ++recto verso ++Fichier ou répertoire introuvable ++Aucun forfait de données actif ++Tout sélectionner ++Le fichier manifeste est incorrect. ++Les pages suivantes ne répondent plus. Vous pouvez attendre qu'elles soient de nouveau accessibles ou les supprimer. ++ minutes restantes ++Le certificat du serveur n'est pas encore valide. ++Menu contenant des extensions masquées ++Enregistrer les infos ++Configuration de l'accès à distance à cet ordinateur. ++Point ++Ajouter un moteur de recherche ++Impossible d'atteindre le serveur. ++Importer mes favoris... ++Enregistrer le &cadre sous... ++Vous n'êtes pas autorisé à accéder à la page Web . Votre connexion peut être requise. ++Envoyer la capture d'écran du dernier onglet actif ++Erreur ++Utiliser le thème GTK+ ++Ouvrir une fenêtre du navigateur ++ a planté. Cliquez sur cette info-bulle pour redémarrer l'application. ++Définir comme navigateur par défaut ++Certificat de courrier électronique ++Clavier en superposition ++La connexion est compressée avec . ++Exporter mes favoris... ++Format : ++Ignorer ++Déplacer un mot ++Index de ++Mémoire ++Impossible d'utiliser cette langue pour corriger l'orthographe. ++Recherche ++Ajouter une autre carte de paiement... ++Envoyer la dernière capture d'écran enregistrée ++Ce fichier contient du code malveillant. Voulez-vous vraiment continuer ? ++Erreur de synchronisation... ++Clavier brésilien ++Utiliser TLS 1.0 ++&Signaler un problème... ++Créer des raccourci&s vers des applications... ++Le certificat "" a été émis par : ++ 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 gestionnaire des extensions. ++Logiciels malveillants ++Mise en page ou mise en forme de la page ++Acheter davantage... ++Traduire ++Tout ++Créé : ++Annuler l'importation ++Le mode indiqué est incorrect. ++ copié(s) sur ++L'accès à distance à cet ordinateur est activé pour . ++Options... ++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. ++Toujours &afficher la barre de favoris ++Erreur SSL ++Confirmer les préférences de synchronisation ++Utiliser les valeurs par défaut ++Code secret manquant ++L'accès à distance à cet ordinateur est désactivé. ++API des extensions expérimentales ++Inclure les informations système ++Date d'expiration ++Autorité de certification compromise ++À propos de la saisie automatique ++Activer la fonction "Taper pour cliquer" ++Accès à la page Web refusé ++&Gestionnaire de favoris ++Erreur serveur ++Cette carte SIM est désactivée et ne peut être utilisée. Veuillez demander à votre fournisseur de services de la remplacer. ++Outils ++Clavier néerlandais ++EAP-TTLS ++Choisissez une image à associer à votre compte. Celle-ci s'affichera sur l'écran de connexion. ++Configurer le blocage des fenêtres pop-up... ++des 4 dernières semaines ++Une situation inattendue s'est produite tandis que le serveur tentait de traiter la demande. ++Impossible d'afficher certaines parties de ce document PDF. Souhaitez-vous l'ouvrir dans Adobe Reader ? ++Proxy FTP ++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é. ++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. ++Activer l'onglet 1 ++Communication à distance ++Importer les favoris et les paramètres... ++À propos ++Modifier le favori de cette page ++Ajouter un format d'exception ++Configurer l'accès à distance... ++Supprimer le certificat "" ? ++Cache des images ++Configuration du proxy ++En l'absence de connexion Wi-Fi, Google Chrome utilise les données 3G. ++Appuyez sur pour sélectionner le mode de saisie précédent. ++La création du support de récupération du système d'exploitation a été annulée. ++Le plug-in suivant est bloqué : ++Erreur de connexion réseau ++Mot de passe multiterme ++Internet ++Configurer les paramètres de blocage des plug-ins... ++Afficher dan&s un onglet ++Synchroniser vos mots de passe ++Le serveur proxy agit comme un intermédiaire entre votre ordinateur et les autres serveurs. Votre configuration système utilise actuellement un proxy, mais ++ ++ ne parvient pas à s'y connecter. ++Sélectionner par type d'application ++Procéder à l'i&nspection de l'élément ++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. ++Impossible de déplacer le répertoire d'extensions dans le profil. ++Supprimer ces paramètres pour les prochaines visites ++L'accessibilité est activée. ++Appuyer sur la touche Espace pour sélectionner la suggestion ++Vos favoris ++ () ++Fermer les onglets ++Applications en arrière-plan ++Favoris ++Supprimer les données synchronisées de Google Dashboard ++Veuillez patienter pendant que installe les dernières mises à jour système. ++Utilisez les touches Maj gauche et droite pour sélectionner les 2e et 3e propositions ++WEP ++Mode Zhuyin complet. La sélection automatique de la suggestion et les options associées sont désactivées ou ignorées. ++&Paramètres linguistiques... ++CRX-less Web Apps ++Connexion au réseau ++Reliure bord long ++ utilise les paramètres proxy du système pour se connecter au réseau. ++Application : ++&Descendre ++? Toutes les données présentes sur le périphérique seront supprimées. ++Adresse IP ++Active le nouveau design de la page "Nouvel onglet" (en cours de développement). ++Échec de l'activation ++Ne pas vérifier ++Le chinois simplifié est le mode de saisie initial ++Paramètres SSL sur tout l'ordinateur : ++Votre connexion à n'est pas chiffrée. ++matériel requis ++Gérer les exceptions... ++Rechercher des mises à jour ++Utiliser un mot de passe multiterme pour chiffrer mes données de synchronisation ++Connexion en cours... ++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. ++Historique ++Destination ++Web audio ++Cookies placés par cette page ++Accès partagé ++Afficher... ++Veuillez vous connecter à . ++Ajouter un nouvel e-mail ++Personnaliser les polices... ++Matériel : ++Erreur de connexion au réseau. ++Cette page Web est introuvable. ++En&registrer la vidéo sous... ++Le certificat du serveur n'est pas approuvé. ++Cop&ier l'image ++Sebeol-sik 390 ++ mins ago ++Autre réseau mobile... ++Erreur HTTP () : ++Signature du code ++Aide ++<sans nom> ++Version du micrologiciel : ++La page ne se charge pas ++Attention, ces fonctionnalités expérimentales peuvent mordre. ++Verrouiller l'écran ou éteindre ++La connexion est chiffrée au moyen de , avec pour l'authentification des messages et pour la méthode d'échange de clés. ++Clavier portugais ++Importation ++Connexion au réseau ++Langues ++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. ++ Dans ce cas, une intervention ++ est requise sur le serveur. ++ ++ n'utilise pas de connexion non sécurisée pour protéger la confidentialité ++ de vos données. ++La synchronisation de vous permet de partager vos données (favoris, préférences) sur vos ordinateurs en toute simplicité. Pour ce faire, enregistre vos données en ligne via Google lorsque vous vous connectez à votre compte. ++Format ou mise en forme de la page ++Clavier suisse ++En&registrer l'image sous... ++Basculer en mode pleine chasse ou demi-chasse ++Sélectionner... ++Effacer les paramètres d'ouverture automatique ++Ajouter un réseau privé ++ secondes ++Ne pas envoyer de capture d'écran ++Hébreu ++Toujours afficher la barre de favoris ++Impossible de trouver le chemin d'accès absolu du répertoire à empaqueter. ++Créer un profil ++Diners Club ++Paramètres de contenu... ++Activer la recherche instantanée pour accélérer la recherche et la navigation ? ++Plus d'extensions >> ++La valeur d'entrée de la clé privée est obligatoire. ++PEAP ++Fonctionnalités de localisation expérimentales ++Ou&vrir la vidéo dans un nouvel onglet ++Toujours afficher la barre de favoris ++Reliure ++Utilisation de la clé du certificat ++Clavier belge ++ heures restantes ++Afficher toutes les images (recommandé) ++Cliquez sur ++ Démarrer, ++ puis sur ++ Exécuter. ++ Saisissez ++ et cliquez sur ++ OK. ++Nom de volume ++Reliure bord court ++ va configurer les mises à jour automatiques pour tous les utilisateurs de cet ordinateur. ++Effacer les éléments datant : ++Interdire à tous les sites d'afficher des notifications sur le Bureau ++Appuyez sur pour passer d'un mode de saisie à l'autre. ++Ajouter un nouveau fax ++Recherche de contenu en cours... ++Style d'entrée avec Espace ++Vous avez tenté d'accéder à , mais, au lieu de cela, vous communiquez actuellement avec un serveur identifié comme . 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 , donc potentiellement préjudiciable. Nous vous déconseillons vivement de continuer. ++Masquer le bouton ++Modifier l'image ++Ne pas synchroniser mes mots de passe ++JavaScript ++Activer les notifications de ++Afficher les incompatibilités ++Nouvel onglet ++Impossible de charger l'icône "" d'action de page. ++Fermer l'onglet ++Nom d'hôte du serveur : ++Ajouter une carte de paiement ++ : type de fichier inconnu ++Non défini ++Gérer les paramètres de saisie automatique... ++Respecter la &casse ++Autoriser tous les sites à exécuter JavaScript (recommandé) ++Gérer vos périphériques depuis le cloud ++Clavier catalan ++Me demander lorsqu'un site essaie de stocker des données ++Menu ++Sélectionnez le périphérique de stockage amovible à utiliser. ++La traduction a échoué, car nous n'avons pas pu déterminer la langue de la page. ++Vous pouvez créer des profils supplémentaires pour autoriser plusieurs personnes à utiliser et personnaliser Google Chrome. ++Deux-points ++Toujours traduire en ++(désactivée) ++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 et non avec un pirate informatique ayant émis son propre certificat en prétendant être . Nous vous déconseillons vivement de continuer. ++ requiert que vous cryptiez vos données à l'aide de votre mot de passe Google ou de votre propre mot de passe multiterme. ++ ++N'est pas une autorité de certification ++Date et heure : ++Impossible de charger "" pour le thème. ++Rechercher à nouveau ++En attente de ... ++Masquer ce plug-in ++Enregistrer &sous... ++Ajouter une langue : ++Annuler ++Ouvrir une &adresse... ++Supprimer le mot ++Vous devez saisir deux fois le même mot de passe multiterme. ++Ouvrir dans un onglet épinglé ++Page Web, tout type de contenu ++Activer ++Considérer ce certificat comme fiable pour identifier les développeurs de logiciels. ++Ajouter tous les onglets aux favoris ++Impossible de créer le dossier de favoris. ++Copier l'URL ++Si vous pensez ne pas avoir à utiliser de serveur proxy, procédez comme suit : ++ ++Définir les utilisateurs autorisés à se connecter à un périphérique et autoriser les sessions de navigation en tant qu'invité ++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. ++Pool restreint : ++&Rafraîchir cette page ++&Normal ++PKCS #1 SHA-384 avec chiffrement RSA ++Numéro de série : ++HTTPS/SSL ++Toutes les données de votre ordinateur et des sites Web que vous visitez ++Ceci est une installation secondaire de et ce dernier ne peut pas être défini comme navigateur par défaut. ++Erreur lors de la signature de l'extension ++ permet d'effectuer des recherches sur Internet à l'aide du champ polyvalent. Sélectionnez le moteur de recherche à utiliser : ++Sécurité : ++Adobe Reader n'est pas à jour ++Extensions ou applications ++Effacer les cookies et autres données de site et de plug-in lorsque je ferme le navigateur ++Téléchargement de l'image terminé. ++Favoris ++Afficher les vignettes ++Ne plus afficher ce message ++Agent de récupération de clé Microsoft ++Page de diagnostic de navigation sécurisée ++Adresse ligne 2 ++Décrivez-nous le problème recontré. (Champ obligatoire) ++Cela peut prendre quelques minutes. ++Options de base ++Niveau de zoom par défaut : ++Stockage externe ++Modifi&er les moteurs de recherche... ++Cartes de paiement ++Nom du fichier ++Arrêter la synchronisation ++Actualiser le cadre ++Connexion ++Mémoire privée ++Tout effacer ++Activer la correction orthographique automatique ++Afficher les &infos sur la page ++favoris_.html ++Autoriser uniquement les utilisateurs suivants à se connecter : ++&Muet ++Nouvel ongle&t ++Erreur de synchronisation ++Zoom ++ sur ++Type de clavier ++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. ++Déplacer ++Mode de saisie du vietnamien (VIQR) ++Ajouter la page actuelle aux favoris ++<aucun cookie sélectionné> ++Le mot de passe de l'application est incorrect. ++Sélectionner "un mot à la fois" ++Tout bloquer ++Chargement en cours… ++&Rouvrir la fenêtre fermée ++Gérer les exceptions... ++URL ++Katakana demi-chasse ++Ouvrir une adresse ++Données de navigation ++Code PIN incorrect. Veuillez réessayer. ++Non ++Utiliser cette langue pour corriger l'orthographe ++Fermer les autres onglets ++Clé privée non valide. ++Une erreur réseau s'est produite pendant la communication avec le service de gestion des périphériques. ++Importer les données d'un autre navigateur... ++&Ajouter au dictionnaire ++Voulez-vous vraiment ouvrir  onglets ? ++Fabricant du périphérique : ++Afficher l'historique complet ++Illimité ++Clavier Neo2 allemand ++Liste déroulante ++Clé : ++Coller en texte brut ++Afficher les &commandes ++Cette page suit votre position géographique. ++Certificat de chiffrement de courrier électronique ++&Profilage activé ++En savoir plus sur la récupération du système ++Essentielle ++Type MIME ++Autoriser le téléchargement ? ++Clavier croate ++Interdire à tous les sites de suivre ma position géographique ++Utiliser la barre de titre du système et les bordures de la fenêtre ++En bas à gauche ++Informations sur l'erreur : ++Essayez : saisissez "orchidées", puis appuyez sur Entrée. ++Pour utiliser Chrome Web Store, vous devez être connecté à un compte Google. ++Configurer ++Petit problème... Nous n'avons pas réussi à vous authentifier. Veuillez vérifier vos identifiants de connexion puis réessayer. ++Pour gérer à distance la configuration de ce périphérique depuis le cloud, connectez-vous avec votre compte Google Apps. ++Réactiver ++Fermer la fenêtre ++Présentation ++Vous avez saisi un trop grand nombre de codes PIN incorrects. Veuillez contacter pour obtenir une nouvelle clé de déverrouillage du code PIN à 8 chiffres. ++Bloquer le contenu inapproprié ++Configuration avancée ++Utiliser SSL 3.0 ++Règles de confidentialité liées à la navigation sécurisée ++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. ++Ouvrir le lien dans une nouvelle &fenêtre ++Les cookies de ont été bloqués. ++Copies ++&Ouvrir le fichier audio dans un nouvel onglet ++Afficher les noms d'utilisateurs et leur photo sur la page de connexion ++URL de révocation de certificat Netscape ++Ajoute des options de regroupement des onglets dans le menu contextuel des onglets. ++Clavier finnois ++Vérifiez votre connexion Internet. Redémarrez votre routeur, votre modem ++ ou tout autre périphérique réseau que vous utilisez. ++&Afficher le code source de la page ++Le serveur ne prend pas en charge la version HTTP utilisée dans la demande. ++Vos mots de passe enregistrés s'afficheront ici. ++Fermer les onglets sur la droite ++ heures ++ ++ 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 ++ , ++ vous devez configurer les produits ESET de manière à éviter ces ++ interceptions. Cliquez sur le lien En savoir plus pour obtenir des instructions. ++Les requêtes adressées au serveur ont été temporairement limitées. ++Impossible de télécharger l'image. Gravure annulée. ++Activer/désactiver le mode Hanja ++Effacer les paramètres d'ouverture automatique ++Pas après le ++Manifeste : ++Visa ++Clavier anglais canadien ++Récupération de fichier Microsoft ++En&registrer l'image sous... ++Zone ++Chemin : ++Prendre la photo ++Inactif ++Certains de vos certificats enregistrés identifient ces autorités de certification : ++Impossible d'afficher la page Web, car le serveur n'a envoyé aucune donnée. ++Considérer ce certificat comme fiable pour identifier les utilisateurs de messageries. ++Ce type de fichier peut endommager votre ordinateur. Voulez-vous vraiment télécharger  ? ++Importés depuis la barre d'outils Google ++Recherche instantanée et saisie semi-automatique ++Tout synchroniser ++Dvorak (Hsu) ++Exécuter la commande  : ++Le fichier contenait un certificat, qui n'a pas été importé : ++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. ++Impossible de détecter les modules chargés. ++Cette fonctionnalité affiche une bordure autour des couches de rendu afin de déboguer et d'étudier leur composition. ++Processus de traitement Web : ++Invité ++Nom du fichier : ++Un élément est manquant. ++Échec de la tentative de connexion au serveur. ++Des fenêtres pop-up ont été bloquées sur cette page. ++Personnaliser les langues et la saisie... ++Proxy HTTP ++Mot de passe multiterme de chiffrement ++Nouvelle fenêtre ++Certains certificats provenant de ces organisations vous identifient : ++Autoriser l'itinérance des données mobiles ++Alt ++Utiliser les pages actuelles ++Version ++En&registrer le fichier audio sous... ++Itinérance ++Romaji ++ minutes ++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) ++Autoriser tous les sites à afficher des notifications sur le Bureau ++Ouvrir le fichier... ++Changer de fenêtre ++Chèvres téléportées ++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. ++Le certificat du serveur ne correspond pas à l'URL. ++Commune ++Impossible d'activer les plug-ins désactivés par une stratégie d'entreprise. ++Ne pas afficher sur cette page ++Méthodes EAP en Wi-Fi expérimentales ++Disque dur ++Nouveau dossier ++intégration sur ++Autorité de certification de messagerie ++Cache CSS ++En haut à gauche ++Saisissez le nom du nouveau dossier. ++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. ++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. ++pause ++Afficher tous les téléchargements... ++Connexion à ++Impossible de résoudre l'adresse DNS du serveur. ++En attente de l'affichage du cache ++Thèmes ++Impossible de se connecter au serveur proxy. ++Ignorer la synchronisation des données chiffrées ? ++Oui ++Nombre de suggestions par page ++Modifier le code PIN ++Un problème est survenu lors de la copie de l'image de récupération sur le périphérique. ++ jours restants ++La connexion Internet a été interrompue. ++Début ++« Précédent ++C&opier l'URL de la vidéo ++Vos données sur et ++Langue ++Mappages des stratégies de certificat ++ minutes restantes ++Sélectionner la position ++Le site Web à l'adresse 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. ++ ++Hors de portée ++Effacer l'historique de navigation ++Aucune page Web trouvée à l'adresse : ++Nouveau moteur de recherche : ++Jamais pour ce site ++ jours restants ++Impossible d'accéder au réseau. ++Modifier l'adresse ++&Masquer le panneau de la vérification orthographique ++1 cookie ++Activer ces fonctionnalités... ++ secondes restantes ++Cou&per ++Fermer la barre de recherche ++Signataire de la liste de révocation de certificats ++Afficher des informations à propos du site ++UC ++Fermer les pages ++MSPY ++ () ++Très petite ++Afficher les réseaux privés dans le menu Réseau pour activer la connexion à un VPN ++Vérification de la mise à jour du système... ++L'entrée demandée est introuvable dans le cache. ++Connectez-vous à pour importer le certificat client. ++Modifier la mise en page ++Se déconnecter ++Espace insuffisant. ++Préférences synchronisées ++Imp&rimer le cadre... ++MSCHAP ++Continuer à bloquer les images ++Lorsqu'un site utilise des plug-ins : ++Échec d'exportation de la clé publique ++Choisir les éléments à synchroniser ++Paramètres de saisie du Pinyin ++Ouvrir les pages suivantes : ++Déconnexion ++Moteurs de recherche ++Erreur de suppression de certificat ++Ne rien faire ++Épingler l'onglet ++réinitialiser la synchronisation ++PKCS #1 SHA-256 avec chiffrement RSA ++Katakana ++Choisir un réseau mobile ++&Historique ++Mode développeur : ++Lien ++Système ++Sélectionnez le fichier de clé privée. ++Insérez une carte SD ou une carte mémoire USB. ++Temps restant : ++Cliquez ici pour exécuter le plug-in . ++Hanyu ++pages ++Configuration en cours ++Erreur inconnue ++ days ago ++La langue utilisée pour Google Chrome est passée de "" à "" après la synchronisation de vos paramètres. ++&Aucune suggestion orthographique ++Erreur de protocole SSL ++Mode de saisie du thaï (clavier Pattachote) ++Ce compte utilisateur n'est pas compatible avec ce service. ++ ne peut pas être exécuté en tant que root. ++C&oller ++Paramètres de contenu... ++Dimensions de l'image ++Sélectionner le fichier à enregistrer sous ++Disque Flash ++Moteur de recherche actuel : ++L'identité de situé à a été vérifiée par . ++Version de l'autorité de certification Microsoft ++Enregistrer une capture d'écran ++Page sur ++Console &JavaScript ++Réponse reçue incorrecte lors de la tentative de chargement de . ++ Cela peut être dû à une opération de maintenance ou à une configuration incorrecte sur le serveur. ++Serveur DNS : ++Pré-rendu : ++Impossible d'accéder à la bibliothèque réseau. ++Des images ont été bloquées sur cette page. ++Toujours autoriser à afficher les images ++Copier l'adr&esse du lien ++Ouvrir dans une fenêtre de navigation privée ++Mode développeur ++Chiffrement des données ++Activer la protection contre le phishing et les logiciels malveillants ++Appuyez sur pour rechercher sur ++Voulez-vous utiliser () au lieu de pour gérer les liens :// à partir de maintenant ? ++Démarrer une session en tant qu'invité ++Nombre maximal de caractères chinois dans la mémoire tampon de pré-édition, notamment les entrées de symboles Zhuyin ++Barre de menus GNOME expérimentale disponible ++Effacer l'historique des téléchargements ++Navigateur bloqué... ++Parenthèse drte ++Calcul du temps de chargement ++Ajouter une carte de paiement... ++Itinérance : ++Arrêter ++Erreur de synchronisation... ++ID de clé de l'autorité de certification ++Extensions ++Imprimez où que vous soyez. ++Un problème est survenu lors du téléchargement de l'image de récupération. ++Cloud Print Proxy ++Une erreur s'est produite lors de la configuration de la synchronisation. ++Fichiers personnalisés ++Impossible de définir le mode une fois la fenêtre créée. ++Date et heure ++Personnaliser les préférences de synchronisation ++Aucune information disponible sur le forfait ++Signaler un problème ++Importer mes favoris et paramètres ++Sélectionnez le menu clé à molette > Paramètres > Options avancées > Modifier les paramètres du proxy et vérifiez que vos paramètres sont définis sur "sans proxy" ou "direct". ++Fermer et annuler les téléchargements ++Autres moteurs de recherche ++ peut maintenant synchroniser vos mots de passe. Pour protéger vos données, vous devez confirmer les informations relatives à votre compte. ++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. ++L'identité de ce site Web n'a pas été vérifiée. ++Sessions à l'étranger ++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. ++Afficher les &infos sur le cadre ++Connecté ++Paramètres d'entrée en Chewing ++Conserver sur cette page ++Votre support de récupération est prêt. Vous pouvez le retirer du système. ++Copi&er l'adresse e-mail ++Interdire à tous les sites d'afficher des fenêtres pop-up (recommandé) ++Console &JavaScript ++Empêcher cette page de générer des boîtes de dialogue supplémentaires ++En haut à droite ++Entrez le mot de passe associé à votre application : ++Mode de saisie Google du japonais (pour clavier japonais) ++ est à jour () ++Le serveur a mis fin à la connexion de manière inattendue. ++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, ++ a temporairement ++ bloqué l'acceptation des requêtes adressées au serveur. ++ ++ 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 , ++ sur laquelle vous pourrez trouver plus d'informations ou désactiver cette fonctionnalité. ++Gestionnaire de &fichiers ++Arrêter la synchronisation du compte ++Ajouter ++Sélectionnez le fichier à ouvrir ++Aucun forfait de données ++Créer un compte Google ++Code postal ++&Gestionnaire de tâches ++Téléphone ++Sélectionner l'onglet précédent ++Sélectionner un certificat ++Mots de passe enregistrés ++Autoriser l'accès aux URL de fichier ++Modifi&er les moteurs de recherche... ++Gérer les moteurs de recherche... ++PKCS #7, chaîne de certificats ++Clé pré-partagée : ++Contrôlez et partagez l'accès à vos imprimantes depuis n'importe quel compte Google. ++Ajoute l'option "Utiliser les onglets latéraux" 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. ++S'inscrire ++, ++Saisissez un nouveau nom. ++ ne peut pas être affiché dans cette langue. ++Erreur : impossible de décoder l'extension. ++Clavier bulgare ++Accès aux informations de l'autorité ++Ajouter un réseau privé... ++C&opier l'URL du fichier audio ++Masque de sous-réseau : ++Impossible d'accéder à votre compte ? ++Utiliser les touches - et = pour paginer une liste d'entrées ++Le serveur ne prend pas en charge l'extension de renégociation TLS. ++Résolution de l'hôte... ++Récemment fermés ++Le certificat de sécurité du site a été signé avec un algorithme de signature faible. ++Votre mot de passe a été modifié ++Gérer les paramètres de saisie automatique... ++La page Web n'est pas disponible pour le moment. Cela peut être dû à une surcharge ou à une opération de maintenance. ++Éjecter ++Impossible de lancer le processus de service. ++ Mo restants ++Nouvelle fenêtre ++Sélectionnez ++ ++ Applications > Préférences système > Réseau > Avancé > Proxys ++ ++ et désélectionnez les serveurs proxy sélectionnés. ++Gravure en cours d'initialisation... ++Téléchargement de l'image de récupération... ++il y a  minutes ++Mode de saisie du chinois (cangjie) ++Comté ++Ouvrir l'adresse dans un nouvel onglet ++&Rouvrir l'onglet fermé ++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. ++Certificat utilisateur : ++Autoriser ++Appuyer sur Entrée pour revenir en arrière et sur la touche de menu contextuel pour afficher l'historique ++ZRM ++Cookies et autres données ++Page(s) ne répondant pas ++Paramètres d'entrée hangûl ++Configurer la synchronisation... ++Modules (). Conflits connus : , conflits probables : ++Date de renouvellement du certificat Netscape ++Ouvrir tous les favoris dans une nouvelle fenêtre ++bouton radio concernant l'étendue de pages ++Saisissez votre mot de passe ++ est conçu pour rendre l'impression plus intuitive, accessible et utile. vous permet de rendre vos imprimantes accessibles depuis n'importe quelle application Web ou mobile associée à . ++Dictionnaire de kanji unique ++Rétablir tous les onglets ++Échec de l'installation de l'extension ++En savoir plus sur la manière de se protéger des logiciels malveillants en ligne ++Toujours afficher les fenêtres pop-up de ++Authentification phase 2 : ++Aucun résultat de recherche trouvé ++Ouvrir une fois le téléchargement &terminé ++ cookies ++Mode de saisie du japonais (pour clavier américain) ++Continuer à bloquer les plug-ins ++Épingler sur la barre des tâches ++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. ++Fermer la session d'invité ++Contrôlez vos tâches d'impression et l'état de connexion de vos imprimantes en ligne. ++Désinstallation ++Empreinte SHA-1 ++Modifier le code PIN de la carte SIM ++État du réseau : ++sans limite ++(Choisir une autre capture d'écran) ++Désactivation ++Aucune erreur n'a été signalée récemment. Les erreurs n'apparaissent ici que lorsque l'envoi de rapports d'erreur est activé. ++Rouvrir les dernières pages ouvertes ++Ouvrir en mode plein écran ++Ajouter une carte de paiement ++Désactivé ++Chargement des informations sur votre forfait Internet mobile, veuillez patienter... ++Acheter un forfait de données... ++Inclure cet e-mail : ++Le titre doit comporter au moins un caractère. ++Espaces de noms réseau ++ n'a pas pu terminer l'installation, mais va poursuivre son exécution à partir de son image disque. ++Gestion des services Internet mobiles ++Utiliser les onglets latéraux ++Non ++Saisir le code PIN de la carte SIM ++&Afficher dans le Finder ++Maintenez la touche Ctrl, Alt ou Maj enfoncée<br>pour afficher le raccourci clavier qui lui est associé. ++Vraiment désolé, aucun prototype n'est disponible pour le moment. ++&Afficher dans le Finder ++Informations relatives au certificat ++/ ++La synchronisation des mots de passe requiert votre attention. ++Veuillez saisir l'ancien et le nouveau code PIN. ++Langue : ++Résultats de recherche ++Serveur SSL avec fonction d'optimisation ++Enregistrer en PDF ++Mémoire USB détectée ++M'avertir lorsque le flux de données est faible ou presque inexistant ++Saisir le mot de passe multiterme ++Clavier lituanien ++Les éléments saisis dans le champ polyvalent peuvent être enregistrés. ++Les informations de connexion à votre compte sont obsolètes. Cliquez ici pour saisir à nouveau votre mot de passe. ++Vietnamien ++feuilles de papier ++URL de mot de passe perdu Netscape ++Rouvrir l'onglet fermé ++Effacer les mots de passe enregistrés ++Créer un nouveau dossier... ++Basculer en mode ponctuation pleine chasse ou demi-chasse ++Gérer les paramètres de localisation... ++Synchronisé... ++Options de recherche par défaut ++Aucune sélection ++Mode de saisie du vietnamien (TELEX) ++ minute restante ++Le nombre maximal d'autorités de certification intermédiaires a été dépassé : ++Saisissez un mot de passe pour chiffrer ce fichier de certificat. ++Organiser ++Votre mot de passe a changé. Veuillez réessayer avec votre nouveau mot de passe. ++PKCS #1 MD5 avec chiffrement RSA ++Utiliser le thème classique ++Échap efface toute la mémoire tampon de pré-édition ++Ajouter aux favoris ++Un incident est survenu sur une partie de cette page (HTML WebWorker). Elle risque de ne pas fonctionner correctement. ++Clavier américain ++Signataire de code ++Adobe Reader n'est pas à jour et risque de ne plus être sécurisé. ++Personnaliser les paramètres de synchronisation... ++Co&ller et rechercher ++Détails du certificat sélectionné : ++Impossible de se connecter ++Activation : ++Système de révocation introuvable dans le certificat du serveur ++Numéro de série ++Afficher la page "Nouvel onglet" ++il y a  heure ++Votre ordinateur redémarrera une fois la mise à jour effectuée. ++Verr. maj. ++L'accès au répertoire de l'hôte de communication à distance a été refusé. Essayez avec un autre compte. ++Accessible aux scripts : ++Cette page place des cookies. ++Proxy ++Avec sous-menu ++Afficher dans le dossier ++il y a  minutes ++<saisir une requête> ++Appuyez sur pour envoyer des commandes à . ++Prévisualiser le rapport ++Activer le dernier onglet ++Lorsque je quitte le navigateur ++Continuer sans mettre à jour Adobe Reader (non recommandé) ++Le champ de mot clé doit être vide ou comporter un mot unique ++Déplacer le curseur automatiquement au caractère suivant ++Enregistrer ++Ouvrir le lien dans un nouvel ongle&t ++Nouvelle fenêtre de navigation privée ++Celtique ++Réinitialiser le zoom ++Prototypes ++Mémoriser mes choix pour tous les liens de ce type ++Les plug-ins de cette page ont été bloqués. ++Site ++Sélectionner ++Couper ++Restaurer toutes les miniatures supprimées ++Utiliser les onglets latéraux ++Se connecter au dispositif de sécurité ++Enregistrer le fichier ++Pages ++Options de ++Barre de lancement rapide ++Annuler ++Choisir un fichier ++&Rechercher avec ++(pas encore valide) ++Mettre à jour le plug-in... ++Police Sans-Serif ++Impossible de charger le JavaScript "" du script de contenu. ++Connexion... ++Mots de passe ++Fichiers PKCS #12 ++Options de saisie automatique de ++Fenêtre précédente ++Astuce ++ / fichiers ++Localisation utilisée, mais les paramètres régionaux par défaut (default_locale) n'ont pas été indiqués dans le manifeste. ++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. ++Votre commentaire a bien été envoyé. ++Fonction d'optimisation internationale Netscape ++Taille de police minimale ++Suivant ++Utilitaire : ++Cette opération peut prendre une minute... ++Le format du mot de passe est incorrect. ++Chargement... ++Ajouter la page... ++Masquer ce plug-in ++&Paramètres linguistiques... ++Créer un support de récupération du système d'exploitation ++Le fichier de clé privée est incorrect. ++Valeur : ++Stable ++Grande ++Certificat serveur invalide ++Jamais enregistrés ++Cette page a été préchargée. ++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 à chrome://plugins, activer "Chrome PDF Viewer" et réessayer. ++Nouveau ! ++Vous êtes passé en navigation privée. 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. Passer en navigation privée n'a aucun effet sur les autres utilisateurs, serveurs ou logiciels. Méfiez-vous : Des sites Web qui collectent ou partagent des informations vous concernant Des fournisseurs d'accès Internet ou des employeurs qui conservent une trace des pages que vous visitez Des programmes indésirables qui enregistrent vos frappes en échange d'émoticônes gratuites Des personnes qui pourraient surveiller vos activités Des personnes qui se tiennent derrière vous En savoir plus sur la navigation privée ++Le certificat du serveur a été révoqué. ++&Reprendre ++Sélectionnez un réseau : ++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. ++Effacer les données de navigation ++Si vous êtes conscient que la visite de ce site peut être préjudiciable à votre ordinateur, vous pouvez . ++Votre périphérique est inscrit pour bénéficier de la gestion d'entreprise. ++Serveur de mise à jour non disponible (erreur : ) ++Module client natif : ++Rechercher sur  : ++Style de symboles ++ hours ++Département ++Continuer à utiliser ++OK ++Essayez de désactiver les prédictions d'actions du réseau en procédant comme suit : ++ Sélectionnez le ++ ++ menu clé à molette > ++ ++ > ++ ++ ++ et désélectionnez "". ++ Si le problème n'est pas résolu, nous vous conseillons de sélectionner de nouveau ++ cette option pour améliorer les performances. ++Date ++Sélectionnez un certificat pour vous authentifier sur . ++Barre latérale ++La clé publique éphémère Diffie-Hellman associée au serveur est peu sûre. ++Rechercher dans Dictionnaire ++Sélectionnez un moteur de recherche ++Clavier russe ++Nom Microsoft principal ++Clavier polonais ++Clavier serbe ++ minute ++Erreur lors de la lecture des données du cache. ++Canary ++Commentaire du certificat Netscape ++Page suivante ++ est à jour. ++Paramètres de saisie automatique... ++Tout ramener au premier plan ++Sélectionner le dossier à ouvrir ++Activer le mode Pinyin fuzzy ++Résolution du proxy... ++Le téléchargement de l'image a été interrompu. ++Eten 26 ++URI ++Clavier Dvorak américain ++Rechercher sur le Web... ++Si vous utilisez un serveur proxy, vérifiez les paramètres associés ou demandez à votre administrateur réseau ++ si ce serveur fonctionne. ++Impossible de charger l'icône de l'extension "". ++Définir un proxy pour se connecter au réseau ++zone de texte concernant l'étendue de pages ++Activation ++Clavier Dvorak britannique ++ jours ++MS-IME ++Aucun forfait de données ++Les fenêtres pop-up suivantes ont été bloquées sur cette page : ++ heures restantes ++Impossible de charger le modèle du favori. ++Échec d'exportation de la clé privée ++Utiliser comme page d'accueil ++Compte ++Plein écran ++Autoriser tous les sites à suivre ma position géographique ++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. ++Les cookies suivants ont été bloqués : ++Inclure les adresses de ma fiche de Carnet d’adresses ++Le certificat de sécurité du site n'est pas approuvé ! ++Votre liste d'applications, d'extensions et de thèmes installés ++Exécuter tous les plug-ins de cette page ++Augmente la taille du texte ++Accepter et continuer » ++Fichiers ++ ++Appliquer uniquement à cette session de navigation privée ++Latin large ++Exécuter ce plug-in ++MasterCard ++Sur le Web, Tab permet de sélectionner les liens, ainsi que les champs de formulaire. ++À propos de ++Paramètres de sécurité du système ++Images ++Cyrillique ++Pas maintenant ++Attendre la fin du téléchargement ++Thème "" installé ++&Supprimer ++Menu Démarrer ++&Zoom ++Transfert en cours ( %)... ++Géré par ++&Rafraîchir ++Configuration du module de plate-forme sécurisée (TPM) en cours. Veuillez patienter, cela peut prendre quelques minutes. ++Vous avez acheté une quantité illimitée de données le . ++Confirmer avant de quitter () ++Mémoire partagée ++Rech. dans les paramètres ++mot de passe d'accès au réseau ++Activer la vérification orthographique ++Types de données ++Bloquer ++Certificat de l'autorité de certification du serveur : ++Clavier grec ++Taille réelle ++Toujours en haut ++Aucun plug-in installé. ++Alerte JavaScript ++Autoriser en mode navigation privée ++Rétablir ++, expire le : ++Confirmer la désinstallation ++Impossible de désactiver les plug-ins ayant été activés par une stratégie d'entreprise. ++Cette fonctionnalité active l'option "Lire en un clic" dans les paramètres de contenu du plug-in. ++Attendre la fin des téléchargements ++Synchronisation ++&Rouvrir l'onglet fermé ++Votre mot de passe de compte Google a changé depuis votre dernière connexion à partir de cet ordinateur. ++Le fichier contenait plusieurs certificats, aucun d'eux n'a été importé : ++Afficher le bouton ++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. ++Configuration en cours... ++Rechercher dans les téléchargements ++Me demander lorsqu'un site tente de suivre ma position géographique (recommandé) ++La nouvelle version de "" a été désactivée, car elle nécessite davantage d'autorisations. ++Clavier phonétique russe ++American Express ++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. ++Lorsque vous vous connectez à un site Web sécurisé, le serveur hébergeant ce site présente à votre navigateur un "certificat" 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). ++À l'instant ++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 : ++ / octets, Interrompu ++Importés depuis Firefox ++Détection automatique ++&Aide ++Les codes PIN sont différents ! ++Contenu Web ++Vider le cache ++Rétablir le thème par défaut ++Le service de communication à distance a démarré correctement. Vous devriez maintenant pouvoir vous connecter à distance à cet ordinateur. ++Outils de développement ++Police Serif ++Copier ++Adresse e-mail ou mot de passe incorrect. Veuillez réessayer. ++Le plug-in suivant ne répond pas : souhaitez-vous interrompre ? ++Sens ++ secondes ++Clavier tchèque ++Voulez-vous que "" soit considérée comme une autorité de certification fiable ? ++Gestionnaire de sécurité natif du client ++<p>Lorsque vous exécutez 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.</p> ++ ++ <p>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 à <code>man </code>.</p> ++La page Web à l'adresse 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. ++SSID : ++Nom du forfait : ++Adresse X.400 ++Fermer l'onglet ++Impossible de trouver un navigateur pris en charge. ++Le compte associé à la boutique en ligne est le suivant : . L'utilisation d'un autre compte pour la synchronisation provoque des erreurs. ++Enregistrer la p&age sous... ++ importe actuellement les éléments suivants à partir de  : ++Appuyez sur Entrée pour avancer et sur la touche de menu contextuel pour afficher l'historique ++Actualiser le cadre ++Annulé ++Autorités ++Sélectionnez votre clavier : ++Signaler un problème... ++Ignorer ce réseau ++Nouveau matériel détecté ++Vous disposez de certificats qui n'appartiennent à aucune autre catégorie : ++&Profilage activé ++(Navigation privée) ++&Nouveau dossier ++Types MIME : ++Afficher les pages en arrière-plan () ++Fichiers audio ++Plug-ins ++Plus d'informations... ++Se connecter automatiquement à ce réseau ++Titulaire de la carte ++Elle peut désormais accéder à : ++Noir et blanc ++Ignorer la connexion et naviguer en tant qu'invité ++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. ++Accès réseau interrompu ++La dernière version de l'extension "" requiert d'autres permissions. Elle a donc été désactivée. ++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. ++Veuillez saisir la clé de déverrouillage du code PIN à 8 chiffres fournie par . ++Exceptions... ++Connexion à ++Voulez-vous vraiment quitter alors qu'un téléchargement est en cours ? ++Le site Web ne parvient pas à gérer la demande associée à . ++Précédent ++&Arrêter ++Lorsque l'option permettant de bloquer l'enregistrement des cookies tiers est activée, la lecture de ces cookies est également bloquée. ++ : ++Console JavaScript ++Les sites pour lesquels vos mots de passe ne seront jamais enregistrés s'afficheront ici. ++Inconnu ++Mode de saisie du thaï (clavier TIS-820.2538) ++&Favoris ++Désinstaller ++Voulez-vous vraiment quitter alors que  téléchargements sont en cours ? ++Configurer le blocage des cookies... ++ABC ++L'application hébergée par est inaccessible, car vous êtes déconnecté du réseau. Cette page s'affichera dès que la connexion réseau sera rétablie. <br> ++Gestionnaire de favoris ++ secs ago ++Quitter le mode plein écran ++Ouvrir l'&image dans un nouvel onglet ++Favori ajouté ! ++Choisir un autre dossier... ++Kotoeri ++Hier ++Synchroniser automatiquement les éléments suivants : ++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. ++Contraintes des stratégies de certificat ++Créer un support de récupération ++Emplacement : ++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. ++Options pour les développeurs ++Cookies ++Toujours exécuter pour ce site ++Afficher le dossier ++Autres favoris ++Clavier français ++ ne parvient pas à déterminer ou à définir le navigateur par défaut. ++Sélectionner l'onglet suivant ++Voulez-vous vraiment supprimer ces pages de votre historique ? ++Déverrouiller ++ID de clé : ++Exécuter ce plug-in ++Utilisateurs ++Ajouter un moteur ++Répondeur OCSP : ++Ignorer ++Champs de certificat ++Modules () : aucun conflit détecté. ++Onglet : ++Impossible de charger la page d'options "". ++Coller en adaptant le style ++Veuillez ajouter une autre langue avant de supprimer celle-ci. ++nom du réseau ++ synchronise de manière sécurisée vos données avec votre compte Google. ++Taille ++Activer... ++Recherche ++Poursuivre quand même ++Nouveau code PIN : ++La capacité du périphérique doit être d'au moins 4 Go. ++De droite à gauche ++Activer le mode Pinyin double ++Menu contenant des favoris masqués ++Utiliser le dictionnaire système ++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. ++Personnaliser les polices... ++Enregistrement des informations de date ++&Vérifier l'orthographe du texte de ce champ ++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. ++ minutes restantes ++Île ++Contraintes de base du certificat ++Autres... ++Activer l'onglet 4 ++Données personnelles ++Afficher l'original ++Nom de la société ++Définir Adobe Reader comme visionneuse de documents PDF par défaut ? ++Pour gérer les extensions installées, cliquez sur Extensions dans le menu Fenêtre. ++Accédez à vos imprimantes depuis n'importe quel ordinateur ou smartphone. En savoir plus ++Me &le rappeler plus tard ++Opération réussie ! ++Carte SD détectée. ++ - Propriétaire ++Clavier japonais ++WPA ++janv.^févr.^mars^avr.^mai^juin^juil.^août^sept.^oct.^nov.^déc. ++Sélectionnez ++ ++ Applications > Préférences système > Réseau > Assistant ++ ++ pour tester votre connexion. ++&Afficher le code source de la page ++Micrologiciel : ++Créer un profil ++Date de modification ++Sandbox seccomp ++Signale&r un problème... ++Entrée de symboles simplifiée ++Chinois simplifié ++Hôte SOCKS ++Considérer ce certificat comme fiable pour identifier les sites Web. ++Exceptions de localisation ++Hiragana ++Le mot de passe TPM ci-dessous, généré de façon aléatoire, a été attribué à votre ordinateur : ++Importer mes favoris maintenant... ++ ++Avertissement : Un problème a été détecté sur cette page. ++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. ++Ajouter tous les onglets aux favoris... ++Émis le ++Lorsque je ferme le navigateur ++Localisation ++Attendre ++Infos sur la clé publique de l'objet ++Plug-in inconnu ++Connexion en mode invité ++Toujours autoriser à paramétrer les cookies ++Résumé ++Le certificat du serveur n'est pas valide. ++Chaîne de certificats codés Base 64 ASCII ++Choisir mon propre mot de passe multiterme ++Certificat de serveur non répertorié ++Rechercher la sélection ++Activer ++L'accès à ce réseau est protégé. ++E-mail ++Erreur () : ++Signature permanente Microsoft ++Sélectionnez ++ ++ Démarrer > Panneau de configuration > Réseau et Internet > Centre Réseau et partage > Résolution des problèmes (en bas) > Connexions Internet. ++ ++Gestionnaire de &fichiers ++Service client ++Impossible de se connecter au réseau "". ++J'accepte ces termes ++Copier l'adr&esse du lien ++Veuillez démarrer 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. ++Le processus du connecteur est bloqué. Voulez-vous le redémarrer ? ++Contenu Web ++Clé compromise ++Supprimer ++Si vous fermez maintenant, ces téléchargements seront annulés. ++Dachen 26 ++Voulez-vous vraiment graver l'image sur le périphérique suivant : ++Mémoire SQLite ++/, ++ est affiché dans cette langue. ++Fenêtre pop-up bloquée ++ : ++Ouvrir tous les favoris ++Effacer les cookies et autres données de site et de plug-in lorsque je ferme le navigateur ++Page d'accueil ++Onglet ++Non configuré ++Opération réussie ! ++Relancez pour terminer la mise à jour. ++Chiffrer toutes mes données ++Veuillez patienter pendant que nous configurons votre réseau pour mobile. ++Points de distribution de listes de révocation des certificats ++Association ++Appuyez sur Maj+Alt pour changer la disposition du clavier. ++Utiliser l'horloge au format 24 heures ++ est prêt à terminer l'installation. ++Lancez votre recherche à partir d'ici ++OK, synchroniser tout ++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. ++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. ++Clavier slovaque ++Nom de partie EDI ++Vous avez déjà chiffré des données avec un mot de passe multiterme. Saisissez-le ci-dessous. ++Informations sur le site ++Créer un raccourci vers l'application... ++Authentification requise ++ n'est pas parvenu à se connecter à . Sélectionnez un autre réseau ou réessayez. ++Votre compte n'est pas compatible avec . Contactez l'administrateur de votre domaine ou utilisez un compte Google standard pour vous connecter. ++Adresse e-mail ++Client natif ++Chargé depuis : ++Active les balises canvas hautes performances dans un contexte 2D, pour effectuer le rendu via le processeur graphique. ++Cette page ena été traduite en ++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 ++Autorité de certification SSL ++ contient un logiciel malveillant. Votre ordinateur pourrait être infecté par un virus si vous consultez ce site. ++Aucune donnée disponible. ++Im&primer... ++Description ++Voix non reconnue. ++Aucun fichier sélectionné ++Souhaitez-vous installer  ? ++Activité récente ++de la dernière semaine ++Profil ++Actualiser sans utiliser le cache ++Logiciels malveillants et sites de phishing détectés ! ++Onglet fermé ! ++il y a  jours ++Paramètres ++Ce site exige que vous vous identifiiez avec un certificat : ++Supprimer... ++&Aucune suggestion orthographique ++Clavier franco-canadien ++Clavier étendu américain ++Le serveur a mis fin à la connexion sans envoyer de données. ++Autoriser à afficher des notifications sur le Bureau ? ++Pour gérer les extensions installées, cliquez sur Extensions dans le menu Outils. ++Erreur de synchronisation, veuillez vous connecter à nouveau. ++La liste suivante fait état des éléments dangereux détectés sur la page. Cliquez sur le lien "Diagnostic" pour obtenir plus d'informations sur un élément particulier. ++Émirat ++Synchroniser tous les paramètres et toutes les données\n(peut prendre un certain temps) ++Toujours demander où enregistrer les fichiers ++Créer un raccourci ++Tous les fichiers ++Sebeol-sik No-shift ++Anglais (pleine chasse) ++2D avec canvas et accélération matérielle ++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. ++Nouvelle connexion ++GPU ++Moins de 1 Mo disponible ++Nouvelle fenêtre de navigation privée ++Europe du Sud ++Sélectionner les éléments à synchroniser ++Attributs du répertoire de l'objet du certificat ++Accédez à vos imprimantes et partagez-les en ligne via . ++&Ajouter... ++Afficher dans cette langue ++il y a  jour ++Police Sans-Serif ++Ne rien faire ++ seconde ++Configurer les mises à jour automatiques pour tous les utilisateurs ++Échec de la création du répertoire des données ++Fermer et annuler le chargement ++ ++Adresse : ++Détails ++Notification : ++Cette opération peut prendre quelques minutes. ++Géré par () ++Type de certificat Netscape ++Impossible d'afficher certaines parties de ce document PDF. Souhaitez-vous installer Adobe Reader ? ++Vos certificats ++Vos données personnelles sur , et ++URL de stratégie de l'autorité de certification Netscape ++Dernier accès : ++Synchroniser les mots de passe ++&Quitter ++&Fermer l'onglet ++, ++Le site Web à l'adresse 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. ++Total : ++Les cookies de plusieurs sites ont été autorisés pour la session uniquement. ++Nom du service : ++Imprimer une page de test ++Aperçu avant impression - ++ ++ ne peut pas à afficher la page Web, car votre ordinateur n'est pas connecté à Internet. ++Exécuter cette fois ++Chiffrement de la clé ++Échec de la vérification AAA ++Ouverture dans ... ++&Copier ++Logiciel ++Moteurs de recherche ++&Méthodes d'entrée ++Jamais enregistrés ++Impossible d'analyser le fichier. ++Subordination qualifiée Microsoft ++Bureau ++Toujours traduire en les pages en ++Thaï ++Activer l'onglet suivant ++: de chargement ++Style de mappage du clavier ++Un problème lié à votre microphone s'est produit. ++ vous permet d'accéder aux imprimantes de cet ordinateur, où que vous soyez. Connectez-vous pour l'activer. ++District ++Ethernet ++Erreur de réseau inconnue. ++Nom du certificat ++Plus d'informations ++Mise à jour du système disponible. Préparation du téléchargement… ++Galerie des thèmes Google Chrome ++Copi&er l'adresse e-mail ++Activer l'onglet 3 ++Adresses ++Plug-in : () ++Pas maintenant ++Pays ++<Ne fait pas partie du certificat> ++il y a  seconde ++Fichiers image ++Europe centrale ++Certificat client SSL ++Pour accélérer l'affichage des pages Web, ++ ++ enregistre temporairement les fichiers téléchargés sur le disque. Si ++ ++ 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. ++ ++ 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. ++Thème créé par ++Données de navigation ++Ajouter cette page aux favoris ++Le serveur a refusé la connexion. ++Module ( bits) :\n\n\nExposant public ( bits) :\n ++ secondes restantes ++Vous utilisez un indicateur de ligne de commande non pris en charge : . La stabilité et la sécurité en seront affectées. ++Emplacement de téléchargement ++À propos de Google Traduction ++Les lettres ne sont pas sensibles à la casse. ++Mot clé ++Connexion ++Redémarrez pour appliquer la mise à jour. ++Sélectionner un carré dans l'image ++Bases de données Web ++Codage ++Mise en route ++Utiliser les pages actuelles ++Signaler une erreur ++Recherche de réseaux en cours ++Effacer les données de navigation ++Ouvrir le lien dans une nouvelle &fenêtre ++Nom d'utilisateur ++Clavier letton ++Forcer l'actualisation de cette page ++Disposition du clavier : ++Non utilisé ++ minutes ++Chiffrement RSA PKCS #1 ++Ce n'est probablement pas le site que vous recherchez ! ++&Général ++Configurer la synchronisation ++État de connexion : ++Ancien code PIN : ++Désactiver le contrôle des liens hypertexte ++&Modifier ++Chinois ++Navigateur par défaut ++Extension en mode navigation privée : ++Mot clé : ++Un problème est survenu lors du téléchargement de l'image de récupération. La connexion réseau a été perdue. ++Votre administrateur a désactivé certaines préférences. ++Forfait de données épuisé ++Veuillez indiquer à quel niveau vous rencontrez des problèmes avant d'envoyer vos commentaires. ++Authentification du serveur WWW TLS ++Le service de synchronisation n'est pas disponible pour votre domaine. ++Modifier les paramètres du proxy... ++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. ++Un logiciel malveillant a été détecté ! ++Forfait de données presque épuisé ++des dernières 24 heures ++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 ++ hours left ++poursuivre quand même ++Autorisé ++Retirer l'onglet ++Moins de ++Impossible de charger l'icône "" d'action du navigateur. ++Consulter Google Dashboard ++Plus d'informations ++Ajoutez des langues puis faites-les glisser pour les classer dans l'ordre souhaité. ++ 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 sont fermées. ++Non (HttpOnly) ++Saisir votre mot de passe multiterme pour la synchronisation ++Police standard ++ (par défaut) ++Cette fonctionnalité active la prise en charge du client natif. ++La puce du module de plate-forme sécurisée (TPM) est désactivée ou inexistante. ++L'index de l'onglet indiqué est incorrect. ++Nom du serveur SSL du certificat Netscape ++Est une autorité de certification ++Wi-Fi ++Connexion à ++Confirmer le mot de passe : ++Aperçu avant impression ++Clavier italien ++Impossible d'établir une connexion sécurisée à cause de l'antivirus ESET. ++(expiré) ++Voulez-vous vraiment quitter cette page ? ++Faire défiler d'une page vers le haut ++Empreintes ++Le plus récent ++ heures ++Agrandir ++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. Cette erreur peut également être due à un cache endommagé lors d'une fermeture incorrecte. Si le problème persiste, essayez d'effacer le cache. ++Le plug-in n'est plus à jour. ++Vous avez changé de position. Souhaitez-vous utiliser  ? ++EAP-TLS ++ heures ++Rafraîchir cette page ++Non-répudiation ++Extension : ++Active l'API Web audio. ++Zone de saisie de mot de passe ++Effacer les cookies et autres données de site lorsque je quitte le navigateur ++Ajouter l'option de regroupement au menu contextuel des onglets ++Version ++Latin ++Le connecteur requiert l'installation du pack Microsoft XML Paper Specification Essentials. ++Effacer les cookies et autres données de site lorsque je ferme le navigateur ++Case décochée ++Mise en page ++Saisir un nom ou une adresse ++Choix de l'image du compte ++Me montrer ++ minutes restantes ++De : ++Ouvrir un rapport de phishing ++Page de phishing ++Inspecter le pop-up ++Importer... ++Modifier la carte de paiement ++Cette langue est utilisée pour corriger l'orthographe. ++Le type d'enregistrement indiqué est incorrect. ++Forfait de données arrivé à expiration ++Exporter mes favoris... ++Clavier britannique ++Importer des favoris... ++ ++Nom d'utilisateur ou mot de passe incorrect ++&Tout sélectionner ++Erreur inconnue liée au certificat du serveur. ++Envoyer une capture d'écran enregistrée ++Émis pour : ++Page précédente ++Hsu ++Réduire ++Navigateur par défaut ++Afficher le mot de passe ++Activer les fonctionnalités d'accessibilité ++Inclure la capture d'écran actuelle : ++ - ++(cette extension est gérée et ne peut être désinstallée ni désactivée) ++OID enregistré ++Importés depuis IE ++Fichier de clé privée (facultatif) : ++Autoriser pour la session uniquement ++ mins ++Mise à jour en cours ++Aucun microphone trouvé. ++Connexion à ++ n'est pas votre navigateur par défaut. ++Forfait ++Cette page contient des éléments des sites ci-dessous qui suivent votre position géographique : ++Plusieurs profils ++Plantages ++Par défaut ++ jours ++PKCS #1 MD4 avec chiffrement RSA ++Modifier le moteur de recherche ++ minutes ++Indiquez le mot de passe approprié ci-dessus, puis saisissez les caractères figurant dans l'image ci-dessous. ++Expiration de imminente ++Activer l'onglet précédent ++Désactiver l'accès à distance ++Afficher les incompatibilités ++Carte de débit Solo ++Actualisez cette page Web ultérieurement. ++Pour utiliser cette extension, saisissez "", TAB, puis votre commande ou votre recherche. ++ synchronisera les applications installées, afin que vous puissiez y accéder en vous connectant depuis tout navigateur . ++Langues et paramètres du correcteur orthographique... ++Erreur de sécurité ++Applications ++La ressource demandée n'existe plus et aucune adresse de transfert n'est disponible. Il semble que cet état de fait soit permanent. ++Certificat unique binaire codé DER ++&Plein écran ++Fichiers vidéo ++ 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. ++Vos onglets et activités de navigation ++Créer ++Turc ++Une erreur s'est produite lors de la tentative d'enregistrement du certificat client. Erreur  () ++Bienvenue dans ++Sélectionner ++Nom du modèle de certificat Microsoft ++Configuration de l'adresse IP pour ++Immortalisez votre plus beau sourire et utilisez la photo comme image de compte. ++Extension créée : ++ ++ ++Mot de passe incorrect. Veuillez réessayer. ++LEAP ++Échec de la connexion aux serveurs de reconnaissance vocale. ++Très grande ++Sélectionner le répertoire de l'extension ++&Téléchargements ++Gestionnaire des certificats ++ souhaite suivre votre position géographique ++Quitter ++ sur ++Proposer d'enregistrer les mots de passe ++Modification de l'affiliation ++par exemple : 1-5, 8, 11-13 ++(Activé par une stratégie d'entreprise) ++Qu'est-ce que c'est ? ++Les cookies de plusieurs sites ont été bloqués. ++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. ++Désactivé ++Annuler ++Répertoire racine de l'extension : ++Mode de saisie du vietnamien (TCVN6064) ++Échec de l'activation ++Ajouter cette page aux favoris ++L'identité de ce site Web a été vérifiée par . ++Adresse e-mail : ++Activation... ++PAP ++Sites de phishing ++ secs ago ++Interdire à tous les sites d'exécuter JavaScript ++Vouliez-vous accéder à  ? ++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. ++Paramètres des fenêtres pop-up : ++Tout exporter... ++Barre de favoris ++Stocké dans : ++Développer... ++Gin Yieh ++Rechercher sur ++Modifier la carte de paiement ++Au démarrage ++Nous examinons actuellement le problème. ++Notifications ++Navigateur ++Case d'option décochée ++ days ++Anglais ++Modifier le nom du dossier ++Grammaire et orthographe ++Réseaux privés ++Toutes sortes de connexions ++Exceptions liées aux notifications ++Épingler les onglets ++Barre d'onglets ++Activer l'onglet 6 ++Gestionnaire de &tâches ++Paramètres de stockage d'Adobe Flash Player... ++Connexion au réseau ++Mode de saisie du vietnamien (VNI) ++L'application suivante va être lancée si vous acceptez cette requête :\n\n ++Importation... ++N° de carte ++Gérer les moteurs de recherche... ++Utiliser un moniteur externe ++Gravure de l'image en cours... ++État ++Éteindre ++Enregistrer les fichiers dans le dossier : ++Fermer les autres onglets ++Répertoire de fichiers ++Masquer les autres ++Annuler l'épinglage des onglets ++Plus ++Échec de la vérification de la révocation ++Ajouter www. et .com, puis ouvrir la page ++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. ++&Répéter ++ est à présent activé. a enregistré les imprimantes installées sur cette machine en les associant à <b></b>. Vous pouvez désormais utiliser vos imprimantes depuis n'importe quelle application Web ou mobile associée à . ++Le serveur de ++ 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 ++ ++ au réseau. ++ZGPY ++Impossible de charger le fichier css "" du script de contenu. ++Créer des raccourcis vers des applications aux emplacements suivants : ++Moyenne ++Envoyé pour : ++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 . ++[répertoire parent] ++Touches de sélection du clavier Hsu ++Copie de l'image de récupération... ++Tout supprimer ++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. ++Ouvrir dans une nouvelle fenêtre ++Dommage... Aucune extension n'est installée. :-( ++Style de ponctuation ++Historique de navigation ++Ce site tente de télécharger plusieurs fichiers. Voulez-vous autoriser le chargement ? ++Établissement de la liaison avec le service de gestion des périphériques en attente... ++ heures ++Ne pas utiliser les paramètres du proxy pour les hôtes et domaines suivants : ++&Avancer ++Impossible de charger "" pour le plug-in. ++À propos du système ++Utilisation de la clé du certificat : ++Mode de saisie du chinois (quick) ++Une erreur s'est produite lors de la tentative d'écriture du fichier : . ++Renommer ++La synchronisation requiert votre attention. ++Aujourd'hui ++Imp&rimer le cadre... ++Le site 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 , consultez notre Google. ++Adresse de serveur DNS spécifiée par l'utilisateur. ++&Rechercher... ++, ++&Afficher dans le dossier ++Non connecté à Internet. ++Sélectionnez ++ ++ Démarrer > Panneau de configuration > Connexions réseau > Assistant Nouvelle connexion ++ ++ pour tester votre connexion. ++Cette icône s'affiche lorsque l'extension peut agir sur la page active. ++Reconnexion ++Réponses OCSP de signature ++Erreur d'importation de l'autorité de certification ++Gravure de l'image terminée. ++&Historique ++Installer ++Ajouter des utilisateurs ++Enregistrer l'authentification et le mot de passe ++Clavier norvégien ++Basculer en mode chinois/anglais ++Traduction de la page en ... ++À propos de & ++ onglets ++il y a  minutes ++Qu'est-ce que c'est ? ++Tout &sélectionner ++&Ajouter au dictionnaire ++Utilisation étendue de la clé ++Le chinois est la langue de saisie initiale ++Agent utilisateur ++Émetteurs de l'autorité de certification : ++ jours ++Code source ++Redémarrer maintenant ++La largeur de caractères initiale est Complète ++Pour masquer l'accès à ce programme, vous devez le désinstaller au moyen de \n du Panneau de configuration.\n\nSouhaitez-vous exécuter  ? ++Paramètres du microphone ++Port ++&Toujours ouvrir les fichiers de ce type ++Uniquement les connexions sécurisées ++Les informations de connexion au compte sont obsolètes. ++La traduction a échoué, car la page est déjà en . ++Sandbox SUID ++(vide) ++Échec de l'authentification basée sur le certificat ++Envoyer le code source de la page actuelle ++Votre administrateur a désactivé certains paramètres. ++Coréen ++Avertissement : Il s'agit peut-être d'un site de phishing ! ++Dvorak ++Étendue de pages incorrecte ++ heures restantes ++Exceptions liées aux cookies et aux données de site ++ n'est pas accessible ++P&lus grand ++Connecté à ++Le certificat de sécurité du site a été révoqué ! ++Désactiver ++ risque de ne pas rester à jour. ++intégré sur tout autre site ++ ++ 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é. ++À propos de la reconnaissance vocale ++Langues et paramètres du correcteur orthographique... ++ seconde restante ++Nom d'utilisateur : ++Le stockage des cookies n'est pas autorisé pour cette page. ++JavaScript ++La connexion utilise . ++Échec de l'installation ++Afficher les &infos sur la page ++Serveurs ++Système de fichiers de chiffrement Microsoft ++Un plug-in supplémentaire est requis pour afficher certains éléments sur cette page. ++Appuyez sur Ctrl+Alt+/ ou sur Échap pour masquer ++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). ++Connexion avec ++Imprimer depuis la boîte de dialogue système… ++ heure restante ++Échec de l'initialisation de l'appareil photo ++Importés ++Type de fournisseur : ++IP restreinte : ++Refuser ++Démarrage du téléchargement en cours... ++Une nouvelle version de est disponible. ++Clavier hébreu ++Police à largeur fixe ++Safari ++Les champs obligatoires ne doivent pas rester vides. ++Ouvrir une fois le téléchargement &terminé ++Stratégies de certificat ++Pavé tactile ++Aucun ++Activer la barre d'adresse en mode recherche ++Exceptions pour JavaScript ++Ouvrir dans une nouvelle fenêtre ++Veuillez saisir le code PIN. ++Options de saisie automatique... ++Arabe ++Nouveau dossier ++E/S réseau interrompue ++Rechercher le précédent ++Ne jamais enregistrer les mots de passe ++Ouvrir dans un nouvel onglet ++Fenêtre suivante ++&Rechercher le suivant ++JCB ++Signature de liste d'approbation Microsoft ++Gérer les certificats... ++Sélectionnez le fichier de certificat. ++Fabricant : ++Bloquer le contenu inapproprié ++Le fichier contenait plusieurs certificats, dont certains n'ont pas été importés : ++Exceptions pour les images ++Autoriser tous les sites à afficher des fenêtres pop-up ++Vous devez indiquer un chemin valide comme valeur de clé privée. ++Sans mot de passe multiterme, vos mots de passe et autres données chiffrées ne seront pas synchronisés sur cet ordinateur. ++Extensions ou applications ++Ce site recommande Google Chrome Frame (déjà installé). ++Les plus visités ++Vous n'êtes pas autorisé à vous connecter. Consultez le propriétaire de cet ordinateur portable. ++Remarque : Lorsque vous cliquez sur "Envoyer", 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 "Inclure cette URL" et/ou "Inclure cette capture d'écran". Vous acceptez que Google utilise vos commentaires pour améliorer ses produits ou services. ++Vos modifications seront prises en compte au prochain démarrage de . ++Taille de police minimale ++Entrée directe ++Fermer la fenêtre ++Mode de saisie du japonais (pour clavier japonais) ++Inscription réussie ++Échec de création du fichier zip temporaire lors de la création du pack ++Modifier les paramètres de confiance : ++Votre historique de navigation ++&Masquer le panneau de la vérification orthographique ++Plug-in ne répondant pas ++IBM ++Les paramètres seront effacés lors de la prochaine actualisation. ++ introuvable ++Démarrage... ++Nouvelle fenêtre de nav&igation privée ++Ajouter une adresse postale... ++Authentification en cours... ++Ouvrir dans un nouvel onglet ++Déconnecté ++Action du navigateur ++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. ++Ouvrir cette page : ++Voulez-vous vraiment supprimer tous les mots de passe ? ++Sélectionner par domaine ++Ouvrir le lien dans une fenêtre en navi&gation privée ++Émetteur ++Options de saisie automatique ++Les nouveaux paramètres des cookies seront appliqués quand vous aurez actualisé la page. ++Votre connexion à est sécurisée par un chiffrement bits. ++Menu Applications ++Outi&ls ++Onglets latéraux ++Reprendre ++Zoom ++Inspecteur de DOM ++Document sans titre ++Police standard ++Activer ++Opération en cours... ++C&opier l'URL de l'image ++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. ++Enregistrer le &cadre sous... ++Général ++Défaillance ++Demander ++Coller l'URL et y a&ccéder ++Pour plus de sécurité, va chiffrer vos mots de passe. ++Si vous avez oublié votre mot de passe multiterme, vous devrez arrêter la synchronisation via Google Dashboard. ++ sur  ++Adresse ligne 1 ++Impossible de créer le favori. ++Basculer en mode chinois simplifié/traditionnel ++Votre système Sandbox n'est pas correctement configuré. ++Sélectionnez le menu clé à molette > Préférences > Options avancées > Modifier les paramètres du proxy et vérifiez que vos paramètres sont définis sur "sans proxy" ou "direct". ++Impossible de vérifier si le certificat a été révoqué. ++Stockage des données ++/, Interrompu ++Acheter un forfait ++Installer le plug-in... ++Afficher le code source ++Fermer les onglets sur la droite ++Afficher le bouton "Accueil" ++Version  ++Parenthèse gche ++Microsoft Server Gated Cryptography ++Zoom avant ++Protection du courrier électronique ++Les cookies suivants ont été bloqués (tous les cookies tiers sont bloqués, sans exception) : ++Connectez-vous à pour importer le certificat client de ++La batterie est chargée. ++Créer un nouveau profil... ++Maintenez la touche enfoncée pour quitter. ++Masquer ce plug-in ++Ouvrir le fichier ++Conteneur de barres d'infos ++Le certificat du serveur a expiré. ++Utiliser ++Réduit la taille du texte ++Épingler l'onglet ++Mise à jour du système ++ days left ++Inclure les informations système ++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. ++Impossible de valider votre mot de passe sur le réseau actuel. Sélectionnez un autre réseau. ++Cette page n'est pas rédigée en  ? Signaler l'erreur ++Certificat unique codé Base 64 ASCII ++Activer le mode plein écran ++Codag&e ++ est déjà utilisé pour gérer les liens ://. ++Touches de sélection ++Clavier international américain ++Je comprends que la visite de ce site peut être préjudiciable à mon ordinateur. ++Ressource cache manquante. ++Impossible d'accéder à mon compte ++C&opier l'URL de la vidéo ++Vous préférez parcourir la galerie ? ++Impossible de vérifier le certificat du serveur. ++Outils de &développement ++Exporter... ++Paramètres de saisie automatique ++Téléchargement de l'image en cours... ++&Options du vérificateur d'orthographe ++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. ++Vérifier le document maintenant ++Zone de liste ++Clavier slovène ++Sens de l'écriture ++Vos données sur ++Ouvrir une fenêtre du navigateur ++Conversion de la date et de l'heure ++PKCS #1 SHA-512 avec chiffrement RSA ++Clavier coréen ++Dupliquer ++Ne pas conserver sur cette page ++Désactiver ++ATOK ++téléchargement ++E-mail : ++Ouvrir tous les favoris dans une nouvelle &fenêtre ++Rafraîchir la page actuelle ++Accessibilité ++Exceptions pour les plug-ins ++Remplissage automatique des formulaires ++Nom d'utilisateur ++Enregistrer le lie&n sous... ++Réessayer ++Personnaliser et configurer ++Préférences de saisie automatique ++Votre position géographique ++Afficher les pages en arrière-plan () ++Configurer les mises à jour automatiques ++il y a  heures ++Valeur du champ ++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. ++Index erroné. ++Préparation du module de plate-forme sécurisée (TPM) en cours. Veuillez patienter, l'opération peut prendre quelques minutes. ++Nombre de copies incorrect ++Personnaliser ++Prédire les actions du réseau pour améliorer les performances de chargement des pages ++Données restantes : ++N'est pas une autorité de certification. ++L'URL doit être valide. ++Envoyer automatiquement les statistiques d'utilisation et les rapports d'erreur à Google ++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. ++Impossible de charger le profil. ++Toujours exécuter JavaScript sur ++Version PRL : ++Confirmer la réactivation ++Ajouter une adresse postale... ++PKCS #1 MD2 avec chiffrement RSA ++Dim.^Lun.^Mar.^Mer.^Jeu.^Ven.^Sam. ++Zone de saisie ++Trier par nom ++Fenêtre ++Nouvel onglet ++Vous tentez d'accéder au site , 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. ++Composition hors écran ++ mins left ++Technologie : ++Accédez à la page d'accueil du site : ++Configurer la communication à distance ++L'émetteur d'un certificat n'ayant pas expiré est tenu d'assurer la maintenance de ce qui s'appelle "une liste de révocation". 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 "révoqué" 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. ++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 "certificat racine". 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. ++Définir en tant que navigateur par défaut ++Avertissement ++Préférences de recherche ++Clavier Colemak américain ++Activer la navigation en tant qu'invité ++Utiliser les paramètres par défaut ++Rétablir ++Désinstaller "" ? ++Établissement de la connexion sécurisée... ++Dossier : ++Erreur non reconnue ++Consultez les conflits connus avec des modules tiers. ++Co&ller et rechercher ++&Remonter ++Échec de la traduction en raison d'une erreur de serveur ++Version : ++Polices et codage ++ - ++Signature du code commercial Microsoft ++Sélectionnez les éléments à importer : ++De gauche à droite ++Votre connexion à est sécurisée par le biais d'un faible chiffrement. ++sans objet ++Toujours afficher la barre de favoris ++Activer l'onglet 2 ++Aperçu des onglets ++Redémarrer ++Configuration en cours... ++Une erreur s'est produite lors de la récupération des fonctions de l'imprimante . Cette imprimante n'a pas pu être enregistrée dans . ++Exceptions pour les fenêtres pop-up ++Signataire du certificat ++La page Web n'existe plus. ++Vous n'avez jamais visité ce site auparavant. ++Personnaliser... ++Fe&rmer la fenêtre ++Arrêter le chargement de cette page ++Autres favoris ++ a été mis à jour. ++Mode hors connexion ++Erreur d'importation de fichier PKCS #12 ++Algorithme de clé publique de l'objet ++Le site Web a rencontré une erreur lors de l'extraction de . ++ Cela peut être dû à une opération de maintenance ou à une configuration incorrecte. ++Échec du chargement de la page ++Mot de passe : ++Conserver en tant que moteur de recherche par défaut ++Fichier manifeste absent ou illisible ++La connexion a été réinitialisée. ++Le mot de passe choisi vous sera demandé pour restaurer le fichier. Veillez à le conserver en lieu sûr. ++La vérification de la provenance du certificat DNS est activée, ce qui peut entraîner l'envoi d'informations privées à Google. ++À propos de la reconnaissance vocale ++Aller à la sélection ++Ajouter un certificat ++Espaces de noms PID ++Préférences de saisie automatique ++Activer l'onglet 8 ++Le certificat "" représente une autorité de certification. ++Envoyer par e-mail l'emplacement de la page ++Enregistrement des informations de date Microsoft ++Validité ++&Traduire en ++Batterie :  % ++Désactivé ++Japonais ++ () ++L'application est actuellement inaccessible. ++Lecteur du certificat : ++ jour restant ++Gestionnaire de tâches ++Gérer les extensions... ++Continuer ++Synchronisation avec effectuée. Dernière synchronisation : ++Le certificat de sécurité du site n'est pas encore valide ! ++Ouverture de session par carte à puce Microsoft ++Format ++Tapez votre requête ou saisissez une URL pour commencer la navigation : c'est à vous de choisir. ++Enregistrer &sous... ++Le serveur requiert un nom d'utilisateur et un mot de passe. ++Supprimer tous les mots de passe ++Le certificat du serveur contient des erreurs. ++Recherche de réseaux Wi-Fi... ++Description : ++Séparateur ++Si vous supprimez l'un de vos propres certificats, vous ne pouvez plus l'utiliser pour vous identifier. ++Rafraîchir cette page ++État d'activation : ++Créer des raccourcis vers des applications ++Clavier franco-suisse ++GUID de domaine Microsoft ++Entrer ++Supprimer ++Échec de l'enregistrement de cet ordinateur pour l'accès à distance. ++Ouvrir l'&image dans un nouvel onglet ++Préférences... ++L'administrateur a désactivé les mises à jour. ++À propos de la version ++Veuillez nous indiquer ce qu'il se passe avant d'envoyer votre rapport. ++Connectez-vous à afin de générer une clé pour . ++Clavier roumain ++Autres ++Sécurité : ++Imprimer ++Mode de saisie standard ++Signature ++Erreur lors de la connexion ++Pour cette session uniquement ++Une erreur s'est produite. ++(Désactivé par une stratégie d'entreprise) ++Erreur lors de la tentative d'impression. Vérifiez votre imprimante et réessayez. ++Grec ++Ignorer les exceptions et bloquer l'enregistrement des cookies tiers ++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 "Mode Présentation" pour enregistrer une capture d'écran. Vos trois dernières captures apparaissent ici. ++Activation effectuée ++Aucun système de révocation trouvé ++Bouton ++Interrompu ++Dictionnaire de symboles ++Technologie EvDo requise ++Internet mobile ++Envoyer le commentaire ++Associé au profil Chrome . Dernière synchronisation : ++Carte de crédit (autre) ++Langues et saisie ++Utiliser la barre de titre et les bordures de fenêtre du système ++Ann&uler ++ () ++Le serveur a refusé d'exécuter la demande. ++ Ko ++ a été mis à jour vers la version . ++Navigateur de contenu ++&Rouvrir la fenêtre fermée ++Se connecter directement à Internet ++Émis pour ++Ouverture de en cours ++Cibles disponibles pour l'image ++Signaler un problème ++Taille de police : ++Aide pour la configuration de proxy ++Petite ++ hours ago ++Rester sur cette page ++Type de bug : ++Inclure cette URL : ++Cookies et données de site... ++Police Serif ++Avertissement : 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. ++ChromiumOs Image Burn ++Services ++Paramètres du proxy... ++1 onglet ++Vous naviguez en tant qu'invité. 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. ++ ++ En savoir plus sur le mode invité ++Chargement des pages impossible ++Inclure cette capture d'écran : ++Certificat ++&Effacer les données de navigation... ++Valeur de signature du certificat ++URL de renouvellement du certificat Netscape ++Afficher la s&ource ++ - , ++Reprendre ++ secs ago ++Activé ++Carte SIM désactivée ++Compatibilité avec VPN ++Développement - Instable ++Échec de création du raccourci vers l'application ++Barre de favoris ++Le serveur est en mesure de répondre à la demande. ++Modifier les paramètres du proxy... ++(non empaquetée) ++Rechercher le suivant ++Paramètres réseau ++Votre service Internet mobile est activé et prêt à l'emploi. ++Modifier la police et la langue par défaut des pages Web ++Composition graphique avec accélération matérielle ++Autre nom de l'émetteur du certificat ++ ++&Afficher le code source du cadre ++Mode de saisie du persan (clavier ISIRI 2901) ++OpenVPN ++Arrêter le processus ++Votre compte a peut-être été supprimé ou désactivé. Veuillez vous déconnecter. ++Authentification anonyme : ++Bases de données indexées ++En savoir plus sur les escroqueries par phishing ++Page à l'apparence anormale ++Adresse e-mail ou mot de passe incorrect. Essayez tout d'abord de vous connecter à un réseau. ++Périphérique ++Informations sur la connexion ++Confirmer la navigation ++Nom du point d'accès : ++Rechercher ++Au démarrage ++Paramètres... ++Lire en un clic ++Connectez-vous à pour vous authentifier auprès de avec votre certificat. ++Votre administrateur informatique a désactivé certaines options. ++Supprimer le certificat de serveur ""? ++Configurer la synchronisation... ++Valider automatiquement une chaîne ++Empreinte SHA-256 ++Retour ++Réseau domestique, sans itinérance ++ - ++Gérer les mots de passe enregistrés... ++Sélectionnez ++ ++ Menu clé à molette > Options > Options avancées > Modifier les paramètres du proxy > Paramètres réseau ++ ++ et désélectionnez l'option "Utiliser un serveur proxy pour votre réseau local". ++Ne jamais traduire ce site ++(suite) ++Trop de redirections ++Ces paramètres ne peuvent être modifiés que par le propriétaire : ++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. ++Clavier latino-américain ++Phishing détecté ! ++Voulez-vous utiliser () pour gérer les liens :// à partir de maintenant ? ++ (Navigation privée) ++Prendre une photo ++Non merci ++Ne pas afficher les images ++Toujours autoriser ces plug-ins sur ++Sélectionner un dossier ++&Paramètres ++Informations sur l'erreur : ++Toujours autoriser les plug-ins sur ++Effacer les données de saisie automatique enregistrées ++Changer de moteur de recherche ++En attente du tunnel proxy... ++Veuillez ajouter un autre mode de saisie avant de supprimer celui-ci. ++ secondes ++Paramètres de contenu ++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. ++ va être installé. ++Code postal ++En bas à droite ++&Ouvrir ++ téléchargé(s) sur ++Commentaires d'ordre général/Autres ++Ou&vrir la vidéo dans un nouvel onglet ++Identité : ++Échec de l'opération OTASP ++Action sur la page ++Modifier des éléments... ++Le répertoire d'extensions est obligatoire. ++ Mo disponibles ++Ancien ++ fournit du contenu provenant de , un site connu pour distribuer des logiciels malveillants. Votre ordinateur pourrait être infecté par un virus si vous consultez ce site. ++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. ++Configuration manuelle du proxy ++Mettre à jour Adobe Reader maintenant ++ days ago ++Désactiver les plug-ins individuels... ++Envoyer une capture d'écran de la page en cours ++Point d'exclamation ++ 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. ++Petit problème... Tentons de le résoudre. ++Cette fonctionnalité permet d'établir des correspondances entre les sous-chaînes et plusieurs fragments d'URL figurant dans l'historique. ++Page "Nouvel onglet" expérimentale ++Les cookies suivants étaient autorisés lorsque vous avez consulté cette page : ++votre opérateur ++Activer ces fonctionnalités... ++Quitter Firefox avant l'importation ++Ajouter un nouveau téléphone ++Proxy HTTP sécurisé ++Accès à refusé. ++Finalisation de la mise à jour du système... ++Voulez-vous continuer ? ++ Ko ( Ko effectifs) ++Autre réseau Wi-Fi... ++Choisir un autre répertoire... ++L'extension indiquée est déjà associée à une clé privée. Utilisez cette clé ou supprimez-la. ++Ajouter une adresse ++Stockage local ++Afficher tous les téléchargements ++Navigateur de contenu ++Magasin de certificats ++Émis par ++Quitter le mode plein écran () ++Le certificat du serveur a été signé avec un algorithme de signature faible. ++Ajouter un utilisateur ++Me proposer de traduire les pages qui sont écrites dans une langue que je ne sais pas lire ++Récemment fermés ++Saisir la clé de déverrouillage du code PIN ++ jours ++Rechercher du texte ++Sans-Serif ++Zoom &avant ++Confirmer le nouvel envoi du formulaire ++ heure ++Cet élément doit être installé depuis . ++Mise en veille ou reprise ++Mettre à jour maintenant ++Ajouter une page ++Chiffrer seulement ++Ne jamais intervertir les touches de modification ++Conversion des numéros spéciaux ++Relancer maintenant ++Synchronisation avec effectuée ++il y a  minute ++Édition ++Préférences synchronisées ++ hours ago ++Personnalisé ++Rechercher à partir de la barre d'adresse ++Ouvrir ++Inspecter les vues actives : ++Contraintes de nom du certificat ++Ajouter une adresse ++Mot de passe incorrect. ++Statistiques avancées ++Clavier espagnol ++Sélectionnez votre réseau ++ jour ++Le serveur de synchronisation est occupé. Veuillez réessayer ultérieurement. ++Quitter cette page ++Naviguer en tant qu'invité ++Discover ++&Toujours afficher la barre de favoris ++Options de recherche ++Taille : ++il y a  secondes ++La liste suivante fait état des éléments dangereux détectés sur la page. Cliquez sur le lien "Diagnostic" 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 "Signaler une erreur". ++Cette page rédigée dans une langue non identifiée a été traduite en . ++Les exceptions ci-dessous s'appliquent uniquement à la session de navigation privée actuelle. ++Ne plus afficher la boîte de dialogue pour les liens de ce type ++ secondes restantes ++Insérez dans l'URL où les termes de recherche devraient apparaître. ++En&registrer la vidéo sous... ++Nom : ++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 : ++Informations relatives au certificat ++Activer l'onglet 7 ++En savoir plus sur ce problème. ++Version du matériel : ++Le site Web à l'adresse 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. ++Afficher la liste ++Désactivez l'affichage des messages de confirmation et le blocage de l'envoi des formulaires. ++Ce cadre a été bloqué, car il contient des éléments non sécurisés. ++Tout ++Kana ++Gérer les mots de passe enregistrés... ++Boîte de dialogue "Effacer les données de navigation" ++Mettre à jour les extensions maintenant ++Nouvelle application en arrière-plan installée ++Envoyer une capture d'écran de la page actuelle ++L'URL indiquée est incorrecte. ++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 . ++Actuellement connecté ++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 ? ++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. ++E&xporter... ++&Ouvrir un fichier... ++Configurer le contrôle d'accès pour vos périphériques ++Ouvrir un rapport de phishing ++Activer la barre d'adresse ++ secs ago ++Cartes de paiement ++Code opérateur : ++Erreur de connexion ++Vous avez la possibilité de désactiver ces services. ++Essayer d'afficher la page malgré tout ++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. ++Ajouter un nouveau nom ++Obtenir d'autres thèmes ++Rétablir les valeurs par défaut ++Aucun Plug-in installé. ++Action ++Extensions de fichier ++Les plus visités ++&Gestionnaire de favoris ++Caches des applications ++Cette langue est actuellement utilisée par . ++ % ++Empaqueter l'extension ++Activer l'onglet 5 ++Sélectionner le mode de saisie précédent ++Configurer le blocage de JavaScript... ++Réseaux mémorisés ++Impossible de se connecter à Internet. ++URL incorrecte ++Informations sur la sécurité ++Impossible d'installer l'application, car elle est en conflit avec "", qui est déjà installé. ++Votre périphérique n'est pas connecté. ++Fermer ++Accord de la clé ++ mins ago ++Vous ne trouvez pas ce que vous recherchez ? ++Désactivez l'envoi des pings de contrôle des liens hypertexte. ++En&registrer le fichier audio sous... ++Accédez rapidement à vos favoris en les ajoutant à la barre de favoris. ++Vérifier la grammaire et l'orthographe ++Épingler les onglets ++Sélectionner par type d'application ++assembler ++ souhaite devenir votre moteur de recherche. ++Utiliser l'historique d'entrée ++Fermer les onglets ++Voici quelques suggestions : ++Sélectionnez un ou plusieurs fichiers ++Afficher le panneau de la &vérification orthographique ++Veuillez relancer . ++Sans titre ++Pour saisir du texte, sélectionnez une langue et consultez la liste des modes de saisie disponibles. ++Ville ++Modifier... ++Réinitialiser la synchronisation ++Inclure une capture d'écran enregistrée : ++Tout supprimer ++Passer automatiquement en demi-chasse ++&Accéder à ++La capacité de ce périphérique de stockage est de . Veuillez insérer une carte SD ou une clé USB d'au moins 4 Go. ++Rouvrir le dernier onglet fermé ++(blocage) ++Erreurs () ++Traitement de la sélection... ++Aide ++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. ++Impossible d'ouvrir , car vous êtes déconnecté du réseau. Cette page s'affichera dès que la connexion réseau sera rétablie. <br> ++Télécopie ++version ++La passerelle ou le serveur proxy a reçu une réponse incorrecte d'un serveur en amont. ++Nouvelle fenêtre ouverte dans la session du navigateur ++Réessayer ++ 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. ++Les paramètres réseau de votre proxy sont gérés par une extension. ++Retirer l'onglet ++Valable du au ++Case cochée ++Mode de saisie ++AVERTISSEMENT ++Clavier estonien ++Ajout de bordures aux couches de rendu composées ++Imp&rimer... ++Paramètres de saisie automatique ++Rafraîchir ++Barre d'outils ++Nom de la base de données : ++Certificat du répondeur d'état ++Utiliser le mot de passe de mon compte Google ++Vos favoris sont maintenant synchronisés avec Google Documents ! ++Pour fusionner et synchroniser vos favoris dans sur un autre ordinateur, procédez de la même manière que précédemment sur l'ordinateur voulu. ++Renommer... ++ 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. ++Affichage des pages impossible ++Utiliser par défaut ++La carte SIM est verrouillée. Veuillez saisir votre code PIN. Nombre de tentatives restantes : ++Point-virgule ++Réseau domestique requis ++PKCS #7, certificat unique ++Langues baltes ++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. ++Données de diagnostic système ++Page Web, contenu HTML uniquement ++Continuer » ++Gérez vos imprimantes. ++(Choisir un problème dans la liste ci-dessous) ++&Afficher le code source du cadre ++Authentification du client WWW TLS ++Les cookies de plusieurs sites sont autorisés. ++Impossible d'afficher la page de la barre latérale "". ++Vous avez acheté de données le . ++Enregistrer le mot de passe ++Configurer... ++Mode de saisie du pinyin ++Intervertir les touches Ctrl et Alt de gauche ++Confirmer le mot de passe multiterme ++Activer ++Ajouter... ++Voulez-vous que Google Chrome enregistre ces informations de carte de paiement pour le remplissage de formulaires Web ? ++Continuer à bloquer les fenêtres pop-up ++Échec de la vérification DHCP ++Choisir un autre dossier... ++Le profil semble être utilisé par le processus sur l'hôte . Si vous êtes certain qu'aucun autre processus n'utilise ce profil, supprimez le fichier et relancez . ++Chinois traditionnel ++Effacer les données de navigation... ++Ré&activer le son ++Aucun réseau trouvé. ++&Ouvrir le fichier audio dans un nouvel onglet ++&Méthodes d'entrée ++Onglets ou fenêtres ++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. ++Automatique ++&Extensions ++Roumain ++Paramètres du réseau... ++Changer... ++Clé privée ++Format : ++Se connecter ++ : ++Vous avez tenté de contacter , mais le certificat présenté par le serveur est incorrect. ++Nouvelle fenêtre de nav&igation privée ++La page à l'adresse indique : ++Le serveur associé à n'a pas répondu à temps. Cela peut être dû à une surcharge. ++L2TP/IPSec + Clé pré-partagée ++XSS Auditor ++Durée de validité ++WebGL ++Mot de passe multiterme erroné ++Corriger automatiquement la saisie ++Unité d'organisation ++Compte Google ++&Aide ++Utiliser un service de prédiction afin de compléter les recherches et les URL saisies dans la barre d'adresse ++Certificat du signataire de courrier électronique ++En savoir plus ++Vous avez enregistré vos imprimantes sur via le compte . ++il y a  jours ++: restantes ++Cette page est enVoulez-vous la traduire ? ++Saisie automatique des formulaires ++Clavier ukrainien ++Ouvrir tous les favoris dans une fenêtre de navigation privée ++Ctrl ++ minutes ++Le certificat de sécurité du serveur contient des erreurs ! ++avec votre compte Google ++Rechercher dans les favoris ++MSCHAPv2 ++Avertissement utilisateur ++&Options du vérificateur d'orthographe ++Échec de la connexion ++Identifiant de l'erreur ++ jours restants ++Émis par : ++Mode de saisie du thaï (clavier Kesmanee) ++Passerelle : ++Chiffrement ++Autre... ++Pas avant le ++ n'a pas pu synchroniser vos données, car la connexion avec le serveur de synchronisation n'a pas pu être établie. Nouvel essai... ++Me demander lorsqu'un site souhaite afficher des notifications sur le Bureau (recommandé) ++Source : ++Erreur de lecture du cache ++Raccourci de sélection ++Vérifier la révocation du certificat serveur ++Vous rencontrez des problèmes lors de l'installation ? ++Bêta ++Pointeur de la déclaration CPS (Certification Practice Statement) ++Le , vous avez reçu à utiliser librement. ++Nom complet ++Inscription d'entreprise ++Clavier allemand ++Impossible de lancer l'impression. ++&Lire ++Récent ++Cette page Web présente une boucle de redirection. ++Supprimer le certificat "" émis par l'autorité de certification ? ++Mots de passe enregistrés ++Moins ++Options avancées ++Vérifiez vos paramètres DNS. Contactez votre administrateur réseau si vous n'êtes pas sûr de vous. ++Une erreur inconnue s'est produite. ++Émetteur : ++Passer d'un mode de saisie à l'autre ++Organisation (O) ++PKCS #1 SHA-1 avec chiffrement RSA ++Entrez la requête de recherche ici. ++Application en mode navigation privée : ++La largeur de ponctuation initiale est Complète ++Installer  ? ++Installation d'une nouvelle version... ++Ouvrir le lien dans un nouvel ongle&t ++Pour accéder aux paramètres de sécurité, saisissez le code PIN de la carte SIM. ++Jamais ++Impossible d'accéder au réseau. ++Effacer le formulaire ++Échec de la mise à jour du système ++Saisissez le mot de passe utilisé pour chiffrer ce fichier de certificat. ++CHAP ++Enregistrer le lie&n sous... ++Effacer les données de navigation... ++Options de reconnaissance vocale ++Réseau câblé ++Nouvel ongle&t ++R&etour ++Téléchargement suspendu ++Ouvrir la page d'accueil ++Connexion ++L'installation de est terminée. ++&Outils ++Page d'accueil ++Clavier phonétique bulgare ++Cookies et données de site... ++Batterie faible ++Commentaires ++Package incorrect : "". ++ heures restantes ++Configurer les paramètres de blocage des images... ++Options ++ hours ago ++&Détails ++Masquer la barre de titre du système et utiliser les bordures ++Elle peut accéder aux éléments suivants : ++Confidentialité ++Objets : ++Paramètres d'entrée du japonais ++Sebeol-sik Final ++Veuillez vous connecter à Internet pour continuer. ++Afficher les &infos sur le cadre ++Fichier ++Cop&ier l'image ++Utiliser le même proxy pour tous les protocoles ++Masquer ++Requête de protocole externe ++Afficher ++Certains de vos certificats enregistrés identifient ces serveurs : ++Vous avez visité ce site pour la première fois le . ++Cliquer pour avancer, maintenir pour voir l'historique ++La dernière version de l'application "" requiert d'autres autorisations. Elle a donc été désactivée. ++ secondes ++Vous avez choisi d'ouvrir automatiquement certains types de fichiers après leur téléchargement. ++EAP-MD5 ++&Nouvelle fenêtre +++ ++Vous avez essayé d'accéder au site , 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. ++L'envoi de rapports d'erreur est désactivé. ++Clé WEP incorrecte ++Date d'expiration : ++URL de base du certificat Netscape ++Réduire... ++Rechercher dans l'historique ++Ouvrir le lien dans la fenêtre de navi&gation privée ++ secondes ++Historique avancé pour le champ polyvalent ++Active l'utilisation de graphismes 3D dans les éléments canvas via l'API WebGL. ++Occident ++État non reconnu ++ ++Impossible de vérifier si le certificat du serveur a été révoqué. ++Se connecter ++Fuseau horaire : ++Cette option est soumise à une stratégie d'entreprise. Contactez votre administrateur pour plus d'informations. ++Résultats de recherche pour "" ++Revenir à "" (redémarrage requis) ++ va exécuter les tâches suivantes : ++Ajouter la page ++Changement de mode via la touche Maj ++Options de date et d'heure... ++Nom DNS ++Build de développement ++ sur ... ++Verrouiller la carte SIM (code PIN obligatoire pour utiliser les données mobiles) ++Sélectionnez le répertoire racine de l'extension. ++Les cookies de sont autorisés uniquement pour cette session. ++Confirmer les préférences de synchronisation ++RSN ++Ajuster la conversion en fonction de l'entrée précédente ++Afficher le panneau de la &vérification orthographique ++(Revenir à la capture d'écran d'origine) ++Mettre à jour & ++SSID ++- ++Les informations de connexion au compte n'ont pas encore été saisies. ++URL de configuration automatique ++URL : ++Avancer ++ jours restants ++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. ++Lancer ++ ++ ne parvient pas à accéder au réseau. ++ ++ Il est possible que votre pare-feu ou votre antivirus considère ++ ++ comme un intrus dans votre ordinateur et qu'il bloque ses tentatives de connexion à Internet. ++Ce certificat a été vérifié pour les utilisations suivantes : ++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. ++Ajouter aux favoris ++Gérer les certificats... ++Analyse du périphérique... ++PYJJ ++Web Store ++Annuler l'épinglage des onglets ++Favori ++Sélectionnez votre langue : ++Réessayer le téléchargement ++Configurer la synchronisation des mots de passe ++Nom d'utilisateur : ++Enregistrer la p&age sous... ++Vos données sur tous les sites Web ++Un problème est survenu lors de l'extraction de l'image sur l'ordinateur. ++Lancer l'application ++Dubeol-sik ++Remplacé ++Afficher les cookies et autres données de site... ++Si vous pensez avoir cerné les risques, vous pouvez . ++Apparence ++Créer des raccourci&s vers des applications... ++Exécuter le flash PPAPI dans le processus du moteur de rendu ++Définir en tant que navigateur par défaut ++Veuillez choisir un nouveau code PIN. ++ () ++Autre ++&Options ++Impossible de charger la page d'arrière-plan "". ++À quel niveau rencontrez-vous des problèmes ? (Champ obligatoire) ++Retour à la sécurité ++Eten ++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. ++Options de reconnaissance vocale ++Tout sélectionner ++Impossible de charger le fichier "" pour le script de contenu, car ce fichier n'est pas codé en UTF-8. ++&Annuler ++Désactiver temporairement la personnalisation des conversions, les suggestions basées sur l'historique et le dictionnaire utilisateur ++Les cookies de sont autorisés. ++Au fil du temps, la zone ci-dessous affichera les huit sites que vous avez le plus visités. ++Le plug-in a besoin de votre autorisation pour s'exécuter. ++Échec de création de clé privée ++Ouvrir tous les favoris dans une fenêtre de navigation privée ++Placer dans la file d'attente ++Erreur de certificat serveur inconnue ++Mode de saisie du coréen ++&Plein écran ++ ++ ++ Vous pouvez essayer de diagnostiquer le problème en procédant comme suit : ++ ++ ++Bienvenue sur votre page d'accueil ! ++Vos modifications seront prises en compte au prochain démarrage de . ++Adresse du matériel : ++Lecture seule ++Erreur d'importation du certificat serveur ++Données stockées localement ++Tous les fichiers de vont être effacés. ++Modèle du périphérique : ++Afficher dans le Finder ++Nom X.500 ++Vous devez sélectionner au moins un type de données à synchroniser. ++Tout afficher ++Ouvrir tous les favoris ++Clavier danois ++Cette fonctionnalité permet de réaliser un rendu hors écran de la texture, au lieu d'un affichage direct. ++Partiellement activé ++Votre système Sandbox est correctement configuré. ++Ne pas installer ++Activer la recherche instantanée pour accélérer la recherche et la navigation ++Utiliser par défaut ++Non essentielle ++Fenêtres pop-up ++Dernière modification : ++Désélectionné ++Sécurité ++Rechercher... ++Le script de la page utilisait trop de mémoire. Rafraîchissez la page pour réactiver le script. ++Signature du code individuel Microsoft ++Mozilla Firefox ++Page Web inaccessible ++Recadrer l'image ++Si vous fermez maintenant, le téléchargement sera annulé. ++Coller ++Retour ++Impossible de graver l'image. ++Mode de saisie du Chewing ++Province ++JavaScript a été bloqué sur cette page. ++Remarque : Lorsque vous cliquez sur "Envoyer", 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 Règles de confidentialité. ++ Si vous ne souhaitez pas envoyer de journaux système, décochez la case ++ "Inclure les informations système". ++Interdire à tous les sites de stocker des données ++Trier par nom ++Afficher un aperçu des onglets... ++Annuler l'importation ++Ce serveur envoie des données que ne comprend pas. Veuillez signaler un bug et inclure la liste des raw. ++Calcul de la durée restante ++Mode de saisie Google du japonais (pour clavier américain) ++Envoi de la requête... ++Parlez maintenant ++Impossible d'installer le package : "" ++Modèle : ++Bloquer tous les cookies tiers ++Remplacer par ++Espace ++Arrêt du fonctionnement ++Préférences ++Nom inconnu ++Nom ++Choisir un nouveau code PIN ++ [] ++Erreur d'exportation de fichier PKCS #12 ++Le répertoire racine de l'extension doit être indiqué. ++Miniature supprimée ++Tout développer... ++Aie aie aie ++Choisir les expressions en arrière-plan, sans déplacer le pointeur ++Plu&s petit ++Afficher le clavier en superposition ++C&opier l'URL de l'image ++OK ++Aucun réseau détecté ++Charger l'extension non empaquetée... ++page ++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. ++Clavier turc ++Modifier le favori ++Couleur ++Personnaliser les paramètres de synchronisation... ++Activation de votre service Internet mobile ++ ++Ouvrir dans un onglet standard ++ - ++Portrait ++ days ago ++Active un service en arrière-plan qui connecte le service aux éventuelles imprimantes installées sur cet ordinateur. Une fois ce labo activé, vous pouvez lancer en vous connectant à votre compte Google via Options/Préférences dans la section Options avancées. ++Outils de &développement ++Le répertoire racine de l'extension est incorrect. ++ID de clé de l'objet du certificat ++Téléchargements ++Le stockage du certificat client généré par a réussi. ++Sélectionnée ++Suspendre ++Taille sur le disque : ++ID du processus ++Réduire ++Expire le ++Votre administrateur a désactivé l'accès aux fichiers locaux sur votre ordinateur. ++Erreur de connexion SSL ++Envoyer ++Virgule ++&Pause ++Parcourir... ++Mode de saisie Google du japonais (pour clavier Dvorak américain) ++Police à largeur fixe ++Stockage de session ++Page en arrière-plan : ++Le serveur a renvoyé un certificat client incorrect. Erreur  () ++Signature de document Microsoft ++Bloqué ++Gérer les paramètres d'impression... ++Cette page Web a désactivé la saisie automatique dans ce formulaire. ++Clavier hongrois ++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. ++Ajouter un dossier... ++Mode de saisie du japonais (pour clavier Dvorak américain) ++Signaler un bug ++&Toujours ouvrir les fichiers de ce type ++Disque optique ++Nom commun ++Très grande ++Modification terminée ++Objet ++Opérateur : ++Algorithme de signature du certificat ++Cette fonctionnalité permet d'afficher un onglet d'aperçu avant de lancer une impression. ++Autre nom de l'objet du certificat ++Activer la saisie automatique pour remplir les formulaires Web d'un simple clic ++Le site Web à l'adresse 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. ++Rechercher directement sur ++Connectez-vous à pour exporter le certificat client. ++Créer un compte Google maintenant ++État Sandbox ++Retirer de la liste ++rapide ++&Effacer les données de navigation… ++Vérification des mises à jour... ++Télécharger les mises à jour de sécurité essentielles ++Package incorrect. Détails : "". ++Continuer à bloquer les cookies ++Ligne de commande ++Coller l'URL et y a&ccéder ++Désactiver ++Build officiel ++Se connecter à un réseau Wi-Fi ++Périphérique inconnu ++Supprimer les cookies et autres données de site ++URL saisies ++Utiliser les touches , et . pour paginer une liste d'entrées ++Vider la mémoire ++Supprimer les éléments sélectionnés ++Toujours ++de moins d'une heure ++Aucun réseau n'est disponible. ++Supprimer les cookies et autres données de site et de plug-in ++Intervertir les touches Rechercher et Ctrl gauche ++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. ++Nouveau ! Configurer la synchronisation des mots de passe ++Échec de la tentative de connexion au serveur ++Désactiver les notifications de ++Les fichiers suivants ont été créés : ++ ++Extension : ++Fichier de clé : ++ ++Conservez votre fichier de clé en lieu sûr. Vous en aurez besoin lors de la création de nouvelles versions de l'extension. ++&Oui ++Ouvrir les fichiers ++Clavier suédois ++Mémoire JavaScript ++Quitter le mode plein écran ++Hiérarchie des certificats ++Afficher l'onglet existant si l'URL associée est demandée dans un autre ++&Afficher ++Vos données personnelles sur , et sur  autres sites Web ++SMS de ++Afficher la s&ource ++Zoom arrière ++C&opier l'URL du fichier audio ++Importer et associer au périphérique... ++() () ++Plug-ins (par ex. Adobe Flash Player, QuickTime, etc.) ++Empaqueter l'extension... ++Vérifier l'orthographe lors de la frappe ++Échec de la traduction en raison d'un problème de connexion réseau ++Code PIN incorrect. Veuillez réessayer. Nombre de tentatives restantes : ++Vous pouvez effectuer une recherche directement à partir du champ ci-dessus. ++Ajouter les expressions au premier plan ++Lorem ipsum dolor sit amet, consectetur adipiscing elit. ++Opérateur ++Confirmer l'installation ++(Mot clé : ) ++Sensibilité : ++ +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 @@ ++ ++ ++ ++ +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 @@ ++ ++ ++ ++ +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 @@ ++ ++ ++ ++ ++ +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 @@ ++ ++[$~TITLE~$] ++ ++ ++ ++ ++[EXTRA_META] ++ ++ +\ 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 @@ ++ ++Google Desktop Search ++ ++ ++ ++ ++ ++ ++ ++ ++
++ ++
Google Desktop Search

++
++ ++ ++ ++
++[$~LINKS~$] ++
++ ++ ++ ++ ++ ++
 
  Desktop Preferences
++

Search your own computer.

++[$~MESSAGE~$]
++
[$~SETHOMEPAGE~$][$~BOTTOMLINE~$]

++

©2005 Google - Searching [NUM_ITEMS] items

+\ 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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
++Sidebar ++Minibar ++Close ++
++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
++ ++
++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
  
++
++ ++ ++
++ ++
++
++ ++ ++ +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 @@ ++ ++ ++should be kept ++ ++in the middle... ++ ++should be removed ++ ++ ++ ++should be removed ++ ++ should be removed because outer expr is False ++ ++should be removed ++ ++ ++ ++ ++ ++ nested true should be kept ++ ++ ++ should be removed ++ ++ ++ ++ silbing true should be kept ++ ++ ++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 @@ ++ ++Google Desktop Search Index Speed ++ ++ ++ ++ ++ ++ ++ ++ ++
++ Go to Google Desktop Search  ++ ++ ++ ++ ++
++ ++ ++ ++ ++
 Index SpeedIndex Speed ++ Help | About Google Desktop Search
++ ++

++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. ++

++You have the option of speeding up this process. ++

Warning: 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. ++
 
++

++ ++
++

++ ++
++
++ ++

 
++ ++ ++ ++
++ ++ ++ ++ ++
Google Desktop Search Home - Status - About Google Desktop Search - [$~BUILDNUMBER~$] - ©2005 Google

++ ++ +\ 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 @@ ++ ++Google Desktop Search: Initial Preferences ++ ++ ++ ++ ++ ++ ++ ++

++ ++
++
To continue, please set these initial preferences:

++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
 
 
  ++
++ ++ ++ ++ ++ ++ ++ ++ ++
++ ++ ++
Deskbar
++ ++ ++
++
  ++
You can change these and other preferences at any time.
++


++

++
++[SCRIPT] ++ ++ ++ +\ 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 @@ ++ ++Indexing has Started ++ ++ ++ ++ ++ ++ ++ ++
++

++
++
++One-time indexing has started.

++An index is being prepared on your computer to allow you ++to search your information as fast as you can search the web.

++
  • This is a one-time process that may take several hours. ++
  • You may continue to use your computer as usual and it is safe to shut down your computer. ++
  • Indexing will be performed only when your computer is idle. ++ ++ ++


    ++ ++

  • ++ ++
    ++ ++[SCRIPT] ++ +\ 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&5o6cXFE~=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+j1L48poSUd1!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-|(IYVUuHA@wRN;~7h6y+xyz_&R~;+XxM^eZ-_q~|%%b86_{3Asa-8FBk+Z7c-kJgN>UjHR +zv*J6C_vNv`hUxGkXMvL0T0vKhVQf$p6QjfeUR}fgl_wW2W!gk%O?IOa`tbcYLDwE{AY?>BR88vkIj3pcp9ftwy~b;A8i-;T|_p=$nY5yWU!`jTE^ib +ze*F+mE-Vf$>e;=5g*fg0^w_NUeElxZA2EAWiGRui^1Zl<=<)P1 +zF&Y;=yo$%KVIA>VGwa=@)kgQbrt31jq~WuvvL2VO`$zNqGSmHCaU;Y2q0yQ$JF7mTwL~ub{sOMb^Vh8GFZ(K1F-x>#wroJR +z&tF1@??TN-{BD^HbgJ;mLeTx&a +ztSZO1p-!t5NvMLINn_JEi1N%h$mrKfeZ%Sxtv*(Sq%E`|5` +zMQa!2rJ*fu!l%|$%^a7pE^XVFE$AM-((pNcct~BK8YqR1Sd~Aqmi*&@D)rQ&bN`YW +zMPvC%+;<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-PCWGR&NeH+c%h>-Yw>z7_{+>=5WWql;yg~F}V-&+XR>jna$uG|ylUKW)Rw*m^ +z_oOjp@vHny>%lMrW)jt@&DG$}J`tOC!twxncz`|R{U9wplS^%1SD7h)zLPS$9KxSJ +z^(luqF00#DmestFZxDq%2fL5@KE>#HI|)#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^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 @@ ++아웃룩 +\ 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 @@ ++ +\ 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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++ ++ ++
      
    ++
    ++
    +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 @@ ++ ++Google Desktop Search Download ++ ++ ++ ++ ++ ++
    ++ ++ ++ ++ ++ ++
    ++
    Google Desktop Search
                Search your own ++computer.

    ++ ++ ++ ++ ++ ++
    ++
  • Find your email, files, web history and chats instantly ++
  • View web pages you've seen, even when you're not online ++
  • Search as easily as you do on Google ++

    Google Desktop Search finds:

    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    Outlook  Email from Outlook, Outlook Express, & ++ Thunderbird
    Internet Explorer  Web history ++ from IE/Firefox/Mozilla/Netscape
    Text  Files in Word, ++ Excel, Powerpoint, PDF, & media formats
    AOL IM  Chats from AOL ++ Instant Messenger
     
     About Desktop ++ Search   Screenshots   ++ Help   Contact ++ Us
  •     ++ ++ ++ ++

    ++
    By using, you agree to our
    Terms & ++ Conditions
    and Privacy ++ Policy
    ++

    ++
    ++

    ++

    * Automatically starts when you turn on ++ your computer
    ++

    ++

    ++
    ©2005 Google ++

    +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 @@ ++ ++Google Desktop Search Preferences ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++


    We're sorry, but you need administrator access to ++enable Desktop Search.

    ++

    To install or run Google Desktop Search you need administrator access on this ++computer. Please try installing again once you have administrator ++access.

    ++


    ++
    +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 @@ ++Google Desktop Search Results ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +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] ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    ++ [EMAIL_TOP_CHROME] ++ ++

    ++ ++ [EMAIL] ++
    ++

    ++ [FREQ_TOP_CHROME] ++

    ++ ++ [$~FREQ~$] ++
    ++

    ++ [RECENT_TOP_CHROME] ++ ++ [$~RECENT~$] ++
    ++

    ++

    ++ ++ ++[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 @@ ++ ++Password Required ++ ++ ++ ++ ++ ++ ++ ++ ++
    ++ ++
    Google Desktop Search

    ++
    ++ ++ ++
    ++ ++ ++ ++
    Password required:   ++ ++   ++
    ++ ++
    ++
    [$~BOTTOMLINE~$]

    ++

    ©2005 Google

    +\ 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 @@ ++ ++Google Desktop Search Preferences ++ ++ ++ ++ ++ ++
    ++ ++ ++ ++ ++
    ++Go to Google Desktop Search  ++ ++ ++ ++ ++
    Preferences ++Preferences Help ++
    ++ ++
    ++ ++ ++ ++ ++
    ++Save your preferences when finished.
    ++ ++[STATUS-MESSAGE] ++ ++ ++
    Preferences (changes apply to Google Desktop Search application)
    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    Search types
    Index the following items so that you can ++search for them:
     
    ++
    ++ ++ ++ ++ ++ ++
    ++
    ++
    ++ ++ ++
    ++ ++ ++
    ++ ++
    ++ ++
    ++
    ++ ++
    ++ ++
    ++

    ++ ++
    ++
    ++
    Plug-ins
    Index these additional items:

    ++[ADDIN-DO] ++[ADDIN-OPTIONS]

    ++To install plug-ins to index other items, visit the ++Plug-ins Download page.
    ++
    Don't search these items
    ++
    ++c:\Documents and Settings\username\Private Stuff
    ++http://www.domain.com/
    ++
     
    ++
    ++
    Search Box Display ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++
    Deskbar
    ++ ++ ++
    ++ ++ ++ ++
    ++ ++
    Number of Results ++
    Google integration ++ ++ ++ ++
    ++ Your personal results are private from Google. ++
    ++
    Help us improve ++ ++
    ++ ++ ++ ++ ++
    Save your preferences ++when finished.
    ++ ++

    [$~BOTTOMLINE~$]
    ++
    ©2005 Google
    ++
    +\ 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 @@ ++ ++should be kept ++ ++in the middle... ++ ++should be removed ++ +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 ++ ++ ++ ++
    ++

    Privacy and Google Desktop Search

    ++ ++

    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. ++

    ++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. ++

    ++Your computer's content is not made accessible to Google or anyone else without your explicit permission. ++ ++

    You can read the ++Privacy Policy ++and Privacy FAQ online. ++ ++

    ++ ++

    ++ ++
    ++[$~PRIVACY_BOTTOMLINE~$] - ©2005 Google ++
    +\ 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 @@ ++ ++Google Desktop Search Preferences ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    ++
    ++


    To start using Google Desktop Search, we may need to close the following programs if they are running:

    ++

    You can start these programs once Google Desktop Search is running.

    ++ ++
  • AOL Instant Messenger ++
  • Firefox ++
  • Internet Explorer ++
  • Microsoft Excel ++
  • Microsoft Outlook ++
  • Microsoft Word ++
  • Mozilla ++
  • Mozilla Thunderbird ++
  • Netscape ++
  • Opera ++
  • Other web browsers ++ ++

    This will take only a few seconds to complete.

  • ++

    ++

    ++
    +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 @@ ++ ++Refresh index ++ ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++ ++ ++
    Google Desktop Search ++
    ++
    ++
    Google Desktop Search is now recrawling your drive to index new files.
    ++
    Note that new files are indexed automatically, and this step is generally not needed.
    ++
    Click here to continue.
    ++ ++
    ++[$~BOTTOMLINE~$] ++

    ©2005 Google

    ++
    ++ ++ +\ 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 @@ ++ +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 @@ ++ ++ ++ ++ ++ ++
    Go to Google Desktop Search   ++ ++ ++ ++ ++
    ++ ++ ++
    ++[$~LINKS~$] ++
    ++
    ++[$~FLAGS~$]
      Desktop Preferences
      [DELETE_NAME]
    ++ ++ ++
     
    ++
    +\ 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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    ++ ++
    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
     Google Desktop Search
    ++
    ++ ++
    ++ ++[HEADER_SECTION1] ++[CONTENT_INBOX] ++ ++ ++ ++[HEADER_SECTION2] ++[CONTENT_HIST] ++ ++ ++ ++[CONTENT_NEWS] ++[CONTENT_OTHER] ++ ++ ++ ++ ++ ++
    ++ ++ ++ ++[SCRIPT] ++ +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 @@ ++ ++Google Desktop Search Sidebar ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    ++ ++
    ++ ++ ++ ++ ++ ++ ++
    Google Desktop Search  Web 
    ++
    ++ ++

    ++
    ++
     News
    ++
    ++ ++ ++[CONTENT_NEWS] ++

    ++ ++ ++
    ++
     Email
    ++
    ++ ++ ++[CONTENT_INBOX] ++

    ++ ++ ++
    ++
     Related History
    ++
    ++ ++ ++[CONTENT_HIST] ++

    ++ ++ ++ ++
    ++
     Recent
    ++
    ++ ++ ++[CONTENT_RECENT] ++

    ++ ++ ++ ++
    ++
     Frequently Visited
    ++
    ++ ++ ++[CONTENT_POPULAR] ++

    ++ ++ ++ ++
    ++
     Implicit Query Debug
    ++
    ++ ++ ++[CONTENT_QUIB_DEBUG] ++ ++ ++ ++ ++[CONTENT_OTHER] ++ ++[SCRIPT] ++ ++ +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 @@ ++ ++ ++ ++ ++ Hello earthlings! ++ ++ ++ ++ ++ ++ ++ ++ ++ Go! ++ ++ Hello %sJoi, how are you doing today? ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +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 @@ ++

    ++ Hello! ++

    +\ 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] ++ ++ ++
    ++ ++ ++
     Desktop Search Status
    ++
    ++
    ++[$~MESSAGE~$] ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
     Number of itemsTime of newest item
      Total searchable items[TOTAL_COUNT][TOTAL_TIME]
           Emails[EMAIL_COUNT][EMAIL_TIME]
           Chats[IM_COUNT][IM_TIME]
           Web history[WEB_COUNT][WEB_TIME]
           Files[FILE_COUNT][FILE_TIME]
    ++
    ++[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 @@ ++

    [GREETING]!

    ++Some cool things are [THINGS]. ++Did you know that [EQUATION]? ++ +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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Copyright 2008 Google Inc. All Rights Reserved. ++ ++ ++ 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. ++ ++ ++ ++ +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 @@ ++ ++ ++ ++© 2008 Google Inc. Med ensamrätt. ++Google Desktop News gadget ++ ++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. ++ +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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Copyright 2008 Google Inc. All Rights Reserved. ++ ++ ++ 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. ++ ++ ++ ++ +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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Copyright 2008 Google Inc. All Rights Reserved. ++ ++ ++ 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. ++ ++ ++ ++ +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~$]
    ++ ++[CONTENTS] ++

    ++ ++[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 @@ ++ ++ ++ ++About Google Toolbar ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
    ++ ++ ++ ++ ++ ++ ++
    ++
    Google Toolbar
    ++
    ++
    ++ ++ ++
    ++
    ++ ++
    ++
    ++
    ++ ++ ++ De parvis grandis acervus erit ++ ++ ++
    ++ ++ © 2006 Google ++ ++ ++
    ++ ++ ++ ++ +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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +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 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Whitelisted. ++ ++ ++ Not whitelisted. ++ ++ ++ ++ +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( ++ ''' ++ ++ ++ ++ ++ ++ ++ '''), 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 elements and adds them as children of . ++ ++ Args: ++ messages: the element in the strings.xml dom. ++ resources: the element in the grd dom. ++ """ ++ # elements contain the definition of the resource. ++ # The description of a 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 node contents into a tclib.Message, taking ++ # care to handle whitespace transformations and escaped characters, ++ # and coverting placeholders into 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 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 placeholders into placeholders, e.g.: ++ ++ %s ++ becomes ++ google.com%s ++ ++ 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 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 ' ++ ' tag: %s' % output) ++ ++ return output ++ ++ def __FormatPlaceholderText(self, placeholder_node): ++ """Returns the text inside of an 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 element. ++ ++ Message elements correspond to Android 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 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 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 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 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 element is a candidate for translation. ++ ++ A 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''' ++ A simple string''')) ++ self.assertEqual(msg.GetRealContent(), 'A simple string') ++ msg = tool.CreateTclibMessage(self.__Parse(r''' ++ ++ Strip leading/trailing whitespace ++ ''')) ++ self.assertEqual(msg.GetRealContent(), 'Strip leading/trailing whitespace') ++ msg = tool.CreateTclibMessage(self.__Parse(r''' ++ Fold multiple spaces''')) ++ self.assertEqual(msg.GetRealContent(), 'Fold multiple spaces') ++ msg = tool.CreateTclibMessage(self.__Parse(r''' ++ Retain \n escaped\t spaces''')) ++ self.assertEqual(msg.GetRealContent(), 'Retain \n escaped\t spaces') ++ msg = tool.CreateTclibMessage(self.__Parse(r''' ++ " Quotes preserve ++ whitespace" but only for "enclosed elements " ++ ''')) ++ self.assertEqual(msg.GetRealContent(), ''' Quotes preserve ++ whitespace but only for enclosed elements ''') ++ msg = tool.CreateTclibMessage(self.__Parse( ++ r'''Escaped characters: \"\'\\\t\n''' ++ '')) ++ self.assertEqual(msg.GetRealContent(), '''Escaped characters: "'\\\t\n''') ++ msg = tool.CreateTclibMessage(self.__Parse( ++ '' ++ 'Open %s?' ++ '')) ++ 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''' ++ Contains a comment ++ ''')) ++ self.assertEqual(msg.GetRealContent(), 'Contains a comment') ++ ++ def testIsTranslatable(self): ++ tool = android2grd.Android2Grd() ++ string_el = self.__Parse('Hi') ++ self.assertTrue(tool.IsTranslatable(string_el)) ++ string_el = self.__Parse( ++ 'Hi') ++ self.assertTrue(tool.IsTranslatable(string_el)) ++ string_el = self.__Parse( ++ 'Hi') ++ 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 "" 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 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 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 . ++ 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 = '''\ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++''' % 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[A-Z_0-9.]+)[ \t]+ ++ (?P[A-Z_0-9]+)[ \t]+ ++ "(?P.*?([^"]|""))"[ \t]*$''', re.VERBOSE | re.MULTILINE) ++ ++ ++# Matches a dialog section ++_DIALOG = lazy_re.compile( ++ r'^(?P[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[A-Z0-9_]+)\s+MENU.+?^BEGIN\s*$.+?^END\s*$', ++ re.MULTILINE | re.DOTALL) ++ ++ ++# Matches a versioninfo section ++_VERSIONINFO = lazy_re.compile( ++ r'^(?P[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.+?)^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(^\s+//.+?)*) # 0 or more lines of comments preceding the message ++ ^\s* ++ (?P[A-Za-z0-9_]+) # id ++ \s+ ++ "(?P.*?([^"]|""))"([^"]|$) # 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.+?)$', 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( ++ ''' ++ ++ ++ ++ ++ ++ ++ ++ ++ '''), 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 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 ++ 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 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 [USERNAME], 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 ""\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 ++ # ', -+ 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"|\'|)(?P[^"\'()]*)' -+ 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'', -+ InlineScript, -+ flat_text) -+ -+ flat_text = _STYLESHEET_RE.sub( -+ lambda m: InlineCSSFile(m, ''), -+ 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 -+ -+ -+ ''' -+ -+ 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': ''' -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+
    -+
    -+ -+ -+ ''', -+ -+ '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 = ''' -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+
    -+
    -+ -+ -+ ''' -+ -+ 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': ''' -+ -+ -+ -+ -+ -+ ''', -+ -+ 'foo.css': '''''', -+ -+ 'style.css': ''' -+ -+ blink { -+ display: none; -+ } -+ ''', -+ 'style2.css': '''h1 {}''', -+ } -+ -+ expected_inlined = ''' -+ -+ -+ -+ -+ -+ ''' -+ -+ 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': ''' -+ -+ -+ -+ -+ -+ -+ -+ -+ ''', -+ -+ 'test.css': ''' -+ -+ ''', -+ -+ 'test2.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': ''' -+ -+ -+ -+ -+ -+ -+ ''', -+ -+ 'foo.css': ''' -+ @import url(chrome://resources/blurp.css); -+ blink { -+ display: none; -+ } -+ ''', -+ } -+ -+ expected_inlined = ''' -+ -+ -+ -+ -+ -+ -+ ''' -+ -+ 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': ''' -+ -+ -+ -+ -+ -+ -+ -+ -+ ''', -+ 'style1.css': '''h1 {}''', -+ 'tmpl1.html': '''

    ''', -+ 'script1.js': '''console.log('hello');''', -+ 'img1.png': '''abc''', -+ } -+ -+ expected_inlined = ''' -+ -+ -+ -+ -+ -+

    -+ -+ -+ ''' -+ -+ 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': ''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ ''', -+ 'style1.css': '''h1 {}''', -+ 'style2.css': '''h2 {}''', -+ 'tmpl1.html': '''

    ''', -+ 'tmpl2.html': '''

    ''', -+ 'script1.js': '''console.log('hello');''', -+ 'img1.png': '''abc''', -+ } -+ -+ expected_inlined = ''' -+ -+ -+ -+ -+ -+ -+ -+

    -+

    -+

    -+ -+ -+ -+ ''' -+ -+ 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 works inside a comment.''' -+ -+ files = { -+ 'include.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 works inside a comment.''' -+ -+ files = { -+ 'if.js': ''' -+ // -+ yep(); -+ // -+ -+ // -+ nope(); -+ // -+ ''', -+ } -+ -+ 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': ''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ ''', -+ '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 = ''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ ''' -+ -+ 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 = ''' -+ -+ -+ -+ -+ -+ -+ ''' -+ -+ 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': ''' -+ -+ -+ -+ -+ -+ -+ -+ ''', -+ '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 = ''' -+ -+ -+ -+ -+ -+ -+ -+ ''' -+ -+ 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 macros.''' -+ -+ files = { -+ 'index.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 = ''' -+ -+ -+ -+ -+ -+ ''' -+ -+ 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 and only. ''' -+ -+ files = { -+ 'index.html': ''' -+ -+ -+ -+ -+ -+
    -+ -+
    -+ -+
    -+ -+ -+
    -+ -+
    -+    -+
    -+ -+
    -+
    -+ ''')) -+ html.Parse() -+ trans = html.Translate('en') -+ if (html.GetText() != trans): -+ self.fail() -+ -+ -+ def testHtmlToMessageWithBlockTags(self): -+ msg = tr_html.HtmlToMessage( -+ 'Hello

    Howdiebingo', True) -+ result = msg.GetPresentableContent() -+ self.failUnless( -+ result == 'HelloBEGIN_PARAGRAPHHowdieBEGIN_BLOCKbingoEND_BLOCK') -+ -+ msg = tr_html.HtmlToMessage( -+ 'Hello

    Howdie', 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 = '©  & "<hello>"' -+ unescaped = util.UnescapeHtml(text) -+ self.failUnless(unescaped == u'\u00a9\u00a0 & ""') -+ escaped_unescaped = util.EscapeHtml(unescaped, True) -+ self.failUnless(escaped_unescaped == -+ u'\u00a9\u00a0 & "<hello>"') -+ -+ 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( -+ '''''')) -+ 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 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 ") -+ if attrs: -+ raise exception.UnexpectedAttribute( -+ " 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 root node. Note that the first_ids_file -+ parameter should be relative to the cwd, even though the first_ids_file -+ attribute of the 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 ") -+ -+ 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''' -+ -+ -+ -+ -+ -+ -+ -+ -+ Hello %sJoi, how are you doing today? -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+''' -+ 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''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+''' -+ pseudo_file = StringIO(input) -+ tree = grd_reader.Parse(pseudo_file, '.', stop_after='outputs') -+ # only an child -+ self.failUnless(len(tree.children) == 1) -+ self.failUnless(tree.children[0].name == 'outputs') -+ -+ def testLongLinesWithComments(self): -+ input = u''' -+ -+ -+ -+ -+ This is a very long line with no linebreaks yes yes it stretches on and on and on! -+ -+ -+ -+''' -+ 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''' -+ -+ -+ -+ -+ test -+ -+ -+ -+''' % 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''' -+ -+ -+ -+ -+ test -+ -+ -+ -+ -+ test2 -+ -+ -+ -+''' -+ 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''' -+ -+ -+ -+ -+ -+ Hello! -+ -+ -+ -+ -+''' -+ 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''' -+ -+ -+ -+ -+ -+ -+ Hello! -+ -+ -+ -+ -+ Yellow! -+ -+ -+ -+ -+ -+''' -+ 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'''\ -+ -+ test5 -+ ''' -+ tmp_dir = util.TempDir({'arbitrary_path.grp': arbitrary_path_grd}) -+ arbitrary_path_grd_file = tmp_dir.GetPath('arbitrary_path.grp') -+ top_grd = u'''\ -+ -+ -+ -+ -+ test -+ -+ -+ -+ -+ -+ ''' % arbitrary_path_grd_file -+ sub_grd = u'''\ -+ -+ test2 -+ -+ test3 -+ ''' -+ subsub_grd = u'''\ -+ -+ test4 -+ ''' -+ expected_output = u'''\ -+ -+ -+ -+ -+ test -+ -+ -+ -+ test2 -+ -+ -+ -+ test4 -+ -+ -+ -+ test3 -+ -+ -+ -+ -+ test5 -+ -+ -+ -+ -+ ''' % 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''' -+ -+ -+ %s -+ -+ ''' -+ -+ part_failures = [ -+ (exception.UnexpectedContent, u'fnord'), -+ (exception.UnexpectedChild, -+ u''), -+ (exception.FileNotFound, u''), -+ ] -+ for raises, data in part_failures: -+ data = StringIO(template % data) -+ self.assertRaises(raises, grd_reader.Parse, data, '.') -+ -+ gritpart_failures = [ -+ (exception.UnexpectedAttribute, u''), -+ (exception.MissingElement, u''), -+ ] -+ for raises, data in gritpart_failures: -+ top_grd = StringIO(template % u'') -+ 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''' -+ -+ -+ -+ -+ foo -+ -+ -+ -+ boo -+ -+ -+ -+ ''' % 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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -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 .''' -+ 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'\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 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' % (self.name, attribs, inside_content, -+ self.name) -+ elif content_one_line: -+ return u'%s<%s%s>\n%s %s\n%s' % ( -+ indent, self.name, attribs, -+ indent, inside_content, -+ indent, self.name) -+ else: -+ return u'%s<%s%s>\n%s\n%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='', 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 element). -+ - 'context' is the current output context -+ (the 'context' attribute of the 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 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(" ') -+ -+ 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'\n Hello ' -+ u'<young> $1Joi' -+ u'\n') -+ -+ indented_xml = node.FormatXml(u' ') -+ self.failUnless(indented_xml == u' \n Hello ' -+ u'<young> $1Joi' -+ u'\n ') -+ -+ 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 ") -+ -+ 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"\n ''' Hello" -+ u' <young> $1Joi' -+ u" yessiree '''\n") -+ -+ indented_xml = node.FormatXml(u' ') -+ self.failUnless(indented_xml == -+ u" \n ''' Hello" -+ u' <young> $1Joi' -+ u" yessiree '''\n ") -+ -+ self.failUnless(node.GetNodeById('name')) -+ -+ def testXmlFormatContentWithEntities(self): -+ '''Tests a bug where   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', '', 'bla'), -+ tclib.Placeholder('WHITESPACE', ' ', 'bla'), -+ tclib.Placeholder('END_BOLD', '', 'bla')]), -+ 'BINGOBONGO') -+ xml = msg_node.FormatXml() -+ self.failUnless(xml.find(' ') == -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 = ''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Hello! -+ -+ -+ ''' -+ 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 (, , -+ and ).''' -+ def DefaultAttributes(self): -+ return { -+ 'first_id' : '', -+ 'comment' : '', -+ 'fallback_to_english' : 'false', -+ 'fallback_to_low_resolution' : 'false', -+ } -+ -+ -+class IncludesNode(GroupingNode): -+ '''The element.''' -+ def _IsValidChild(self, child): -+ return isinstance(child, (include.IncludeNode, misc.IfNode, misc.PartNode)) -+ -+ -+class MessagesNode(GroupingNode): -+ '''The element.''' -+ def _IsValidChild(self, child): -+ return isinstance(child, (message.MessageNode, misc.IfNode, misc.PartNode)) -+ -+ -+class StructuresNode(GroupingNode): -+ '''The element.''' -+ def _IsValidChild(self, child): -+ return isinstance(child, (structure.StructureNode, -+ misc.IfNode, misc.PartNode)) -+ -+ -+class TranslationsNode(base.Node): -+ '''The element.''' -+ def _IsValidChild(self, child): -+ return isinstance(child, (node_io.FileNode, misc.IfNode, misc.PartNode)) -+ -+ -+class OutputsNode(base.Node): -+ '''The element.''' -+ def _IsValidChild(self, child): -+ return isinstance(child, (node_io.OutputNode, misc.IfNode, misc.PartNode)) -+ -+ -+class IdentifiersNode(GroupingNode): -+ '''The 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 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 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 -+ and . -+ 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( -+ ''' -+ -+ -+ ''' % (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(''' -+ -+ -+ -+ -+ ''', 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( -+ ''' -+ -+ -+ ''', -+ 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 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*)(?P.+?)(?P\s*)\Z', -+ re.DOTALL | re.MULTILINE) -+ -+# placeholder elements should contain the special character formatters -+# used to format 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 () element. -+_FORMATTERS = lazy_re.compile( -+ '(%s)|(%s)|(%s)' % (_ANDROID_FORMAT, _CHROME_FORMAT, _OTHER_FORMAT)) -+_BAD_PLACEHOLDER_MSG = ('ERROR: Placeholder formatter found outside of ' -+ 'tag in message "%s" in %s.') -+_INVALID_PH_CHAR_MSG = ('ERROR: Invalid format characters found in message ' -+ '"%s" 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 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 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 element: fail if any formatters are detected. -+ if _FORMATTERS.search(item): -+ print(_BAD_PLACEHOLDER_MSG % (item, self.source)) -+ raise exception.PlaceholderNotInsidePhNode -+ text += item -+ else: -+ # Extract the element components. -+ presentation = item.attrs['name'].upper() -+ text += presentation -+ ex = ' ' # example element cdata if present. -+ if len(item.children): -+ ex = item.children[0].GetCdata() -+ original = item.GetCdata() -+ -+ # Sanity check the 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 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 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 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 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(''' -+ -+ -+ Hello %sJoi, how are you doing today? -+ -+ ''') -+ 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("""\ -+ -+ -+ ''' Hello there %s ''' -+ -+ """) -+ 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("""\ -+ -+ -+ Text -+ -+ """) -+ 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(''' -+ -+ -+ A...B.... %sA... B... C... -+ -+ ''') -+ 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''' -+ -+ -+ \uFEFFThis\uFEFF i\uFEFFs OK\uFEFF -+ -+ ''') -+ msg, = root.GetChildrenOfType(message.MessageNode) -+ content = msg.Translate('en') -+ self.failUnlessEqual(u'This is OK', content) -+ -+ def testPlaceholderHasTooManyExamples(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ Hi $1JoiJoy -+ -+ """) -+ except exception.TooManyExamples: -+ return -+ self.fail('Should have gotten exception') -+ -+ def testPlaceholderHasInvalidName(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ Hi $1 -+ -+ """) -+ except exception.InvalidPlaceholderName: -+ return -+ self.fail('Should have gotten exception') -+ -+ def testChromeLocalizedFormatIsInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ This message is missing the ph node: $1 -+ -+ """) -+ except exception.PlaceholderNotInsidePhNode: -+ return -+ self.fail('Should have gotten exception') -+ -+ def testAndroidStringFormatIsInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ This message is missing a ph node: %1$s -+ -+ """) -+ except exception.PlaceholderNotInsidePhNode: -+ return -+ self.fail('Should have gotten exception') -+ -+ def testAndroidIntegerFormatIsInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ This message is missing a ph node: %2$d -+ -+ """) -+ except exception.PlaceholderNotInsidePhNode: -+ return -+ self.fail('Should have gotten exception') -+ -+ def testAndroidIntegerWidthFormatIsInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ This message is missing a ph node: %2$3d -+ -+ """) -+ except exception.PlaceholderNotInsidePhNode: -+ return -+ self.fail('Should have gotten exception') -+ -+ def testValidAndroidIntegerWidthFormatInPhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ %2$3d042 -+ -+ """) -+ except: -+ self.fail('Should not have gotten exception') -+ -+ def testAndroidFloatFormatIsInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ This message is missing a ph node: %3$4.5f -+ -+ """) -+ except exception.PlaceholderNotInsidePhNode: -+ return -+ self.fail('Should have gotten exception') -+ -+ def testGritStringFormatIsInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ This message is missing the ph node: %s -+ -+ """) -+ except exception.PlaceholderNotInsidePhNode: -+ return -+ self.fail('Should have gotten exception') -+ -+ def testGritIntegerFormatIsInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ This message is missing the ph node: %d -+ -+ """) -+ except exception.PlaceholderNotInsidePhNode: -+ return -+ self.fail('Should have gotten exception') -+ -+ def testWindowsETWIntegerFormatIsInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ This message is missing the ph node: %1 -+ -+ """) -+ except exception.PlaceholderNotInsidePhNode: -+ return -+ self.fail('Should have gotten exception') -+ -+ def testValidMultipleFormattersInsidePhNodes(self): -+ root = util.ParseGrdForUnittest("""\ -+ -+ -+ %1$d1 error, %2$d1 warning -+ -+ """) -+ 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("""\ -+ -+ -+ %1$d error, %2$d warning -+ -+ """) -+ except exception.PlaceholderNotInsidePhNode: -+ failed = False -+ if failed: -+ self.fail('Should have gotten exception') -+ return -+ -+ failed = True -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ %1$d1 error, %2$d warning -+ -+ """) -+ except exception.PlaceholderNotInsidePhNode: -+ failed = False -+ if failed: -+ self.fail('Should have gotten exception') -+ return -+ -+ failed = True -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ %1$d %2$d -+ -+ """) -+ except exception.InvalidCharactersInsidePhNode: -+ failed = False -+ if failed: -+ self.fail('Should have gotten exception') -+ return -+ -+ def testValidHTMLFormatInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ <span>$1</span>1 -+ -+ """) -+ except: -+ self.fail('Should not have gotten exception') -+ -+ def testValidHTMLWithAttributesFormatInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ <span attribute="js:$this %">$2</span>2 -+ -+ """) -+ except: -+ self.fail('Should not have gotten exception') -+ -+ def testValidHTMLEntityFormatInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ >%1$d<1 -+ -+ """) -+ except: -+ self.fail('Should not have gotten exception') -+ -+ def testValidMultipleDollarFormatInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ $$1 -+ -+ """) -+ except: -+ self.fail('Should not have gotten exception') -+ -+ def testInvalidDollarCharacterInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ %1$d $ -+ -+ """) -+ except exception.InvalidCharactersInsidePhNode: -+ return -+ self.fail('Should have gotten exception') -+ -+ def testInvalidPercentCharacterInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ %1$d % -+ -+ """) -+ except exception.InvalidCharactersInsidePhNode: -+ return -+ self.fail('Should have gotten exception') -+ -+ def testInvalidMixedFormatCharactersInsidePhNode(self): -+ try: -+ util.ParseGrdForUnittest("""\ -+ -+ -+ %1$2 -+ -+ """) -+ 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 and 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( -+ ' element must be ......') -+ 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 with an empty -+ return super(IfNode, self).ActiveChildren() if cond else [] -+ -+ -+class ThenNode(SplicingNode): -+ """A node. Can only appear directly inside an node.""" -+ pass -+ -+ -+class ElseNode(SplicingNode): -+ """An node. Can only appear directly inside an 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 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 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 nodes. -+ """ -+ unique_names = {} -+ duplicate_names = [] -+ # To avoid false positives from mutually exclusive 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 nodes that are descendants of this node's -+ child and are not enclosed by unsatisfied 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 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 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 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 = ''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ ''' % 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 = ''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ ''' % 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(''' -+ -+ bar -+ -+ bar -+ -+ ''') -+ 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, ''' -+ -+ -+ -+ -+ -+ -+ Hello %sJoi, how are you doing today? -+ -+ Frubegfrums -+ ''') -+ -+ def testImplicitOverlapsPreexisting(self): -+ # second message in will overlap preexisting range -+ self.assertRaises(grit.exception.IdRangeOverlap, -+ util.ParseGrdForUnittest, ''' -+ -+ -+ -+ -+ -+ -+ Hello %sJoi, how are you doing today? -+ -+ Frubegfrums -+ ''') -+ -+ def testPredeterminedIds(self): -+ with _MakeTempPredeterminedIdsFile('IDS_A 101\nIDS_B 102') as ids_file: -+ grd = util.ParseGrdForUnittest(''' -+ -+ -+ -+ -+ -+ Hello %sJoi, how are you doing today? -+ -+ -+ Bongo! -+ -+ ''', 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, ''' -+ -+ -+ -+ -+ -+ Hello %sJoi, how are you doing today? -+ -+ -+ Bongo! -+ -+ ''', predetermined_ids_file=ids_file) -+ os.remove(ids_file) -+ -+ -+class IfNodeUnittest(unittest.TestCase): -+ def testIffyness(self): -+ grd = grd_reader.Parse(StringIO(''' -+ -+ -+ -+ -+ -+ Bingo! -+ -+ -+ -+ -+ Hello! -+ -+ -+ -+ -+ Good morning -+ -+ -+ -+ is_win -+ -+ -+ -+ '''), 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(''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ ''') -+ 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(''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ '''), 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''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Bingo! -+ -+ -+ -+ Bingo! -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ '''), dir='.') -+ -+ def testIfBadChildrenNesting(self): -+ # includes -+ xml = StringIO(r''' -+ -+ -+ -+ -+ -+ -+ -+ -+ ''') -+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) -+ # messages -+ xml = StringIO(r''' -+ -+ -+ -+ -+ -+ -+ -+ -+ ''') -+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) -+ # structures -+ xml = StringIO(''' -+ -+ -+ -+ -+ Bingo! -+ -+ -+ -+ ''') -+ # translations -+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) -+ xml = StringIO(''' -+ -+ -+ -+ Bingo! -+ -+ -+ ''') -+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) -+ # same with nesting -+ xml = StringIO(r''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ ''') -+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) -+ xml = StringIO(r''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ ''') -+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) -+ xml = StringIO(''' -+ -+ -+ -+ -+ -+ Bingo! -+ -+ -+ -+ -+ ''') -+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) -+ xml = StringIO(''' -+ -+ -+ -+ -+ Bingo! -+ -+ -+ -+ ''') -+ self.assertRaises(grit.exception.UnexpectedChild, grd_reader.Parse, xml) -+ -+ -+class ReleaseNodeUnittest(unittest.TestCase): -+ def testPseudoControl(self): -+ grd = grd_reader.Parse(StringIO(''' -+ -+ -+ -+ -+ Hello -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Bingo -+ -+ -+ -+ -+ -+ -+ '''), 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 and elements. -+''' -+ -+from __future__ import print_function -+ -+import os -+ -+from grit import xtb_reader -+from grit.node import base -+ -+ -+class FileNode(base.Node): -+ '''A 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 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 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 = ''' -+ -+ -+ -+ -+ -+ -+ Hello! -+ Hello %sJoi -+ -+ -+ ''' -+ 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(''' -+ -+ -+ -+ -+ -+ -+ -+ -+ Hello! -+ Hello %sJoi -+ -+ -+ '''), 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 = ''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Hello! -+ Hello %s -+ Joi -+ -+ -+ ''' -+ grd = grd_reader.Parse(StringIO(xml), -+ util.PathFromRoot('grit/testdata')) -+ grd.SetOutputLanguage('en') -+ grd.RunGatherers() -+ self.VerifyCliquesContainEnglishAndFrenchAndNothingElse(_GetAllCliques(grd)) -+ -+ def testConditionalOutput(self): -+ xml = ''' -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Hello! -+ -+ -+ ''' -+ 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(''' -+ -+ -+ -+ -+ -+ -+ -+ -+ '''), 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 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 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 and . -+ '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 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( -+ ''' -+ -+ -+ ''' % (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(''' -+ -+ -+ -+ -+ ''', 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(''' -+ -+ -+ ''', 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(('

    Hello!

    \n' -+ 'Some cool things are foo, bar, baz.\n' -+ 'Did you know that 2+2==4?\n' -+ '

    \n' -+ ' Hello!\n' -+ '

    \n'), result) -+ os.remove(filepath) -+ -+ def testGetPath(self): -+ base_dir = util.PathFromRoot('grit/testdata') -+ grd = util.ParseGrdForUnittest(''' -+ -+ -+ ''', 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(''' -+ -+ -+ ''', 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( -+ ''' -+ -+ -+ ''', -+ 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(' -+ -+ ''', 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 element. -+''' -+ -+from __future__ import print_function -+ -+from grit.node import base -+ -+ -+class SkeletonNode(base.Node): -+ '''A 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']+|"[^\"]*"|\'[^\']*\'))?' # 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. -+ # -+ # 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='" -+EnterpriseTabResults="Search results URL" -+EnterpriseTabResultsQuery="Check if search results page supports '&&q='" -+ -+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] -+
    -+
     [TITLE]
    -+
    Google Desktop Search: Search your own computer.

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
      Outlook Email   Netscape Mail / Thunderbird
      Outlook Express   Netscape / Firefox / Mozilla
      Word   PDF
      Excel   Music
      PowerPoint   Images
      Internet Explorer   Video
      AOL Instant Messenger   Even more with these plug-ins
      Text and others
    -+
    -+

    -+ -+ -+ -+ -+ -+
    Getting Started - Learn more about using Google Desktop Search
    Online Help - Up-to-date answers to your questions
    Privacy - A few words about privacy and Google Desktop Search
    Uninstall - How to uninstall Google Desktop Search
    Submit Feedback - Send us your comments and ideas

    Google Desktop Search [$~BUILDNUMBER~$]

    -+[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 @@ -+ -+ -+ -+ -+ -+ Open %s? -+ -+ -+ -+ A simple string. -+ -+ -+ Contains a comment. -+ -+ -+ Another simple string. -+ -+ -+ Do not translate me. -+ -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 @@ -+

    We're sorry, but we don't seem to be compatible.

    -+

    Our software suggests that you're using a browser incompatible with Google Desktop Search. -+ Google Desktop Search currently supports the following:

    -+ -+ -+

    You may click here 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. -+

    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 @@ -+ -+[$~TITLE~$] -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    [$~IMAGE~$] -+   -+ -+ -+ -+ -+
    -+ -+ -+ -+ -+
     [$~CHROME_TITLE~$]
    -+
    -+ -+ -+ -+ -+ -+
    -+ [$~BODY~$] -+
    -+[$~FOOTER~$] -+ -\ 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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Copyright 2008 Google Inc. All Rights Reserved. -+ -+ -+ 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. -+ -+ -+ -+ -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 @@ -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+
    This is one version of -+[URL-DISP] from your personal cache.
    -+The page may have changed since that time. Click here for the current page.
    -+Since this page is stored on your computer, publicly linking to this page will not work.[$~EXTRA~$]

    -+Google may not be affiliated with the authors of this page nor responsible for its content. This page may be protected by copyright. -+
    -+ -+ -+


    -+ -\ 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 @@ -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+
    This is one version of [URL-DISP] -+from your personal cache.
    -+The file may have changed since that time. Click here for the current file.
    -+Since this file is stored on your computer, publicly linking to it will not work.[$~EXTRA~$]

    -+Google may not be affiliated with the authors of this page nor responsible for its content. This page may be protected by copyright. -+
    -+
    -+ -+ -+
    -+ -\ 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] -+ -+ -+
    [$~STARTCHAT~$]
    -+
    -+
    -+   [$~TITLE~$] -+

    Participants: [USERNAME], [BUDDYNAME]
    -+Date: [TIME]
    -+
    -+ -+
    -+ -+ -+
    [$~STARTCHAT~$]
    -+ -+ -+[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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ New background app installed -+ -+ -+ $1Background App will launch at system startup and continue to run in the background even once you've closed all other $2Google Chrome windows. -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -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 @@ -+ -+ -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 @@ -+ -+ -+ -+
     Remove checked results and return to search.Check all - Uncheck all   -+ -+
    -+

    [$~BOTTOMLINE~$] - ©2005 Google
    -+ -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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    Go to Google Desktop Search  -+ -+ -+ -+ -+
    -+ -+ -+ -+ -+ -+
     Remove Specific ItemsHelp  
    -+
    -+ -+ -+ -+ -+ -+ -+
     Remove checked results and return to search.Check all - -+Uncheck all  
    -+
    -+ -+ -+ -+ -+
     Remove -+checked items from Google Desktop Search. Other copies of the same items will not be -+affected.
    -+ If you view the item again, it will be added back to Google Desktop Search.
    -+
    -\ 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 @@ -+ -+Database Deleted -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+
    Google Desktop Search -+

    -+
    The database has been deleted. Click here to continue.
    -+ -+ -+
    [$~BOTTOMLINE~$]

    -+

    ©2005 Google

    -\ 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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -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 -+ -+

    This documentation is not yet available

    -+

    -+[$~BOTTOMLINE~$] - ©2005 Google -+
    -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 @@ -+ -+ -+ -+ -+ -+ Hello %sJoi, how are you doing today? -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -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] -+ -+ -+
    [CONV] -+Reply | Reply to All[$~FORWARD_URL~$] | Compose[$~OUTLOOKVIEW~$] -+
    -+
    -+
    -+   [SUBJECT] -+

    [FROM-DISP] -+[TO-DISP] -+[CC-DISP] -+[BCC-DISP] -+[REPLYTO-DISP] -+[DATE-DISP] -+[VIEW-DISP] -+[$~ATTACH~$] -+

    -+

    -+ -+
    -+ -+
    [CONV] -+Reply | Reply to All[$~FORWARD_URL~$] | Compose[$~OUTLOOKVIEW~$] -+
    -+ -+ -+[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] -+
    -+   [SUBJECT]

    -+ -+[CONTENTS] -+
    -+
    -+[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] -+
    -+
    -+[ERROR]

    -+If you think this is an error, please contact us. -+
    -+[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] -+ -+[WEB_TOP_CHROME] -+[$~STATUS~$] -+[$~MESSAGE~$] -+[WEB_FILES] -+
    [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 @@ -+


    -+
    -+ -+ -+

    -+ -+ -+ -+
    [$~BOTTOM~$]

    -+
    -+

    -+[$~BOTTOMLINE~$] - ©2005 Google
    -+[SCRIPT] -+ -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 @@ -+ -+ -+ -+Salut! -+Salut -+Supprime&r -+Activer la barre de favoris -+Déconnexion du réseau privé -+ sur  -+Déconnecter ce compte... -+&Vérifier l'orthographe dans ce champ -+Aucune donnée reçue. -+Une erreur s'est produite lors de la tentative de lecture du fichier : . -+Le mot de passe multiterme est obligatoire. -+Importer les données d'un autre navigateur... -+Saisie automatique -+API P2P -+Exécuter automatiquement (recommandé) -+Le certificat de sécurité du site a expiré ! -+Clé publique de l'objet -+Importer -+Afficher dan&s un onglet -+ID : -+Le certificat n'indique aucun mécanisme permettant de vérifier s'il a été révoqué. -+Touches de modification... -+Signé par : -+Utiliser un service Web pour résoudre les erreurs de navigation -+Guillemet -+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é. -+Autoriser le stockage des données locales (recommandé) -+Ouvrir dans une fenêtre -+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. -+&Rechercher : -+Échec de génération de clé privée RSA aléatoire -+Certificat en attente -+Technologie réseau : -+Le certificat du serveur ne figure pas dans le DNS. -+Demander le mot de passe au retour de veille -+Désactiver la synchronisation -+Base de données indexée -+Ne pas enregistrer -+ 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. -+Le délai imparti à l'opération est dépassé. -+Nordique -+Créer des raccourcis vers des applications -+Pas encore chargé -+Confirmer le nouveau code PIN : -+L2TP/IPSec + Certificat utilisateur -+Préfecture -+Extraction de l'image de récupération... -+Terminé -+Il se peut que la page Web à l'adresse soit temporairement inaccessible ou qu'elle ait été déplacée de façon permanente à une autre adresse Web. -+Cache des scripts -+Barre d'outils Google -+Importés depuis Safari -+Le plug-in n'est pas autorisé. -+Vous exécutez à 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. -+Certificat du serveur SSL -+Z&oom arrière -+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. -+Mise à jour du système : % téléchargés -+Les informations d'identification associées au partage de vos imprimantes via sont arrivées à expiration. Cliquez ici pour saisir à nouveau votre nom d'utilisateur et votre mot de passe. -+Erreur de définition du paramètre de confiance du certificat -+ peut maintenant synchroniser vos mots de passe. -+Sélectionnez le certificat à présenter pour l'identification : -+ a planté. Cliquez sur cette info-bulle pour actualiser l'extension. -+Compatibilité expérimentale avec des méthodes Wi-Fi Extensible Authentication Protocol supplémentaires, telles que EAP-TLS et LEAP. -+Échec de lecture de la clé privée -+Les plug-ins suivants ont été bloqués sur cette page : -+Configuration de la synchronisation -+Case d'option cochée -+Très petite -+URL de révocation de l'autorité de certification Netscape -+Style de pavé numérique -+Active les feuilles de style CSS 3D et la composition graphique haute performance des pages Web via le processeur graphique. -+&Rétablir -+Redémarrer -+La connexion n'est pas compressée. -+Fin -+&Nouvelle fenêtre -+Configuration automatique du proxy -+Afficher l'orthographe et la grammaire -+Aucune imprimante n'a été trouvée. Veuillez en installer une. -+Saisissez les caractères visibles dans l'image ci-dessous. -+Certificat d'authentification de client SSL incorrect -+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. -+Rechercher le précédent -+I&nspecter l'élément -+État d'itinérance : -+&Non -+Effacer les données de navigation... -+Nombre maximal de suggestions -+L'accessibilité est désactivée. -+Sélectionner par domaine -+Tout réduire... -+Ne jamais traduire les pages rédigées en -+Non confirmé -+Avant de vous connecter, démarrez une session en tant qu'invité afin d'activer le réseau . -+La gravure de l'image est terminée. -+Si vous supprimez le certificat d'une autorité de certification, votre navigateur ne fera plus confiance aux certificats émis par cette autorité de certification. -+Synchronisez toutes les données de cet ordinateur ou sélectionnez celles que vous souhaitez synchroniser. -+ hours ago -+Domaine : -+Aperçu -+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. -+Ignorer le verrouillage des majuscules et saisir des minuscules par défaut -+Aucune parole détectée -+Changer de moteur de recherche par défaut -+Cliquer pour revenir en arrière, maintenir pour voir l'historique -+ secondes restantes -+Unicode -+Ouverture à la fin du téléchargement -+Les extensions, les applications et les thèmes peuvent endommager votre ordinateur. Voulez-vous vraiment continuer ? -+Schéma du pinyin double -+Déconnecter ce compte... -+&Fichier -+Microsoft Internet Explorer -+Aucune correspondance trouvée -+État de votre commentaire -+Mise à jour terminée. Veuillez redémarrer le système. -+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é. -+Essayez d'ajouter -+ -+ 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. -+Réseaux sans fil -+Masquer -+Zoom arrière -+Méthode EAP : -+Développer -+Veuillez vous connecter -+de n'importe quand -+Désactiver la validation des formulaires interactifs HTML5 -+Le suivi de votre position géographique sur cette page a été bloqué pour les sites suivants : -+La gravure de l'image a été interrompue. -+&Afficher dans le dossier -+Continuer à bloquer JavaScript -+Enregistrer la page sous... -+Le serveur à l'adresse requiert un nom d'utilisateur et un mot de passe. -+Exceptions de géolocalisation -+13px -+Contenu : -+Le plug-in a été bloqué, car il n'est plus à jour. -+Taille ré&elle -+Échec de la connexion au serveur proxy. -+Vérification de pilote matériel Microsoft Windows -+Paysage -+Détecter automatiquement -+Page -+Nom d'utilisateur : -+Nous aider à améliorer en envoyant automatiquement les statistiques d'utilisation et les rapports d'erreur à Google -+Réseau -+Connexion en cours -+Ajouter tous les onglets aux favoris... -+Onglets ou fenêtres -+Sites récemment consultés -+Ouvrir dans une fenêtre de navigation privée -+Préférences de saisie automatique... -+Compteur d'images par seconde -+Mot de passe -+Afficher le compte -+ : -+Le suivi de votre position géographique a été bloqué pour cette page. -+Connexion à l'aide de votre compte Google -+Le mot de passe multiterme entré est incorrect. -+télécopie : # -+Le navigateur par défaut est actuellement . -+ secondes restantes -+Mot de passe précédent -+Code PIN incorrect -+Modifier l'adresse -+Zoom avant -+Micrologiciel -+Une erreur s'est produite lors de l'affichage de cette page Web. Pour continuer, actualisez cette page ou ouvrez-en une autre. -+Récupération de clé Microsoft -+recto verso -+Fichier ou répertoire introuvable -+Aucun forfait de données actif -+Tout sélectionner -+Le fichier manifeste est incorrect. -+Les pages suivantes ne répondent plus. Vous pouvez attendre qu'elles soient de nouveau accessibles ou les supprimer. -+ minutes restantes -+Le certificat du serveur n'est pas encore valide. -+Menu contenant des extensions masquées -+Enregistrer les infos -+Configuration de l'accès à distance à cet ordinateur. -+Point -+Ajouter un moteur de recherche -+Impossible d'atteindre le serveur. -+Importer mes favoris... -+Enregistrer le &cadre sous... -+Vous n'êtes pas autorisé à accéder à la page Web . Votre connexion peut être requise. -+Envoyer la capture d'écran du dernier onglet actif -+Erreur -+Utiliser le thème GTK+ -+Ouvrir une fenêtre du navigateur -+ a planté. Cliquez sur cette info-bulle pour redémarrer l'application. -+Définir comme navigateur par défaut -+Certificat de courrier électronique -+Clavier en superposition -+La connexion est compressée avec . -+Exporter mes favoris... -+Format : -+Ignorer -+Déplacer un mot -+Index de -+Mémoire -+Impossible d'utiliser cette langue pour corriger l'orthographe. -+Recherche -+Ajouter une autre carte de paiement... -+Envoyer la dernière capture d'écran enregistrée -+Ce fichier contient du code malveillant. Voulez-vous vraiment continuer ? -+Erreur de synchronisation... -+Clavier brésilien -+Utiliser TLS 1.0 -+&Signaler un problème... -+Créer des raccourci&s vers des applications... -+Le certificat "" a été émis par : -+ 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 gestionnaire des extensions. -+Logiciels malveillants -+Mise en page ou mise en forme de la page -+Acheter davantage... -+Traduire -+Tout -+Créé : -+Annuler l'importation -+Le mode indiqué est incorrect. -+ copié(s) sur -+L'accès à distance à cet ordinateur est activé pour . -+Options... -+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. -+Toujours &afficher la barre de favoris -+Erreur SSL -+Confirmer les préférences de synchronisation -+Utiliser les valeurs par défaut -+Code secret manquant -+L'accès à distance à cet ordinateur est désactivé. -+API des extensions expérimentales -+Inclure les informations système -+Date d'expiration -+Autorité de certification compromise -+À propos de la saisie automatique -+Activer la fonction "Taper pour cliquer" -+Accès à la page Web refusé -+&Gestionnaire de favoris -+Erreur serveur -+Cette carte SIM est désactivée et ne peut être utilisée. Veuillez demander à votre fournisseur de services de la remplacer. -+Outils -+Clavier néerlandais -+EAP-TTLS -+Choisissez une image à associer à votre compte. Celle-ci s'affichera sur l'écran de connexion. -+Configurer le blocage des fenêtres pop-up... -+des 4 dernières semaines -+Une situation inattendue s'est produite tandis que le serveur tentait de traiter la demande. -+Impossible d'afficher certaines parties de ce document PDF. Souhaitez-vous l'ouvrir dans Adobe Reader ? -+Proxy FTP -+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é. -+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. -+Activer l'onglet 1 -+Communication à distance -+Importer les favoris et les paramètres... -+À propos -+Modifier le favori de cette page -+Ajouter un format d'exception -+Configurer l'accès à distance... -+Supprimer le certificat "" ? -+Cache des images -+Configuration du proxy -+En l'absence de connexion Wi-Fi, Google Chrome utilise les données 3G. -+Appuyez sur pour sélectionner le mode de saisie précédent. -+La création du support de récupération du système d'exploitation a été annulée. -+Le plug-in suivant est bloqué : -+Erreur de connexion réseau -+Mot de passe multiterme -+Internet -+Configurer les paramètres de blocage des plug-ins... -+Afficher dan&s un onglet -+Synchroniser vos mots de passe -+Le serveur proxy agit comme un intermédiaire entre votre ordinateur et les autres serveurs. Votre configuration système utilise actuellement un proxy, mais -+ -+ ne parvient pas à s'y connecter. -+Sélectionner par type d'application -+Procéder à l'i&nspection de l'élément -+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. -+Impossible de déplacer le répertoire d'extensions dans le profil. -+Supprimer ces paramètres pour les prochaines visites -+L'accessibilité est activée. -+Appuyer sur la touche Espace pour sélectionner la suggestion -+Vos favoris -+ () -+Fermer les onglets -+Applications en arrière-plan -+Favoris -+Supprimer les données synchronisées de Google Dashboard -+Veuillez patienter pendant que installe les dernières mises à jour système. -+Utilisez les touches Maj gauche et droite pour sélectionner les 2e et 3e propositions -+WEP -+Mode Zhuyin complet. La sélection automatique de la suggestion et les options associées sont désactivées ou ignorées. -+&Paramètres linguistiques... -+CRX-less Web Apps -+Connexion au réseau -+Reliure bord long -+ utilise les paramètres proxy du système pour se connecter au réseau. -+Application : -+&Descendre -+? Toutes les données présentes sur le périphérique seront supprimées. -+Adresse IP -+Active le nouveau design de la page "Nouvel onglet" (en cours de développement). -+Échec de l'activation -+Ne pas vérifier -+Le chinois simplifié est le mode de saisie initial -+Paramètres SSL sur tout l'ordinateur : -+Votre connexion à n'est pas chiffrée. -+matériel requis -+Gérer les exceptions... -+Rechercher des mises à jour -+Utiliser un mot de passe multiterme pour chiffrer mes données de synchronisation -+Connexion en cours... -+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. -+Historique -+Destination -+Web audio -+Cookies placés par cette page -+Accès partagé -+Afficher... -+Veuillez vous connecter à . -+Ajouter un nouvel e-mail -+Personnaliser les polices... -+Matériel : -+Erreur de connexion au réseau. -+Cette page Web est introuvable. -+En&registrer la vidéo sous... -+Le certificat du serveur n'est pas approuvé. -+Cop&ier l'image -+Sebeol-sik 390 -+ mins ago -+Autre réseau mobile... -+Erreur HTTP () : -+Signature du code -+Aide -+<sans nom> -+Version du micrologiciel : -+La page ne se charge pas -+Attention, ces fonctionnalités expérimentales peuvent mordre. -+Verrouiller l'écran ou éteindre -+La connexion est chiffrée au moyen de , avec pour l'authentification des messages et pour la méthode d'échange de clés. -+Clavier portugais -+Importation -+Connexion au réseau -+Langues -+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. -+ Dans ce cas, une intervention -+ est requise sur le serveur. -+ -+ n'utilise pas de connexion non sécurisée pour protéger la confidentialité -+ de vos données. -+La synchronisation de vous permet de partager vos données (favoris, préférences) sur vos ordinateurs en toute simplicité. Pour ce faire, enregistre vos données en ligne via Google lorsque vous vous connectez à votre compte. -+Format ou mise en forme de la page -+Clavier suisse -+En&registrer l'image sous... -+Basculer en mode pleine chasse ou demi-chasse -+Sélectionner... -+Effacer les paramètres d'ouverture automatique -+Ajouter un réseau privé -+ secondes -+Ne pas envoyer de capture d'écran -+Hébreu -+Toujours afficher la barre de favoris -+Impossible de trouver le chemin d'accès absolu du répertoire à empaqueter. -+Créer un profil -+Diners Club -+Paramètres de contenu... -+Activer la recherche instantanée pour accélérer la recherche et la navigation ? -+Plus d'extensions >> -+La valeur d'entrée de la clé privée est obligatoire. -+PEAP -+Fonctionnalités de localisation expérimentales -+Ou&vrir la vidéo dans un nouvel onglet -+Toujours afficher la barre de favoris -+Reliure -+Utilisation de la clé du certificat -+Clavier belge -+ heures restantes -+Afficher toutes les images (recommandé) -+Cliquez sur -+ Démarrer, -+ puis sur -+ Exécuter. -+ Saisissez -+ et cliquez sur -+ OK. -+Nom de volume -+Reliure bord court -+ va configurer les mises à jour automatiques pour tous les utilisateurs de cet ordinateur. -+Effacer les éléments datant : -+Interdire à tous les sites d'afficher des notifications sur le Bureau -+Appuyez sur pour passer d'un mode de saisie à l'autre. -+Ajouter un nouveau fax -+Recherche de contenu en cours... -+Style d'entrée avec Espace -+Vous avez tenté d'accéder à , mais, au lieu de cela, vous communiquez actuellement avec un serveur identifié comme . 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 , donc potentiellement préjudiciable. Nous vous déconseillons vivement de continuer. -+Masquer le bouton -+Modifier l'image -+Ne pas synchroniser mes mots de passe -+JavaScript -+Activer les notifications de -+Afficher les incompatibilités -+Nouvel onglet -+Impossible de charger l'icône "" d'action de page. -+Fermer l'onglet -+Nom d'hôte du serveur : -+Ajouter une carte de paiement -+ : type de fichier inconnu -+Non défini -+Gérer les paramètres de saisie automatique... -+Respecter la &casse -+Autoriser tous les sites à exécuter JavaScript (recommandé) -+Gérer vos périphériques depuis le cloud -+Clavier catalan -+Me demander lorsqu'un site essaie de stocker des données -+Menu -+Sélectionnez le périphérique de stockage amovible à utiliser. -+La traduction a échoué, car nous n'avons pas pu déterminer la langue de la page. -+Vous pouvez créer des profils supplémentaires pour autoriser plusieurs personnes à utiliser et personnaliser Google Chrome. -+Deux-points -+Toujours traduire en -+(désactivée) -+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 et non avec un pirate informatique ayant émis son propre certificat en prétendant être . Nous vous déconseillons vivement de continuer. -+ requiert que vous cryptiez vos données à l'aide de votre mot de passe Google ou de votre propre mot de passe multiterme. -+ -+N'est pas une autorité de certification -+Date et heure : -+Impossible de charger "" pour le thème. -+Rechercher à nouveau -+En attente de ... -+Masquer ce plug-in -+Enregistrer &sous... -+Ajouter une langue : -+Annuler -+Ouvrir une &adresse... -+Supprimer le mot -+Vous devez saisir deux fois le même mot de passe multiterme. -+Ouvrir dans un onglet épinglé -+Page Web, tout type de contenu -+Activer -+Considérer ce certificat comme fiable pour identifier les développeurs de logiciels. -+Ajouter tous les onglets aux favoris -+Impossible de créer le dossier de favoris. -+Copier l'URL -+Si vous pensez ne pas avoir à utiliser de serveur proxy, procédez comme suit : -+ -+Définir les utilisateurs autorisés à se connecter à un périphérique et autoriser les sessions de navigation en tant qu'invité -+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. -+Pool restreint : -+&Rafraîchir cette page -+&Normal -+PKCS #1 SHA-384 avec chiffrement RSA -+Numéro de série : -+HTTPS/SSL -+Toutes les données de votre ordinateur et des sites Web que vous visitez -+Ceci est une installation secondaire de et ce dernier ne peut pas être défini comme navigateur par défaut. -+Erreur lors de la signature de l'extension -+ permet d'effectuer des recherches sur Internet à l'aide du champ polyvalent. Sélectionnez le moteur de recherche à utiliser : -+Sécurité : -+Adobe Reader n'est pas à jour -+Extensions ou applications -+Effacer les cookies et autres données de site et de plug-in lorsque je ferme le navigateur -+Téléchargement de l'image terminé. -+Favoris -+Afficher les vignettes -+Ne plus afficher ce message -+Agent de récupération de clé Microsoft -+Page de diagnostic de navigation sécurisée -+Adresse ligne 2 -+Décrivez-nous le problème recontré. (Champ obligatoire) -+Cela peut prendre quelques minutes. -+Options de base -+Niveau de zoom par défaut : -+Stockage externe -+Modifi&er les moteurs de recherche... -+Cartes de paiement -+Nom du fichier -+Arrêter la synchronisation -+Actualiser le cadre -+Connexion -+Mémoire privée -+Tout effacer -+Activer la correction orthographique automatique -+Afficher les &infos sur la page -+favoris_.html -+Autoriser uniquement les utilisateurs suivants à se connecter : -+&Muet -+Nouvel ongle&t -+Erreur de synchronisation -+Zoom -+ sur -+Type de clavier -+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. -+Déplacer -+Mode de saisie du vietnamien (VIQR) -+Ajouter la page actuelle aux favoris -+<aucun cookie sélectionné> -+Le mot de passe de l'application est incorrect. -+Sélectionner "un mot à la fois" -+Tout bloquer -+Chargement en cours… -+&Rouvrir la fenêtre fermée -+Gérer les exceptions... -+URL -+Katakana demi-chasse -+Ouvrir une adresse -+Données de navigation -+Code PIN incorrect. Veuillez réessayer. -+Non -+Utiliser cette langue pour corriger l'orthographe -+Fermer les autres onglets -+Clé privée non valide. -+Une erreur réseau s'est produite pendant la communication avec le service de gestion des périphériques. -+Importer les données d'un autre navigateur... -+&Ajouter au dictionnaire -+Voulez-vous vraiment ouvrir  onglets ? -+Fabricant du périphérique : -+Afficher l'historique complet -+Illimité -+Clavier Neo2 allemand -+Liste déroulante -+Clé : -+Coller en texte brut -+Afficher les &commandes -+Cette page suit votre position géographique. -+Certificat de chiffrement de courrier électronique -+&Profilage activé -+En savoir plus sur la récupération du système -+Essentielle -+Type MIME -+Autoriser le téléchargement ? -+Clavier croate -+Interdire à tous les sites de suivre ma position géographique -+Utiliser la barre de titre du système et les bordures de la fenêtre -+En bas à gauche -+Informations sur l'erreur : -+Essayez : saisissez "orchidées", puis appuyez sur Entrée. -+Pour utiliser Chrome Web Store, vous devez être connecté à un compte Google. -+Configurer -+Petit problème... Nous n'avons pas réussi à vous authentifier. Veuillez vérifier vos identifiants de connexion puis réessayer. -+Pour gérer à distance la configuration de ce périphérique depuis le cloud, connectez-vous avec votre compte Google Apps. -+Réactiver -+Fermer la fenêtre -+Présentation -+Vous avez saisi un trop grand nombre de codes PIN incorrects. Veuillez contacter pour obtenir une nouvelle clé de déverrouillage du code PIN à 8 chiffres. -+Bloquer le contenu inapproprié -+Configuration avancée -+Utiliser SSL 3.0 -+Règles de confidentialité liées à la navigation sécurisée -+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. -+Ouvrir le lien dans une nouvelle &fenêtre -+Les cookies de ont été bloqués. -+Copies -+&Ouvrir le fichier audio dans un nouvel onglet -+Afficher les noms d'utilisateurs et leur photo sur la page de connexion -+URL de révocation de certificat Netscape -+Ajoute des options de regroupement des onglets dans le menu contextuel des onglets. -+Clavier finnois -+Vérifiez votre connexion Internet. Redémarrez votre routeur, votre modem -+ ou tout autre périphérique réseau que vous utilisez. -+&Afficher le code source de la page -+Le serveur ne prend pas en charge la version HTTP utilisée dans la demande. -+Vos mots de passe enregistrés s'afficheront ici. -+Fermer les onglets sur la droite -+ heures -+ -+ 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 -+ , -+ vous devez configurer les produits ESET de manière à éviter ces -+ interceptions. Cliquez sur le lien En savoir plus pour obtenir des instructions. -+Les requêtes adressées au serveur ont été temporairement limitées. -+Impossible de télécharger l'image. Gravure annulée. -+Activer/désactiver le mode Hanja -+Effacer les paramètres d'ouverture automatique -+Pas après le -+Manifeste : -+Visa -+Clavier anglais canadien -+Récupération de fichier Microsoft -+En&registrer l'image sous... -+Zone -+Chemin : -+Prendre la photo -+Inactif -+Certains de vos certificats enregistrés identifient ces autorités de certification : -+Impossible d'afficher la page Web, car le serveur n'a envoyé aucune donnée. -+Considérer ce certificat comme fiable pour identifier les utilisateurs de messageries. -+Ce type de fichier peut endommager votre ordinateur. Voulez-vous vraiment télécharger  ? -+Importés depuis la barre d'outils Google -+Recherche instantanée et saisie semi-automatique -+Tout synchroniser -+Dvorak (Hsu) -+Exécuter la commande  : -+Le fichier contenait un certificat, qui n'a pas été importé : -+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. -+Impossible de détecter les modules chargés. -+Cette fonctionnalité affiche une bordure autour des couches de rendu afin de déboguer et d'étudier leur composition. -+Processus de traitement Web : -+Invité -+Nom du fichier : -+Un élément est manquant. -+Échec de la tentative de connexion au serveur. -+Des fenêtres pop-up ont été bloquées sur cette page. -+Personnaliser les langues et la saisie... -+Proxy HTTP -+Mot de passe multiterme de chiffrement -+Nouvelle fenêtre -+Certains certificats provenant de ces organisations vous identifient : -+Autoriser l'itinérance des données mobiles -+Alt -+Utiliser les pages actuelles -+Version -+En&registrer le fichier audio sous... -+Itinérance -+Romaji -+ minutes -+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) -+Autoriser tous les sites à afficher des notifications sur le Bureau -+Ouvrir le fichier... -+Changer de fenêtre -+Chèvres téléportées -+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. -+Le certificat du serveur ne correspond pas à l'URL. -+Commune -+Impossible d'activer les plug-ins désactivés par une stratégie d'entreprise. -+Ne pas afficher sur cette page -+Méthodes EAP en Wi-Fi expérimentales -+Disque dur -+Nouveau dossier -+intégration sur -+Autorité de certification de messagerie -+Cache CSS -+En haut à gauche -+Saisissez le nom du nouveau dossier. -+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. -+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. -+pause -+Afficher tous les téléchargements... -+Connexion à -+Impossible de résoudre l'adresse DNS du serveur. -+En attente de l'affichage du cache -+Thèmes -+Impossible de se connecter au serveur proxy. -+Ignorer la synchronisation des données chiffrées ? -+Oui -+Nombre de suggestions par page -+Modifier le code PIN -+Un problème est survenu lors de la copie de l'image de récupération sur le périphérique. -+ jours restants -+La connexion Internet a été interrompue. -+Début -+« Précédent -+C&opier l'URL de la vidéo -+Vos données sur et -+Langue -+Mappages des stratégies de certificat -+ minutes restantes -+Sélectionner la position -+Le site Web à l'adresse 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. -+ -+Hors de portée -+Effacer l'historique de navigation -+Aucune page Web trouvée à l'adresse : -+Nouveau moteur de recherche : -+Jamais pour ce site -+ jours restants -+Impossible d'accéder au réseau. -+Modifier l'adresse -+&Masquer le panneau de la vérification orthographique -+1 cookie -+Activer ces fonctionnalités... -+ secondes restantes -+Cou&per -+Fermer la barre de recherche -+Signataire de la liste de révocation de certificats -+Afficher des informations à propos du site -+UC -+Fermer les pages -+MSPY -+ () -+Très petite -+Afficher les réseaux privés dans le menu Réseau pour activer la connexion à un VPN -+Vérification de la mise à jour du système... -+L'entrée demandée est introuvable dans le cache. -+Connectez-vous à pour importer le certificat client. -+Modifier la mise en page -+Se déconnecter -+Espace insuffisant. -+Préférences synchronisées -+Imp&rimer le cadre... -+MSCHAP -+Continuer à bloquer les images -+Lorsqu'un site utilise des plug-ins : -+Échec d'exportation de la clé publique -+Choisir les éléments à synchroniser -+Paramètres de saisie du Pinyin -+Ouvrir les pages suivantes : -+Déconnexion -+Moteurs de recherche -+Erreur de suppression de certificat -+Ne rien faire -+Épingler l'onglet -+réinitialiser la synchronisation -+PKCS #1 SHA-256 avec chiffrement RSA -+Katakana -+Choisir un réseau mobile -+&Historique -+Mode développeur : -+Lien -+Système -+Sélectionnez le fichier de clé privée. -+Insérez une carte SD ou une carte mémoire USB. -+Temps restant : -+Cliquez ici pour exécuter le plug-in . -+Hanyu -+pages -+Configuration en cours -+Erreur inconnue -+ days ago -+La langue utilisée pour Google Chrome est passée de "" à "" après la synchronisation de vos paramètres. -+&Aucune suggestion orthographique -+Erreur de protocole SSL -+Mode de saisie du thaï (clavier Pattachote) -+Ce compte utilisateur n'est pas compatible avec ce service. -+ ne peut pas être exécuté en tant que root. -+C&oller -+Paramètres de contenu... -+Dimensions de l'image -+Sélectionner le fichier à enregistrer sous -+Disque Flash -+Moteur de recherche actuel : -+L'identité de situé à a été vérifiée par . -+Version de l'autorité de certification Microsoft -+Enregistrer une capture d'écran -+Page sur -+Console &JavaScript -+Réponse reçue incorrecte lors de la tentative de chargement de . -+ Cela peut être dû à une opération de maintenance ou à une configuration incorrecte sur le serveur. -+Serveur DNS : -+Pré-rendu : -+Impossible d'accéder à la bibliothèque réseau. -+Des images ont été bloquées sur cette page. -+Toujours autoriser à afficher les images -+Copier l'adr&esse du lien -+Ouvrir dans une fenêtre de navigation privée -+Mode développeur -+Chiffrement des données -+Activer la protection contre le phishing et les logiciels malveillants -+Appuyez sur pour rechercher sur -+Voulez-vous utiliser () au lieu de pour gérer les liens :// à partir de maintenant ? -+Démarrer une session en tant qu'invité -+Nombre maximal de caractères chinois dans la mémoire tampon de pré-édition, notamment les entrées de symboles Zhuyin -+Barre de menus GNOME expérimentale disponible -+Effacer l'historique des téléchargements -+Navigateur bloqué... -+Parenthèse drte -+Calcul du temps de chargement -+Ajouter une carte de paiement... -+Itinérance : -+Arrêter -+Erreur de synchronisation... -+ID de clé de l'autorité de certification -+Extensions -+Imprimez où que vous soyez. -+Un problème est survenu lors du téléchargement de l'image de récupération. -+Cloud Print Proxy -+Une erreur s'est produite lors de la configuration de la synchronisation. -+Fichiers personnalisés -+Impossible de définir le mode une fois la fenêtre créée. -+Date et heure -+Personnaliser les préférences de synchronisation -+Aucune information disponible sur le forfait -+Signaler un problème -+Importer mes favoris et paramètres -+Sélectionnez le menu clé à molette > Paramètres > Options avancées > Modifier les paramètres du proxy et vérifiez que vos paramètres sont définis sur "sans proxy" ou "direct". -+Fermer et annuler les téléchargements -+Autres moteurs de recherche -+ peut maintenant synchroniser vos mots de passe. Pour protéger vos données, vous devez confirmer les informations relatives à votre compte. -+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. -+L'identité de ce site Web n'a pas été vérifiée. -+Sessions à l'étranger -+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. -+Afficher les &infos sur le cadre -+Connecté -+Paramètres d'entrée en Chewing -+Conserver sur cette page -+Votre support de récupération est prêt. Vous pouvez le retirer du système. -+Copi&er l'adresse e-mail -+Interdire à tous les sites d'afficher des fenêtres pop-up (recommandé) -+Console &JavaScript -+Empêcher cette page de générer des boîtes de dialogue supplémentaires -+En haut à droite -+Entrez le mot de passe associé à votre application : -+Mode de saisie Google du japonais (pour clavier japonais) -+ est à jour () -+Le serveur a mis fin à la connexion de manière inattendue. -+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, -+ a temporairement -+ bloqué l'acceptation des requêtes adressées au serveur. -+ -+ 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 , -+ sur laquelle vous pourrez trouver plus d'informations ou désactiver cette fonctionnalité. -+Gestionnaire de &fichiers -+Arrêter la synchronisation du compte -+Ajouter -+Sélectionnez le fichier à ouvrir -+Aucun forfait de données -+Créer un compte Google -+Code postal -+&Gestionnaire de tâches -+Téléphone -+Sélectionner l'onglet précédent -+Sélectionner un certificat -+Mots de passe enregistrés -+Autoriser l'accès aux URL de fichier -+Modifi&er les moteurs de recherche... -+Gérer les moteurs de recherche... -+PKCS #7, chaîne de certificats -+Clé pré-partagée : -+Contrôlez et partagez l'accès à vos imprimantes depuis n'importe quel compte Google. -+Ajoute l'option "Utiliser les onglets latéraux" 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. -+S'inscrire -+, -+Saisissez un nouveau nom. -+ ne peut pas être affiché dans cette langue. -+Erreur : impossible de décoder l'extension. -+Clavier bulgare -+Accès aux informations de l'autorité -+Ajouter un réseau privé... -+C&opier l'URL du fichier audio -+Masque de sous-réseau : -+Impossible d'accéder à votre compte ? -+Utiliser les touches - et = pour paginer une liste d'entrées -+Le serveur ne prend pas en charge l'extension de renégociation TLS. -+Résolution de l'hôte... -+Récemment fermés -+Le certificat de sécurité du site a été signé avec un algorithme de signature faible. -+Votre mot de passe a été modifié -+Gérer les paramètres de saisie automatique... -+La page Web n'est pas disponible pour le moment. Cela peut être dû à une surcharge ou à une opération de maintenance. -+Éjecter -+Impossible de lancer le processus de service. -+ Mo restants -+Nouvelle fenêtre -+Sélectionnez -+ -+ Applications > Préférences système > Réseau > Avancé > Proxys -+ -+ et désélectionnez les serveurs proxy sélectionnés. -+Gravure en cours d'initialisation... -+Téléchargement de l'image de récupération... -+il y a  minutes -+Mode de saisie du chinois (cangjie) -+Comté -+Ouvrir l'adresse dans un nouvel onglet -+&Rouvrir l'onglet fermé -+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. -+Certificat utilisateur : -+Autoriser -+Appuyer sur Entrée pour revenir en arrière et sur la touche de menu contextuel pour afficher l'historique -+ZRM -+Cookies et autres données -+Page(s) ne répondant pas -+Paramètres d'entrée hangûl -+Configurer la synchronisation... -+Modules (). Conflits connus : , conflits probables : -+Date de renouvellement du certificat Netscape -+Ouvrir tous les favoris dans une nouvelle fenêtre -+bouton radio concernant l'étendue de pages -+Saisissez votre mot de passe -+ est conçu pour rendre l'impression plus intuitive, accessible et utile. vous permet de rendre vos imprimantes accessibles depuis n'importe quelle application Web ou mobile associée à . -+Dictionnaire de kanji unique -+Rétablir tous les onglets -+Échec de l'installation de l'extension -+En savoir plus sur la manière de se protéger des logiciels malveillants en ligne -+Toujours afficher les fenêtres pop-up de -+Authentification phase 2 : -+Aucun résultat de recherche trouvé -+Ouvrir une fois le téléchargement &terminé -+ cookies -+Mode de saisie du japonais (pour clavier américain) -+Continuer à bloquer les plug-ins -+Épingler sur la barre des tâches -+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. -+Fermer la session d'invité -+Contrôlez vos tâches d'impression et l'état de connexion de vos imprimantes en ligne. -+Désinstallation -+Empreinte SHA-1 -+Modifier le code PIN de la carte SIM -+État du réseau : -+sans limite -+(Choisir une autre capture d'écran) -+Désactivation -+Aucune erreur n'a été signalée récemment. Les erreurs n'apparaissent ici que lorsque l'envoi de rapports d'erreur est activé. -+Rouvrir les dernières pages ouvertes -+Ouvrir en mode plein écran -+Ajouter une carte de paiement -+Désactivé -+Chargement des informations sur votre forfait Internet mobile, veuillez patienter... -+Acheter un forfait de données... -+Inclure cet e-mail : -+Le titre doit comporter au moins un caractère. -+Espaces de noms réseau -+ n'a pas pu terminer l'installation, mais va poursuivre son exécution à partir de son image disque. -+Gestion des services Internet mobiles -+Utiliser les onglets latéraux -+Non -+Saisir le code PIN de la carte SIM -+&Afficher dans le Finder -+Maintenez la touche Ctrl, Alt ou Maj enfoncée<br>pour afficher le raccourci clavier qui lui est associé. -+Vraiment désolé, aucun prototype n'est disponible pour le moment. -+&Afficher dans le Finder -+Informations relatives au certificat -+/ -+La synchronisation des mots de passe requiert votre attention. -+Veuillez saisir l'ancien et le nouveau code PIN. -+Langue : -+Résultats de recherche -+Serveur SSL avec fonction d'optimisation -+Enregistrer en PDF -+Mémoire USB détectée -+M'avertir lorsque le flux de données est faible ou presque inexistant -+Saisir le mot de passe multiterme -+Clavier lituanien -+Les éléments saisis dans le champ polyvalent peuvent être enregistrés. -+Les informations de connexion à votre compte sont obsolètes. Cliquez ici pour saisir à nouveau votre mot de passe. -+Vietnamien -+feuilles de papier -+URL de mot de passe perdu Netscape -+Rouvrir l'onglet fermé -+Effacer les mots de passe enregistrés -+Créer un nouveau dossier... -+Basculer en mode ponctuation pleine chasse ou demi-chasse -+Gérer les paramètres de localisation... -+Synchronisé... -+Options de recherche par défaut -+Aucune sélection -+Mode de saisie du vietnamien (TELEX) -+ minute restante -+Le nombre maximal d'autorités de certification intermédiaires a été dépassé : -+Saisissez un mot de passe pour chiffrer ce fichier de certificat. -+Organiser -+Votre mot de passe a changé. Veuillez réessayer avec votre nouveau mot de passe. -+PKCS #1 MD5 avec chiffrement RSA -+Utiliser le thème classique -+Échap efface toute la mémoire tampon de pré-édition -+Ajouter aux favoris -+Un incident est survenu sur une partie de cette page (HTML WebWorker). Elle risque de ne pas fonctionner correctement. -+Clavier américain -+Signataire de code -+Adobe Reader n'est pas à jour et risque de ne plus être sécurisé. -+Personnaliser les paramètres de synchronisation... -+Co&ller et rechercher -+Détails du certificat sélectionné : -+Impossible de se connecter -+Activation : -+Système de révocation introuvable dans le certificat du serveur -+Numéro de série -+Afficher la page "Nouvel onglet" -+il y a  heure -+Votre ordinateur redémarrera une fois la mise à jour effectuée. -+Verr. maj. -+L'accès au répertoire de l'hôte de communication à distance a été refusé. Essayez avec un autre compte. -+Accessible aux scripts : -+Cette page place des cookies. -+Proxy -+Avec sous-menu -+Afficher dans le dossier -+il y a  minutes -+<saisir une requête> -+Appuyez sur pour envoyer des commandes à . -+Prévisualiser le rapport -+Activer le dernier onglet -+Lorsque je quitte le navigateur -+Continuer sans mettre à jour Adobe Reader (non recommandé) -+Le champ de mot clé doit être vide ou comporter un mot unique -+Déplacer le curseur automatiquement au caractère suivant -+Enregistrer -+Ouvrir le lien dans un nouvel ongle&t -+Nouvelle fenêtre de navigation privée -+Celtique -+Réinitialiser le zoom -+Prototypes -+Mémoriser mes choix pour tous les liens de ce type -+Les plug-ins de cette page ont été bloqués. -+Site -+Sélectionner -+Couper -+Restaurer toutes les miniatures supprimées -+Utiliser les onglets latéraux -+Se connecter au dispositif de sécurité -+Enregistrer le fichier -+Pages -+Options de -+Barre de lancement rapide -+Annuler -+Choisir un fichier -+&Rechercher avec -+(pas encore valide) -+Mettre à jour le plug-in... -+Police Sans-Serif -+Impossible de charger le JavaScript "" du script de contenu. -+Connexion... -+Mots de passe -+Fichiers PKCS #12 -+Options de saisie automatique de -+Fenêtre précédente -+Astuce -+ / fichiers -+Localisation utilisée, mais les paramètres régionaux par défaut (default_locale) n'ont pas été indiqués dans le manifeste. -+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. -+Votre commentaire a bien été envoyé. -+Fonction d'optimisation internationale Netscape -+Taille de police minimale -+Suivant -+Utilitaire : -+Cette opération peut prendre une minute... -+Le format du mot de passe est incorrect. -+Chargement... -+Ajouter la page... -+Masquer ce plug-in -+&Paramètres linguistiques... -+Créer un support de récupération du système d'exploitation -+Le fichier de clé privée est incorrect. -+Valeur : -+Stable -+Grande -+Certificat serveur invalide -+Jamais enregistrés -+Cette page a été préchargée. -+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 à chrome://plugins, activer "Chrome PDF Viewer" et réessayer. -+Nouveau ! -+Vous êtes passé en navigation privée. 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. Passer en navigation privée n'a aucun effet sur les autres utilisateurs, serveurs ou logiciels. Méfiez-vous : Des sites Web qui collectent ou partagent des informations vous concernant Des fournisseurs d'accès Internet ou des employeurs qui conservent une trace des pages que vous visitez Des programmes indésirables qui enregistrent vos frappes en échange d'émoticônes gratuites Des personnes qui pourraient surveiller vos activités Des personnes qui se tiennent derrière vous En savoir plus sur la navigation privée -+Le certificat du serveur a été révoqué. -+&Reprendre -+Sélectionnez un réseau : -+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. -+Effacer les données de navigation -+Si vous êtes conscient que la visite de ce site peut être préjudiciable à votre ordinateur, vous pouvez . -+Votre périphérique est inscrit pour bénéficier de la gestion d'entreprise. -+Serveur de mise à jour non disponible (erreur : ) -+Module client natif : -+Rechercher sur  : -+Style de symboles -+ hours -+Département -+Continuer à utiliser -+OK -+Essayez de désactiver les prédictions d'actions du réseau en procédant comme suit : -+ Sélectionnez le -+ -+ menu clé à molette > -+ -+ > -+ -+ -+ et désélectionnez "". -+ Si le problème n'est pas résolu, nous vous conseillons de sélectionner de nouveau -+ cette option pour améliorer les performances. -+Date -+Sélectionnez un certificat pour vous authentifier sur . -+Barre latérale -+La clé publique éphémère Diffie-Hellman associée au serveur est peu sûre. -+Rechercher dans Dictionnaire -+Sélectionnez un moteur de recherche -+Clavier russe -+Nom Microsoft principal -+Clavier polonais -+Clavier serbe -+ minute -+Erreur lors de la lecture des données du cache. -+Canary -+Commentaire du certificat Netscape -+Page suivante -+ est à jour. -+Paramètres de saisie automatique... -+Tout ramener au premier plan -+Sélectionner le dossier à ouvrir -+Activer le mode Pinyin fuzzy -+Résolution du proxy... -+Le téléchargement de l'image a été interrompu. -+Eten 26 -+URI -+Clavier Dvorak américain -+Rechercher sur le Web... -+Si vous utilisez un serveur proxy, vérifiez les paramètres associés ou demandez à votre administrateur réseau -+ si ce serveur fonctionne. -+Impossible de charger l'icône de l'extension "". -+Définir un proxy pour se connecter au réseau -+zone de texte concernant l'étendue de pages -+Activation -+Clavier Dvorak britannique -+ jours -+MS-IME -+Aucun forfait de données -+Les fenêtres pop-up suivantes ont été bloquées sur cette page : -+ heures restantes -+Impossible de charger le modèle du favori. -+Échec d'exportation de la clé privée -+Utiliser comme page d'accueil -+Compte -+Plein écran -+Autoriser tous les sites à suivre ma position géographique -+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. -+Les cookies suivants ont été bloqués : -+Inclure les adresses de ma fiche de Carnet d’adresses -+Le certificat de sécurité du site n'est pas approuvé ! -+Votre liste d'applications, d'extensions et de thèmes installés -+Exécuter tous les plug-ins de cette page -+Augmente la taille du texte -+Accepter et continuer » -+Fichiers -+ -+Appliquer uniquement à cette session de navigation privée -+Latin large -+Exécuter ce plug-in -+MasterCard -+Sur le Web, Tab permet de sélectionner les liens, ainsi que les champs de formulaire. -+À propos de -+Paramètres de sécurité du système -+Images -+Cyrillique -+Pas maintenant -+Attendre la fin du téléchargement -+Thème "" installé -+&Supprimer -+Menu Démarrer -+&Zoom -+Transfert en cours ( %)... -+Géré par -+&Rafraîchir -+Configuration du module de plate-forme sécurisée (TPM) en cours. Veuillez patienter, cela peut prendre quelques minutes. -+Vous avez acheté une quantité illimitée de données le . -+Confirmer avant de quitter () -+Mémoire partagée -+Rech. dans les paramètres -+mot de passe d'accès au réseau -+Activer la vérification orthographique -+Types de données -+Bloquer -+Certificat de l'autorité de certification du serveur : -+Clavier grec -+Taille réelle -+Toujours en haut -+Aucun plug-in installé. -+Alerte JavaScript -+Autoriser en mode navigation privée -+Rétablir -+, expire le : -+Confirmer la désinstallation -+Impossible de désactiver les plug-ins ayant été activés par une stratégie d'entreprise. -+Cette fonctionnalité active l'option "Lire en un clic" dans les paramètres de contenu du plug-in. -+Attendre la fin des téléchargements -+Synchronisation -+&Rouvrir l'onglet fermé -+Votre mot de passe de compte Google a changé depuis votre dernière connexion à partir de cet ordinateur. -+Le fichier contenait plusieurs certificats, aucun d'eux n'a été importé : -+Afficher le bouton -+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. -+Configuration en cours... -+Rechercher dans les téléchargements -+Me demander lorsqu'un site tente de suivre ma position géographique (recommandé) -+La nouvelle version de "" a été désactivée, car elle nécessite davantage d'autorisations. -+Clavier phonétique russe -+American Express -+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. -+Lorsque vous vous connectez à un site Web sécurisé, le serveur hébergeant ce site présente à votre navigateur un "certificat" 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). -+À l'instant -+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 : -+ / octets, Interrompu -+Importés depuis Firefox -+Détection automatique -+&Aide -+Les codes PIN sont différents ! -+Contenu Web -+Vider le cache -+Rétablir le thème par défaut -+Le service de communication à distance a démarré correctement. Vous devriez maintenant pouvoir vous connecter à distance à cet ordinateur. -+Outils de développement -+Police Serif -+Copier -+Adresse e-mail ou mot de passe incorrect. Veuillez réessayer. -+Le plug-in suivant ne répond pas : souhaitez-vous interrompre ? -+Sens -+ secondes -+Clavier tchèque -+Voulez-vous que "" soit considérée comme une autorité de certification fiable ? -+Gestionnaire de sécurité natif du client -+<p>Lorsque vous exécutez 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.</p> -+ -+ <p>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 à <code>man </code>.</p> -+La page Web à l'adresse 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. -+SSID : -+Nom du forfait : -+Adresse X.400 -+Fermer l'onglet -+Impossible de trouver un navigateur pris en charge. -+Le compte associé à la boutique en ligne est le suivant : . L'utilisation d'un autre compte pour la synchronisation provoque des erreurs. -+Enregistrer la p&age sous... -+ importe actuellement les éléments suivants à partir de  : -+Appuyez sur Entrée pour avancer et sur la touche de menu contextuel pour afficher l'historique -+Actualiser le cadre -+Annulé -+Autorités -+Sélectionnez votre clavier : -+Signaler un problème... -+Ignorer ce réseau -+Nouveau matériel détecté -+Vous disposez de certificats qui n'appartiennent à aucune autre catégorie : -+&Profilage activé -+(Navigation privée) -+&Nouveau dossier -+Types MIME : -+Afficher les pages en arrière-plan () -+Fichiers audio -+Plug-ins -+Plus d'informations... -+Se connecter automatiquement à ce réseau -+Titulaire de la carte -+Elle peut désormais accéder à : -+Noir et blanc -+Ignorer la connexion et naviguer en tant qu'invité -+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. -+Accès réseau interrompu -+La dernière version de l'extension "" requiert d'autres permissions. Elle a donc été désactivée. -+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. -+Veuillez saisir la clé de déverrouillage du code PIN à 8 chiffres fournie par . -+Exceptions... -+Connexion à -+Voulez-vous vraiment quitter alors qu'un téléchargement est en cours ? -+Le site Web ne parvient pas à gérer la demande associée à . -+Précédent -+&Arrêter -+Lorsque l'option permettant de bloquer l'enregistrement des cookies tiers est activée, la lecture de ces cookies est également bloquée. -+ : -+Console JavaScript -+Les sites pour lesquels vos mots de passe ne seront jamais enregistrés s'afficheront ici. -+Inconnu -+Mode de saisie du thaï (clavier TIS-820.2538) -+&Favoris -+Désinstaller -+Voulez-vous vraiment quitter alors que  téléchargements sont en cours ? -+Configurer le blocage des cookies... -+ABC -+L'application hébergée par est inaccessible, car vous êtes déconnecté du réseau. Cette page s'affichera dès que la connexion réseau sera rétablie. <br> -+Gestionnaire de favoris -+ secs ago -+Quitter le mode plein écran -+Ouvrir l'&image dans un nouvel onglet -+Favori ajouté ! -+Choisir un autre dossier... -+Kotoeri -+Hier -+Synchroniser automatiquement les éléments suivants : -+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. -+Contraintes des stratégies de certificat -+Créer un support de récupération -+Emplacement : -+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. -+Options pour les développeurs -+Cookies -+Toujours exécuter pour ce site -+Afficher le dossier -+Autres favoris -+Clavier français -+ ne parvient pas à déterminer ou à définir le navigateur par défaut. -+Sélectionner l'onglet suivant -+Voulez-vous vraiment supprimer ces pages de votre historique ? -+Déverrouiller -+ID de clé : -+Exécuter ce plug-in -+Utilisateurs -+Ajouter un moteur -+Répondeur OCSP : -+Ignorer -+Champs de certificat -+Modules () : aucun conflit détecté. -+Onglet : -+Impossible de charger la page d'options "". -+Coller en adaptant le style -+Veuillez ajouter une autre langue avant de supprimer celle-ci. -+nom du réseau -+ synchronise de manière sécurisée vos données avec votre compte Google. -+Taille -+Activer... -+Recherche -+Poursuivre quand même -+Nouveau code PIN : -+La capacité du périphérique doit être d'au moins 4 Go. -+De droite à gauche -+Activer le mode Pinyin double -+Menu contenant des favoris masqués -+Utiliser le dictionnaire système -+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. -+Personnaliser les polices... -+Enregistrement des informations de date -+&Vérifier l'orthographe du texte de ce champ -+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. -+ minutes restantes -+Île -+Contraintes de base du certificat -+Autres... -+Activer l'onglet 4 -+Données personnelles -+Afficher l'original -+Nom de la société -+Définir Adobe Reader comme visionneuse de documents PDF par défaut ? -+Pour gérer les extensions installées, cliquez sur Extensions dans le menu Fenêtre. -+Accédez à vos imprimantes depuis n'importe quel ordinateur ou smartphone. En savoir plus -+Me &le rappeler plus tard -+Opération réussie ! -+Carte SD détectée. -+ - Propriétaire -+Clavier japonais -+WPA -+janv.^févr.^mars^avr.^mai^juin^juil.^août^sept.^oct.^nov.^déc. -+Sélectionnez -+ -+ Applications > Préférences système > Réseau > Assistant -+ -+ pour tester votre connexion. -+&Afficher le code source de la page -+Micrologiciel : -+Créer un profil -+Date de modification -+Sandbox seccomp -+Signale&r un problème... -+Entrée de symboles simplifiée -+Chinois simplifié -+Hôte SOCKS -+Considérer ce certificat comme fiable pour identifier les sites Web. -+Exceptions de localisation -+Hiragana -+Le mot de passe TPM ci-dessous, généré de façon aléatoire, a été attribué à votre ordinateur : -+Importer mes favoris maintenant... -+ -+Avertissement : Un problème a été détecté sur cette page. -+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. -+Ajouter tous les onglets aux favoris... -+Émis le -+Lorsque je ferme le navigateur -+Localisation -+Attendre -+Infos sur la clé publique de l'objet -+Plug-in inconnu -+Connexion en mode invité -+Toujours autoriser à paramétrer les cookies -+Résumé -+Le certificat du serveur n'est pas valide. -+Chaîne de certificats codés Base 64 ASCII -+Choisir mon propre mot de passe multiterme -+Certificat de serveur non répertorié -+Rechercher la sélection -+Activer -+L'accès à ce réseau est protégé. -+E-mail -+Erreur () : -+Signature permanente Microsoft -+Sélectionnez -+ -+ Démarrer > Panneau de configuration > Réseau et Internet > Centre Réseau et partage > Résolution des problèmes (en bas) > Connexions Internet. -+ -+Gestionnaire de &fichiers -+Service client -+Impossible de se connecter au réseau "". -+J'accepte ces termes -+Copier l'adr&esse du lien -+Veuillez démarrer 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. -+Le processus du connecteur est bloqué. Voulez-vous le redémarrer ? -+Contenu Web -+Clé compromise -+Supprimer -+Si vous fermez maintenant, ces téléchargements seront annulés. -+Dachen 26 -+Voulez-vous vraiment graver l'image sur le périphérique suivant : -+Mémoire SQLite -+/, -+ est affiché dans cette langue. -+Fenêtre pop-up bloquée -+ : -+Ouvrir tous les favoris -+Effacer les cookies et autres données de site et de plug-in lorsque je ferme le navigateur -+Page d'accueil -+Onglet -+Non configuré -+Opération réussie ! -+Relancez pour terminer la mise à jour. -+Chiffrer toutes mes données -+Veuillez patienter pendant que nous configurons votre réseau pour mobile. -+Points de distribution de listes de révocation des certificats -+Association -+Appuyez sur Maj+Alt pour changer la disposition du clavier. -+Utiliser l'horloge au format 24 heures -+ est prêt à terminer l'installation. -+Lancez votre recherche à partir d'ici -+OK, synchroniser tout -+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. -+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. -+Clavier slovaque -+Nom de partie EDI -+Vous avez déjà chiffré des données avec un mot de passe multiterme. Saisissez-le ci-dessous. -+Informations sur le site -+Créer un raccourci vers l'application... -+Authentification requise -+ n'est pas parvenu à se connecter à . Sélectionnez un autre réseau ou réessayez. -+Votre compte n'est pas compatible avec . Contactez l'administrateur de votre domaine ou utilisez un compte Google standard pour vous connecter. -+Adresse e-mail -+Client natif -+Chargé depuis : -+Active les balises canvas hautes performances dans un contexte 2D, pour effectuer le rendu via le processeur graphique. -+Cette page ena été traduite en -+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 -+Autorité de certification SSL -+ contient un logiciel malveillant. Votre ordinateur pourrait être infecté par un virus si vous consultez ce site. -+Aucune donnée disponible. -+Im&primer... -+Description -+Voix non reconnue. -+Aucun fichier sélectionné -+Souhaitez-vous installer  ? -+Activité récente -+de la dernière semaine -+Profil -+Actualiser sans utiliser le cache -+Logiciels malveillants et sites de phishing détectés ! -+Onglet fermé ! -+il y a  jours -+Paramètres -+Ce site exige que vous vous identifiiez avec un certificat : -+Supprimer... -+&Aucune suggestion orthographique -+Clavier franco-canadien -+Clavier étendu américain -+Le serveur a mis fin à la connexion sans envoyer de données. -+Autoriser à afficher des notifications sur le Bureau ? -+Pour gérer les extensions installées, cliquez sur Extensions dans le menu Outils. -+Erreur de synchronisation, veuillez vous connecter à nouveau. -+La liste suivante fait état des éléments dangereux détectés sur la page. Cliquez sur le lien "Diagnostic" pour obtenir plus d'informations sur un élément particulier. -+Émirat -+Synchroniser tous les paramètres et toutes les données\n(peut prendre un certain temps) -+Toujours demander où enregistrer les fichiers -+Créer un raccourci -+Tous les fichiers -+Sebeol-sik No-shift -+Anglais (pleine chasse) -+2D avec canvas et accélération matérielle -+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. -+Nouvelle connexion -+GPU -+Moins de 1 Mo disponible -+Nouvelle fenêtre de navigation privée -+Europe du Sud -+Sélectionner les éléments à synchroniser -+Attributs du répertoire de l'objet du certificat -+Accédez à vos imprimantes et partagez-les en ligne via . -+&Ajouter... -+Afficher dans cette langue -+il y a  jour -+Police Sans-Serif -+Ne rien faire -+ seconde -+Configurer les mises à jour automatiques pour tous les utilisateurs -+Échec de la création du répertoire des données -+Fermer et annuler le chargement -+ -+Adresse : -+Détails -+Notification : -+Cette opération peut prendre quelques minutes. -+Géré par () -+Type de certificat Netscape -+Impossible d'afficher certaines parties de ce document PDF. Souhaitez-vous installer Adobe Reader ? -+Vos certificats -+Vos données personnelles sur , et -+URL de stratégie de l'autorité de certification Netscape -+Dernier accès : -+Synchroniser les mots de passe -+&Quitter -+&Fermer l'onglet -+, -+Le site Web à l'adresse 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. -+Total : -+Les cookies de plusieurs sites ont été autorisés pour la session uniquement. -+Nom du service : -+Imprimer une page de test -+Aperçu avant impression - -+ -+ ne peut pas à afficher la page Web, car votre ordinateur n'est pas connecté à Internet. -+Exécuter cette fois -+Chiffrement de la clé -+Échec de la vérification AAA -+Ouverture dans ... -+&Copier -+Logiciel -+Moteurs de recherche -+&Méthodes d'entrée -+Jamais enregistrés -+Impossible d'analyser le fichier. -+Subordination qualifiée Microsoft -+Bureau -+Toujours traduire en les pages en -+Thaï -+Activer l'onglet suivant -+: de chargement -+Style de mappage du clavier -+Un problème lié à votre microphone s'est produit. -+ vous permet d'accéder aux imprimantes de cet ordinateur, où que vous soyez. Connectez-vous pour l'activer. -+District -+Ethernet -+Erreur de réseau inconnue. -+Nom du certificat -+Plus d'informations -+Mise à jour du système disponible. Préparation du téléchargement… -+Galerie des thèmes Google Chrome -+Copi&er l'adresse e-mail -+Activer l'onglet 3 -+Adresses -+Plug-in : () -+Pas maintenant -+Pays -+<Ne fait pas partie du certificat> -+il y a  seconde -+Fichiers image -+Europe centrale -+Certificat client SSL -+Pour accélérer l'affichage des pages Web, -+ -+ enregistre temporairement les fichiers téléchargés sur le disque. Si -+ -+ 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. -+ -+ 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. -+Thème créé par -+Données de navigation -+Ajouter cette page aux favoris -+Le serveur a refusé la connexion. -+Module ( bits) :\n\n\nExposant public ( bits) :\n -+ secondes restantes -+Vous utilisez un indicateur de ligne de commande non pris en charge : . La stabilité et la sécurité en seront affectées. -+Emplacement de téléchargement -+À propos de Google Traduction -+Les lettres ne sont pas sensibles à la casse. -+Mot clé -+Connexion -+Redémarrez pour appliquer la mise à jour. -+Sélectionner un carré dans l'image -+Bases de données Web -+Codage -+Mise en route -+Utiliser les pages actuelles -+Signaler une erreur -+Recherche de réseaux en cours -+Effacer les données de navigation -+Ouvrir le lien dans une nouvelle &fenêtre -+Nom d'utilisateur -+Clavier letton -+Forcer l'actualisation de cette page -+Disposition du clavier : -+Non utilisé -+ minutes -+Chiffrement RSA PKCS #1 -+Ce n'est probablement pas le site que vous recherchez ! -+&Général -+Configurer la synchronisation -+État de connexion : -+Ancien code PIN : -+Désactiver le contrôle des liens hypertexte -+&Modifier -+Chinois -+Navigateur par défaut -+Extension en mode navigation privée : -+Mot clé : -+Un problème est survenu lors du téléchargement de l'image de récupération. La connexion réseau a été perdue. -+Votre administrateur a désactivé certaines préférences. -+Forfait de données épuisé -+Veuillez indiquer à quel niveau vous rencontrez des problèmes avant d'envoyer vos commentaires. -+Authentification du serveur WWW TLS -+Le service de synchronisation n'est pas disponible pour votre domaine. -+Modifier les paramètres du proxy... -+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. -+Un logiciel malveillant a été détecté ! -+Forfait de données presque épuisé -+des dernières 24 heures -+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 -+ hours left -+poursuivre quand même -+Autorisé -+Retirer l'onglet -+Moins de -+Impossible de charger l'icône "" d'action du navigateur. -+Consulter Google Dashboard -+Plus d'informations -+Ajoutez des langues puis faites-les glisser pour les classer dans l'ordre souhaité. -+ 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 sont fermées. -+Non (HttpOnly) -+Saisir votre mot de passe multiterme pour la synchronisation -+Police standard -+ (par défaut) -+Cette fonctionnalité active la prise en charge du client natif. -+La puce du module de plate-forme sécurisée (TPM) est désactivée ou inexistante. -+L'index de l'onglet indiqué est incorrect. -+Nom du serveur SSL du certificat Netscape -+Est une autorité de certification -+Wi-Fi -+Connexion à -+Confirmer le mot de passe : -+Aperçu avant impression -+Clavier italien -+Impossible d'établir une connexion sécurisée à cause de l'antivirus ESET. -+(expiré) -+Voulez-vous vraiment quitter cette page ? -+Faire défiler d'une page vers le haut -+Empreintes -+Le plus récent -+ heures -+Agrandir -+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. Cette erreur peut également être due à un cache endommagé lors d'une fermeture incorrecte. Si le problème persiste, essayez d'effacer le cache. -+Le plug-in n'est plus à jour. -+Vous avez changé de position. Souhaitez-vous utiliser  ? -+EAP-TLS -+ heures -+Rafraîchir cette page -+Non-répudiation -+Extension : -+Active l'API Web audio. -+Zone de saisie de mot de passe -+Effacer les cookies et autres données de site lorsque je quitte le navigateur -+Ajouter l'option de regroupement au menu contextuel des onglets -+Version -+Latin -+Le connecteur requiert l'installation du pack Microsoft XML Paper Specification Essentials. -+Effacer les cookies et autres données de site lorsque je ferme le navigateur -+Case décochée -+Mise en page -+Saisir un nom ou une adresse -+Choix de l'image du compte -+Me montrer -+ minutes restantes -+De : -+Ouvrir un rapport de phishing -+Page de phishing -+Inspecter le pop-up -+Importer... -+Modifier la carte de paiement -+Cette langue est utilisée pour corriger l'orthographe. -+Le type d'enregistrement indiqué est incorrect. -+Forfait de données arrivé à expiration -+Exporter mes favoris... -+Clavier britannique -+Importer des favoris... -+ -+Nom d'utilisateur ou mot de passe incorrect -+&Tout sélectionner -+Erreur inconnue liée au certificat du serveur. -+Envoyer une capture d'écran enregistrée -+Émis pour : -+Page précédente -+Hsu -+Réduire -+Navigateur par défaut -+Afficher le mot de passe -+Activer les fonctionnalités d'accessibilité -+Inclure la capture d'écran actuelle : -+ - -+(cette extension est gérée et ne peut être désinstallée ni désactivée) -+OID enregistré -+Importés depuis IE -+Fichier de clé privée (facultatif) : -+Autoriser pour la session uniquement -+ mins -+Mise à jour en cours -+Aucun microphone trouvé. -+Connexion à -+ n'est pas votre navigateur par défaut. -+Forfait -+Cette page contient des éléments des sites ci-dessous qui suivent votre position géographique : -+Plusieurs profils -+Plantages -+Par défaut -+ jours -+PKCS #1 MD4 avec chiffrement RSA -+Modifier le moteur de recherche -+ minutes -+Indiquez le mot de passe approprié ci-dessus, puis saisissez les caractères figurant dans l'image ci-dessous. -+Expiration de imminente -+Activer l'onglet précédent -+Désactiver l'accès à distance -+Afficher les incompatibilités -+Carte de débit Solo -+Actualisez cette page Web ultérieurement. -+Pour utiliser cette extension, saisissez "", TAB, puis votre commande ou votre recherche. -+ synchronisera les applications installées, afin que vous puissiez y accéder en vous connectant depuis tout navigateur . -+Langues et paramètres du correcteur orthographique... -+Erreur de sécurité -+Applications -+La ressource demandée n'existe plus et aucune adresse de transfert n'est disponible. Il semble que cet état de fait soit permanent. -+Certificat unique binaire codé DER -+&Plein écran -+Fichiers vidéo -+ 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. -+Vos onglets et activités de navigation -+Créer -+Turc -+Une erreur s'est produite lors de la tentative d'enregistrement du certificat client. Erreur  () -+Bienvenue dans -+Sélectionner -+Nom du modèle de certificat Microsoft -+Configuration de l'adresse IP pour -+Immortalisez votre plus beau sourire et utilisez la photo comme image de compte. -+Extension créée : -+ -+ -+Mot de passe incorrect. Veuillez réessayer. -+LEAP -+Échec de la connexion aux serveurs de reconnaissance vocale. -+Très grande -+Sélectionner le répertoire de l'extension -+&Téléchargements -+Gestionnaire des certificats -+ souhaite suivre votre position géographique -+Quitter -+ sur -+Proposer d'enregistrer les mots de passe -+Modification de l'affiliation -+par exemple : 1-5, 8, 11-13 -+(Activé par une stratégie d'entreprise) -+Qu'est-ce que c'est ? -+Les cookies de plusieurs sites ont été bloqués. -+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. -+Désactivé -+Annuler -+Répertoire racine de l'extension : -+Mode de saisie du vietnamien (TCVN6064) -+Échec de l'activation -+Ajouter cette page aux favoris -+L'identité de ce site Web a été vérifiée par . -+Adresse e-mail : -+Activation... -+PAP -+Sites de phishing -+ secs ago -+Interdire à tous les sites d'exécuter JavaScript -+Vouliez-vous accéder à  ? -+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. -+Paramètres des fenêtres pop-up : -+Tout exporter... -+Barre de favoris -+Stocké dans : -+Développer... -+Gin Yieh -+Rechercher sur -+Modifier la carte de paiement -+Au démarrage -+Nous examinons actuellement le problème. -+Notifications -+Navigateur -+Case d'option décochée -+ days -+Anglais -+Modifier le nom du dossier -+Grammaire et orthographe -+Réseaux privés -+Toutes sortes de connexions -+Exceptions liées aux notifications -+Épingler les onglets -+Barre d'onglets -+Activer l'onglet 6 -+Gestionnaire de &tâches -+Paramètres de stockage d'Adobe Flash Player... -+Connexion au réseau -+Mode de saisie du vietnamien (VNI) -+L'application suivante va être lancée si vous acceptez cette requête :\n\n -+Importation... -+N° de carte -+Gérer les moteurs de recherche... -+Utiliser un moniteur externe -+Gravure de l'image en cours... -+État -+Éteindre -+Enregistrer les fichiers dans le dossier : -+Fermer les autres onglets -+Répertoire de fichiers -+Masquer les autres -+Annuler l'épinglage des onglets -+Plus -+Échec de la vérification de la révocation -+Ajouter www. et .com, puis ouvrir la page -+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. -+&Répéter -+ est à présent activé. a enregistré les imprimantes installées sur cette machine en les associant à <b></b>. Vous pouvez désormais utiliser vos imprimantes depuis n'importe quelle application Web ou mobile associée à . -+Le serveur de -+ 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 -+ -+ au réseau. -+ZGPY -+Impossible de charger le fichier css "" du script de contenu. -+Créer des raccourcis vers des applications aux emplacements suivants : -+Moyenne -+Envoyé pour : -+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 . -+[répertoire parent] -+Touches de sélection du clavier Hsu -+Copie de l'image de récupération... -+Tout supprimer -+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. -+Ouvrir dans une nouvelle fenêtre -+Dommage... Aucune extension n'est installée. :-( -+Style de ponctuation -+Historique de navigation -+Ce site tente de télécharger plusieurs fichiers. Voulez-vous autoriser le chargement ? -+Établissement de la liaison avec le service de gestion des périphériques en attente... -+ heures -+Ne pas utiliser les paramètres du proxy pour les hôtes et domaines suivants : -+&Avancer -+Impossible de charger "" pour le plug-in. -+À propos du système -+Utilisation de la clé du certificat : -+Mode de saisie du chinois (quick) -+Une erreur s'est produite lors de la tentative d'écriture du fichier : . -+Renommer -+La synchronisation requiert votre attention. -+Aujourd'hui -+Imp&rimer le cadre... -+Le site 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 , consultez notre Google. -+Adresse de serveur DNS spécifiée par l'utilisateur. -+&Rechercher... -+, -+&Afficher dans le dossier -+Non connecté à Internet. -+Sélectionnez -+ -+ Démarrer > Panneau de configuration > Connexions réseau > Assistant Nouvelle connexion -+ -+ pour tester votre connexion. -+Cette icône s'affiche lorsque l'extension peut agir sur la page active. -+Reconnexion -+Réponses OCSP de signature -+Erreur d'importation de l'autorité de certification -+Gravure de l'image terminée. -+&Historique -+Installer -+Ajouter des utilisateurs -+Enregistrer l'authentification et le mot de passe -+Clavier norvégien -+Basculer en mode chinois/anglais -+Traduction de la page en ... -+À propos de & -+ onglets -+il y a  minutes -+Qu'est-ce que c'est ? -+Tout &sélectionner -+&Ajouter au dictionnaire -+Utilisation étendue de la clé -+Le chinois est la langue de saisie initiale -+Agent utilisateur -+Émetteurs de l'autorité de certification : -+ jours -+Code source -+Redémarrer maintenant -+La largeur de caractères initiale est Complète -+Pour masquer l'accès à ce programme, vous devez le désinstaller au moyen de \n du Panneau de configuration.\n\nSouhaitez-vous exécuter  ? -+Paramètres du microphone -+Port -+&Toujours ouvrir les fichiers de ce type -+Uniquement les connexions sécurisées -+Les informations de connexion au compte sont obsolètes. -+La traduction a échoué, car la page est déjà en . -+Sandbox SUID -+(vide) -+Échec de l'authentification basée sur le certificat -+Envoyer le code source de la page actuelle -+Votre administrateur a désactivé certains paramètres. -+Coréen -+Avertissement : Il s'agit peut-être d'un site de phishing ! -+Dvorak -+Étendue de pages incorrecte -+ heures restantes -+Exceptions liées aux cookies et aux données de site -+ n'est pas accessible -+P&lus grand -+Connecté à -+Le certificat de sécurité du site a été révoqué ! -+Désactiver -+ risque de ne pas rester à jour. -+intégré sur tout autre site -+ -+ 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é. -+À propos de la reconnaissance vocale -+Langues et paramètres du correcteur orthographique... -+ seconde restante -+Nom d'utilisateur : -+Le stockage des cookies n'est pas autorisé pour cette page. -+JavaScript -+La connexion utilise . -+Échec de l'installation -+Afficher les &infos sur la page -+Serveurs -+Système de fichiers de chiffrement Microsoft -+Un plug-in supplémentaire est requis pour afficher certains éléments sur cette page. -+Appuyez sur Ctrl+Alt+/ ou sur Échap pour masquer -+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). -+Connexion avec -+Imprimer depuis la boîte de dialogue système… -+ heure restante -+Échec de l'initialisation de l'appareil photo -+Importés -+Type de fournisseur : -+IP restreinte : -+Refuser -+Démarrage du téléchargement en cours... -+Une nouvelle version de est disponible. -+Clavier hébreu -+Police à largeur fixe -+Safari -+Les champs obligatoires ne doivent pas rester vides. -+Ouvrir une fois le téléchargement &terminé -+Stratégies de certificat -+Pavé tactile -+Aucun -+Activer la barre d'adresse en mode recherche -+Exceptions pour JavaScript -+Ouvrir dans une nouvelle fenêtre -+Veuillez saisir le code PIN. -+Options de saisie automatique... -+Arabe -+Nouveau dossier -+E/S réseau interrompue -+Rechercher le précédent -+Ne jamais enregistrer les mots de passe -+Ouvrir dans un nouvel onglet -+Fenêtre suivante -+&Rechercher le suivant -+JCB -+Signature de liste d'approbation Microsoft -+Gérer les certificats... -+Sélectionnez le fichier de certificat. -+Fabricant : -+Bloquer le contenu inapproprié -+Le fichier contenait plusieurs certificats, dont certains n'ont pas été importés : -+Exceptions pour les images -+Autoriser tous les sites à afficher des fenêtres pop-up -+Vous devez indiquer un chemin valide comme valeur de clé privée. -+Sans mot de passe multiterme, vos mots de passe et autres données chiffrées ne seront pas synchronisés sur cet ordinateur. -+Extensions ou applications -+Ce site recommande Google Chrome Frame (déjà installé). -+Les plus visités -+Vous n'êtes pas autorisé à vous connecter. Consultez le propriétaire de cet ordinateur portable. -+Remarque : Lorsque vous cliquez sur "Envoyer", 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 "Inclure cette URL" et/ou "Inclure cette capture d'écran". Vous acceptez que Google utilise vos commentaires pour améliorer ses produits ou services. -+Vos modifications seront prises en compte au prochain démarrage de . -+Taille de police minimale -+Entrée directe -+Fermer la fenêtre -+Mode de saisie du japonais (pour clavier japonais) -+Inscription réussie -+Échec de création du fichier zip temporaire lors de la création du pack -+Modifier les paramètres de confiance : -+Votre historique de navigation -+&Masquer le panneau de la vérification orthographique -+Plug-in ne répondant pas -+IBM -+Les paramètres seront effacés lors de la prochaine actualisation. -+ introuvable -+Démarrage... -+Nouvelle fenêtre de nav&igation privée -+Ajouter une adresse postale... -+Authentification en cours... -+Ouvrir dans un nouvel onglet -+Déconnecté -+Action du navigateur -+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. -+Ouvrir cette page : -+Voulez-vous vraiment supprimer tous les mots de passe ? -+Sélectionner par domaine -+Ouvrir le lien dans une fenêtre en navi&gation privée -+Émetteur -+Options de saisie automatique -+Les nouveaux paramètres des cookies seront appliqués quand vous aurez actualisé la page. -+Votre connexion à est sécurisée par un chiffrement bits. -+Menu Applications -+Outi&ls -+Onglets latéraux -+Reprendre -+Zoom -+Inspecteur de DOM -+Document sans titre -+Police standard -+Activer -+Opération en cours... -+C&opier l'URL de l'image -+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. -+Enregistrer le &cadre sous... -+Général -+Défaillance -+Demander -+Coller l'URL et y a&ccéder -+Pour plus de sécurité, va chiffrer vos mots de passe. -+Si vous avez oublié votre mot de passe multiterme, vous devrez arrêter la synchronisation via Google Dashboard. -+ sur  -+Adresse ligne 1 -+Impossible de créer le favori. -+Basculer en mode chinois simplifié/traditionnel -+Votre système Sandbox n'est pas correctement configuré. -+Sélectionnez le menu clé à molette > Préférences > Options avancées > Modifier les paramètres du proxy et vérifiez que vos paramètres sont définis sur "sans proxy" ou "direct". -+Impossible de vérifier si le certificat a été révoqué. -+Stockage des données -+/, Interrompu -+Acheter un forfait -+Installer le plug-in... -+Afficher le code source -+Fermer les onglets sur la droite -+Afficher le bouton "Accueil" -+Version  -+Parenthèse gche -+Microsoft Server Gated Cryptography -+Zoom avant -+Protection du courrier électronique -+Les cookies suivants ont été bloqués (tous les cookies tiers sont bloqués, sans exception) : -+Connectez-vous à pour importer le certificat client de -+La batterie est chargée. -+Créer un nouveau profil... -+Maintenez la touche enfoncée pour quitter. -+Masquer ce plug-in -+Ouvrir le fichier -+Conteneur de barres d'infos -+Le certificat du serveur a expiré. -+Utiliser -+Réduit la taille du texte -+Épingler l'onglet -+Mise à jour du système -+ days left -+Inclure les informations système -+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. -+Impossible de valider votre mot de passe sur le réseau actuel. Sélectionnez un autre réseau. -+Cette page n'est pas rédigée en  ? Signaler l'erreur -+Certificat unique codé Base 64 ASCII -+Activer le mode plein écran -+Codag&e -+ est déjà utilisé pour gérer les liens ://. -+Touches de sélection -+Clavier international américain -+Je comprends que la visite de ce site peut être préjudiciable à mon ordinateur. -+Ressource cache manquante. -+Impossible d'accéder à mon compte -+C&opier l'URL de la vidéo -+Vous préférez parcourir la galerie ? -+Impossible de vérifier le certificat du serveur. -+Outils de &développement -+Exporter... -+Paramètres de saisie automatique -+Téléchargement de l'image en cours... -+&Options du vérificateur d'orthographe -+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. -+Vérifier le document maintenant -+Zone de liste -+Clavier slovène -+Sens de l'écriture -+Vos données sur -+Ouvrir une fenêtre du navigateur -+Conversion de la date et de l'heure -+PKCS #1 SHA-512 avec chiffrement RSA -+Clavier coréen -+Dupliquer -+Ne pas conserver sur cette page -+Désactiver -+ATOK -+téléchargement -+E-mail : -+Ouvrir tous les favoris dans une nouvelle &fenêtre -+Rafraîchir la page actuelle -+Accessibilité -+Exceptions pour les plug-ins -+Remplissage automatique des formulaires -+Nom d'utilisateur -+Enregistrer le lie&n sous... -+Réessayer -+Personnaliser et configurer -+Préférences de saisie automatique -+Votre position géographique -+Afficher les pages en arrière-plan () -+Configurer les mises à jour automatiques -+il y a  heures -+Valeur du champ -+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. -+Index erroné. -+Préparation du module de plate-forme sécurisée (TPM) en cours. Veuillez patienter, l'opération peut prendre quelques minutes. -+Nombre de copies incorrect -+Personnaliser -+Prédire les actions du réseau pour améliorer les performances de chargement des pages -+Données restantes : -+N'est pas une autorité de certification. -+L'URL doit être valide. -+Envoyer automatiquement les statistiques d'utilisation et les rapports d'erreur à Google -+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. -+Impossible de charger le profil. -+Toujours exécuter JavaScript sur -+Version PRL : -+Confirmer la réactivation -+Ajouter une adresse postale... -+PKCS #1 MD2 avec chiffrement RSA -+Dim.^Lun.^Mar.^Mer.^Jeu.^Ven.^Sam. -+Zone de saisie -+Trier par nom -+Fenêtre -+Nouvel onglet -+Vous tentez d'accéder au site , 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. -+Composition hors écran -+ mins left -+Technologie : -+Accédez à la page d'accueil du site : -+Configurer la communication à distance -+L'émetteur d'un certificat n'ayant pas expiré est tenu d'assurer la maintenance de ce qui s'appelle "une liste de révocation". 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 "révoqué" 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. -+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 "certificat racine". 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. -+Définir en tant que navigateur par défaut -+Avertissement -+Préférences de recherche -+Clavier Colemak américain -+Activer la navigation en tant qu'invité -+Utiliser les paramètres par défaut -+Rétablir -+Désinstaller "" ? -+Établissement de la connexion sécurisée... -+Dossier : -+Erreur non reconnue -+Consultez les conflits connus avec des modules tiers. -+Co&ller et rechercher -+&Remonter -+Échec de la traduction en raison d'une erreur de serveur -+Version : -+Polices et codage -+ - -+Signature du code commercial Microsoft -+Sélectionnez les éléments à importer : -+De gauche à droite -+Votre connexion à est sécurisée par le biais d'un faible chiffrement. -+sans objet -+Toujours afficher la barre de favoris -+Activer l'onglet 2 -+Aperçu des onglets -+Redémarrer -+Configuration en cours... -+Une erreur s'est produite lors de la récupération des fonctions de l'imprimante . Cette imprimante n'a pas pu être enregistrée dans . -+Exceptions pour les fenêtres pop-up -+Signataire du certificat -+La page Web n'existe plus. -+Vous n'avez jamais visité ce site auparavant. -+Personnaliser... -+Fe&rmer la fenêtre -+Arrêter le chargement de cette page -+Autres favoris -+ a été mis à jour. -+Mode hors connexion -+Erreur d'importation de fichier PKCS #12 -+Algorithme de clé publique de l'objet -+Le site Web a rencontré une erreur lors de l'extraction de . -+ Cela peut être dû à une opération de maintenance ou à une configuration incorrecte. -+Échec du chargement de la page -+Mot de passe : -+Conserver en tant que moteur de recherche par défaut -+Fichier manifeste absent ou illisible -+La connexion a été réinitialisée. -+Le mot de passe choisi vous sera demandé pour restaurer le fichier. Veillez à le conserver en lieu sûr. -+La vérification de la provenance du certificat DNS est activée, ce qui peut entraîner l'envoi d'informations privées à Google. -+À propos de la reconnaissance vocale -+Aller à la sélection -+Ajouter un certificat -+Espaces de noms PID -+Préférences de saisie automatique -+Activer l'onglet 8 -+Le certificat "" représente une autorité de certification. -+Envoyer par e-mail l'emplacement de la page -+Enregistrement des informations de date Microsoft -+Validité -+&Traduire en -+Batterie :  % -+Désactivé -+Japonais -+ () -+L'application est actuellement inaccessible. -+Lecteur du certificat : -+ jour restant -+Gestionnaire de tâches -+Gérer les extensions... -+Continuer -+Synchronisation avec effectuée. Dernière synchronisation : -+Le certificat de sécurité du site n'est pas encore valide ! -+Ouverture de session par carte à puce Microsoft -+Format -+Tapez votre requête ou saisissez une URL pour commencer la navigation : c'est à vous de choisir. -+Enregistrer &sous... -+Le serveur requiert un nom d'utilisateur et un mot de passe. -+Supprimer tous les mots de passe -+Le certificat du serveur contient des erreurs. -+Recherche de réseaux Wi-Fi... -+Description : -+Séparateur -+Si vous supprimez l'un de vos propres certificats, vous ne pouvez plus l'utiliser pour vous identifier. -+Rafraîchir cette page -+État d'activation : -+Créer des raccourcis vers des applications -+Clavier franco-suisse -+GUID de domaine Microsoft -+Entrer -+Supprimer -+Échec de l'enregistrement de cet ordinateur pour l'accès à distance. -+Ouvrir l'&image dans un nouvel onglet -+Préférences... -+L'administrateur a désactivé les mises à jour. -+À propos de la version -+Veuillez nous indiquer ce qu'il se passe avant d'envoyer votre rapport. -+Connectez-vous à afin de générer une clé pour . -+Clavier roumain -+Autres -+Sécurité : -+Imprimer -+Mode de saisie standard -+Signature -+Erreur lors de la connexion -+Pour cette session uniquement -+Une erreur s'est produite. -+(Désactivé par une stratégie d'entreprise) -+Erreur lors de la tentative d'impression. Vérifiez votre imprimante et réessayez. -+Grec -+Ignorer les exceptions et bloquer l'enregistrement des cookies tiers -+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 "Mode Présentation" pour enregistrer une capture d'écran. Vos trois dernières captures apparaissent ici. -+Activation effectuée -+Aucun système de révocation trouvé -+Bouton -+Interrompu -+Dictionnaire de symboles -+Technologie EvDo requise -+Internet mobile -+Envoyer le commentaire -+Associé au profil Chrome . Dernière synchronisation : -+Carte de crédit (autre) -+Langues et saisie -+Utiliser la barre de titre et les bordures de fenêtre du système -+Ann&uler -+ () -+Le serveur a refusé d'exécuter la demande. -+ Ko -+ a été mis à jour vers la version . -+Navigateur de contenu -+&Rouvrir la fenêtre fermée -+Se connecter directement à Internet -+Émis pour -+Ouverture de en cours -+Cibles disponibles pour l'image -+Signaler un problème -+Taille de police : -+Aide pour la configuration de proxy -+Petite -+ hours ago -+Rester sur cette page -+Type de bug : -+Inclure cette URL : -+Cookies et données de site... -+Police Serif -+Avertissement : 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. -+ChromiumOs Image Burn -+Services -+Paramètres du proxy... -+1 onglet -+Vous naviguez en tant qu'invité. 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. -+ -+ En savoir plus sur le mode invité -+Chargement des pages impossible -+Inclure cette capture d'écran : -+Certificat -+&Effacer les données de navigation... -+Valeur de signature du certificat -+URL de renouvellement du certificat Netscape -+Afficher la s&ource -+ - , -+Reprendre -+ secs ago -+Activé -+Carte SIM désactivée -+Compatibilité avec VPN -+Développement - Instable -+Échec de création du raccourci vers l'application -+Barre de favoris -+Le serveur est en mesure de répondre à la demande. -+Modifier les paramètres du proxy... -+(non empaquetée) -+Rechercher le suivant -+Paramètres réseau -+Votre service Internet mobile est activé et prêt à l'emploi. -+Modifier la police et la langue par défaut des pages Web -+Composition graphique avec accélération matérielle -+Autre nom de l'émetteur du certificat -+ -+&Afficher le code source du cadre -+Mode de saisie du persan (clavier ISIRI 2901) -+OpenVPN -+Arrêter le processus -+Votre compte a peut-être été supprimé ou désactivé. Veuillez vous déconnecter. -+Authentification anonyme : -+Bases de données indexées -+En savoir plus sur les escroqueries par phishing -+Page à l'apparence anormale -+Adresse e-mail ou mot de passe incorrect. Essayez tout d'abord de vous connecter à un réseau. -+Périphérique -+Informations sur la connexion -+Confirmer la navigation -+Nom du point d'accès : -+Rechercher -+Au démarrage -+Paramètres... -+Lire en un clic -+Connectez-vous à pour vous authentifier auprès de avec votre certificat. -+Votre administrateur informatique a désactivé certaines options. -+Supprimer le certificat de serveur ""? -+Configurer la synchronisation... -+Valider automatiquement une chaîne -+Empreinte SHA-256 -+Retour -+Réseau domestique, sans itinérance -+ - -+Gérer les mots de passe enregistrés... -+Sélectionnez -+ -+ Menu clé à molette > Options > Options avancées > Modifier les paramètres du proxy > Paramètres réseau -+ -+ et désélectionnez l'option "Utiliser un serveur proxy pour votre réseau local". -+Ne jamais traduire ce site -+(suite) -+Trop de redirections -+Ces paramètres ne peuvent être modifiés que par le propriétaire : -+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. -+Clavier latino-américain -+Phishing détecté ! -+Voulez-vous utiliser () pour gérer les liens :// à partir de maintenant ? -+ (Navigation privée) -+Prendre une photo -+Non merci -+Ne pas afficher les images -+Toujours autoriser ces plug-ins sur -+Sélectionner un dossier -+&Paramètres -+Informations sur l'erreur : -+Toujours autoriser les plug-ins sur -+Effacer les données de saisie automatique enregistrées -+Changer de moteur de recherche -+En attente du tunnel proxy... -+Veuillez ajouter un autre mode de saisie avant de supprimer celui-ci. -+ secondes -+Paramètres de contenu -+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. -+ va être installé. -+Code postal -+En bas à droite -+&Ouvrir -+ téléchargé(s) sur -+Commentaires d'ordre général/Autres -+Ou&vrir la vidéo dans un nouvel onglet -+Identité : -+Échec de l'opération OTASP -+Action sur la page -+Modifier des éléments... -+Le répertoire d'extensions est obligatoire. -+ Mo disponibles -+Ancien -+ fournit du contenu provenant de , un site connu pour distribuer des logiciels malveillants. Votre ordinateur pourrait être infecté par un virus si vous consultez ce site. -+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. -+Configuration manuelle du proxy -+Mettre à jour Adobe Reader maintenant -+ days ago -+Désactiver les plug-ins individuels... -+Envoyer une capture d'écran de la page en cours -+Point d'exclamation -+ 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. -+Petit problème... Tentons de le résoudre. -+Cette fonctionnalité permet d'établir des correspondances entre les sous-chaînes et plusieurs fragments d'URL figurant dans l'historique. -+Page "Nouvel onglet" expérimentale -+Les cookies suivants étaient autorisés lorsque vous avez consulté cette page : -+votre opérateur -+Activer ces fonctionnalités... -+Quitter Firefox avant l'importation -+Ajouter un nouveau téléphone -+Proxy HTTP sécurisé -+Accès à refusé. -+Finalisation de la mise à jour du système... -+Voulez-vous continuer ? -+ Ko ( Ko effectifs) -+Autre réseau Wi-Fi... -+Choisir un autre répertoire... -+L'extension indiquée est déjà associée à une clé privée. Utilisez cette clé ou supprimez-la. -+Ajouter une adresse -+Stockage local -+Afficher tous les téléchargements -+Navigateur de contenu -+Magasin de certificats -+Émis par -+Quitter le mode plein écran () -+Le certificat du serveur a été signé avec un algorithme de signature faible. -+Ajouter un utilisateur -+Me proposer de traduire les pages qui sont écrites dans une langue que je ne sais pas lire -+Récemment fermés -+Saisir la clé de déverrouillage du code PIN -+ jours -+Rechercher du texte -+Sans-Serif -+Zoom &avant -+Confirmer le nouvel envoi du formulaire -+ heure -+Cet élément doit être installé depuis . -+Mise en veille ou reprise -+Mettre à jour maintenant -+Ajouter une page -+Chiffrer seulement -+Ne jamais intervertir les touches de modification -+Conversion des numéros spéciaux -+Relancer maintenant -+Synchronisation avec effectuée -+il y a  minute -+Édition -+Préférences synchronisées -+ hours ago -+Personnalisé -+Rechercher à partir de la barre d'adresse -+Ouvrir -+Inspecter les vues actives : -+Contraintes de nom du certificat -+Ajouter une adresse -+Mot de passe incorrect. -+Statistiques avancées -+Clavier espagnol -+Sélectionnez votre réseau -+ jour -+Le serveur de synchronisation est occupé. Veuillez réessayer ultérieurement. -+Quitter cette page -+Naviguer en tant qu'invité -+Discover -+&Toujours afficher la barre de favoris -+Options de recherche -+Taille : -+il y a  secondes -+La liste suivante fait état des éléments dangereux détectés sur la page. Cliquez sur le lien "Diagnostic" 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 "Signaler une erreur". -+Cette page rédigée dans une langue non identifiée a été traduite en . -+Les exceptions ci-dessous s'appliquent uniquement à la session de navigation privée actuelle. -+Ne plus afficher la boîte de dialogue pour les liens de ce type -+ secondes restantes -+Insérez dans l'URL où les termes de recherche devraient apparaître. -+En&registrer la vidéo sous... -+Nom : -+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 : -+Informations relatives au certificat -+Activer l'onglet 7 -+En savoir plus sur ce problème. -+Version du matériel : -+Le site Web à l'adresse 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. -+Afficher la liste -+Désactivez l'affichage des messages de confirmation et le blocage de l'envoi des formulaires. -+Ce cadre a été bloqué, car il contient des éléments non sécurisés. -+Tout -+Kana -+Gérer les mots de passe enregistrés... -+Boîte de dialogue "Effacer les données de navigation" -+Mettre à jour les extensions maintenant -+Nouvelle application en arrière-plan installée -+Envoyer une capture d'écran de la page actuelle -+L'URL indiquée est incorrecte. -+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 . -+Actuellement connecté -+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 ? -+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. -+E&xporter... -+&Ouvrir un fichier... -+Configurer le contrôle d'accès pour vos périphériques -+Ouvrir un rapport de phishing -+Activer la barre d'adresse -+ secs ago -+Cartes de paiement -+Code opérateur : -+Erreur de connexion -+Vous avez la possibilité de désactiver ces services. -+Essayer d'afficher la page malgré tout -+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. -+Ajouter un nouveau nom -+Obtenir d'autres thèmes -+Rétablir les valeurs par défaut -+Aucun Plug-in installé. -+Action -+Extensions de fichier -+Les plus visités -+&Gestionnaire de favoris -+Caches des applications -+Cette langue est actuellement utilisée par . -+ % -+Empaqueter l'extension -+Activer l'onglet 5 -+Sélectionner le mode de saisie précédent -+Configurer le blocage de JavaScript... -+Réseaux mémorisés -+Impossible de se connecter à Internet. -+URL incorrecte -+Informations sur la sécurité -+Impossible d'installer l'application, car elle est en conflit avec "", qui est déjà installé. -+Votre périphérique n'est pas connecté. -+Fermer -+Accord de la clé -+ mins ago -+Vous ne trouvez pas ce que vous recherchez ? -+Désactivez l'envoi des pings de contrôle des liens hypertexte. -+En&registrer le fichier audio sous... -+Accédez rapidement à vos favoris en les ajoutant à la barre de favoris. -+Vérifier la grammaire et l'orthographe -+Épingler les onglets -+Sélectionner par type d'application -+assembler -+ souhaite devenir votre moteur de recherche. -+Utiliser l'historique d'entrée -+Fermer les onglets -+Voici quelques suggestions : -+Sélectionnez un ou plusieurs fichiers -+Afficher le panneau de la &vérification orthographique -+Veuillez relancer . -+Sans titre -+Pour saisir du texte, sélectionnez une langue et consultez la liste des modes de saisie disponibles. -+Ville -+Modifier... -+Réinitialiser la synchronisation -+Inclure une capture d'écran enregistrée : -+Tout supprimer -+Passer automatiquement en demi-chasse -+&Accéder à -+La capacité de ce périphérique de stockage est de . Veuillez insérer une carte SD ou une clé USB d'au moins 4 Go. -+Rouvrir le dernier onglet fermé -+(blocage) -+Erreurs () -+Traitement de la sélection... -+Aide -+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. -+Impossible d'ouvrir , car vous êtes déconnecté du réseau. Cette page s'affichera dès que la connexion réseau sera rétablie. <br> -+Télécopie -+version -+La passerelle ou le serveur proxy a reçu une réponse incorrecte d'un serveur en amont. -+Nouvelle fenêtre ouverte dans la session du navigateur -+Réessayer -+ 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. -+Les paramètres réseau de votre proxy sont gérés par une extension. -+Retirer l'onglet -+Valable du au -+Case cochée -+Mode de saisie -+AVERTISSEMENT -+Clavier estonien -+Ajout de bordures aux couches de rendu composées -+Imp&rimer... -+Paramètres de saisie automatique -+Rafraîchir -+Barre d'outils -+Nom de la base de données : -+Certificat du répondeur d'état -+Utiliser le mot de passe de mon compte Google -+Vos favoris sont maintenant synchronisés avec Google Documents ! -+Pour fusionner et synchroniser vos favoris dans sur un autre ordinateur, procédez de la même manière que précédemment sur l'ordinateur voulu. -+Renommer... -+ 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. -+Affichage des pages impossible -+Utiliser par défaut -+La carte SIM est verrouillée. Veuillez saisir votre code PIN. Nombre de tentatives restantes : -+Point-virgule -+Réseau domestique requis -+PKCS #7, certificat unique -+Langues baltes -+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. -+Données de diagnostic système -+Page Web, contenu HTML uniquement -+Continuer » -+Gérez vos imprimantes. -+(Choisir un problème dans la liste ci-dessous) -+&Afficher le code source du cadre -+Authentification du client WWW TLS -+Les cookies de plusieurs sites sont autorisés. -+Impossible d'afficher la page de la barre latérale "". -+Vous avez acheté de données le . -+Enregistrer le mot de passe -+Configurer... -+Mode de saisie du pinyin -+Intervertir les touches Ctrl et Alt de gauche -+Confirmer le mot de passe multiterme -+Activer -+Ajouter... -+Voulez-vous que Google Chrome enregistre ces informations de carte de paiement pour le remplissage de formulaires Web ? -+Continuer à bloquer les fenêtres pop-up -+Échec de la vérification DHCP -+Choisir un autre dossier... -+Le profil semble être utilisé par le processus sur l'hôte . Si vous êtes certain qu'aucun autre processus n'utilise ce profil, supprimez le fichier et relancez . -+Chinois traditionnel -+Effacer les données de navigation... -+Ré&activer le son -+Aucun réseau trouvé. -+&Ouvrir le fichier audio dans un nouvel onglet -+&Méthodes d'entrée -+Onglets ou fenêtres -+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. -+Automatique -+&Extensions -+Roumain -+Paramètres du réseau... -+Changer... -+Clé privée -+Format : -+Se connecter -+ : -+Vous avez tenté de contacter , mais le certificat présenté par le serveur est incorrect. -+Nouvelle fenêtre de nav&igation privée -+La page à l'adresse indique : -+Le serveur associé à n'a pas répondu à temps. Cela peut être dû à une surcharge. -+L2TP/IPSec + Clé pré-partagée -+XSS Auditor -+Durée de validité -+WebGL -+Mot de passe multiterme erroné -+Corriger automatiquement la saisie -+Unité d'organisation -+Compte Google -+&Aide -+Utiliser un service de prédiction afin de compléter les recherches et les URL saisies dans la barre d'adresse -+Certificat du signataire de courrier électronique -+En savoir plus -+Vous avez enregistré vos imprimantes sur via le compte . -+il y a  jours -+: restantes -+Cette page est enVoulez-vous la traduire ? -+Saisie automatique des formulaires -+Clavier ukrainien -+Ouvrir tous les favoris dans une fenêtre de navigation privée -+Ctrl -+ minutes -+Le certificat de sécurité du serveur contient des erreurs ! -+avec votre compte Google -+Rechercher dans les favoris -+MSCHAPv2 -+Avertissement utilisateur -+&Options du vérificateur d'orthographe -+Échec de la connexion -+Identifiant de l'erreur -+ jours restants -+Émis par : -+Mode de saisie du thaï (clavier Kesmanee) -+Passerelle : -+Chiffrement -+Autre... -+Pas avant le -+ n'a pas pu synchroniser vos données, car la connexion avec le serveur de synchronisation n'a pas pu être établie. Nouvel essai... -+Me demander lorsqu'un site souhaite afficher des notifications sur le Bureau (recommandé) -+Source : -+Erreur de lecture du cache -+Raccourci de sélection -+Vérifier la révocation du certificat serveur -+Vous rencontrez des problèmes lors de l'installation ? -+Bêta -+Pointeur de la déclaration CPS (Certification Practice Statement) -+Le , vous avez reçu à utiliser librement. -+Nom complet -+Inscription d'entreprise -+Clavier allemand -+Impossible de lancer l'impression. -+&Lire -+Récent -+Cette page Web présente une boucle de redirection. -+Supprimer le certificat "" émis par l'autorité de certification ? -+Mots de passe enregistrés -+Moins -+Options avancées -+Vérifiez vos paramètres DNS. Contactez votre administrateur réseau si vous n'êtes pas sûr de vous. -+Une erreur inconnue s'est produite. -+Émetteur : -+Passer d'un mode de saisie à l'autre -+Organisation (O) -+PKCS #1 SHA-1 avec chiffrement RSA -+Entrez la requête de recherche ici. -+Application en mode navigation privée : -+La largeur de ponctuation initiale est Complète -+Installer  ? -+Installation d'une nouvelle version... -+Ouvrir le lien dans un nouvel ongle&t -+Pour accéder aux paramètres de sécurité, saisissez le code PIN de la carte SIM. -+Jamais -+Impossible d'accéder au réseau. -+Effacer le formulaire -+Échec de la mise à jour du système -+Saisissez le mot de passe utilisé pour chiffrer ce fichier de certificat. -+CHAP -+Enregistrer le lie&n sous... -+Effacer les données de navigation... -+Options de reconnaissance vocale -+Réseau câblé -+Nouvel ongle&t -+R&etour -+Téléchargement suspendu -+Ouvrir la page d'accueil -+Connexion -+L'installation de est terminée. -+&Outils -+Page d'accueil -+Clavier phonétique bulgare -+Cookies et données de site... -+Batterie faible -+Commentaires -+Package incorrect : "". -+ heures restantes -+Configurer les paramètres de blocage des images... -+Options -+ hours ago -+&Détails -+Masquer la barre de titre du système et utiliser les bordures -+Elle peut accéder aux éléments suivants : -+Confidentialité -+Objets : -+Paramètres d'entrée du japonais -+Sebeol-sik Final -+Veuillez vous connecter à Internet pour continuer. -+Afficher les &infos sur le cadre -+Fichier -+Cop&ier l'image -+Utiliser le même proxy pour tous les protocoles -+Masquer -+Requête de protocole externe -+Afficher -+Certains de vos certificats enregistrés identifient ces serveurs : -+Vous avez visité ce site pour la première fois le . -+Cliquer pour avancer, maintenir pour voir l'historique -+La dernière version de l'application "" requiert d'autres autorisations. Elle a donc été désactivée. -+ secondes -+Vous avez choisi d'ouvrir automatiquement certains types de fichiers après leur téléchargement. -+EAP-MD5 -+&Nouvelle fenêtre -++ -+Vous avez essayé d'accéder au site , 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. -+L'envoi de rapports d'erreur est désactivé. -+Clé WEP incorrecte -+Date d'expiration : -+URL de base du certificat Netscape -+Réduire... -+Rechercher dans l'historique -+Ouvrir le lien dans la fenêtre de navi&gation privée -+ secondes -+Historique avancé pour le champ polyvalent -+Active l'utilisation de graphismes 3D dans les éléments canvas via l'API WebGL. -+Occident -+État non reconnu -+ -+Impossible de vérifier si le certificat du serveur a été révoqué. -+Se connecter -+Fuseau horaire : -+Cette option est soumise à une stratégie d'entreprise. Contactez votre administrateur pour plus d'informations. -+Résultats de recherche pour "" -+Revenir à "" (redémarrage requis) -+ va exécuter les tâches suivantes : -+Ajouter la page -+Changement de mode via la touche Maj -+Options de date et d'heure... -+Nom DNS -+Build de développement -+ sur ... -+Verrouiller la carte SIM (code PIN obligatoire pour utiliser les données mobiles) -+Sélectionnez le répertoire racine de l'extension. -+Les cookies de sont autorisés uniquement pour cette session. -+Confirmer les préférences de synchronisation -+RSN -+Ajuster la conversion en fonction de l'entrée précédente -+Afficher le panneau de la &vérification orthographique -+(Revenir à la capture d'écran d'origine) -+Mettre à jour & -+SSID -+- -+Les informations de connexion au compte n'ont pas encore été saisies. -+URL de configuration automatique -+URL : -+Avancer -+ jours restants -+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. -+Lancer -+ -+ ne parvient pas à accéder au réseau. -+ -+ Il est possible que votre pare-feu ou votre antivirus considère -+ -+ comme un intrus dans votre ordinateur et qu'il bloque ses tentatives de connexion à Internet. -+Ce certificat a été vérifié pour les utilisations suivantes : -+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. -+Ajouter aux favoris -+Gérer les certificats... -+Analyse du périphérique... -+PYJJ -+Web Store -+Annuler l'épinglage des onglets -+Favori -+Sélectionnez votre langue : -+Réessayer le téléchargement -+Configurer la synchronisation des mots de passe -+Nom d'utilisateur : -+Enregistrer la p&age sous... -+Vos données sur tous les sites Web -+Un problème est survenu lors de l'extraction de l'image sur l'ordinateur. -+Lancer l'application -+Dubeol-sik -+Remplacé -+Afficher les cookies et autres données de site... -+Si vous pensez avoir cerné les risques, vous pouvez . -+Apparence -+Créer des raccourci&s vers des applications... -+Exécuter le flash PPAPI dans le processus du moteur de rendu -+Définir en tant que navigateur par défaut -+Veuillez choisir un nouveau code PIN. -+ () -+Autre -+&Options -+Impossible de charger la page d'arrière-plan "". -+À quel niveau rencontrez-vous des problèmes ? (Champ obligatoire) -+Retour à la sécurité -+Eten -+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. -+Options de reconnaissance vocale -+Tout sélectionner -+Impossible de charger le fichier "" pour le script de contenu, car ce fichier n'est pas codé en UTF-8. -+&Annuler -+Désactiver temporairement la personnalisation des conversions, les suggestions basées sur l'historique et le dictionnaire utilisateur -+Les cookies de sont autorisés. -+Au fil du temps, la zone ci-dessous affichera les huit sites que vous avez le plus visités. -+Le plug-in a besoin de votre autorisation pour s'exécuter. -+Échec de création de clé privée -+Ouvrir tous les favoris dans une fenêtre de navigation privée -+Placer dans la file d'attente -+Erreur de certificat serveur inconnue -+Mode de saisie du coréen -+&Plein écran -+ -+ -+ Vous pouvez essayer de diagnostiquer le problème en procédant comme suit : -+ -+ -+Bienvenue sur votre page d'accueil ! -+Vos modifications seront prises en compte au prochain démarrage de . -+Adresse du matériel : -+Lecture seule -+Erreur d'importation du certificat serveur -+Données stockées localement -+Tous les fichiers de vont être effacés. -+Modèle du périphérique : -+Afficher dans le Finder -+Nom X.500 -+Vous devez sélectionner au moins un type de données à synchroniser. -+Tout afficher -+Ouvrir tous les favoris -+Clavier danois -+Cette fonctionnalité permet de réaliser un rendu hors écran de la texture, au lieu d'un affichage direct. -+Partiellement activé -+Votre système Sandbox est correctement configuré. -+Ne pas installer -+Activer la recherche instantanée pour accélérer la recherche et la navigation -+Utiliser par défaut -+Non essentielle -+Fenêtres pop-up -+Dernière modification : -+Désélectionné -+Sécurité -+Rechercher... -+Le script de la page utilisait trop de mémoire. Rafraîchissez la page pour réactiver le script. -+Signature du code individuel Microsoft -+Mozilla Firefox -+Page Web inaccessible -+Recadrer l'image -+Si vous fermez maintenant, le téléchargement sera annulé. -+Coller -+Retour -+Impossible de graver l'image. -+Mode de saisie du Chewing -+Province -+JavaScript a été bloqué sur cette page. -+Remarque : Lorsque vous cliquez sur "Envoyer", 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 Règles de confidentialité. -+ Si vous ne souhaitez pas envoyer de journaux système, décochez la case -+ "Inclure les informations système". -+Interdire à tous les sites de stocker des données -+Trier par nom -+Afficher un aperçu des onglets... -+Annuler l'importation -+Ce serveur envoie des données que ne comprend pas. Veuillez signaler un bug et inclure la liste des raw. -+Calcul de la durée restante -+Mode de saisie Google du japonais (pour clavier américain) -+Envoi de la requête... -+Parlez maintenant -+Impossible d'installer le package : "" -+Modèle : -+Bloquer tous les cookies tiers -+Remplacer par -+Espace -+Arrêt du fonctionnement -+Préférences -+Nom inconnu -+Nom -+Choisir un nouveau code PIN -+ [] -+Erreur d'exportation de fichier PKCS #12 -+Le répertoire racine de l'extension doit être indiqué. -+Miniature supprimée -+Tout développer... -+Aie aie aie -+Choisir les expressions en arrière-plan, sans déplacer le pointeur -+Plu&s petit -+Afficher le clavier en superposition -+C&opier l'URL de l'image -+OK -+Aucun réseau détecté -+Charger l'extension non empaquetée... -+page -+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. -+Clavier turc -+Modifier le favori -+Couleur -+Personnaliser les paramètres de synchronisation... -+Activation de votre service Internet mobile -+ -+Ouvrir dans un onglet standard -+ - -+Portrait -+ days ago -+Active un service en arrière-plan qui connecte le service aux éventuelles imprimantes installées sur cet ordinateur. Une fois ce labo activé, vous pouvez lancer en vous connectant à votre compte Google via Options/Préférences dans la section Options avancées. -+Outils de &développement -+Le répertoire racine de l'extension est incorrect. -+ID de clé de l'objet du certificat -+Téléchargements -+Le stockage du certificat client généré par a réussi. -+Sélectionnée -+Suspendre -+Taille sur le disque : -+ID du processus -+Réduire -+Expire le -+Votre administrateur a désactivé l'accès aux fichiers locaux sur votre ordinateur. -+Erreur de connexion SSL -+Envoyer -+Virgule -+&Pause -+Parcourir... -+Mode de saisie Google du japonais (pour clavier Dvorak américain) -+Police à largeur fixe -+Stockage de session -+Page en arrière-plan : -+Le serveur a renvoyé un certificat client incorrect. Erreur  () -+Signature de document Microsoft -+Bloqué -+Gérer les paramètres d'impression... -+Cette page Web a désactivé la saisie automatique dans ce formulaire. -+Clavier hongrois -+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. -+Ajouter un dossier... -+Mode de saisie du japonais (pour clavier Dvorak américain) -+Signaler un bug -+&Toujours ouvrir les fichiers de ce type -+Disque optique -+Nom commun -+Très grande -+Modification terminée -+Objet -+Opérateur : -+Algorithme de signature du certificat -+Cette fonctionnalité permet d'afficher un onglet d'aperçu avant de lancer une impression. -+Autre nom de l'objet du certificat -+Activer la saisie automatique pour remplir les formulaires Web d'un simple clic -+Le site Web à l'adresse 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. -+Rechercher directement sur -+Connectez-vous à pour exporter le certificat client. -+Créer un compte Google maintenant -+État Sandbox -+Retirer de la liste -+rapide -+&Effacer les données de navigation… -+Vérification des mises à jour... -+Télécharger les mises à jour de sécurité essentielles -+Package incorrect. Détails : "". -+Continuer à bloquer les cookies -+Ligne de commande -+Coller l'URL et y a&ccéder -+Désactiver -+Build officiel -+Se connecter à un réseau Wi-Fi -+Périphérique inconnu -+Supprimer les cookies et autres données de site -+URL saisies -+Utiliser les touches , et . pour paginer une liste d'entrées -+Vider la mémoire -+Supprimer les éléments sélectionnés -+Toujours -+de moins d'une heure -+Aucun réseau n'est disponible. -+Supprimer les cookies et autres données de site et de plug-in -+Intervertir les touches Rechercher et Ctrl gauche -+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. -+Nouveau ! Configurer la synchronisation des mots de passe -+Échec de la tentative de connexion au serveur -+Désactiver les notifications de -+Les fichiers suivants ont été créés : -+ -+Extension : -+Fichier de clé : -+ -+Conservez votre fichier de clé en lieu sûr. Vous en aurez besoin lors de la création de nouvelles versions de l'extension. -+&Oui -+Ouvrir les fichiers -+Clavier suédois -+Mémoire JavaScript -+Quitter le mode plein écran -+Hiérarchie des certificats -+Afficher l'onglet existant si l'URL associée est demandée dans un autre -+&Afficher -+Vos données personnelles sur , et sur  autres sites Web -+SMS de -+Afficher la s&ource -+Zoom arrière -+C&opier l'URL du fichier audio -+Importer et associer au périphérique... -+() () -+Plug-ins (par ex. Adobe Flash Player, QuickTime, etc.) -+Empaqueter l'extension... -+Vérifier l'orthographe lors de la frappe -+Échec de la traduction en raison d'un problème de connexion réseau -+Code PIN incorrect. Veuillez réessayer. Nombre de tentatives restantes : -+Vous pouvez effectuer une recherche directement à partir du champ ci-dessus. -+Ajouter les expressions au premier plan -+Lorem ipsum dolor sit amet, consectetur adipiscing elit. -+Opérateur -+Confirmer l'installation -+(Mot clé : ) -+Sensibilité : -+ -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 @@ -+ -+ -+ -+ -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 @@ -+ -+ -+ -+ -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 @@ -+ -+ -+ -+ -+ -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 @@ -+ -+[$~TITLE~$] -+ -+ -+ -+ -+[EXTRA_META] -+ -+ -\ 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 @@ -+ -+Google Desktop Search -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+
    Google Desktop Search

    -+
    -+ -+ -+ -+
    -+[$~LINKS~$] -+
    -+ -+ -+ -+ -+ -+
     
      Desktop Preferences
    -+

    Search your own computer.

    -+[$~MESSAGE~$]
    -+
    [$~SETHOMEPAGE~$][$~BOTTOMLINE~$]

    -+

    ©2005 Google - Searching [NUM_ITEMS] items

    -\ 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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+Sidebar -+Minibar -+Close -+
    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+
    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
      
    -+
    -+ -+ -+
    -+ -+
    -+
    -+ -+ -+ -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 @@ -+ -+ -+should be kept -+ -+in the middle... -+ -+should be removed -+ -+ -+ -+should be removed -+ -+ should be removed because outer expr is False -+ -+should be removed -+ -+ -+ -+ -+ -+ nested true should be kept -+ -+ -+ should be removed -+ -+ -+ -+ silbing true should be kept -+ -+ -+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 @@ -+ -+Google Desktop Search Index Speed -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ Go to Google Desktop Search  -+ -+ -+ -+ -+
    -+ -+ -+ -+ -+
     Index SpeedIndex Speed -+ Help | About Google Desktop Search
    -+ -+

    -+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. -+

    -+You have the option of speeding up this process. -+

    Warning: 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. -+
     
    -+

    -+ -+
    -+

    -+ -+
    -+
    -+ -+

     
    -+ -+ -+ -+
    -+ -+ -+ -+ -+
    Google Desktop Search Home - Status - About Google Desktop Search - [$~BUILDNUMBER~$] - ©2005 Google

    -+ -+ -\ 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 @@ -+ -+Google Desktop Search: Initial Preferences -+ -+ -+ -+ -+ -+ -+ -+

    -+ -+
    -+
    To continue, please set these initial preferences:

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
     
     
      -+
    -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+
    Deskbar
    -+ -+ -+
    -+
      -+
    You can change these and other preferences at any time.
    -+


    -+

    -+
    -+[SCRIPT] -+ -+ -+ -\ 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 @@ -+ -+Indexing has Started -+ -+ -+ -+ -+ -+ -+ -+
    -+

    -+
    -+
    -+One-time indexing has started.

    -+An index is being prepared on your computer to allow you -+to search your information as fast as you can search the web.

    -+
  • This is a one-time process that may take several hours. -+
  • You may continue to use your computer as usual and it is safe to shut down your computer. -+
  • Indexing will be performed only when your computer is idle. -+ -+ -+


    -+ -+

  • -+ -+
    -+ -+[SCRIPT] -+ -\ 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&5o6cXFE~=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+j1L48poSUd1!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-|(IYVUuHA@wRN;~7h6y+xyz_&R~;+XxM^eZ-_q~|%%b86_{3Asa-8FBk+Z7c-kJgN>UjHR -zv*J6C_vNv`hUxGkXMvL0T0vKhVQf$p6QjfeUR}fgl_wW2W!gk%O?IOa`tbcYLDwE{AY?>BR88vkIj3pcp9ftwy~b;A8i-;T|_p=$nY5yWU!`jTE^ib -ze*F+mE-Vf$>e;=5g*fg0^w_NUeElxZA2EAWiGRui^1Zl<=<)P1 -zF&Y;=yo$%KVIA>VGwa=@)kgQbrt31jq~WuvvL2VO`$zNqGSmHCaU;Y2q0yQ$JF7mTwL~ub{sOMb^Vh8GFZ(K1F-x>#wroJR -z&tF1@??TN-{BD^HbgJ;mLeTx&a -ztSZO1p-!t5NvMLINn_JEi1N%h$mrKfeZ%Sxtv*(Sq%E`|5` -zMQa!2rJ*fu!l%|$%^a7pE^XVFE$AM-((pNcct~BK8YqR1Sd~Aqmi*&@D)rQ&bN`YW -zMPvC%+;<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-PCWGR&NeH+c%h>-Yw>z7_{+>=5WWql;yg~F}V-&+XR>jna$uG|ylUKW)Rw*m^ -z_oOjp@vHny>%lMrW)jt@&DG$}J`tOC!twxncz`|R{U9wplS^%1SD7h)zLPS$9KxSJ -z^(luqF00#DmestFZxDq%2fL5@KE>#HI|)#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^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 @@ -+아웃룩 -\ 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 @@ -+ -\ 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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+ -+ -+
      
    -+
    -+
    -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 @@ -+ -+Google Desktop Search Download -+ -+ -+ -+ -+ -+
    -+ -+ -+ -+ -+ -+
    -+
    Google Desktop Search
                Search your own -+computer.

    -+ -+ -+ -+ -+ -+
    -+
  • Find your email, files, web history and chats instantly -+
  • View web pages you've seen, even when you're not online -+
  • Search as easily as you do on Google -+

    Google Desktop Search finds:

    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    Outlook  Email from Outlook, Outlook Express, & -+ Thunderbird
    Internet Explorer  Web history -+ from IE/Firefox/Mozilla/Netscape
    Text  Files in Word, -+ Excel, Powerpoint, PDF, & media formats
    AOL IM  Chats from AOL -+ Instant Messenger
     
     About Desktop -+ Search   Screenshots   -+ Help   Contact -+ Us
  •     -+ -+ -+ -+

    -+
    By using, you agree to our
    Terms & -+ Conditions
    and Privacy -+ Policy
    -+

    -+
    -+

    -+

    * Automatically starts when you turn on -+ your computer
    -+

    -+

    -+
    ©2005 Google -+

    -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 @@ -+ -+Google Desktop Search Preferences -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+


    We're sorry, but you need administrator access to -+enable Desktop Search.

    -+

    To install or run Google Desktop Search you need administrator access on this -+computer. Please try installing again once you have administrator -+access.

    -+


    -+
    -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 @@ -+Google Desktop Search Results -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -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] -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ [EMAIL_TOP_CHROME] -+ -+

    -+ -+ [EMAIL] -+
    -+

    -+ [FREQ_TOP_CHROME] -+

    -+ -+ [$~FREQ~$] -+
    -+

    -+ [RECENT_TOP_CHROME] -+ -+ [$~RECENT~$] -+
    -+

    -+

    -+ -+ -+[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 @@ -+ -+Password Required -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+
    Google Desktop Search

    -+
    -+ -+ -+
    -+ -+ -+ -+
    Password required:   -+ -+   -+
    -+ -+
    -+
    [$~BOTTOMLINE~$]

    -+

    ©2005 Google

    -\ 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 @@ -+ -+Google Desktop Search Preferences -+ -+ -+ -+ -+ -+
    -+ -+ -+ -+ -+
    -+Go to Google Desktop Search  -+ -+ -+ -+ -+
    Preferences -+Preferences Help -+
    -+ -+
    -+ -+ -+ -+ -+
    -+Save your preferences when finished.
    -+ -+[STATUS-MESSAGE] -+ -+ -+
    Preferences (changes apply to Google Desktop Search application)
    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    Search types
    Index the following items so that you can -+search for them:
     
    -+
    -+ -+ -+ -+ -+ -+
    -+
    -+
    -+ -+ -+
    -+ -+ -+
    -+ -+
    -+ -+
    -+
    -+ -+
    -+ -+
    -+

    -+ -+
    -+
    -+
    Plug-ins
    Index these additional items:

    -+[ADDIN-DO] -+[ADDIN-OPTIONS]

    -+To install plug-ins to index other items, visit the -+Plug-ins Download page.
    -+
    Don't search these items
    -+
    -+c:\Documents and Settings\username\Private Stuff
    -+http://www.domain.com/
    -+
     
    -+
    -+
    Search Box Display -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+
    Deskbar
    -+ -+ -+
    -+ -+ -+ -+
    -+ -+
    Number of Results -+
    Google integration -+ -+ -+ -+
    -+ Your personal results are private from Google. -+
    -+
    Help us improve -+ -+
    -+ -+ -+ -+ -+
    Save your preferences -+when finished.
    -+ -+

    [$~BOTTOMLINE~$]
    -+
    ©2005 Google
    -+
    -\ 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 @@ -+ -+should be kept -+ -+in the middle... -+ -+should be removed -+ -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 -+ -+ -+ -+
    -+

    Privacy and Google Desktop Search

    -+ -+

    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. -+

    -+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. -+

    -+Your computer's content is not made accessible to Google or anyone else without your explicit permission. -+ -+

    You can read the -+Privacy Policy -+and Privacy FAQ online. -+ -+

    -+ -+

    -+ -+
    -+[$~PRIVACY_BOTTOMLINE~$] - ©2005 Google -+
    -\ 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 @@ -+ -+Google Desktop Search Preferences -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+
    -+


    To start using Google Desktop Search, we may need to close the following programs if they are running:

    -+

    You can start these programs once Google Desktop Search is running.

    -+ -+
  • AOL Instant Messenger -+
  • Firefox -+
  • Internet Explorer -+
  • Microsoft Excel -+
  • Microsoft Outlook -+
  • Microsoft Word -+
  • Mozilla -+
  • Mozilla Thunderbird -+
  • Netscape -+
  • Opera -+
  • Other web browsers -+ -+

    This will take only a few seconds to complete.

  • -+

    -+

    -+
    -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 @@ -+ -+Refresh index -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+ -+ -+
    Google Desktop Search -+
    -+
    -+
    Google Desktop Search is now recrawling your drive to index new files.
    -+
    Note that new files are indexed automatically, and this step is generally not needed.
    -+
    Click here to continue.
    -+ -+
    -+[$~BOTTOMLINE~$] -+

    ©2005 Google

    -+
    -+ -+ -\ 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 @@ -+ -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 @@ -+ -+ -+ -+ -+ -+
    Go to Google Desktop Search   -+ -+ -+ -+ -+
    -+ -+ -+
    -+[$~LINKS~$] -+
    -+
    -+[$~FLAGS~$]
      Desktop Preferences
      [DELETE_NAME]
    -+ -+ -+
     
    -+
    -\ 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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+
    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
     Google Desktop Search
    -+
    -+ -+
    -+ -+[HEADER_SECTION1] -+[CONTENT_INBOX] -+ -+ -+ -+[HEADER_SECTION2] -+[CONTENT_HIST] -+ -+ -+ -+[CONTENT_NEWS] -+[CONTENT_OTHER] -+ -+ -+ -+ -+ -+
    -+ -+ -+ -+[SCRIPT] -+ -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 @@ -+ -+Google Desktop Search Sidebar -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+
    -+ -+ -+ -+ -+ -+ -+
    Google Desktop Search  Web 
    -+
    -+ -+

    -+
    -+
     News
    -+
    -+ -+ -+[CONTENT_NEWS] -+

    -+ -+ -+
    -+
     Email
    -+
    -+ -+ -+[CONTENT_INBOX] -+

    -+ -+ -+
    -+
     Related History
    -+
    -+ -+ -+[CONTENT_HIST] -+

    -+ -+ -+ -+
    -+
     Recent
    -+
    -+ -+ -+[CONTENT_RECENT] -+

    -+ -+ -+ -+
    -+
     Frequently Visited
    -+
    -+ -+ -+[CONTENT_POPULAR] -+

    -+ -+ -+ -+
    -+
     Implicit Query Debug
    -+
    -+ -+ -+[CONTENT_QUIB_DEBUG] -+ -+ -+ -+ -+[CONTENT_OTHER] -+ -+[SCRIPT] -+ -+ -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 @@ -+ -+ -+ -+ -+ Hello earthlings! -+ -+ -+ -+ -+ -+ -+ -+ -+ Go! -+ -+ Hello %sJoi, how are you doing today? -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -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 @@ -+

    -+ Hello! -+

    -\ 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] -+ -+ -+
    -+ -+ -+
     Desktop Search Status
    -+
    -+
    -+[$~MESSAGE~$] -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
     Number of itemsTime of newest item
      Total searchable items[TOTAL_COUNT][TOTAL_TIME]
           Emails[EMAIL_COUNT][EMAIL_TIME]
           Chats[IM_COUNT][IM_TIME]
           Web history[WEB_COUNT][WEB_TIME]
           Files[FILE_COUNT][FILE_TIME]
    -+
    -+[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 @@ -+

    [GREETING]!

    -+Some cool things are [THINGS]. -+Did you know that [EQUATION]? -+ -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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Copyright 2008 Google Inc. All Rights Reserved. -+ -+ -+ 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. -+ -+ -+ -+ -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 @@ -+ -+ -+ -+© 2008 Google Inc. Med ensamrätt. -+Google Desktop News gadget -+ -+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. -+ -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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Copyright 2008 Google Inc. All Rights Reserved. -+ -+ -+ 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. -+ -+ -+ -+ -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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Copyright 2008 Google Inc. All Rights Reserved. -+ -+ -+ 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. -+ -+ -+ -+ -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~$]
    -+ -+[CONTENTS] -+

    -+ -+[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 @@ -+ -+ -+ -+About Google Toolbar -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+
    -+ -+ -+ -+ -+ -+ -+
    -+
    Google Toolbar
    -+
    -+
    -+ -+ -+
    -+
    -+ -+
    -+
    -+
    -+ -+ -+ De parvis grandis acervus erit -+ -+ -+
    -+ -+ © 2006 Google -+ -+ -+
    -+ -+ -+ -+ -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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -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 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ Whitelisted. -+ -+ -+ Not whitelisted. -+ -+ -+ -+ -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( -+ ''' -+ -+ -+ -+ -+ -+ -+ '''), 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 elements and adds them as children of . -+ -+ Args: -+ messages: the element in the strings.xml dom. -+ resources: the element in the grd dom. -+ """ -+ # elements contain the definition of the resource. -+ # The description of a 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 node contents into a tclib.Message, taking -+ # care to handle whitespace transformations and escaped characters, -+ # and coverting placeholders into 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 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 placeholders into placeholders, e.g.: -+ -+ %s -+ becomes -+ google.com%s -+ -+ 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 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 ' -+ ' tag: %s' % output) -+ -+ return output -+ -+ def __FormatPlaceholderText(self, placeholder_node): -+ """Returns the text inside of an 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 element. -+ -+ Message elements correspond to Android 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 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 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 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 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 element is a candidate for translation. -+ -+ A 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''' -+ A simple string''')) -+ self.assertEqual(msg.GetRealContent(), 'A simple string') -+ msg = tool.CreateTclibMessage(self.__Parse(r''' -+ -+ Strip leading/trailing whitespace -+ ''')) -+ self.assertEqual(msg.GetRealContent(), 'Strip leading/trailing whitespace') -+ msg = tool.CreateTclibMessage(self.__Parse(r''' -+ Fold multiple spaces''')) -+ self.assertEqual(msg.GetRealContent(), 'Fold multiple spaces') -+ msg = tool.CreateTclibMessage(self.__Parse(r''' -+ Retain \n escaped\t spaces''')) -+ self.assertEqual(msg.GetRealContent(), 'Retain \n escaped\t spaces') -+ msg = tool.CreateTclibMessage(self.__Parse(r''' -+ " Quotes preserve -+ whitespace" but only for "enclosed elements " -+ ''')) -+ self.assertEqual(msg.GetRealContent(), ''' Quotes preserve -+ whitespace but only for enclosed elements ''') -+ msg = tool.CreateTclibMessage(self.__Parse( -+ r'''Escaped characters: \"\'\\\t\n''' -+ '')) -+ self.assertEqual(msg.GetRealContent(), '''Escaped characters: "'\\\t\n''') -+ msg = tool.CreateTclibMessage(self.__Parse( -+ '' -+ 'Open %s?' -+ '')) -+ 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''' -+ Contains a comment -+ ''')) -+ self.assertEqual(msg.GetRealContent(), 'Contains a comment') -+ -+ def testIsTranslatable(self): -+ tool = android2grd.Android2Grd() -+ string_el = self.__Parse('Hi') -+ self.assertTrue(tool.IsTranslatable(string_el)) -+ string_el = self.__Parse( -+ 'Hi') -+ self.assertTrue(tool.IsTranslatable(string_el)) -+ string_el = self.__Parse( -+ 'Hi') -+ 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 "" 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 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 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 . -+ 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 = '''\ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+''' % 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[A-Z_0-9.]+)[ \t]+ -+ (?P[A-Z_0-9]+)[ \t]+ -+ "(?P.*?([^"]|""))"[ \t]*$''', re.VERBOSE | re.MULTILINE) -+ -+ -+# Matches a dialog section -+_DIALOG = lazy_re.compile( -+ r'^(?P[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[A-Z0-9_]+)\s+MENU.+?^BEGIN\s*$.+?^END\s*$', -+ re.MULTILINE | re.DOTALL) -+ -+ -+# Matches a versioninfo section -+_VERSIONINFO = lazy_re.compile( -+ r'^(?P[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.+?)^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(^\s+//.+?)*) # 0 or more lines of comments preceding the message -+ ^\s* -+ (?P[A-Za-z0-9_]+) # id -+ \s+ -+ "(?P.*?([^"]|""))"([^"]|$) # 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.+?)$', 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( -+ ''' -+ -+ -+ -+ -+ -+ -+ -+ -+ '''), 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 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 -+ 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 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 [USERNAME], 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 ""\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 -+ #